feat(kanban): card detail modal and rich agent-run console

- RunEventList: grouped activity timeline. Assistant text becomes chat
  bubbles (auto-collapsing long messages); tool_start/tool_end pair into
  entries with spinners and expandable input/result blocks; bevy output
  rolls into a live console; relative timestamps on a left rail
- AgentRunBar: redesigned as a mission console. Live stats header (elapsed
  time, tool count, events), animated status banner with sweep/glow while
  running, clearer action bar. All controls preserved (run/steer/stop,
  diff/merge/bevy) so the human-only merge/complete safety model holds
- tailwind.css: vn-flow, vn-sweep, vn-dots, vn-spin keyframes
- CardModal: full card overlay (orchestrator, references, tags, comments)
- DiffModal: branch-diff review (commits, stat, capped patch)
- useOrchestrator: background polling + bevy status sync + ref-counted SSE
- KanbanCard: pulsing agent/bevy running badge on collapsed cards
This commit is contained in:
2026-06-16 18:17:35 -04:00
parent e4f0abed20
commit 72a41c2d76
11 changed files with 2248 additions and 592 deletions

View File

@@ -34,6 +34,7 @@ export type RunEventType =
| 'tool_start'
| 'tool_end'
| 'log'
| 'bevy'
| 'done'
| 'error';
@@ -79,6 +80,30 @@ export interface StartRunInput {
cleanupOnFinish?: boolean;
}
/** A commit on a run's branch that is not yet on main. */
export interface DiffCommit {
sha: string;
subject: string;
}
/** A run's branch diff vs main. */
export interface DiffResult {
branch: string;
commits: DiffCommit[];
stat: string;
patch: string;
truncated: boolean;
}
/** Outcome of merging a run's branch into the main worktree. */
export interface MergeResult {
ok: boolean;
alreadyMerged: boolean;
target: string;
branch: string;
output: string;
}
export const orchestratorApi = {
listRuns: (cardId?: string) =>
req<{ runs: AgentRun[] }>(`/runs${cardId ? `?cardId=${encodeURIComponent(cardId)}` : ''}`),
@@ -99,4 +124,18 @@ export const orchestratorApi = {
req<{ ok: boolean }>(`/runs/${id}/stop`, { method: 'POST' }),
deleteRun: (id: string) => req<void>(`/runs/${id}`, { method: 'DELETE' }),
// --- worktree review & playtesting ---
getDiff: (id: string) => req<DiffResult>(`/runs/${id}/diff`),
mergeRun: (id: string) =>
req<MergeResult>(`/runs/${id}/merge`, { method: 'POST' }),
bevyStatus: (id: string) => req<{ running: boolean }>(`/runs/${id}/bevy`),
startBevy: (id: string) => req<{ ok: boolean }>(`/runs/${id}/bevy`, { method: 'POST' }),
stopBevy: (id: string) =>
req<{ ok: boolean }>(`/runs/${id}/bevy/stop`, { method: 'POST' }),
};