From a839195d01a432d72783fccd8a235fbb288a975d Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 22 Jul 2026 14:19:20 -0700 Subject: [PATCH] fix(agent-rows): read the live tab so late renames and titles surface Row data patches live entries in place and keeps the tab snapshot from row creation, so a rename or agent-set title landing after the row was built never reached the conversation-name resolver. Select the current tab from the store in the hook; retained rows without a live tab keep the snapshot fallback. --- .../use-agent-row-conversation-name.test.ts | 30 ++++++++++++++----- .../use-agent-row-conversation-name.ts | 8 ++++- 2 files changed, 30 insertions(+), 8 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 11e1f1ea656..dcefeb8b619 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 @@ -4,7 +4,10 @@ import { useAgentRowConversationName } from './use-agent-row-conversation-name' import type { DashboardAgentRow } from './useDashboardData' const storeState = vi.hoisted(() => ({ - current: { settings: {} } as { current?: unknown; settings: Record } + current: { settings: {}, tabsByWorktree: {} } as { + settings: Record + tabsByWorktree: Record + } })) // Why: the mocked selector makes the hook a pure function, so tests can call it @@ -18,7 +21,7 @@ function makeAgent(overrides: Partial = {}): DashboardAgentRo return { paneKey: 'tab-1:leaf-1', entry: { prompt: 'fix the sidebar' }, - tab: { customTitle: 'Patient sync spike', title: '' }, + tab: { id: 'tab-1', worktreeId: 'wt-1', customTitle: 'Patient sync spike', title: '' }, agentType: 'claude', state: 'working', startedAt: 0, @@ -27,7 +30,7 @@ function makeAgent(overrides: Partial = {}): DashboardAgentRo } beforeEach(() => { - storeState.current = { settings: {} } + storeState.current = { settings: {}, tabsByWorktree: {} } }) describe('useAgentRowConversationName', () => { @@ -36,23 +39,36 @@ describe('useAgentRowConversationName', () => { }) it('returns the conversation name when the setting is on', () => { - storeState.current = { settings: { agentRowsUseConversationName: true } } + storeState.current = { settings: { agentRowsUseConversationName: true }, tabsByWorktree: {} } expect(useAgentRowConversationName(makeAgent())).toBe('Patient sync spike') }) it('never renames subagent child rows after the parent tab', () => { - storeState.current = { settings: { agentRowsUseConversationName: true } } + storeState.current = { settings: { agentRowsUseConversationName: true }, tabsByWorktree: {} } expect(useAgentRowConversationName(makeAgent({ rowSource: 'subagent' }))).toBeNull() }) + it('prefers the live store tab over the stale row snapshot', () => { + storeState.current = { + settings: { agentRowsUseConversationName: true }, + // Why: row data patches entries in place and keeps the creation-time tab + // snapshot; a rename landing after that must still surface. + tabsByWorktree: { + 'wt-1': [{ id: 'tab-1', worktreeId: 'wt-1', customTitle: 'Renamed later', title: '' }] + } + } + expect(useAgentRowConversationName(makeAgent())).toBe('Renamed later') + }) + it('honors the generated-titles setting for generated names', () => { const agent = makeAgent({ tab: { customTitle: null, title: '', generatedTitle: 'Fix intake flow' } } as Partial) - storeState.current = { settings: { agentRowsUseConversationName: true } } + storeState.current = { settings: { agentRowsUseConversationName: true }, tabsByWorktree: {} } expect(useAgentRowConversationName(agent)).toBeNull() storeState.current = { - settings: { agentRowsUseConversationName: true, tabAutoGenerateTitle: true } + settings: { agentRowsUseConversationName: true, tabAutoGenerateTitle: true }, + tabsByWorktree: {} } expect(useAgentRowConversationName(agent)).toBe('Fix intake flow') }) 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 91e49ec1862..213db0236bb 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 @@ -6,6 +6,12 @@ import type { DashboardAgentRow } from './useDashboardData' export function useAgentRowConversationName(agent: DashboardAgentRow): string | null { const enabled = useAppStore((s) => s.settings?.agentRowsUseConversationName === true) const generatedTitlesEnabled = useAppStore((s) => s.settings?.tabAutoGenerateTitle === true) + // Why: row data patches live entries in place and keeps the tab snapshot from + // row creation, so renames/agent titles landing after that would never show. + // Read the current tab from the store; retained rows fall back to the snapshot. + const liveTab = useAppStore((s) => + s.tabsByWorktree[agent.tab.worktreeId]?.find((tab) => tab.id === agent.tab.id) + ) if (!enabled) { return null } @@ -14,5 +20,5 @@ export function useAgentRowConversationName(agent: DashboardAgentRow): string | if (agent.rowSource === 'subagent') { return null } - return getAgentRowConversationName(agent.tab, agent.agentType, generatedTitlesEnabled) + return getAgentRowConversationName(liveTab ?? agent.tab, agent.agentType, generatedTitlesEnabled) }