From 1963ec767f1fe3357bb5bcb4147156e87a1c1a83 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 13 Aug 2026 01:36:45 -0700 Subject: [PATCH] fix(mobile): preserve leaf title projection --- .../components/terminal-pane/TerminalPane.tsx | 4 ++- ...ync-runtime-graph-publication-cost.test.ts | 3 +- ...sync-runtime-graph-terminal-layout.test.ts | 28 ++++++++++++++++++ .../src/runtime/sync-runtime-graph.test.ts | 3 +- .../src/runtime/sync-runtime-graph.ts | 29 ++++++++++--------- 5 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/renderer/src/components/terminal-pane/TerminalPane.tsx b/src/renderer/src/components/terminal-pane/TerminalPane.tsx index 3e8a4a08796..2c2dd44cf92 100644 --- a/src/renderer/src/components/terminal-pane/TerminalPane.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalPane.tsx @@ -549,7 +549,9 @@ function TerminalPane( return leafIds.length === 1 ? leafIds[0] : null }, [getNativeChatLeafIds, tabWideAgentHintLeafId]) const getTabWideAgentHintLeafIdRef = useRef(getTabWideAgentHintLeafId) - getTabWideAgentHintLeafIdRef.current = getTabWideAgentHintLeafId + useEffect(() => { + getTabWideAgentHintLeafIdRef.current = getTabWideAgentHintLeafId + }, [getTabWideAgentHintLeafId]) useEffect(() => { if (tabWideAgentHintLeafId !== undefined) { return diff --git a/src/renderer/src/runtime/sync-runtime-graph-publication-cost.test.ts b/src/renderer/src/runtime/sync-runtime-graph-publication-cost.test.ts index 3c285761cf5..2470a868f9a 100644 --- a/src/renderer/src/runtime/sync-runtime-graph-publication-cost.test.ts +++ b/src/renderer/src/runtime/sync-runtime-graph-publication-cost.test.ts @@ -289,7 +289,8 @@ function makeLiveSurface(tabId: string): { worktreeId: MOUNTED_WT, getManager: () => manager, getContainer: () => null, - getPtyIdForPane: (paneId: number) => ptyIdByPaneId.get(paneId) ?? null + getPtyIdForPane: (paneId: number) => ptyIdByPaneId.get(paneId) ?? null, + getTabWideAgentHintLeafId: () => MOUNTED_LEAF_ID } as unknown as Parameters[0] return { registration, diff --git a/src/renderer/src/runtime/sync-runtime-graph-terminal-layout.test.ts b/src/renderer/src/runtime/sync-runtime-graph-terminal-layout.test.ts index 385a7764fd1..dbfe8f75bc3 100644 --- a/src/renderer/src/runtime/sync-runtime-graph-terminal-layout.test.ts +++ b/src/renderer/src/runtime/sync-runtime-graph-terminal-layout.test.ts @@ -553,4 +553,32 @@ describe('terminal mobile session layout publication', () => { 'parentLayout.titlesByLeafId' ) }) + + it('keeps pane-owned titles across split parking', () => { + const firstLeaf = '11111111-1111-4111-8111-111111111111' + const secondLeaf = '22222222-2222-4222-8222-222222222222' + const splitState = makeState({ + tabsByWorktree: { + 'wt-1': [{ id: 'term-1', title: 'Leaked tab title', customTitle: null }] + } as unknown as AppState['tabsByWorktree'], + terminalLayoutsByTabId: { + 'term-1': { + root: { + type: 'split', + direction: 'horizontal', + first: { type: 'leaf', leafId: firstLeaf }, + second: { type: 'leaf', leafId: secondLeaf } + }, + activeLeafId: firstLeaf, + expandedLeafId: null, + titlesByLeafId: { [firstLeaf]: 'agent pane', [secondLeaf]: 'shell pane' } + } + } as AppState['terminalLayoutsByTabId'] + }) + + expect(buildMobileSessionTabSnapshots(splitState)[0]?.tabs).toMatchObject([ + { leafId: firstLeaf, title: 'agent pane' }, + { leafId: secondLeaf, title: 'shell pane' } + ]) + }) }) diff --git a/src/renderer/src/runtime/sync-runtime-graph.test.ts b/src/renderer/src/runtime/sync-runtime-graph.test.ts index 8f19058beab..93ab169dbc2 100644 --- a/src/renderer/src/runtime/sync-runtime-graph.test.ts +++ b/src/renderer/src/runtime/sync-runtime-graph.test.ts @@ -1038,7 +1038,8 @@ describe('buildMobileSessionTabSnapshots', () => { text: 'inspect mobile routing', createdAt: 2 } - } + }, + runtimePaneTitlesByTabId: { 'term-collapse': { 1: 'Codex live title' } } }) expect(buildMobileSessionTabSnapshots(state)[0]?.tabs).toEqual([ diff --git a/src/renderer/src/runtime/sync-runtime-graph.ts b/src/renderer/src/runtime/sync-runtime-graph.ts index 4c717053a7f..52872628116 100644 --- a/src/renderer/src/runtime/sync-runtime-graph.ts +++ b/src/renderer/src/runtime/sync-runtime-graph.ts @@ -1848,10 +1848,13 @@ function buildMobileLaunchDraftsByPaneKey(args: { NonNullable[string] > }): MobileSessionWorktreeInputs['launchDraftByPaneKey'] { - const draftsByPaneKey = new Map< + if (args.launchDraftByTabId.size === 0) { + return EMPTY_NARROWED_BY_KEY + } + let draftsByPaneKey: Map< string, NonNullable[string] - >() + > | null = null for (const terminal of args.terminalTabs) { const draft = args.launchDraftByTabId.get(terminal.id) if (!draft || draft.resolved) { @@ -1870,9 +1873,10 @@ function buildMobileLaunchDraftsByPaneKey(args: { if (!launchAgent || launchAgent !== draft.agent || !ownerLeafId) { continue } + draftsByPaneKey ??= new Map() draftsByPaneKey.set(makePaneKey(terminal.id, ownerLeafId), draft) } - return draftsByPaneKey + return draftsByPaneKey ?? EMPTY_NARROWED_BY_KEY } function buildMobileTerminalSurfaceTabs( @@ -1929,19 +1933,18 @@ function buildMobileTerminalSurfaceTabs( : legacyPaneId ? paneTitles[Number(legacyPaneId)] : undefined + const leafTitle = paneTitle?.trim() || sanitizedSavedLayout?.titlesByLeafId?.[leafId]?.trim() const paneKey = isTerminalLeafId(leafId) ? makePaneKey(terminal.id, leafId) : null const tabWideFallbackSafe = isNativeChatTabWideFallbackSafe(parentLayout) && launchAgentLeafId === leafId - const title = - paneTitle?.trim() || - (tabWideFallbackSafe - ? resolveRuntimeTerminalTitle( - terminal, - generatedTitlesEnabled, - terminal.title ?? 'Terminal' - ) - : 'Terminal') - const agentStatusTitle = paneTitle ?? (tabWideFallbackSafe ? terminal.title : '') ?? '' + const title = tabWideFallbackSafe + ? resolveRuntimeTerminalTitle( + terminal, + generatedTitlesEnabled, + leafTitle ?? terminal.title ?? 'Terminal' + ) + : (leafTitle ?? 'Terminal') + const agentStatusTitle = leafTitle ?? (tabWideFallbackSafe ? terminal.title : '') ?? '' const agentStatus = paneKey && !isClaudeManagementTitle(agentStatusTitle) ? inputs.agentStatusByPaneKey.get(paneKey)