From 3311fe5a0e294f11ad81ec8f04aa45ac31c180a0 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 13 Apr 2026 09:41:11 -0700 Subject: [PATCH 1/4] feat(desktop): v2 diff viewer opens in its own tab + pane-derived tab titles openDiffPane now scans all tabs for an existing diff pane (focus + scroll) and falls back to addTab, so clicking a file in the Changes sidebar never hijacks the focused editor tab. Collapses tab/pane title resolution onto a single canonical field: PaneDefinition.getTitle is tightened to (pane) => string, file's rich JSX moves into the existing renderTitle hook, and a new resolveTabTitle helper powers both the tab bar and the "Move to Tab" context menu. tab.titleOverride is reserved for user renames; every auto-default caller is stripped and multi-pane tabs fall back to "Tab N" instead of "tab-". --- .../useDefaultContextMenuActions.tsx | 14 +++++-- .../components/BrowserPane/BrowserPane.tsx | 14 ------- .../components/BrowserPane/index.ts | 1 - .../hooks/usePaneRegistry/usePaneRegistry.tsx | 15 +++++-- .../useV2PresetExecution.ts | 10 ++--- .../useWorkspaceHotkeys.ts | 3 -- .../v2-workspace/$workspaceId/page.tsx | 40 ++++++++----------- packages/panes/src/core/store/store.test.ts | 2 - packages/panes/src/core/store/store.ts | 3 +- packages/panes/src/index.ts | 2 +- .../react/components/Workspace/Workspace.tsx | 4 +- .../components/Tab/components/Pane/Pane.tsx | 2 +- .../src/react/components/Workspace/index.ts | 1 + .../Workspace/utils/resolveTabTitle.ts | 17 ++++++++ packages/panes/src/react/index.ts | 2 +- packages/panes/src/react/types.ts | 3 +- 16 files changed, 68 insertions(+), 65 deletions(-) create mode 100644 packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useDefaultContextMenuActions/useDefaultContextMenuActions.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useDefaultContextMenuActions/useDefaultContextMenuActions.tsx index 22da979ff64..20fda20fd90 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useDefaultContextMenuActions/useDefaultContextMenuActions.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useDefaultContextMenuActions/useDefaultContextMenuActions.tsx @@ -1,4 +1,9 @@ -import type { ContextMenuActionConfig, RendererContext } from "@superset/panes"; +import { + type ContextMenuActionConfig, + type PaneRegistry, + type RendererContext, + resolveTabTitle, +} from "@superset/panes"; import { useMemo } from "react"; import { LuColumns2, @@ -18,7 +23,9 @@ import type { TerminalPaneData, } from "../../types"; -export function useDefaultContextMenuActions(): ContextMenuActionConfig[] { +export function useDefaultContextMenuActions( + paneRegistry: PaneRegistry, +): ContextMenuActionConfig[] { const splitDownShortcut = useHotkeyDisplay("SPLIT_DOWN").text; const splitRightShortcut = useHotkeyDisplay("SPLIT_RIGHT").text; const splitWithChatShortcut = useHotkeyDisplay("SPLIT_WITH_CHAT").text; @@ -115,7 +122,7 @@ export function useDefaultContextMenuActions(): ContextMenuActionConfig[] = otherTabs.map((tab) => ({ key: `move-to-${tab.id}`, - label: tab.titleOverride ?? tab.id, + label: resolveTabTitle(tab, tabs, paneRegistry), onSelect: () => { ctx.store .getState() @@ -154,6 +161,7 @@ export function useDefaultContextMenuActions(): ContextMenuActionConfig, -): string | undefined { - const browser = getSingleBrowserPane(tab); - if (!browser) return undefined; - if (browser.data.pageTitle) return browser.data.pageTitle; - if (browser.data.url && browser.data.url !== "about:blank") { - try { - return new URL(browser.data.url).hostname; - } catch {} - } - return undefined; -} - export function renderBrowserTabIcon(tab: Tab) { const browser = getSingleBrowserPane(tab); if (!browser?.data.faviconUrl) return null; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/index.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/index.ts index ac9bea3ed43..e6b7ed03397 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/index.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/index.ts @@ -1,7 +1,6 @@ export { BrowserPane, BrowserPaneToolbar, - getBrowserTabTitle, renderBrowserTabIcon, } from "./BrowserPane"; export { browserRuntimeRegistry } from "./browserRuntimeRegistry"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx index d7373ba2258..4a18154f982 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx @@ -120,7 +120,8 @@ export function usePaneRegistry( const name = getFileName(data.filePath); return ; }, - getTitle: (ctx: RendererContext) => { + getTitle: (pane) => getFileName((pane.data as FilePaneData).filePath), + renderTitle: (ctx: RendererContext) => { const data = ctx.pane.data as FilePaneData; const name = getFileName(data.filePath); return ( @@ -262,9 +263,15 @@ export function usePaneRegistry( }, browser: { getIcon: () => , - getTitle: (ctx: RendererContext) => { - const data = ctx.pane.data as BrowserPaneData; - return data.pageTitle || data.url; + getTitle: (pane) => { + const data = pane.data as BrowserPaneData; + if (data.pageTitle) return data.pageTitle; + if (data.url && data.url !== "about:blank") { + try { + return new URL(data.url).hostname; + } catch {} + } + return undefined; }, renderPane: (ctx: RendererContext) => ( diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts index 87209a159d0..eb9e485fad9 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts @@ -84,7 +84,7 @@ export function useV2PresetExecution({ preset.commands[0] as string, ); state.addTab({ - titleOverride: preset.name || "Terminal", + titleOverride: preset.name || undefined, panes: [makeTerminalPane(id)], }); break; @@ -96,7 +96,7 @@ export function useV2PresetExecution({ ); const panes = ids.map((id) => makeTerminalPane(id)); state.addTab({ - titleOverride: preset.name || "Terminal", + titleOverride: preset.name || undefined, panes: panes.length > 0 ? (panes as [ @@ -114,7 +114,7 @@ export function useV2PresetExecution({ ); for (let i = 0; i < ids.length; i++) { state.addTab({ - titleOverride: preset.name || "Terminal", + titleOverride: preset.name || undefined, panes: [makeTerminalPane(ids[i] as string)], }); } @@ -127,7 +127,7 @@ export function useV2PresetExecution({ ); if (!activeTabId) { state.addTab({ - titleOverride: preset.name || "Terminal", + titleOverride: preset.name || undefined, panes: [makeTerminalPane(id)], }); break; @@ -146,7 +146,7 @@ export function useV2PresetExecution({ if (!activeTabId) { const panes = ids.map((id) => makeTerminalPane(id)); state.addTab({ - titleOverride: preset.name || "Terminal", + titleOverride: preset.name || undefined, panes: panes.length > 0 ? (panes as [ diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts index c791446a404..2de8bc35427 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts @@ -39,7 +39,6 @@ export function useWorkspaceHotkeys({ useHotkey("NEW_GROUP", () => { store.getState().addTab({ - titleOverride: "Terminal", panes: [ { kind: "terminal", @@ -51,14 +50,12 @@ export function useWorkspaceHotkeys({ useHotkey("NEW_CHAT", () => { store.getState().addTab({ - titleOverride: "Chat", panes: [{ kind: "chat", data: { sessionId: null } as ChatPaneData }], }); }); useHotkey("NEW_BROWSER", () => { store.getState().addTab({ - titleOverride: "Browser", panes: [ { kind: "browser", diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx index ca7271c5c81..50c115cf8d7 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx @@ -22,10 +22,7 @@ import { WorkspaceNotFoundState } from "./components/WorkspaceNotFoundState"; import { WorkspaceSidebar } from "./components/WorkspaceSidebar"; import { useDefaultContextMenuActions } from "./hooks/useDefaultContextMenuActions"; import { usePaneRegistry } from "./hooks/usePaneRegistry"; -import { - getBrowserTabTitle, - renderBrowserTabIcon, -} from "./hooks/usePaneRegistry/components/BrowserPane"; +import { renderBrowserTabIcon } from "./hooks/usePaneRegistry/components/BrowserPane"; import { useV2PresetExecution } from "./hooks/useV2PresetExecution"; import { useV2WorkspacePaneLayout } from "./hooks/useV2WorkspacePaneLayout"; import { useWorkspaceHotkeys } from "./hooks/useWorkspaceHotkeys"; @@ -93,7 +90,7 @@ function WorkspaceContent({ projectId, }); const paneRegistry = usePaneRegistry(workspaceId); - const defaultContextMenuActions = useDefaultContextMenuActions(); + const defaultContextMenuActions = useDefaultContextMenuActions(paneRegistry); const selectedFilePath = useStore(store, (s) => { const tab = s.tabs.find((t) => t.id === s.activeTabId); @@ -108,7 +105,6 @@ function WorkspaceContent({ const state = store.getState(); if (openInNewTab) { state.addTab({ - titleOverride: filePath.split(/[/\\]/).pop(), panes: [ { kind: "file", @@ -139,7 +135,6 @@ function WorkspaceContent({ hasChanges: false, } as FilePaneData, }, - tabTitle: "Files", }); }, [store], @@ -148,9 +143,8 @@ function WorkspaceContent({ const openDiffPane = useCallback( (filePath: string) => { const state = store.getState(); - const activeTab = state.tabs.find((t) => t.id === state.activeTabId); - if (activeTab) { - for (const pane of Object.values(activeTab.panes)) { + for (const tab of state.tabs) { + for (const pane of Object.values(tab.panes)) { if (pane.kind !== "diff") continue; const prev = pane.data as DiffPaneData; state.setPaneData({ @@ -160,19 +154,21 @@ function WorkspaceContent({ path: filePath, } as PaneViewerData, }); - state.setActivePane({ tabId: activeTab.id, paneId: pane.id }); + state.setActiveTab(tab.id); + state.setActivePane({ tabId: tab.id, paneId: pane.id }); return; } } - state.openPane({ - pane: { - kind: "diff", - data: { - path: filePath, - collapsedFiles: [], - } as DiffPaneData, - }, - tabTitle: "Changes", + state.addTab({ + panes: [ + { + kind: "diff", + data: { + path: filePath, + collapsedFiles: [], + } as DiffPaneData, + }, + ], }); }, [store], @@ -180,7 +176,6 @@ function WorkspaceContent({ const addTerminalTab = useCallback(() => { store.getState().addTab({ - titleOverride: "Terminal", panes: [ { kind: "terminal", @@ -194,7 +189,6 @@ function WorkspaceContent({ const addChatTab = useCallback(() => { store.getState().addTab({ - titleOverride: "Chat", panes: [ { kind: "chat", @@ -206,7 +200,6 @@ function WorkspaceContent({ const addBrowserTab = useCallback(() => { store.getState().addTab({ - titleOverride: "Browser", panes: [ { kind: "browser", @@ -270,7 +263,6 @@ function WorkspaceContent({ registry={paneRegistry} paneActions={defaultPaneActions} contextMenuActions={defaultContextMenuActions} - getTabTitle={getBrowserTabTitle} renderTabIcon={renderBrowserTabIcon} renderBelowTabBar={() => ( { store.getState().openPane({ pane: tp("p1", "opened"), - tabTitle: "My Tab", }); expect(store.getState().tabs).toHaveLength(1); - expect(store.getState().tabs[0]?.titleOverride).toBe("My Tab"); expect(store.getState().getActivePane()?.pane.data.label).toBe("opened"); }); diff --git a/packages/panes/src/core/store/store.ts b/packages/panes/src/core/store/store.ts index 06442cf2a33..34778b9cb25 100644 --- a/packages/panes/src/core/store/store.ts +++ b/packages/panes/src/core/store/store.ts @@ -121,7 +121,7 @@ export interface WorkspaceStore extends WorkspaceState { newPane: CreatePaneInput; }) => void; - openPane: (args: { pane: CreatePaneInput; tabTitle?: string }) => void; + openPane: (args: { pane: CreatePaneInput }) => void; splitPane: (args: { tabId: string; @@ -406,7 +406,6 @@ export function createWorkspaceStore( // No tab → create one if (!tab || !activeTabId) { get().addTab({ - titleOverride: args.tabTitle, panes: [args.pane], }); return; diff --git a/packages/panes/src/index.ts b/packages/panes/src/index.ts index 4e08a915239..a4070aa2dcf 100644 --- a/packages/panes/src/index.ts +++ b/packages/panes/src/index.ts @@ -17,7 +17,7 @@ export type { TabContext, WorkspaceProps, } from "./react"; -export { Workspace } from "./react"; +export { resolveTabTitle, Workspace } from "./react"; export type { LayoutNode, Pane, diff --git a/packages/panes/src/react/components/Workspace/Workspace.tsx b/packages/panes/src/react/components/Workspace/Workspace.tsx index 34847171540..f0d3a72dd87 100644 --- a/packages/panes/src/react/components/Workspace/Workspace.tsx +++ b/packages/panes/src/react/components/Workspace/Workspace.tsx @@ -5,6 +5,7 @@ import type { Pane } from "../../../types"; import type { WorkspaceProps } from "../../types"; import { Tab } from "./components/Tab"; import { TabBar } from "./components/TabBar"; +import { resolveTabTitle } from "./utils/resolveTabTitle"; export function Workspace({ store, @@ -12,7 +13,6 @@ export function Workspace({ className, renderTabAccessory, renderTabIcon, - getTabTitle, renderEmptyState, renderAddTabMenu, renderBelowTabBar, @@ -78,7 +78,7 @@ export function Workspace({ onReorderTab={(tabId, toIndex) => store.getState().reorderTab({ tabId, toIndex }) } - getTabTitle={(tab) => tab.titleOverride ?? getTabTitle?.(tab) ?? tab.id} + getTabTitle={(tab) => resolveTabTitle(tab, tabs, registry)} renderTabIcon={renderTabIcon} renderAddTabMenu={renderAddTabMenu} renderTabAccessory={renderTabAccessory} diff --git a/packages/panes/src/react/components/Workspace/components/Tab/components/Pane/Pane.tsx b/packages/panes/src/react/components/Workspace/components/Tab/components/Pane/Pane.tsx index 1ac744d2c51..22096e349fe 100644 --- a/packages/panes/src/react/components/Workspace/components/Tab/components/Pane/Pane.tsx +++ b/packages/panes/src/react/components/Workspace/components/Tab/components/Pane/Pane.tsx @@ -211,7 +211,7 @@ export function Pane({ } const title = definition - ? (pane.titleOverride ?? definition.getTitle?.(context) ?? pane.id) + ? (pane.titleOverride ?? definition.getTitle?.(pane) ?? pane.id) : `Unknown: ${pane.kind}`; const icon = definition?.getIcon?.(context); const titleContent = definition?.renderTitle?.(context); diff --git a/packages/panes/src/react/components/Workspace/index.ts b/packages/panes/src/react/components/Workspace/index.ts index e904b737efa..ff44d323596 100644 --- a/packages/panes/src/react/components/Workspace/index.ts +++ b/packages/panes/src/react/components/Workspace/index.ts @@ -1 +1,2 @@ +export { resolveTabTitle } from "./utils/resolveTabTitle"; export { Workspace } from "./Workspace"; diff --git a/packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts b/packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts new file mode 100644 index 00000000000..d2a5daf7f1a --- /dev/null +++ b/packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts @@ -0,0 +1,17 @@ +import type { Tab } from "../../../../types"; +import type { PaneRegistry } from "../../../types"; + +export function resolveTabTitle( + tab: Tab, + tabs: Tab[], + registry: PaneRegistry, +): string { + if (tab.titleOverride) return tab.titleOverride; + const panes = Object.values(tab.panes); + const onlyPane = panes.length === 1 ? panes[0] : undefined; + if (onlyPane) { + const fromRegistry = registry[onlyPane.kind]?.getTitle?.(onlyPane); + if (fromRegistry) return fromRegistry; + } + return `Tab ${tabs.indexOf(tab) + 1}`; +} diff --git a/packages/panes/src/react/index.ts b/packages/panes/src/react/index.ts index c54e0d47c8f..4d825812559 100644 --- a/packages/panes/src/react/index.ts +++ b/packages/panes/src/react/index.ts @@ -1,4 +1,4 @@ -export { Workspace } from "./components/Workspace"; +export { resolveTabTitle, Workspace } from "./components/Workspace"; export type { ContextMenuActionConfig, PaneActionConfig, diff --git a/packages/panes/src/react/types.ts b/packages/panes/src/react/types.ts index d21c1f8d0f2..ab4b3058de0 100644 --- a/packages/panes/src/react/types.ts +++ b/packages/panes/src/react/types.ts @@ -58,7 +58,7 @@ export interface RendererContext { export interface PaneDefinition { renderPane(context: RendererContext): ReactNode; - getTitle?(context: RendererContext): ReactNode; + getTitle?(pane: Pane): string | undefined; getIcon?(context: RendererContext): ReactNode; renderTitle?(context: RendererContext): ReactNode; renderHeaderExtras?(context: RendererContext): ReactNode; @@ -88,7 +88,6 @@ export interface WorkspaceProps { className?: string; renderTabAccessory?: (tab: Tab) => ReactNode; renderTabIcon?: (tab: Tab) => ReactNode; - getTabTitle?: (tab: Tab) => string | undefined; renderEmptyState?: () => ReactNode; renderAddTabMenu?: () => ReactNode; renderBelowTabBar?: () => ReactNode; From 17d466688921be72c26bfc930393850670b21d58 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 13 Apr 2026 09:55:55 -0700 Subject: [PATCH 2/4] feat(desktop): pane-derived tab titles reserve tab.titleOverride for user renames Preset execution and workspace bootstrap were baking preset.name / terminal.label onto tab.titleOverride, which meant those names persisted misleadingly after a tab was split and couldn't be distinguished from a real user rename. Move both to the pane's titleOverride instead, and teach resolveTabTitle to read pane.titleOverride before falling through to getTitle() for single-pane tabs. tab.titleOverride is now written only by the tab-bar rename action; splitting a named tab flips the label to "Tab N" while the pane keeps its name in its header, and user renames still win over everything. --- .../$pendingId/buildSetupPaneLayout.ts | 2 +- .../useV2PresetExecution.ts | 45 ++++++++++++------- .../Workspace/utils/resolveTabTitle.ts | 5 ++- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildSetupPaneLayout.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildSetupPaneLayout.ts index 6a8abfb8746..8221d5ff994 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildSetupPaneLayout.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildSetupPaneLayout.ts @@ -17,7 +17,6 @@ export function buildSetupPaneLayout( const tabId = `tab-${crypto.randomUUID()}`; return { id: tabId, - titleOverride: t.label, createdAt: Date.now(), activePaneId: paneId, layout: { type: "pane" as const, paneId }, @@ -25,6 +24,7 @@ export function buildSetupPaneLayout( [paneId]: { id: paneId, kind: "terminal", + titleOverride: t.label, data: { terminalId: t.id } as TerminalPaneData, }, }, diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts index eb9e485fad9..5cea7c54be9 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts @@ -10,9 +10,13 @@ import { filterMatchingPresetsForProject } from "shared/preset-project-targeting import type { StoreApi } from "zustand/vanilla"; import type { PaneViewerData, TerminalPaneData } from "../../types"; -function makeTerminalPane(terminalId: string): CreatePaneInput { +function makeTerminalPane( + terminalId: string, + titleOverride?: string, +): CreatePaneInput { return { kind: "terminal", + titleOverride, data: { terminalId } as TerminalPaneData, }; } @@ -84,8 +88,7 @@ export function useV2PresetExecution({ preset.commands[0] as string, ); state.addTab({ - titleOverride: preset.name || undefined, - panes: [makeTerminalPane(id)], + panes: [makeTerminalPane(id, preset.name || undefined)], }); break; } @@ -94,16 +97,22 @@ export function useV2PresetExecution({ const ids = await Promise.all( preset.commands.map((cmd) => createSessionWithCommand(cmd)), ); - const panes = ids.map((id) => makeTerminalPane(id)); + const panes = ids.map((id) => + makeTerminalPane(id, preset.name || undefined), + ); state.addTab({ - titleOverride: preset.name || undefined, panes: panes.length > 0 ? (panes as [ CreatePaneInput, ...CreatePaneInput[], ]) - : [makeTerminalPane(crypto.randomUUID())], + : [ + makeTerminalPane( + crypto.randomUUID(), + preset.name || undefined, + ), + ], }); break; } @@ -114,8 +123,9 @@ export function useV2PresetExecution({ ); for (let i = 0; i < ids.length; i++) { state.addTab({ - titleOverride: preset.name || undefined, - panes: [makeTerminalPane(ids[i] as string)], + panes: [ + makeTerminalPane(ids[i] as string, preset.name || undefined), + ], }); } break; @@ -127,14 +137,13 @@ export function useV2PresetExecution({ ); if (!activeTabId) { state.addTab({ - titleOverride: preset.name || undefined, - panes: [makeTerminalPane(id)], + panes: [makeTerminalPane(id, preset.name || undefined)], }); break; } state.addPane({ tabId: activeTabId, - pane: makeTerminalPane(id), + pane: makeTerminalPane(id, preset.name || undefined), }); break; } @@ -144,23 +153,29 @@ export function useV2PresetExecution({ preset.commands.map((cmd) => createSessionWithCommand(cmd)), ); if (!activeTabId) { - const panes = ids.map((id) => makeTerminalPane(id)); + const panes = ids.map((id) => + makeTerminalPane(id, preset.name || undefined), + ); state.addTab({ - titleOverride: preset.name || undefined, panes: panes.length > 0 ? (panes as [ CreatePaneInput, ...CreatePaneInput[], ]) - : [makeTerminalPane(crypto.randomUUID())], + : [ + makeTerminalPane( + crypto.randomUUID(), + preset.name || undefined, + ), + ], }); break; } for (const id of ids) { state.addPane({ tabId: activeTabId, - pane: makeTerminalPane(id), + pane: makeTerminalPane(id, preset.name || undefined), }); } break; diff --git a/packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts b/packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts index d2a5daf7f1a..388466a791a 100644 --- a/packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts +++ b/packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts @@ -10,8 +10,9 @@ export function resolveTabTitle( const panes = Object.values(tab.panes); const onlyPane = panes.length === 1 ? panes[0] : undefined; if (onlyPane) { - const fromRegistry = registry[onlyPane.kind]?.getTitle?.(onlyPane); - if (fromRegistry) return fromRegistry; + const fromPane = + onlyPane.titleOverride ?? registry[onlyPane.kind]?.getTitle?.(onlyPane); + if (fromPane) return fromPane; } return `Tab ${tabs.indexOf(tab) + 1}`; } From 409b5f769c0f768eedca002c7d1a9d3045f55c58 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 13 Apr 2026 10:03:28 -0700 Subject: [PATCH 3/4] fix(desktop): browser.getTitle falls back to "Browser" for about:blank Unnavigated browser panes had their pane header fall through to pane.id (a raw UUID) because getTitle returned undefined for about:blank and the old titleOverride: "Browser" default was removed along with the other auto-default titleOverride writes. --- .../$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx index 4a18154f982..2a4cd4272eb 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx @@ -271,7 +271,7 @@ export function usePaneRegistry( return new URL(data.url).hostname; } catch {} } - return undefined; + return "Browser"; }, renderPane: (ctx: RendererContext) => ( From 7effd34fb9ce0dbc019187a4fe5497590f950b82 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 13 Apr 2026 10:13:14 -0700 Subject: [PATCH 4/4] fix(desktop): browser tab title uses URL.host to preserve port URL.hostname drops the port, so localhost:3000 and localhost:4000 both rendered as "localhost" in the tab bar. URL.host keeps the port when one is explicitly set. --- .../$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx index 2a4cd4272eb..5b4b796ff86 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx @@ -268,7 +268,7 @@ export function usePaneRegistry( if (data.pageTitle) return data.pageTitle; if (data.url && data.url !== "about:blank") { try { - return new URL(data.url).hostname; + return new URL(data.url).host; } catch {} } return "Browser";