mirror of
https://github.com/stablyai/orca.git
synced 2026-10-25 00:03:59 +00:00
fix(tab-bar): keep launchAgent in the terminal-tab status contract (#9040)
resolveTerminalTabActivityStatus narrows its tab with Pick<'id'|'title'>, so the type said launchAgent is never read even though the status gate now reads it. Structural typing means the real caller (SortableTab passes a whole TerminalTab) still worked at runtime, but the contract was wrong: re-narrowing or building an input object literal would silently drop the tab-bar dot back to pre-#9040 behavior with no type error, because launchAgent is optional. Widen the Pick to match what the resolver actually consumes, and pin the tab-bar path with its own test so the regression cannot land silently.
This commit is contained in:
@@ -104,7 +104,10 @@ function parseAgentStatusPaneKey(paneKey: string): { tabId: string; paneId: stri
|
||||
const EMPTY_PANE_IDS: ReadonlySet<string> = new Set()
|
||||
|
||||
type TerminalTabActivityInput = {
|
||||
tab: Pick<TerminalTab, 'id' | 'title'>
|
||||
// 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<TerminalTab, 'id' | 'title' | 'launchAgent'>
|
||||
agentStatusByPaneKey?: Record<string, AgentStatusEntry>
|
||||
// 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).
|
||||
|
||||
@@ -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<TerminalTab> 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')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user