diff --git a/src/renderer/src/components/tab-bar/terminal-tab-activity-status.ts b/src/renderer/src/components/tab-bar/terminal-tab-activity-status.ts index 211aae2c3b2..b64ba41b0f5 100644 --- a/src/renderer/src/components/tab-bar/terminal-tab-activity-status.ts +++ b/src/renderer/src/components/tab-bar/terminal-tab-activity-status.ts @@ -104,7 +104,10 @@ function parseAgentStatusPaneKey(paneKey: string): { tabId: string; paneId: stri const EMPTY_PANE_IDS: ReadonlySet = new Set() type TerminalTabActivityInput = { - tab: Pick + // Why: launchAgent is read, not just carried — the status gate needs it to attribute a + // bare spinner title to an agent (#9040). Narrowing it away here compiles (it is optional) + // but silently drops the tab-bar dot back to the pre-#9040 behavior. + tab: Pick agentStatusByPaneKey?: Record // Why: the store bumps this at the 30m stale boundary without replacing the // pane-status map; it is the flag cache's invalidation key (see above). diff --git a/src/renderer/src/components/tab-bar/terminal-tab-spinner-launch-agent.test.ts b/src/renderer/src/components/tab-bar/terminal-tab-spinner-launch-agent.test.ts new file mode 100644 index 00000000000..4259ff8ade3 --- /dev/null +++ b/src/renderer/src/components/tab-bar/terminal-tab-spinner-launch-agent.test.ts @@ -0,0 +1,48 @@ +import { beforeEach, describe, expect, it } from 'vitest' +import type { TerminalTab } from '../../../../shared/types' +import { + resetTerminalTabActivityFlagsCacheForTest, + resolveTerminalTabActivityStatus +} from './terminal-tab-activity-status' + +// Why: #9040 — the tab-bar dot shares the sidebar's attribution gate, so Claude's +// token-less spinner title must reach 'working' here too. TerminalTabActivityInput +// narrows `tab` with a Pick; because launchAgent is optional, dropping it from that +// Pick compiles cleanly and would silently revert this path alone. +describe('#9040 terminal tab dot attributes spinner titles to the launched agent', () => { + beforeEach(() => { + resetTerminalTabActivityFlagsCacheForTest() + }) + + it('reports working for a Claude spinner title on a live tab', () => { + const status = resolveTerminalTabActivityStatus({ + tab: { + id: 'tab-1', + title: '⠋ implementing the feature', + launchAgent: 'claude' + } satisfies Partial as TerminalTab, + ptyIdsByTabId: { 'tab-1': ['pty-0'] } + }) + + expect(status).toBe('working') + }) + + // Control: the named-provider path this must stay at parity with. + it('reports working for a named-provider title', () => { + const status = resolveTerminalTabActivityStatus({ + tab: { id: 'tab-1', title: 'claude [working]' } as TerminalTab, + ptyIdsByTabId: { 'tab-1': ['pty-0'] } + }) + + expect(status).toBe('working') + }) + + it('stays out of working for a spinner title with no launch identity', () => { + const status = resolveTerminalTabActivityStatus({ + tab: { id: 'tab-1', title: '⠐ Review branch for regressions' } as TerminalTab, + ptyIdsByTabId: { 'tab-1': ['pty-0'] } + }) + + expect(status).not.toBe('working') + }) +})