Skip to content

Commit 3dd1de2

Browse files
authored
feat(desktop): v2 diff viewer opens in its own tab + collapsed tab/pane title resolution (#3420)
* 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-<uuid>". * 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. * 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. * 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.
1 parent b18a00c commit 3dd1de2

File tree

17 files changed

+95
-76
lines changed

17 files changed

+95
-76
lines changed

apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildSetupPaneLayout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ export function buildSetupPaneLayout(
1717
const tabId = `tab-${crypto.randomUUID()}`;
1818
return {
1919
id: tabId,
20-
titleOverride: t.label,
2120
createdAt: Date.now(),
2221
activePaneId: paneId,
2322
layout: { type: "pane" as const, paneId },
2423
panes: {
2524
[paneId]: {
2625
id: paneId,
2726
kind: "terminal",
27+
titleOverride: t.label,
2828
data: { terminalId: t.id } as TerminalPaneData,
2929
},
3030
},

apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useDefaultContextMenuActions/useDefaultContextMenuActions.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { ContextMenuActionConfig, RendererContext } from "@superset/panes";
1+
import {
2+
type ContextMenuActionConfig,
3+
type PaneRegistry,
4+
type RendererContext,
5+
resolveTabTitle,
6+
} from "@superset/panes";
27
import { useMemo } from "react";
38
import {
49
LuColumns2,
@@ -18,7 +23,9 @@ import type {
1823
TerminalPaneData,
1924
} from "../../types";
2025

21-
export function useDefaultContextMenuActions(): ContextMenuActionConfig<PaneViewerData>[] {
26+
export function useDefaultContextMenuActions(
27+
paneRegistry: PaneRegistry<PaneViewerData>,
28+
): ContextMenuActionConfig<PaneViewerData>[] {
2229
const splitDownShortcut = useHotkeyDisplay("SPLIT_DOWN").text;
2330
const splitRightShortcut = useHotkeyDisplay("SPLIT_RIGHT").text;
2431
const splitWithChatShortcut = useHotkeyDisplay("SPLIT_WITH_CHAT").text;
@@ -115,7 +122,7 @@ export function useDefaultContextMenuActions(): ContextMenuActionConfig<PaneView
115122
const items: ContextMenuActionConfig<PaneViewerData>[] =
116123
otherTabs.map((tab) => ({
117124
key: `move-to-${tab.id}`,
118-
label: tab.titleOverride ?? tab.id,
125+
label: resolveTabTitle(tab, tabs, paneRegistry),
119126
onSelect: () => {
120127
ctx.store
121128
.getState()
@@ -154,6 +161,7 @@ export function useDefaultContextMenuActions(): ContextMenuActionConfig<PaneView
154161
splitWithBrowserShortcut,
155162
equalizePaneSplitsShortcut,
156163
closePaneShortcut,
164+
paneRegistry,
157165
],
158166
);
159167
}

apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/BrowserPane.tsx

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,6 @@ function getSingleBrowserPane(
2222
return { id: pane.id, data: pane.data as BrowserPaneData };
2323
}
2424

25-
export function getBrowserTabTitle(
26-
tab: Tab<PaneViewerData>,
27-
): string | undefined {
28-
const browser = getSingleBrowserPane(tab);
29-
if (!browser) return undefined;
30-
if (browser.data.pageTitle) return browser.data.pageTitle;
31-
if (browser.data.url && browser.data.url !== "about:blank") {
32-
try {
33-
return new URL(browser.data.url).hostname;
34-
} catch {}
35-
}
36-
return undefined;
37-
}
38-
3925
export function renderBrowserTabIcon(tab: Tab<PaneViewerData>) {
4026
const browser = getSingleBrowserPane(tab);
4127
if (!browser?.data.faviconUrl) return null;
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
export {
22
BrowserPane,
33
BrowserPaneToolbar,
4-
getBrowserTabTitle,
54
renderBrowserTabIcon,
65
} from "./BrowserPane";
76
export { browserRuntimeRegistry } from "./browserRuntimeRegistry";

apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ export function usePaneRegistry(
120120
const name = getFileName(data.filePath);
121121
return <FileIcon fileName={name} className="size-4" />;
122122
},
123-
getTitle: (ctx: RendererContext<PaneViewerData>) => {
123+
getTitle: (pane) => getFileName((pane.data as FilePaneData).filePath),
124+
renderTitle: (ctx: RendererContext<PaneViewerData>) => {
124125
const data = ctx.pane.data as FilePaneData;
125126
const name = getFileName(data.filePath);
126127
return (
@@ -262,9 +263,15 @@ export function usePaneRegistry(
262263
},
263264
browser: {
264265
getIcon: () => <Globe className="size-4" />,
265-
getTitle: (ctx: RendererContext<PaneViewerData>) => {
266-
const data = ctx.pane.data as BrowserPaneData;
267-
return data.pageTitle || data.url;
266+
getTitle: (pane) => {
267+
const data = pane.data as BrowserPaneData;
268+
if (data.pageTitle) return data.pageTitle;
269+
if (data.url && data.url !== "about:blank") {
270+
try {
271+
return new URL(data.url).host;
272+
} catch {}
273+
}
274+
return "Browser";
268275
},
269276
renderPane: (ctx: RendererContext<PaneViewerData>) => (
270277
<BrowserPane ctx={ctx} />

apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@ import { filterMatchingPresetsForProject } from "shared/preset-project-targeting
1010
import type { StoreApi } from "zustand/vanilla";
1111
import type { PaneViewerData, TerminalPaneData } from "../../types";
1212

13-
function makeTerminalPane(terminalId: string): CreatePaneInput<PaneViewerData> {
13+
function makeTerminalPane(
14+
terminalId: string,
15+
titleOverride?: string,
16+
): CreatePaneInput<PaneViewerData> {
1417
return {
1518
kind: "terminal",
19+
titleOverride,
1620
data: { terminalId } as TerminalPaneData,
1721
};
1822
}
@@ -84,8 +88,7 @@ export function useV2PresetExecution({
8488
preset.commands[0] as string,
8589
);
8690
state.addTab({
87-
titleOverride: preset.name || "Terminal",
88-
panes: [makeTerminalPane(id)],
91+
panes: [makeTerminalPane(id, preset.name || undefined)],
8992
});
9093
break;
9194
}
@@ -94,16 +97,22 @@ export function useV2PresetExecution({
9497
const ids = await Promise.all(
9598
preset.commands.map((cmd) => createSessionWithCommand(cmd)),
9699
);
97-
const panes = ids.map((id) => makeTerminalPane(id));
100+
const panes = ids.map((id) =>
101+
makeTerminalPane(id, preset.name || undefined),
102+
);
98103
state.addTab({
99-
titleOverride: preset.name || "Terminal",
100104
panes:
101105
panes.length > 0
102106
? (panes as [
103107
CreatePaneInput<PaneViewerData>,
104108
...CreatePaneInput<PaneViewerData>[],
105109
])
106-
: [makeTerminalPane(crypto.randomUUID())],
110+
: [
111+
makeTerminalPane(
112+
crypto.randomUUID(),
113+
preset.name || undefined,
114+
),
115+
],
107116
});
108117
break;
109118
}
@@ -114,8 +123,9 @@ export function useV2PresetExecution({
114123
);
115124
for (let i = 0; i < ids.length; i++) {
116125
state.addTab({
117-
titleOverride: preset.name || "Terminal",
118-
panes: [makeTerminalPane(ids[i] as string)],
126+
panes: [
127+
makeTerminalPane(ids[i] as string, preset.name || undefined),
128+
],
119129
});
120130
}
121131
break;
@@ -127,14 +137,13 @@ export function useV2PresetExecution({
127137
);
128138
if (!activeTabId) {
129139
state.addTab({
130-
titleOverride: preset.name || "Terminal",
131-
panes: [makeTerminalPane(id)],
140+
panes: [makeTerminalPane(id, preset.name || undefined)],
132141
});
133142
break;
134143
}
135144
state.addPane({
136145
tabId: activeTabId,
137-
pane: makeTerminalPane(id),
146+
pane: makeTerminalPane(id, preset.name || undefined),
138147
});
139148
break;
140149
}
@@ -144,23 +153,29 @@ export function useV2PresetExecution({
144153
preset.commands.map((cmd) => createSessionWithCommand(cmd)),
145154
);
146155
if (!activeTabId) {
147-
const panes = ids.map((id) => makeTerminalPane(id));
156+
const panes = ids.map((id) =>
157+
makeTerminalPane(id, preset.name || undefined),
158+
);
148159
state.addTab({
149-
titleOverride: preset.name || "Terminal",
150160
panes:
151161
panes.length > 0
152162
? (panes as [
153163
CreatePaneInput<PaneViewerData>,
154164
...CreatePaneInput<PaneViewerData>[],
155165
])
156-
: [makeTerminalPane(crypto.randomUUID())],
166+
: [
167+
makeTerminalPane(
168+
crypto.randomUUID(),
169+
preset.name || undefined,
170+
),
171+
],
157172
});
158173
break;
159174
}
160175
for (const id of ids) {
161176
state.addPane({
162177
tabId: activeTabId,
163-
pane: makeTerminalPane(id),
178+
pane: makeTerminalPane(id, preset.name || undefined),
164179
});
165180
}
166181
break;

apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ export function useWorkspaceHotkeys({
3939

4040
useHotkey("NEW_GROUP", () => {
4141
store.getState().addTab({
42-
titleOverride: "Terminal",
4342
panes: [
4443
{
4544
kind: "terminal",
@@ -51,14 +50,12 @@ export function useWorkspaceHotkeys({
5150

5251
useHotkey("NEW_CHAT", () => {
5352
store.getState().addTab({
54-
titleOverride: "Chat",
5553
panes: [{ kind: "chat", data: { sessionId: null } as ChatPaneData }],
5654
});
5755
});
5856

5957
useHotkey("NEW_BROWSER", () => {
6058
store.getState().addTab({
61-
titleOverride: "Browser",
6259
panes: [
6360
{
6461
kind: "browser",

apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ import { WorkspaceNotFoundState } from "./components/WorkspaceNotFoundState";
2222
import { WorkspaceSidebar } from "./components/WorkspaceSidebar";
2323
import { useDefaultContextMenuActions } from "./hooks/useDefaultContextMenuActions";
2424
import { usePaneRegistry } from "./hooks/usePaneRegistry";
25-
import {
26-
getBrowserTabTitle,
27-
renderBrowserTabIcon,
28-
} from "./hooks/usePaneRegistry/components/BrowserPane";
25+
import { renderBrowserTabIcon } from "./hooks/usePaneRegistry/components/BrowserPane";
2926
import { useV2PresetExecution } from "./hooks/useV2PresetExecution";
3027
import { useV2WorkspacePaneLayout } from "./hooks/useV2WorkspacePaneLayout";
3128
import { useWorkspaceHotkeys } from "./hooks/useWorkspaceHotkeys";
@@ -93,7 +90,7 @@ function WorkspaceContent({
9390
projectId,
9491
});
9592
const paneRegistry = usePaneRegistry(workspaceId);
96-
const defaultContextMenuActions = useDefaultContextMenuActions();
93+
const defaultContextMenuActions = useDefaultContextMenuActions(paneRegistry);
9794

9895
const selectedFilePath = useStore(store, (s) => {
9996
const tab = s.tabs.find((t) => t.id === s.activeTabId);
@@ -108,7 +105,6 @@ function WorkspaceContent({
108105
const state = store.getState();
109106
if (openInNewTab) {
110107
state.addTab({
111-
titleOverride: filePath.split(/[/\\]/).pop(),
112108
panes: [
113109
{
114110
kind: "file",
@@ -139,7 +135,6 @@ function WorkspaceContent({
139135
hasChanges: false,
140136
} as FilePaneData,
141137
},
142-
tabTitle: "Files",
143138
});
144139
},
145140
[store],
@@ -148,9 +143,8 @@ function WorkspaceContent({
148143
const openDiffPane = useCallback(
149144
(filePath: string) => {
150145
const state = store.getState();
151-
const activeTab = state.tabs.find((t) => t.id === state.activeTabId);
152-
if (activeTab) {
153-
for (const pane of Object.values(activeTab.panes)) {
146+
for (const tab of state.tabs) {
147+
for (const pane of Object.values(tab.panes)) {
154148
if (pane.kind !== "diff") continue;
155149
const prev = pane.data as DiffPaneData;
156150
state.setPaneData({
@@ -160,27 +154,28 @@ function WorkspaceContent({
160154
path: filePath,
161155
} as PaneViewerData,
162156
});
163-
state.setActivePane({ tabId: activeTab.id, paneId: pane.id });
157+
state.setActiveTab(tab.id);
158+
state.setActivePane({ tabId: tab.id, paneId: pane.id });
164159
return;
165160
}
166161
}
167-
state.openPane({
168-
pane: {
169-
kind: "diff",
170-
data: {
171-
path: filePath,
172-
collapsedFiles: [],
173-
} as DiffPaneData,
174-
},
175-
tabTitle: "Changes",
162+
state.addTab({
163+
panes: [
164+
{
165+
kind: "diff",
166+
data: {
167+
path: filePath,
168+
collapsedFiles: [],
169+
} as DiffPaneData,
170+
},
171+
],
176172
});
177173
},
178174
[store],
179175
);
180176

181177
const addTerminalTab = useCallback(() => {
182178
store.getState().addTab({
183-
titleOverride: "Terminal",
184179
panes: [
185180
{
186181
kind: "terminal",
@@ -194,7 +189,6 @@ function WorkspaceContent({
194189

195190
const addChatTab = useCallback(() => {
196191
store.getState().addTab({
197-
titleOverride: "Chat",
198192
panes: [
199193
{
200194
kind: "chat",
@@ -206,7 +200,6 @@ function WorkspaceContent({
206200

207201
const addBrowserTab = useCallback(() => {
208202
store.getState().addTab({
209-
titleOverride: "Browser",
210203
panes: [
211204
{
212205
kind: "browser",
@@ -270,7 +263,6 @@ function WorkspaceContent({
270263
registry={paneRegistry}
271264
paneActions={defaultPaneActions}
272265
contextMenuActions={defaultContextMenuActions}
273-
getTabTitle={getBrowserTabTitle}
274266
renderTabIcon={renderBrowserTabIcon}
275267
renderBelowTabBar={() => (
276268
<V2PresetsBar

packages/panes/src/core/store/store.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,11 +433,9 @@ describe("openPane", () => {
433433

434434
store.getState().openPane({
435435
pane: tp("p1", "opened"),
436-
tabTitle: "My Tab",
437436
});
438437

439438
expect(store.getState().tabs).toHaveLength(1);
440-
expect(store.getState().tabs[0]?.titleOverride).toBe("My Tab");
441439
expect(store.getState().getActivePane()?.pane.data.label).toBe("opened");
442440
});
443441

packages/panes/src/core/store/store.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export interface WorkspaceStore<TData> extends WorkspaceState<TData> {
121121
newPane: CreatePaneInput<TData>;
122122
}) => void;
123123

124-
openPane: (args: { pane: CreatePaneInput<TData>; tabTitle?: string }) => void;
124+
openPane: (args: { pane: CreatePaneInput<TData> }) => void;
125125

126126
splitPane: (args: {
127127
tabId: string;
@@ -406,7 +406,6 @@ export function createWorkspaceStore<TData>(
406406
// No tab → create one
407407
if (!tab || !activeTabId) {
408408
get().addTab({
409-
titleOverride: args.tabTitle,
410409
panes: [args.pane],
411410
});
412411
return;

0 commit comments

Comments
 (0)