From 515feb71ccb76350c5a696b11b59718b8cd764ce Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:47:38 -0700 Subject: [PATCH] fix(agent-rows): preserve same-tab child labels --- .../use-agent-row-conversation-name.test.ts | 38 +++++++++++++++++++ .../use-agent-row-conversation-name.ts | 18 ++++++--- ....lineage-agent-expansion-coupling.test.tsx | 5 ++- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/components/dashboard/use-agent-row-conversation-name.test.ts b/src/renderer/src/components/dashboard/use-agent-row-conversation-name.test.ts index 048fa6eac66..a56d0c24cfd 100644 --- a/src/renderer/src/components/dashboard/use-agent-row-conversation-name.test.ts +++ b/src/renderer/src/components/dashboard/use-agent-row-conversation-name.test.ts @@ -56,6 +56,44 @@ describe('useAgentRowConversationName', () => { expect(useAgentRowConversationName(makeAgent({ rowSource: 'subagent' }))).toBeNull() }) + it('does not inherit a same-tab lineage parent conversation name', () => { + const tabsByWorktree = new Proxy( + {}, + { + get: () => { + throw new Error('same-tab child rows must not read the parent tab') + } + } + ) + storeState.current = { settings: {}, tabsByWorktree } + expect( + useAgentRowConversationName( + makeAgent({ + entry: { + prompt: 'child prompt', + orchestration: { + parentPaneKey: 'tab-1:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' + } + }, + lineage: { depth: 1, isFirstSibling: true, isLastSibling: true, childCount: 0 } + } as Partial) + ) + ).toBeNull() + }) + + it('uses a lineage child conversation name when it owns a separate tab', () => { + const agent = makeAgent({ + entry: { + prompt: 'child prompt', + orchestration: { + parentPaneKey: 'parent-tab:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' + } + }, + lineage: { depth: 1, isFirstSibling: true, isLastSibling: true, childCount: 0 } + } as Partial) + expect(useAgentRowConversationName(agent)).toBe('Patient sync spike') + }) + it('indexes one immutable tab array once across rows', () => { let tabReads = 0 const tabs = new Proxy( diff --git a/src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts b/src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts index 5aaecef8506..820fea5da0b 100644 --- a/src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts +++ b/src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts @@ -1,4 +1,5 @@ import { getAgentRowConversationName } from '../../../../shared/agent-row-conversation-name' +import { parsePaneKey } from '../../../../shared/stable-pane-id' import { useAppStore } from '@/store' import type { AppState } from '@/store/types' import type { DashboardAgentRow } from './useDashboardData' @@ -24,15 +25,22 @@ function getIndexedTab( /** The row's conversation name, or null when nothing usable exists. */ export function useAgentRowConversationName(agent: DashboardAgentRow): string | null { - const isSubagent = agent.rowSource === 'subagent' + const parentPaneKey = agent.entry.orchestration?.parentPaneKey + const usesParentTab = + agent.lineage?.depth === 1 && + parentPaneKey !== undefined && + parsePaneKey(parentPaneKey)?.tabId === agent.tab.id + const cannotOwnTabName = agent.rowSource === 'subagent' || usesParentTab const generatedTitlesEnabled = useAppStore( - (s) => !isSubagent && s.settings?.tabAutoGenerateTitle === true + (s) => !cannotOwnTabName && s.settings?.tabAutoGenerateTitle === true ) - // Why: child rows describe the child, so the parent tab would mislabel them. const liveTab = useAppStore((s) => - isSubagent ? undefined : getIndexedTab(s.tabsByWorktree[agent.tab.worktreeId], agent.tab.id) + cannotOwnTabName + ? undefined + : getIndexedTab(s.tabsByWorktree[agent.tab.worktreeId], agent.tab.id) ) - if (isSubagent) { + // Why: synthetic and same-tab child rows do not own the parent tab's name. + if (cannotOwnTabName) { return null } // Why: retained row snapshots need a fallback after their live tab disappears. diff --git a/src/renderer/src/components/sidebar/WorktreeList.lineage-agent-expansion-coupling.test.tsx b/src/renderer/src/components/sidebar/WorktreeList.lineage-agent-expansion-coupling.test.tsx index 6190a6acd5a..82630b66d8b 100644 --- a/src/renderer/src/components/sidebar/WorktreeList.lineage-agent-expansion-coupling.test.tsx +++ b/src/renderer/src/components/sidebar/WorktreeList.lineage-agent-expansion-coupling.test.tsx @@ -474,8 +474,9 @@ describe('WorktreeCard agent-list <-> child-worktrees expansion coupling', () => setAgentLineageState({ agentActivityDisplayMode: 'full' }) const { container } = await renderWorktreeList() - // Sanity: real agent rows rendered (parent + its lineage child agent). - expect(container.textContent).toContain('PARENT_AGENT_PROMPT') + // Same-tab lineage children must not inherit the parent's conversation name. + expect(container.textContent).toContain('Parent Terminal') + expect(container.textContent).not.toContain('PARENT_AGENT_PROMPT') expect(container.textContent).toContain('CHILD_AGENT_PROMPT') // Both controls exist and are independent DOM elements.