From 97d3600437515d2e4a391478b998bde191022d24 Mon Sep 17 00:00:00 2001 From: Aaron Rosenthal Date: Sat, 7 Nov 2026 05:12:44 -0500 Subject: [PATCH 1/2] fix(desktop): select the agent tab when opening a CLI-created workspace Auto-adopted terminals each became the active tab, and the previous tab was restored only when the layout already had tabs. A new workspace from `superset ws create --agent` therefore opened on the setup tab. In an empty layout, select the oldest terminal; the host starts the agent first. Refs #9260 --- .../useAutoAdoptBackgroundSessions.ts | 15 +++---- .../adoptTerminalPanes.test.ts | 44 +++++++++++++++++++ .../adoptTerminalPanes/adoptTerminalPanes.ts | 33 ++++++++++++++ .../utils/adoptTerminalPanes/index.ts | 1 + 4 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.test.ts create mode 100644 apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.ts create mode 100644 apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/index.ts diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptBackgroundSessions/useAutoAdoptBackgroundSessions.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptBackgroundSessions/useAutoAdoptBackgroundSessions.ts index bd43067ddbc..5033b7b9f70 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptBackgroundSessions/useAutoAdoptBackgroundSessions.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptBackgroundSessions/useAutoAdoptBackgroundSessions.ts @@ -5,13 +5,13 @@ import { logStressEvent } from "renderer/lib/performance/stress-instrumentation" import { getTerminalBackgroundMarkerIdsKey } from "renderer/lib/terminal/terminal-background-intents"; import type { StoreApi } from "zustand/vanilla"; import type { PaneViewerData } from "../../types"; +import { adoptTerminalPanes } from "../../utils/adoptTerminalPanes"; import { getAttachedTerminalIdsKey, getBackgroundTerminalSessions, parseAttachedTerminalIdsKey, } from "../../utils/backgroundTerminals"; import { isTerminalReplacementCancelled } from "../../utils/cancelledTerminalReplacements"; -import { focusOrAddTerminalPane } from "../../utils/focusTerminalPane"; interface UseAutoAdoptBackgroundSessionsArgs { store: StoreApi>; @@ -75,15 +75,10 @@ export function useAutoAdoptBackgroundSessions({ ); if (toAdopt.length === 0) return; - // Oldest→newest so tabs read chronologically; restore the active tab so - // adoption never steals focus. - const restoreActiveTabId = state.tabs.length > 0 ? state.activeTabId : null; - for (const session of toAdopt.reverse()) { - focusOrAddTerminalPane(store, session.terminalId, { focus: false }); - } - if (restoreActiveTabId) { - store.getState().setActiveTab(restoreActiveTabId); - } + adoptTerminalPanes( + store, + toAdopt.reverse().map((session) => session.terminalId), + ); logStressEvent("background-terminals.auto-adopt", { count: toAdopt.length, workspaceId, diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.test.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.test.ts new file mode 100644 index 00000000000..60b36a0b0a4 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "bun:test"; +import { createWorkspaceStore } from "@superset/panes"; +import type { PaneViewerData } from "../../types"; +import { adoptTerminalPanes } from "./adoptTerminalPanes"; + +function tabTerminalIds(store: ReturnType) { + return store + .getState() + .tabs.map( + (tab) => + (Object.values(tab.panes)[0]?.data as { terminalId: string }) + .terminalId, + ); +} + +function activeTerminalId(store: ReturnType) { + const state = store.getState(); + const tab = state.tabs.find((t) => t.id === state.activeTabId); + return (Object.values(tab?.panes ?? {})[0]?.data as { terminalId: string }) + ?.terminalId; +} + +function emptyStore() { + return createWorkspaceStore({ + initialState: { version: 1, tabs: [], activeTabId: null }, + }); +} + +describe("adoptTerminalPanes", () => { + it("selects the oldest terminal when the layout was empty", () => { + const store = emptyStore(); + adoptTerminalPanes(store, ["agent", "setup"]); + expect(tabTerminalIds(store)).toEqual(["agent", "setup"]); + expect(activeTerminalId(store)).toBe("agent"); + }); + + it("keeps the tab the user already had selected", () => { + const store = emptyStore(); + adoptTerminalPanes(store, ["existing"]); + adoptTerminalPanes(store, ["agent", "setup"]); + expect(tabTerminalIds(store)).toEqual(["existing", "agent", "setup"]); + expect(activeTerminalId(store)).toBe("existing"); + }); +}); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.ts new file mode 100644 index 00000000000..fa9ea72d905 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.ts @@ -0,0 +1,33 @@ +import type { WorkspaceStore } from "@superset/panes"; +import type { StoreApi } from "zustand/vanilla"; +import type { PaneViewerData } from "../../types"; +import { focusOrAddTerminalPane } from "../focusTerminalPane"; + +/** + * Give each terminal a tab, oldest first, without moving the selection off a + * tab the user already has. In an empty layout the oldest terminal is + * selected: the host starts an independent agent before the setup terminal. + */ +export function adoptTerminalPanes( + store: StoreApi>, + oldestFirstTerminalIds: readonly string[], +): void { + const [firstTerminalId] = oldestFirstTerminalIds; + if (firstTerminalId === undefined) return; + + const previousActiveTabId = store.getState().activeTabId; + for (const terminalId of oldestFirstTerminalIds) { + focusOrAddTerminalPane(store, terminalId, { focus: false }); + } + + const state = store.getState(); + const selectedTabId = + previousActiveTabId ?? + state.tabs.find((tab) => + Object.values(tab.panes).some( + (pane) => + (pane.data as { terminalId?: string }).terminalId === firstTerminalId, + ), + )?.id; + if (selectedTabId) state.setActiveTab(selectedTabId); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/index.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/index.ts new file mode 100644 index 00000000000..78fba0bbd87 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/index.ts @@ -0,0 +1 @@ +export { adoptTerminalPanes } from "./adoptTerminalPanes"; From 030684c23a15842acdfda1d27da103a60eadd0d4 Mon Sep 17 00:00:00 2001 From: Aaron Rosenthal Date: Sat, 7 Nov 2026 05:19:08 -0500 Subject: [PATCH 2/2] refactor(desktop): reuse findTerminalPaneLocation in adoptTerminalPanes --- .../utils/adoptTerminalPanes/adoptTerminalPanes.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.ts index fa9ea72d905..728e56578e8 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/adoptTerminalPanes/adoptTerminalPanes.ts @@ -1,7 +1,10 @@ import type { WorkspaceStore } from "@superset/panes"; import type { StoreApi } from "zustand/vanilla"; import type { PaneViewerData } from "../../types"; -import { focusOrAddTerminalPane } from "../focusTerminalPane"; +import { + findTerminalPaneLocation, + focusOrAddTerminalPane, +} from "../focusTerminalPane"; /** * Give each terminal a tab, oldest first, without moving the selection off a @@ -23,11 +26,6 @@ export function adoptTerminalPanes( const state = store.getState(); const selectedTabId = previousActiveTabId ?? - state.tabs.find((tab) => - Object.values(tab.panes).some( - (pane) => - (pane.data as { terminalId?: string }).terminalId === firstTerminalId, - ), - )?.id; + findTerminalPaneLocation(state, firstTerminalId)?.tabId; if (selectedTabId) state.setActiveTab(selectedTabId); }