Add editor tabs too
diff --git a/ui/src/assets/components/query_history.scss b/ui/src/assets/components/query_history.scss
index dbf9faf..f0ee1cb 100644
--- a/ui/src/assets/components/query_history.scss
+++ b/ui/src/assets/components/query_history.scss
@@ -13,20 +13,6 @@
 // limitations under the License.
 
 .pf-query-history {
-  &__header {
-    position: sticky;
-    top: 0;
-    display: flex;
-    align-content: baseline;
-    background-color: var(--pf-color-background-secondary);
-    font-size: 15px;
-    font-weight: 400;
-    padding: 4px 10px;
-    vertical-align: middle;
-    border-bottom: 1px solid var(--pf-color-border);
-    z-index: 1;
-  }
-
   &__item {
     border-bottom: 1px solid var(--pf-color-border-secondary);
     padding: 0.25em 0.5em;
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 1eb1501..ae0e945 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -72,8 +72,8 @@
 @import "widgets/split_panel.scss";
 @import "widgets/sql_table";
 @import "widgets/stack";
-@import "widgets/tab_bar";
 @import "widgets/tabs";
+@import "widgets/tab_strip";
 @import "widgets/tag_input";
 @import "widgets/text_input";
 @import "widgets/text_paragraph";
diff --git a/ui/src/assets/widgets/split_panel.scss b/ui/src/assets/widgets/split_panel.scss
index 2fb9898..a2c06bc 100644
--- a/ui/src/assets/widgets/split_panel.scss
+++ b/ui/src/assets/widgets/split_panel.scss
@@ -36,7 +36,7 @@
 
 .pf-split-panel__handle {
   flex-shrink: 0;
-  background-color: var(--pf-color-background-secondary);
+  background-color: var(--pf-color-background);
   touch-action: none;
 }
 
diff --git a/ui/src/assets/widgets/tab_bar.scss b/ui/src/assets/widgets/tab_bar.scss
deleted file mode 100644
index 82b5529..0000000
--- a/ui/src/assets/widgets/tab_bar.scss
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (C) 2025 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-@import "../theme";
-
-.pf-tab-bar {
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-
-  &__tabs {
-    display: flex;
-    background-color: var(--pf-color-background-secondary);
-    border-bottom: 1px solid var(--pf-color-border);
-    padding-inline: 3px;
-    align-items: flex-end;
-  }
-
-  &__tab {
-    display: flex;
-    align-items: baseline;
-    gap: 2px;
-
-    font-family: var(--pf-font-compact);
-    font-size: 14px;
-
-    padding: 5px;
-    margin-top: 2px;
-    margin-bottom: -1px; // Overlap the border
-    cursor: pointer;
-
-    border-radius: 3px 3px 0 0;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    border-style: solid;
-    border-color: var(--pf-color-border-secondary);
-    border-width: 1px 1px 0 1px;
-
-    &:not(:first-child) {
-      margin-left: -1px;
-    }
-
-    &:hover {
-      background: color_hover(transparent);
-    }
-
-    &--active {
-      cursor: default;
-      background-color: var(--pf-color-background);
-      box-shadow: var(--pf-color-box-shadow) 0px 0px 3px;
-      border-color: var(--pf-color-border);
-      z-index: 1;
-    }
-  }
-
-  &__tab-title {
-    margin: 0px 4px;
-    overflow: hidden;
-    user-select: none;
-  }
-
-  &__content {
-    flex: 1;
-    overflow: auto;
-  }
-}
diff --git a/ui/src/assets/widgets/tab_strip.scss b/ui/src/assets/widgets/tab_strip.scss
new file mode 100644
index 0000000..7c37fe7
--- /dev/null
+++ b/ui/src/assets/widgets/tab_strip.scss
@@ -0,0 +1,69 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+.pf-tab-strip {
+  display: flex;
+  align-items: baseline;
+
+  &__tabs {
+    display: flex;
+    overflow: hidden;
+    flex: 1;
+  }
+
+  &__tab {
+    color: var(--pf-color-text-muted);
+    padding: 4px;
+    margin-top: 3px;
+    align-items: center;
+    cursor: pointer;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    display: flex;
+    align-items: baseline;
+    gap: 2px;
+
+    &:hover {
+      background-color: color_hover(transparent);
+    }
+
+    &[active] {
+      color: var(--pf-color-primary);
+      border-bottom: solid 2px var(--pf-color-primary);
+      cursor: default;
+    }
+
+    &:nth-child(1) {
+      margin-left: 3px;
+    }
+  }
+
+  &__tab-title {
+    margin: 0px 4px;
+    overflow: hidden;
+  }
+
+  &__tab-icon {
+    &--left {
+      margin-right: 4px;
+      align-self: center;
+    }
+
+    &--right {
+      margin-left: 4px;
+      align-self: center;
+    }
+  }
+}
diff --git a/ui/src/assets/widgets/tabs.scss b/ui/src/assets/widgets/tabs.scss
index 29ef45c..0baec22 100644
--- a/ui/src/assets/widgets/tabs.scss
+++ b/ui/src/assets/widgets/tabs.scss
@@ -12,58 +12,66 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+@import "../theme";
+
 .pf-tabs {
   display: flex;
-  align-items: baseline;
+  flex-direction: column;
+  height: 100%;
+  margin: 2px;
 
   &__tabs {
     display: flex;
-    overflow: hidden;
-    flex: 1;
+    background-color: var(--pf-color-background);
+    padding-inline: 3px;
+    align-items: flex-end;
   }
 
   &__tab {
     color: var(--pf-color-text-muted);
-    padding: 4px;
-    margin-top: 3px;
+    display: flex;
     align-items: center;
+    gap: 4px;
+
+    padding: 4px 6px;
+    margin-top: 2px;
     cursor: pointer;
+
+    border-radius: 3px 3px 0 0;
     overflow: hidden;
     white-space: nowrap;
     text-overflow: ellipsis;
-    display: flex;
-    align-items: baseline;
-    gap: 2px;
+
+    &:not(:first-child) {
+      margin-left: -1px;
+    }
 
     &:hover {
-      background-color: color_hover(transparent);
+      color: var(--pf-color-text);
+      background: color_hover(transparent);
     }
 
-    &[active] {
-      color: var(--pf-color-primary);
-      border-bottom: solid 2px var(--pf-color-primary);
+    &--active {
       cursor: default;
-    }
+      color: var(--pf-color-text);
+      background-color: var(--pf-color-background-secondary);
 
-    &:nth-child(1) {
-      margin-left: 3px;
+      &:hover {
+        background: var(--pf-color-background-secondary);
+      }
     }
   }
 
   &__tab-title {
     margin: 0px 4px;
     overflow: hidden;
+    user-select: none;
   }
 
-  &__tab-icon {
-    &--left {
-      margin-right: 4px;
-      align-self: center;
-    }
-
-    &--right {
-      margin-left: 4px;
-      align-self: center;
-    }
+  &__content {
+    flex: 1;
+    overflow: auto;
+    background: var(--pf-color-background-secondary);
+    border-radius: 3px;
   }
 }
diff --git a/ui/src/components/widgets/query_history.ts b/ui/src/components/widgets/query_history.ts
index 9ef86e0..70f14b5 100644
--- a/ui/src/components/widgets/query_history.ts
+++ b/ui/src/components/widgets/query_history.ts
@@ -48,10 +48,6 @@
       {
         ...rest,
       },
-      m(
-        '.pf-query-history__header',
-        `Query history (${queryHistoryStorage.data.length} queries)`,
-      ),
       starred.map((attrs) => m(HistoryItemComponent, attrs)),
       unstarred.map((attrs) => m(HistoryItemComponent, attrs)),
     );
diff --git a/ui/src/plugins/dev.perfetto.QueryPage/index.ts b/ui/src/plugins/dev.perfetto.QueryPage/index.ts
index 110082e..8406792 100644
--- a/ui/src/plugins/dev.perfetto.QueryPage/index.ts
+++ b/ui/src/plugins/dev.perfetto.QueryPage/index.ts
@@ -21,45 +21,99 @@
 import {Flag} from '../../public/feature_flag';
 import {PerfettoPlugin} from '../../public/plugin';
 import {Trace} from '../../public/trace';
-import {QueryPage} from './query_page';
+import {QueryPage, QueryTabState} from './query_page';
 import {queryHistoryStorage} from '../../components/widgets/query_history';
 import {EmptyState} from '../../widgets/empty_state';
 import {Anchor} from '../../widgets/anchor';
 
+let nextTabId = 1;
+
+function createNewTab(): QueryTabState {
+  return {
+    id: `query-${nextTabId++}`,
+    editorText: '',
+    executedQuery: undefined,
+    queryResult: undefined,
+  };
+}
+
 export default class QueryPagePlugin implements PerfettoPlugin {
   static readonly id = 'dev.perfetto.QueryPage';
   static addQueryPageMiniFlag: Flag;
 
   async onTraceLoad(trace: Trace): Promise<void> {
-    // The query page and tab share the same query data.
-    let executedQuery: string | undefined;
-    let queryResult: QueryResponse | undefined;
+    // State for multiple query tabs
+    const tabs: QueryTabState[] = [createNewTab()];
+    let activeTabId = tabs[0].id;
     let isLoading = false;
-    let editorText = '';
+
+    function getActiveTab(): QueryTabState | undefined {
+      return tabs.find((t) => t.id === activeTabId);
+    }
 
     async function onExecute(text: string) {
       if (!text) return;
 
-      executedQuery = text;
-      queryResult = undefined;
+      const tab = getActiveTab();
+      if (!tab) return;
+
+      tab.executedQuery = text;
+      tab.queryResult = undefined;
       queryHistoryStorage.saveQuery(text);
 
       isLoading = true;
-      queryResult = await runQueryForQueryTable(text, trace.engine);
+      m.redraw();
+
+      tab.queryResult = await runQueryForQueryTable(text, trace.engine);
       isLoading = false;
 
       trace.tabs.showTab('dev.perfetto.QueryPage');
     }
 
+    function onAddTab() {
+      const newTab = createNewTab();
+      tabs.push(newTab);
+      activeTabId = newTab.id;
+    }
+
+    function onCloseTab(tabId: string) {
+      const index = tabs.findIndex((t) => t.id === tabId);
+      if (index === -1) return;
+
+      // Don't close the last tab
+      if (tabs.length === 1) return;
+
+      tabs.splice(index, 1);
+
+      // If we closed the active tab, switch to another
+      if (activeTabId === tabId) {
+        activeTabId = tabs[Math.min(index, tabs.length - 1)].id;
+      }
+    }
+
+    function onTabChange(tabId: string) {
+      activeTabId = tabId;
+    }
+
+    function onEditorContentUpdate(tabId: string, text: string) {
+      const tab = tabs.find((t) => t.id === tabId);
+      if (tab) {
+        tab.editorText = text;
+      }
+    }
+
     trace.pages.registerPage({
       route: '/query',
       render: () =>
         m(QueryPage, {
           trace,
-          editorText,
-          executedQuery,
-          queryResult,
-          onEditorContentUpdate: (text) => (editorText = text),
+          tabs,
+          activeTabId,
+          isLoading,
+          onTabChange,
+          onAddTab,
+          onCloseTab,
+          onEditorContentUpdate,
           onExecute,
         }),
     });
@@ -77,11 +131,12 @@
       isEphemeral: false,
       content: {
         render() {
+          const activeTab = getActiveTab();
           return m(QueryResultsTable, {
             trace,
             isLoading,
-            query: executedQuery,
-            resp: queryResult,
+            query: activeTab?.executedQuery,
+            resp: activeTab?.queryResult,
             fillHeight: true,
             emptyState: m(
               EmptyState,
diff --git a/ui/src/plugins/dev.perfetto.QueryPage/query_page.scss b/ui/src/plugins/dev.perfetto.QueryPage/query_page.scss
index b700409..4dbfd7d 100644
--- a/ui/src/plugins/dev.perfetto.QueryPage/query_page.scss
+++ b/ui/src/plugins/dev.perfetto.QueryPage/query_page.scss
@@ -83,14 +83,18 @@
   display: flex;
   flex-direction: column;
   height: 100%;
+  padding: 2px;
+  gap: 2px;
 
   &__search {
-    margin: 8px;
+    margin: 2px;
   }
 
   &__items {
     flex: 1;
     overflow: auto;
+    background: var(--pf-color-background);
+    border-radius: 3px;
   }
 
   &__item {
@@ -119,22 +123,27 @@
     }
   }
 
+  &__item-icon {
+    color: var(--pf-color-foreground-secondary);
+  }
+
   &__item-name {
-    cursor: pointer;
     font-family: var(--pf-font-monospace);
     font-size: 12px;
     flex: 1;
-
-    &:hover {
-      color: var(--pf-color-primary);
-      text-decoration: underline;
-    }
   }
 
   &__item-details {
     padding: 0 8px 12px 32px;
   }
 
+  &__description {
+    font-size: 12px;
+    color: var(--pf-color-foreground-secondary);
+    margin-bottom: 8px;
+    line-height: 1.4;
+  }
+
   &__detail-row {
     display: flex;
     gap: 8px;
@@ -167,10 +176,16 @@
 
   &__column {
     display: flex;
-    gap: 8px;
+    align-items: center;
+    gap: 4px;
     padding: 2px 0;
   }
 
+  &__column-icon {
+    font-size: 14px;
+    color: var(--pf-color-foreground-secondary);
+  }
+
   &__column-name {
     font-size: 12px;
     min-width: 120px;
diff --git a/ui/src/plugins/dev.perfetto.QueryPage/query_page.ts b/ui/src/plugins/dev.perfetto.QueryPage/query_page.ts
index c4122e9..689c49f 100644
--- a/ui/src/plugins/dev.perfetto.QueryPage/query_page.ts
+++ b/ui/src/plugins/dev.perfetto.QueryPage/query_page.ts
@@ -33,12 +33,13 @@
 import {HotkeyGlyphs} from '../../widgets/hotkey_glyphs';
 import {Spinner} from '../../widgets/spinner';
 import {SplitPanel} from '../../widgets/split_panel';
-import {TabBar} from '../../widgets/tab_bar';
+import {Tabs} from '../../widgets/tabs';
 import {Stack, StackAuto} from '../../widgets/stack';
 import {CopyToClipboardButton} from '../../widgets/copy_to_clipboard_button';
 import {Anchor} from '../../widgets/anchor';
 import {getSliceId, isSliceish} from '../../components/query_table/query_table';
 import {DataSource} from '../../components/widgets/datagrid/data_source';
+import {Row} from '../../trace_processor/query_result';
 import {PopupMenu} from '../../widgets/menu';
 import {PopupPosition} from '../../widgets/popup';
 import {AddDebugTrackMenu} from '../../components/tracks/add_debug_track_menu';
@@ -47,35 +48,118 @@
 
 const HIDE_PERFETTO_SQL_AGENT_BANNER_KEY = 'hidePerfettoSqlAgentBanner';
 
+export interface QueryTabState {
+  id: string;
+  editorText: string;
+  executedQuery?: string;
+  queryResult?: QueryResponse;
+}
+
 export interface QueryPageAttrs {
   readonly trace: Trace;
-  readonly editorText: string;
-  readonly executedQuery?: string;
-  readonly queryResult?: QueryResponse;
+  readonly tabs: QueryTabState[];
+  readonly activeTabId: string;
+  readonly isLoading: boolean;
 
-  onEditorContentUpdate?(content: string): void;
+  onTabChange(tabId: string): void;
+  onAddTab(): void;
+  onCloseTab(tabId: string): void;
+  onEditorContentUpdate(tabId: string, content: string): void;
+  onExecute(query: string): void;
+}
 
-  onExecute?(query: string): void;
+interface DataSourceInfo {
+  dataSource: DataSource;
+  sourceRows: readonly Row[];
 }
 
 export class QueryPage implements m.ClassComponent<QueryPageAttrs> {
-  private dataSource?: DataSource;
-
-  onbeforeupdate(
-    vnode: m.Vnode<QueryPageAttrs>,
-    oldVnode: m.Vnode<QueryPageAttrs>,
-  ) {
-    // Update the datasource if present
-    if (vnode.attrs.queryResult !== oldVnode.attrs.queryResult) {
-      if (vnode.attrs.queryResult) {
-        this.dataSource = new InMemoryDataSource(vnode.attrs.queryResult.rows);
-      } else {
-        this.dataSource = undefined;
-      }
-    }
-  }
+  // Map of tab ID to data source info (including source rows for comparison)
+  private dataSourceInfo = new Map<string, DataSourceInfo>();
 
   view({attrs}: m.CVnode<QueryPageAttrs>) {
+    const activeTab = attrs.tabs.find((t) => t.id === attrs.activeTabId);
+
+    // Build tabs for the left panel
+    const queryTabs = attrs.tabs.map((tab, index) => ({
+      key: tab.id,
+      title: `Query ${index + 1}`,
+      icon: 'code',
+      closable: attrs.tabs.length > 1,
+      content: this.renderQueryTab(attrs, tab),
+    }));
+
+    const leftPanel = m(Tabs, {
+      className: 'pf-query-page__query-tabs',
+      tabs: queryTabs,
+      activeTabKey: attrs.activeTabId,
+      onTabChange: attrs.onTabChange,
+      onTabClose: attrs.onCloseTab,
+      onAddTab: attrs.onAddTab,
+    });
+
+    const sidebarPanel = m(Tabs, {
+      className: 'pf-query-page__sidebar',
+      tabs: [
+        {
+          key: 'history',
+          title: 'History',
+          icon: 'history',
+          content: m(QueryHistoryComponent, {
+            className: 'pf-query-page__history',
+            trace: attrs.trace,
+            runQuery: (query: string) => {
+              attrs.onExecute(query);
+            },
+            setQuery: (query: string) => {
+              if (activeTab) {
+                attrs.onEditorContentUpdate(activeTab.id, query);
+              }
+            },
+          }),
+        },
+        {
+          key: 'tables',
+          title: 'Tables',
+          icon: 'table',
+          content: this.renderTablesTab(attrs),
+        },
+      ],
+    });
+
+    return m(
+      '.pf-query-page',
+      m(SplitPanel, {
+        direction: 'horizontal',
+        split: {percent: 70},
+        minSize: 100,
+        firstPanel: leftPanel,
+        secondPanel: sidebarPanel,
+      }),
+    );
+  }
+
+  private renderQueryTab(
+    attrs: QueryPageAttrs,
+    tab: QueryTabState,
+  ): m.Children {
+    // Get or create data source for this tab
+    let info = this.dataSourceInfo.get(tab.id);
+    let dataSource = info?.dataSource;
+    if (tab.queryResult && tab.queryResult.rows) {
+      // Check if we need to update the data source
+      if (!info || info.sourceRows !== tab.queryResult.rows) {
+        dataSource = new InMemoryDataSource(tab.queryResult.rows);
+        this.dataSourceInfo.set(tab.id, {
+          dataSource,
+          sourceRows: tab.queryResult.rows,
+        });
+      }
+    } else if (info && !tab.queryResult) {
+      this.dataSourceInfo.delete(tab.id);
+      dataSource = undefined;
+    }
+
     const editorPanel = m('.pf-query-page__editor-panel', [
       m(Box, {className: 'pf-query-page__toolbar'}, [
         m(Stack, {orientation: 'horizontal'}, [
@@ -85,7 +169,7 @@
             intent: Intent.Primary,
             variant: ButtonVariant.Filled,
             onclick: () => {
-              attrs.onExecute?.(attrs.editorText);
+              attrs.onExecute(tab.editorText);
             },
           }),
           m(
@@ -97,7 +181,7 @@
             'or press',
             m(HotkeyGlyphs, {hotkey: 'Mod+Enter'}),
           ),
-          m(StackAuto), // The spacer pushes the following buttons to the right.
+          m(StackAuto),
           attrs.trace.isInternalUser &&
             m(Button, {
               icon: 'wand_stars',
@@ -109,7 +193,7 @@
               },
             }),
           m(CopyToClipboardButton, {
-            textToCopy: attrs.editorText,
+            textToCopy: tab.editorText,
             title: 'Copy query to clipboard',
             label: 'Copy Query',
           }),
@@ -152,7 +236,7 @@
             ],
           ),
         ),
-      attrs.editorText.includes('"') &&
+      tab.editorText.includes('"') &&
         m(
           Box,
           m(
@@ -165,21 +249,17 @@
         ),
       m(Editor, {
         language: 'perfetto-sql',
-        text: attrs.editorText,
-        onUpdate: attrs.onEditorContentUpdate,
+        text: tab.editorText,
+        onUpdate: (text) => attrs.onEditorContentUpdate(tab.id, text),
         onExecute: attrs.onExecute,
       }),
     ]);
 
     const resultsPanel = m(
       '.pf-query-page__results-panel',
-      this.dataSource && attrs.queryResult
-        ? this.renderQueryResult(
-            attrs.trace,
-            attrs.queryResult,
-            this.dataSource,
-          )
-        : attrs.executedQuery
+      dataSource && tab.queryResult
+        ? this.renderQueryResult(attrs.trace, tab.queryResult, dataSource)
+        : tab.executedQuery && attrs.isLoading && tab.id === attrs.activeTabId
           ? m(EmptyState, {
               title: 'Running query...',
               icon: 'hourglass_empty',
@@ -191,49 +271,13 @@
             }),
     );
 
-    const sidebarPanel = m(TabBar, {
-      className: 'pf-query-page__sidebar',
-      tabs: [
-        {
-          key: 'history',
-          title: 'History',
-          content: m(QueryHistoryComponent, {
-            className: 'pf-query-page__history',
-            trace: attrs.trace,
-            runQuery: (query: string) => {
-              attrs.onExecute?.(query);
-            },
-            setQuery: (query: string) => {
-              attrs.onEditorContentUpdate?.(query);
-            },
-          }),
-        },
-        {
-          key: 'tables',
-          title: 'Tables',
-          content: this.renderTablesTab(attrs),
-        },
-      ],
-    });
-
-    const leftPanel = m(SplitPanel, {
+    return m(SplitPanel, {
       direction: 'vertical',
-      split: {percent: 70},
+      split: {percent: 50},
       minSize: 100,
       firstPanel: editorPanel,
       secondPanel: resultsPanel,
     });
-
-    return m(
-      '.pf-query-page',
-      m(SplitPanel, {
-        direction: 'horizontal',
-        split: {percent: 70},
-        minSize: 100,
-        firstPanel: leftPanel,
-        secondPanel: sidebarPanel,
-      }),
-    );
   }
 
   private renderQueryResult(
@@ -349,12 +393,7 @@
       );
     }
 
-    return m(SimpleTableList, {
-      sqlModules,
-      onTableClick: (tableName) => {
-        attrs.onExecute?.(`SELECT * FROM ${tableName} LIMIT 100`);
-      },
-    });
+    return m(SimpleTableList, {sqlModules});
   }
 
   private shouldDisplayPerfettoSqlAgentBanner(attrs: QueryPageAttrs) {
diff --git a/ui/src/plugins/dev.perfetto.QueryPage/simple_table_list.ts b/ui/src/plugins/dev.perfetto.QueryPage/simple_table_list.ts
index b5ea321..784fd8b 100644
--- a/ui/src/plugins/dev.perfetto.QueryPage/simple_table_list.ts
+++ b/ui/src/plugins/dev.perfetto.QueryPage/simple_table_list.ts
@@ -13,13 +13,43 @@
 // limitations under the License.
 
 import m from 'mithril';
+import {
+  PerfettoSqlType,
+  perfettoSqlTypeToString,
+} from '../../trace_processor/perfetto_sql_type';
 import {Icon} from '../../widgets/icon';
 import {TextInput} from '../../widgets/text_input';
 import {SqlModules, SqlTable} from '../dev.perfetto.SqlModules/sql_modules';
 
+function getTypeIcon(type?: PerfettoSqlType): string {
+  if (!type) return 'help_outline';
+  switch (type.kind) {
+    case 'int':
+      return 'tag';
+    case 'double':
+      return 'decimal_increase';
+    case 'string':
+      return 'text_fields';
+    case 'boolean':
+      return 'toggle_on';
+    case 'timestamp':
+      return 'schedule';
+    case 'duration':
+      return 'timer';
+    case 'bytes':
+      return 'memory';
+    case 'arg_set_id':
+      return 'data_object';
+    case 'id':
+    case 'joinid':
+      return 'key';
+    default:
+      return 'help_outline';
+  }
+}
+
 export interface SimpleTableListAttrs {
   readonly sqlModules: SqlModules;
-  readonly onTableClick: (tableName: string) => void;
 }
 
 export class SimpleTableList implements m.ClassComponent<SimpleTableListAttrs> {
@@ -49,17 +79,12 @@
       }),
       m(
         '.pf-simple-table-list__items',
-        filteredTables.map((table) =>
-          this.renderTableItem(table, attrs.onTableClick),
-        ),
+        filteredTables.map((table) => this.renderTableItem(table)),
       ),
     );
   }
 
-  private renderTableItem(
-    table: SqlTable,
-    onTableClick: (tableName: string) => void,
-  ): m.Children {
+  private renderTableItem(table: SqlTable): m.Children {
     const isExpanded = this.expandedTables.has(table.name);
 
     return m(
@@ -81,16 +106,16 @@
           },
           m(Icon, {icon: isExpanded ? 'expand_more' : 'chevron_right'}),
         ),
-        m(
-          '.pf-simple-table-list__item-name',
-          {onclick: () => onTableClick(table.name)},
-          table.name,
-        ),
+        m(Icon, {icon: 'table', className: 'pf-simple-table-list__item-icon'}),
+        m('.pf-simple-table-list__item-name', table.name),
       ),
       // Expandable details
       isExpanded &&
         m(
           '.pf-simple-table-list__item-details',
+          // Description
+          table.description &&
+            m('.pf-simple-table-list__description', table.description),
           // Module
           table.includeKey &&
             m(
@@ -108,10 +133,15 @@
                 table.columns.map((col) =>
                   m(
                     '.pf-simple-table-list__column',
+                    {title: col.description},
+                    m(Icon, {
+                      icon: getTypeIcon(col.type),
+                      className: 'pf-simple-table-list__column-icon',
+                    }),
                     m('code.pf-simple-table-list__column-name', col.name),
                     m(
                       'span.pf-simple-table-list__column-type',
-                      col.type ?? 'unknown',
+                      perfettoSqlTypeToString(col.type),
                     ),
                   ),
                 ),
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tab_bar_demo.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tab_bar_demo.ts
index bb10ee5..8da9229 100644
--- a/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tab_bar_demo.ts
+++ b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tab_bar_demo.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import m from 'mithril';
-import {TabBar, TabBarTab} from '../../../widgets/tab_bar';
+import {Tabs, TabBarTab} from '../../../widgets/tabs';
 import {renderWidgetShowcase} from '../widgets_page_utils';
 
 export function renderTabBar(): m.Children {
@@ -70,7 +70,7 @@
               border: '1px solid var(--pf-color-border)',
             },
           },
-          m(TabBar, {
+          m(Tabs, {
             tabs,
             onTabClose: opts.closable
               ? (key) => {
diff --git a/ui/src/widgets/tab_strip.ts b/ui/src/widgets/tab_strip.ts
index 5c2f488..54a3970 100644
--- a/ui/src/widgets/tab_strip.ts
+++ b/ui/src/widgets/tab_strip.ts
@@ -33,10 +33,10 @@
   view({attrs}: m.CVnode<TabStripAttrs>) {
     const {tabs, currentTabKey, onTabChange, className} = attrs;
     return m(
-      '.pf-tabs',
+      '.pf-tab-strip',
       {className},
       m(
-        '.pf-tabs__tabs',
+        '.pf-tab-strip__tabs',
         tabs.map((tab) => {
           const {key, title, leftIcon, rightIcon} = tab;
           const renderIcon = (
@@ -49,10 +49,10 @@
             if (typeof icon === 'string') {
               return m(Icon, {icon, className});
             }
-            return m('.pf-tabs__tab-icon', {className}, icon);
+            return m('.pf-tab-strip__tab-icon', {className}, icon);
           };
           return m(
-            '.pf-tabs__tab',
+            '.pf-tab-strip__tab',
             {
               active: currentTabKey === key,
               key,
@@ -61,9 +61,9 @@
               },
             },
             [
-              renderIcon(leftIcon, 'pf-tabs__tab-icon--left'),
-              m('span.pf-tabs__tab-title', title),
-              renderIcon(rightIcon, 'pf-tabs__tab-icon--right'),
+              renderIcon(leftIcon, 'pf-tab-strip__tab-icon--left'),
+              m('span.pf-tab-strip__tab-title', title),
+              renderIcon(rightIcon, 'pf-tab-strip__tab-icon--right'),
             ],
           );
         }),
diff --git a/ui/src/widgets/tab_bar.ts b/ui/src/widgets/tabs.ts
similarity index 74%
rename from ui/src/widgets/tab_bar.ts
rename to ui/src/widgets/tabs.ts
index bcefe54..1dec8d3 100644
--- a/ui/src/widgets/tab_bar.ts
+++ b/ui/src/widgets/tabs.ts
@@ -16,6 +16,7 @@
 import {classNames} from '../base/classnames';
 import {Gate} from '../base/mithril_utils';
 import {Button} from './button';
+import {Icon} from './icon';
 import {Icons} from '../base/semantic_icons';
 
 export interface TabBarTab {
@@ -25,11 +26,13 @@
   readonly title: m.Children;
   // Content to display when this tab is active.
   readonly content: m.Children;
+  // Optional icon to display before the title.
+  readonly icon?: string;
   // Whether to show a close button on the tab.
   readonly closable?: boolean;
 }
 
-export interface TabBarAttrs {
+export interface TabsAttrs {
   // The tabs to display.
   readonly tabs: TabBarTab[];
   // The currently active tab key (controlled mode).
@@ -39,12 +42,16 @@
   onTabChange?(key: string): void;
   // Called when a tab's close button is clicked.
   onTabClose?(key: string): void;
+  // Called when the add tab button is clicked.
+  // If provided, an add tab button will be shown.
+  onAddTab?(): void;
   // Additional class name for the container.
   readonly className?: string;
 }
 
 interface TabHandleAttrs {
   readonly active?: boolean;
+  readonly icon?: string;
   readonly hasCloseButton?: boolean;
   readonly onClose?: () => void;
   readonly onclick?: () => void;
@@ -52,15 +59,16 @@
 
 class TabHandle implements m.ClassComponent<TabHandleAttrs> {
   view({attrs, children}: m.CVnode<TabHandleAttrs>): m.Children {
-    const {active, hasCloseButton, onClose, onclick} = attrs;
+    const {active, icon, hasCloseButton, onClose, onclick} = attrs;
     return m(
-      '.pf-tab-bar__tab',
+      '.pf-tabs__tab',
       {
-        className: classNames(active && 'pf-tab-bar__tab--active'),
+        className: classNames(active && 'pf-tabs__tab--active'),
         onclick,
         onauxclick: () => onClose?.(),
       },
-      m('.pf-tab-bar__tab-title', children),
+      icon && m(Icon, {icon}),
+      m('.pf-tabs__tab-title', children),
       hasCloseButton &&
         m(Button, {
           compact: true,
@@ -74,12 +82,13 @@
   }
 }
 
-export class TabBar implements m.ClassComponent<TabBarAttrs> {
+export class Tabs implements m.ClassComponent<TabsAttrs> {
   // Current active tab key (for uncontrolled mode).
   private internalActiveTab?: string;
 
-  view({attrs}: m.CVnode<TabBarAttrs>): m.Children {
-    const {tabs, activeTabKey, onTabChange, onTabClose, className} = attrs;
+  view({attrs}: m.CVnode<TabsAttrs>): m.Children {
+    const {tabs, activeTabKey, onTabChange, onTabClose, onAddTab, className} =
+      attrs;
 
     if (tabs.length === 0) {
       return null;
@@ -89,15 +98,16 @@
     const activeKey = activeTabKey ?? this.internalActiveTab ?? tabs[0].key;
 
     return m(
-      '.pf-tab-bar',
+      '.pf-tabs',
       {className},
       m(
-        '.pf-tab-bar__tabs',
+        '.pf-tabs__tabs',
         tabs.map((tab) =>
           m(
             TabHandle,
             {
               active: tab.key === activeKey,
+              icon: tab.icon,
               hasCloseButton: tab.closable,
               onclick: () => {
                 this.internalActiveTab = tab.key;
@@ -108,9 +118,17 @@
             tab.title,
           ),
         ),
+        onAddTab &&
+          m(Button, {
+            className: 'pf-tabs__add-tab',
+            compact: true,
+            icon: Icons.Add,
+            title: 'Add new tab',
+            onclick: onAddTab,
+          }),
       ),
       m(
-        '.pf-tab-bar__content',
+        '.pf-tabs__content',
         tabs.map((tab) => m(Gate, {open: tab.key === activeKey}, tab.content)),
       ),
     );