Files
orca/src/shared/dashboard-snapshot.ts
T
Brennan Benson 6785f9d1a8 feat(dashboard): open-worktree dialog control + finished-time parity (#9686)
* feat(dashboard): open-worktree dialog control + finished-time parity

Pop-out agent dashboard polish:
- Terminal dialog: replace the footer Close button with the standard top-right
  X, rename the primary action from Focus worktree to Open worktree, and prefix
  it with an open-in-window icon.
- Card time column: finished agents now read time-since-finish (matching the
  left worktree sidebar) instead of time-since-start. Extracts the sidebar's
  finished-timestamp logic into a shared helper so the two surfaces cannot
  drift, and threads finishedAt through the dashboard snapshot contract.

* fix(dashboard): thread finishedAt through payload validator + test fixture

CI typecheck caught a DashboardCard fixture in dashboard-payload-validation.test.ts
missing the new finishedAt field; also validate finishedAt in the relay guard.

* feat(dashboard): swap pending-question emoji for MessageCircleQuestion icon

The amber pending-question chip used a raw hand emoji (renders differently
per-OS and off the design system); use the lucide chat-with-question icon.
2026-07-20 22:55:22 -07:00

79 lines
3.3 KiB
TypeScript

import type { AgentType } from './agent-status-types'
/**
* Serializable contract for the pop-out agent dashboard. The main renderer owns
* the live store, derives this snapshot, and relays it (through the main
* process) to the separate pop-out renderer, which renders it presentationally.
* Every field must be structured-clone-safe (no functions / class instances).
*/
/** The three kanban columns. "Idle" is everything that isn't actively working
* and isn't blocking you — this includes explicitly-completed ('done') agents,
* which Orca only reports when a completion hook fires, so they're folded in
* rather than split into a separate, inconsistently-populated column. */
export type DashboardBucket = 'attention' | 'working' | 'idle'
/** Column order shared by producer and pop-out so they never drift. */
export const DASHBOARD_BUCKET_ORDER: readonly DashboardBucket[] = ['attention', 'working', 'idle']
/** Precise per-card state marker (drives AgentStateDot). Kept distinct from
* `bucket` so the "Needs You" column can still show amber (waiting/permission)
* vs red (blocked) dots. */
export type DashboardCardDotState = 'working' | 'blocked' | 'waiting' | 'done' | 'idle'
export type DashboardCard = {
/** Stable identity for React keys. */
paneKey: string
/** Resolved live PTY id for the terminal preview, or null when the agent has
* no live pane (e.g. a retained/done row whose pane is gone). */
ptyId: string | null
agentType: AgentType
bucket: DashboardBucket
dotState: DashboardCardDotState
/** One-line task/prompt text shown on the card. */
task: string
/** The most recent message the user sent this agent (its current prompt). */
lastUserMessage?: string
/** The most recent message the agent sent back. */
lastAgentMessage?: string
/** Routing target for click-to-focus. leafId is null when unresolved. */
repoId: string
worktreeId: string
tabId: string
leafId: string | null
repoName: string
worktreeName: string
/** "Started … ago" display. */
startedAt: number
/** When the agent last entered `done`, or null if it never finished. Drives
* the card's time column: finished cards read time-since-finish (parity with
* the left worktree sidebar), active cards fall back to startedAt. */
finishedAt: number | null
/** When the agent entered its current state — column ordering key (cards
* that moved into a bucket most recently sort first). 0 when unknown. */
stateChangedAt: number
/** Mirrors the sidebar's unvisited signal: the agent changed state since the
* user last acknowledged it (visited its tab / opened its dashboard dialog).
* Derived from the app-wide ack map so both surfaces mute in lockstep. */
unseen: boolean
/** Short summary of the pending question when bucket === 'attention'. */
askSummary?: string
}
export type DashboardSnapshot = {
generatedAt: number
cards: DashboardCard[]
}
export const EMPTY_DASHBOARD_SNAPSHOT: DashboardSnapshot = { generatedAt: 0, cards: [] }
/** Routing payload for click-to-focus: reveal this agent's pane in the main
* window. leafId is null when the pane could not be resolved (best-effort:
* the worktree is still activated). */
export type DashboardRevealAgentArgs = {
repoId: string
worktreeId: string
tabId: string
leafId: string | null
}