fix dashboard bucket parity for unified agent tabs

This commit is contained in:
Neil
2026-08-30 17:09:01 -07:00
parent adcecd7af1
commit 7e128dcbb4
7 changed files with 66 additions and 1 deletions
@@ -3,6 +3,7 @@ import { makePaneKey } from '../../../../shared/stable-pane-id'
import { folderWorkspaceKey } from '../../../../shared/workspace-scope'
import type { FolderWorkspace } from '../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../shared/project-group-types'
import type { Tab } from '../../../../shared/tab-types'
import type { TerminalTab } from '../../../../shared/terminal-tab-types'
import type { AgentStatusEntry } from '../../../../shared/agent-status-types'
import { buildDashboardSnapshot, type DashboardSnapshotState } from './build-dashboard-snapshot'
@@ -205,6 +206,46 @@ describe('buildDashboardSnapshot folder workspaces', () => {
expect(buildDashboardBucketCounts(mixedState, NOW)).toEqual(expected)
})
it('keeps done structured sessions visible when their tab exists only in unified tabs', () => {
const structuredState = state()
structuredState.tabsByWorktree = { [WORKSPACE_ID]: [] }
structuredState.unifiedTabsByWorktree = {
[WORKSPACE_ID]: [
{
id: TAB_ID,
entityId: 'session-1',
groupId: 'group-1',
worktreeId: WORKSPACE_ID,
contentType: 'agent-session',
label: 'Codex Chat',
customLabel: null,
color: null,
sortOrder: 0,
createdAt: NOW,
isPinned: false,
agentSessionAgent: 'codex'
} satisfies Tab
]
}
structuredState.agentStatusByPaneKey = {
[PANE_KEY]: {
...entry(),
state: 'done',
sessionBoundary: true
}
}
const snapshot = buildDashboardSnapshot(structuredState, NOW)
const expected = { attention: 0, working: 0, done: 0, idle: 0 }
for (const card of snapshot.cards) {
expected[card.bucket] += 1
}
expect(snapshot.cards).toHaveLength(1)
expect(snapshot.cards[0]).toMatchObject({ paneKey: PANE_KEY, bucket: 'done' })
expect(buildDashboardBucketCounts(structuredState, NOW)).toEqual(expected)
})
it('places folder-workspace agents in their real project group without git assumptions', () => {
const sshState = state()
sshState.sshTargetLabels = new Map([['ssh-1', 'openclaw']])
@@ -78,7 +78,7 @@ export type DashboardSnapshotState = Pick<
Partial<
DashboardCardTerminalInputState &
DashboardLaunchDetectionState &
Pick<AppState, 'runtimeEnvironments' | 'sshTargetLabels'>
Pick<AppState, 'runtimeEnvironments' | 'sshTargetLabels' | 'unifiedTabsByWorktree'>
>
/**
@@ -8,6 +8,7 @@ const mocks = vi.hoisted(() => ({
repos: [],
worktreesByRepo: {},
tabsByWorktree: {},
unifiedTabsByWorktree: {},
agentStatusByPaneKey: {},
retainedAgentsByPaneKey: {},
migrationUnsupportedByPtyId: {},
@@ -16,6 +16,7 @@ export function useAgentBucketCounts(): AgentBucketCounts {
repos,
worktreesByRepo,
tabsByWorktree,
unifiedTabsByWorktree,
agentStatusByPaneKey,
retainedAgentsByPaneKey,
migrationUnsupportedByPtyId,
@@ -31,6 +32,7 @@ export function useAgentBucketCounts(): AgentBucketCounts {
repos: s.repos,
worktreesByRepo: s.worktreesByRepo,
tabsByWorktree: s.tabsByWorktree,
unifiedTabsByWorktree: s.unifiedTabsByWorktree,
agentStatusByPaneKey: s.agentStatusByPaneKey,
retainedAgentsByPaneKey: s.retainedAgentsByPaneKey,
migrationUnsupportedByPtyId: s.migrationUnsupportedByPtyId,
@@ -50,6 +52,7 @@ export function useAgentBucketCounts(): AgentBucketCounts {
repos,
worktreesByRepo,
tabsByWorktree,
unifiedTabsByWorktree,
agentStatusByPaneKey,
retainedAgentsByPaneKey,
migrationUnsupportedByPtyId,
@@ -72,6 +75,7 @@ export function useAgentBucketCounts(): AgentBucketCounts {
repos,
worktreesByRepo,
tabsByWorktree,
unifiedTabsByWorktree,
agentStatusByPaneKey,
retainedAgentsByPaneKey,
migrationUnsupportedByPtyId,
@@ -59,6 +59,7 @@ function makeSnapshotWatchState(): DashboardSnapshotWatchState {
repos: [],
worktreesByRepo: {},
tabsByWorktree: {},
unifiedTabsByWorktree: {},
agentStatusByPaneKey: {},
retainedAgentsByPaneKey: {},
migrationUnsupportedByPtyId: {},
@@ -182,6 +183,7 @@ describe('useDashboardPopoutBridge', () => {
'repos',
'worktreesByRepo',
'tabsByWorktree',
'unifiedTabsByWorktree',
'retainedAgentsByPaneKey',
'migrationUnsupportedByPtyId',
'runtimeAgentOrchestrationByPaneKey',
@@ -241,6 +243,19 @@ describe('useDashboardPopoutBridge', () => {
expect(republished).toEqual(profileInputs.map((next) => Object.keys(next)[0]))
})
it('republishes when the unified agent-session tab projection changes', () => {
const previousState = makeSnapshotWatchState()
expect(
dashboardSnapshotInputsChanged(
{
...previousState,
unifiedTabsByWorktree: { 'worktree-1': [] }
},
previousState
)
).toBe(true)
})
it('releases every dashboard listener when the experiment is disabled', async () => {
await act(async () => root.render(<Harness enabled />))
@@ -46,6 +46,7 @@ export function dashboardSnapshotInputsChanged(
state.repos !== previousState.repos ||
state.worktreesByRepo !== previousState.worktreesByRepo ||
state.tabsByWorktree !== previousState.tabsByWorktree ||
state.unifiedTabsByWorktree !== previousState.unifiedTabsByWorktree ||
state.retainedAgentsByPaneKey !== previousState.retainedAgentsByPaneKey ||
state.migrationUnsupportedByPtyId !== previousState.migrationUnsupportedByPtyId ||
state.runtimeAgentOrchestrationByPaneKey !== previousState.runtimeAgentOrchestrationByPaneKey ||
@@ -13,6 +13,7 @@ export function useLiveDashboardSnapshot(): DashboardSnapshot {
const repos = useAppStore((s) => s.repos)
const worktreesByRepo = useAppStore((s) => s.worktreesByRepo)
const tabsByWorktree = useAppStore((s) => s.tabsByWorktree)
const unifiedTabsByWorktree = useAppStore((s) => s.unifiedTabsByWorktree)
const agentStatusByPaneKey = useAppStore((s) => s.agentStatusByPaneKey)
const retainedAgentsByPaneKey = useAppStore((s) => s.retainedAgentsByPaneKey)
const migrationUnsupportedByPtyId = useAppStore((s) => s.migrationUnsupportedByPtyId)
@@ -68,6 +69,7 @@ export function useLiveDashboardSnapshot(): DashboardSnapshot {
repos,
worktreesByRepo,
tabsByWorktree,
unifiedTabsByWorktree,
agentStatusByPaneKey,
retainedAgentsByPaneKey,
migrationUnsupportedByPtyId,
@@ -107,6 +109,7 @@ export function useLiveDashboardSnapshot(): DashboardSnapshot {
repos,
worktreesByRepo,
tabsByWorktree,
unifiedTabsByWorktree,
agentStatusByPaneKey,
retainedAgentsByPaneKey,
migrationUnsupportedByPtyId,