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.
This commit is contained in:
Brennan Benson
2026-07-22 14:19:20 -07:00
parent a3a2771b53
commit a839195d01
2 changed files with 30 additions and 8 deletions
@@ -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<string, unknown> }
current: { settings: {}, tabsByWorktree: {} } as {
settings: Record<string, unknown>
tabsByWorktree: Record<string, unknown[]>
}
}))
// Why: the mocked selector makes the hook a pure function, so tests can call it
@@ -18,7 +21,7 @@ function makeAgent(overrides: Partial<DashboardAgentRow> = {}): 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<DashboardAgentRow> = {}): 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<DashboardAgentRow>)
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')
})
@@ -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)
}