From 8ee70fba7c6e594009ac23b7e0ed1791d8764819 Mon Sep 17 00:00:00 2001 From: Alex Kuleshov Date: Wed, 29 Apr 2026 17:56:57 -0400 Subject: [PATCH 1/6] feat(dashboard): redesign harness UI as an agent run control center Replaces the legacy chat dashboard chrome with a clean, dark-first agent harness composed of an icon rail, secondary sidebar, top bar with status pills, main agent thread and a tabbed inspector panel. Adds the agent run primitives (TaskHeader, PlanBlock, ToolCallTimeline, IncidentCard, StatusPill) per the redesign spec, a Cmd/Ctrl+K command palette, session export and persisted layout preferences. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../agentRun/ApprovalRequestCard.tsx | 36 + .../src/components/agentRun/ArtifactCard.tsx | 33 + .../components/agentRun/IncidentCard.test.tsx | 39 + .../src/components/agentRun/IncidentCard.tsx | 98 ++ .../components/agentRun/PlanBlock.test.tsx | 48 + .../src/components/agentRun/PlanBlock.tsx | 111 ++ .../src/components/agentRun/PlanStepRow.tsx | 29 + .../components/agentRun/StatusPill.test.tsx | 21 + .../src/components/agentRun/StatusPill.tsx | 34 + .../components/agentRun/TaskHeader.test.tsx | 49 + .../src/components/agentRun/TaskHeader.tsx | 140 +++ .../src/components/agentRun/ToolCallRow.tsx | 95 ++ .../agentRun/ToolCallTimeline.test.tsx | 51 + .../components/agentRun/ToolCallTimeline.tsx | 43 + .../agentRun/agentRunFormat.test.ts | 44 + .../src/components/agentRun/agentRunFormat.ts | 65 + .../agentRun/normalizeRunEvents.test.ts | 64 + .../components/agentRun/normalizeRunEvents.ts | 83 ++ .../src/components/agentRun/sampleAgentRun.ts | 83 ++ dashboard/src/components/agentRun/types.ts | 177 +++ .../commandPalette/CommandPalette.tsx | 162 +++ .../commandPalette/commandRegistry.ts | 118 ++ .../commandPalette/fuzzyMatch.test.ts | 30 + .../components/commandPalette/fuzzyMatch.ts | 43 + .../useCommandPaletteShortcut.ts | 24 + .../commandPalette/useSessionExport.ts | 43 + .../src/components/layout/DashboardLayout.tsx | 29 +- dashboard/src/components/layout/Sidebar.tsx | 255 ---- dashboard/src/components/layout/Topbar.tsx | 161 --- .../layout/harness/HarnessShell.tsx | 67 + .../layout/harness/HarnessTopBar.tsx | 197 +++ .../components/layout/harness/IconRail.tsx | 94 ++ .../layout/harness/InspectorPanel.tsx | 73 ++ .../layout/harness/SecondarySidebar.tsx | 144 +++ .../harness/SidebarChatSessionsList.tsx | 81 ++ .../inspector/InspectorActionsCard.tsx | 42 + .../inspector/InspectorContextCard.tsx | 50 + .../harness/inspector/InspectorLogsTab.tsx | 96 ++ .../harness/inspector/InspectorMemoryTab.tsx | 55 + .../harness/inspector/InspectorPlanTab.tsx | 52 + .../harness/inspector/InspectorSummaryTab.tsx | 75 ++ .../harness/inspector/InspectorToolsTab.tsx | 38 + .../layout/harness/inspector/contextUsage.ts | 34 + .../layout/harness/sidebarNavGroups.ts | 82 ++ .../components/layout/harness/topbarStatus.ts | 43 + .../layout/harness/useRailVersionLabel.ts | 6 + dashboard/src/hooks/useInspectorLogs.ts | 23 + dashboard/src/main.tsx | 2 + dashboard/src/store/commandPaletteStore.ts | 15 + dashboard/src/store/inspectorStore.ts | 33 + dashboard/src/styles/agentRun.scss | 447 +++++++ dashboard/src/styles/harness.scss | 1075 +++++++++++++++++ 52 files changed, 4589 insertions(+), 443 deletions(-) create mode 100644 dashboard/src/components/agentRun/ApprovalRequestCard.tsx create mode 100644 dashboard/src/components/agentRun/ArtifactCard.tsx create mode 100644 dashboard/src/components/agentRun/IncidentCard.test.tsx create mode 100644 dashboard/src/components/agentRun/IncidentCard.tsx create mode 100644 dashboard/src/components/agentRun/PlanBlock.test.tsx create mode 100644 dashboard/src/components/agentRun/PlanBlock.tsx create mode 100644 dashboard/src/components/agentRun/PlanStepRow.tsx create mode 100644 dashboard/src/components/agentRun/StatusPill.test.tsx create mode 100644 dashboard/src/components/agentRun/StatusPill.tsx create mode 100644 dashboard/src/components/agentRun/TaskHeader.test.tsx create mode 100644 dashboard/src/components/agentRun/TaskHeader.tsx create mode 100644 dashboard/src/components/agentRun/ToolCallRow.tsx create mode 100644 dashboard/src/components/agentRun/ToolCallTimeline.test.tsx create mode 100644 dashboard/src/components/agentRun/ToolCallTimeline.tsx create mode 100644 dashboard/src/components/agentRun/agentRunFormat.test.ts create mode 100644 dashboard/src/components/agentRun/agentRunFormat.ts create mode 100644 dashboard/src/components/agentRun/normalizeRunEvents.test.ts create mode 100644 dashboard/src/components/agentRun/normalizeRunEvents.ts create mode 100644 dashboard/src/components/agentRun/sampleAgentRun.ts create mode 100644 dashboard/src/components/agentRun/types.ts create mode 100644 dashboard/src/components/commandPalette/CommandPalette.tsx create mode 100644 dashboard/src/components/commandPalette/commandRegistry.ts create mode 100644 dashboard/src/components/commandPalette/fuzzyMatch.test.ts create mode 100644 dashboard/src/components/commandPalette/fuzzyMatch.ts create mode 100644 dashboard/src/components/commandPalette/useCommandPaletteShortcut.ts create mode 100644 dashboard/src/components/commandPalette/useSessionExport.ts delete mode 100644 dashboard/src/components/layout/Sidebar.tsx delete mode 100644 dashboard/src/components/layout/Topbar.tsx create mode 100644 dashboard/src/components/layout/harness/HarnessShell.tsx create mode 100644 dashboard/src/components/layout/harness/HarnessTopBar.tsx create mode 100644 dashboard/src/components/layout/harness/IconRail.tsx create mode 100644 dashboard/src/components/layout/harness/InspectorPanel.tsx create mode 100644 dashboard/src/components/layout/harness/SecondarySidebar.tsx create mode 100644 dashboard/src/components/layout/harness/SidebarChatSessionsList.tsx create mode 100644 dashboard/src/components/layout/harness/inspector/InspectorActionsCard.tsx create mode 100644 dashboard/src/components/layout/harness/inspector/InspectorContextCard.tsx create mode 100644 dashboard/src/components/layout/harness/inspector/InspectorLogsTab.tsx create mode 100644 dashboard/src/components/layout/harness/inspector/InspectorMemoryTab.tsx create mode 100644 dashboard/src/components/layout/harness/inspector/InspectorPlanTab.tsx create mode 100644 dashboard/src/components/layout/harness/inspector/InspectorSummaryTab.tsx create mode 100644 dashboard/src/components/layout/harness/inspector/InspectorToolsTab.tsx create mode 100644 dashboard/src/components/layout/harness/inspector/contextUsage.ts create mode 100644 dashboard/src/components/layout/harness/sidebarNavGroups.ts create mode 100644 dashboard/src/components/layout/harness/topbarStatus.ts create mode 100644 dashboard/src/components/layout/harness/useRailVersionLabel.ts create mode 100644 dashboard/src/hooks/useInspectorLogs.ts create mode 100644 dashboard/src/store/commandPaletteStore.ts create mode 100644 dashboard/src/store/inspectorStore.ts create mode 100644 dashboard/src/styles/agentRun.scss create mode 100644 dashboard/src/styles/harness.scss diff --git a/dashboard/src/components/agentRun/ApprovalRequestCard.tsx b/dashboard/src/components/agentRun/ApprovalRequestCard.tsx new file mode 100644 index 000000000..686811c07 --- /dev/null +++ b/dashboard/src/components/agentRun/ApprovalRequestCard.tsx @@ -0,0 +1,36 @@ +import { FiCheck, FiX } from 'react-icons/fi'; +import type { ApprovalRequestViewModel } from './types'; +import { formatTimeOfDay } from './agentRunFormat'; + +interface ApprovalRequestCardProps { + approval: ApprovalRequestViewModel; + onApprove?: () => void; + onReject?: () => void; +} + +export default function ApprovalRequestCard({ approval, onApprove, onReject }: ApprovalRequestCardProps) { + return ( +
+
+ {approval.title} + {formatTimeOfDay(approval.createdAt)} +
+

{approval.description}

+
{approval.prompt}
+
+ {onApprove != null && ( + + )} + {onReject != null && ( + + )} +
+
+ ); +} diff --git a/dashboard/src/components/agentRun/ArtifactCard.tsx b/dashboard/src/components/agentRun/ArtifactCard.tsx new file mode 100644 index 000000000..d29e4cbd0 --- /dev/null +++ b/dashboard/src/components/agentRun/ArtifactCard.tsx @@ -0,0 +1,33 @@ +import { FiDownload, FiFileText } from 'react-icons/fi'; +import type { ArtifactViewModel } from './types'; + +interface ArtifactCardProps { + artifact: ArtifactViewModel; + onOpen?: (id: string) => void; +} + +export default function ArtifactCard({ artifact, onOpen }: ArtifactCardProps) { + return ( +
+ +
+ {artifact.name} + {artifact.description != null && ( + {artifact.description} + )} +
+ {artifact.href != null && ( + + + )} + {artifact.href == null && onOpen != null && ( + + )} +
+ ); +} diff --git a/dashboard/src/components/agentRun/IncidentCard.test.tsx b/dashboard/src/components/agentRun/IncidentCard.test.tsx new file mode 100644 index 000000000..dd73446eb --- /dev/null +++ b/dashboard/src/components/agentRun/IncidentCard.test.tsx @@ -0,0 +1,39 @@ +import { renderToStaticMarkup } from 'react-dom/server'; +import { describe, expect, it } from 'vitest'; +import IncidentCard from './IncidentCard'; +import type { IncidentViewModel } from './types'; + +const incident: IncidentViewModel = { + id: 'inc-1', + runId: 'run-1', + severity: 'error', + title: 'LLM provider is temporarily unavailable', + message: 'Your task has been saved and will retry automatically in 5 minutes.', + code: 'llm.provider.circuit.open', + createdAt: '2026-04-29T10:43:00.000Z', + retryCountdownSeconds: 299, + taskSaved: true, + actions: [ + { id: 'retry_now', label: 'Retry now', kind: 'primary' }, + { id: 'switch_model', label: 'Switch model', kind: 'secondary' }, + { id: 'continue_manually', label: 'Continue manually', kind: 'secondary' }, + { id: 'open_logs', label: 'Open logs', kind: 'secondary' }, + ], +}; + +describe('IncidentCard', () => { + it('renders the title, message and code', () => { + const html = renderToStaticMarkup(); + expect(html).toContain('LLM provider is temporarily unavailable'); + expect(html).toContain('llm.provider.circuit.open'); + expect(html).toContain('Task saved automatically'); + }); + + it('renders all recovery actions', () => { + const html = renderToStaticMarkup(); + expect(html).toContain('Retry now'); + expect(html).toContain('Switch model'); + expect(html).toContain('Continue manually'); + expect(html).toContain('Open logs'); + }); +}); diff --git a/dashboard/src/components/agentRun/IncidentCard.tsx b/dashboard/src/components/agentRun/IncidentCard.tsx new file mode 100644 index 000000000..56e49ebed --- /dev/null +++ b/dashboard/src/components/agentRun/IncidentCard.tsx @@ -0,0 +1,98 @@ +import { useEffect, useState } from 'react'; +import { FiAlertTriangle, FiInfo, FiAlertOctagon } from 'react-icons/fi'; +import type { IncidentActionViewModel, IncidentSeverity, IncidentViewModel } from './types'; +import { formatCountdown, formatTimeOfDay } from './agentRunFormat'; + +type IncidentActionHandler = (actionId: IncidentActionViewModel['id']) => void; + +interface IncidentCardProps { + incident: IncidentViewModel; + onAction?: IncidentActionHandler; +} + +const SEVERITY_CLASS: Record = { + info: 'incident-card--info', + warning: 'incident-card--warning', + error: '', + critical: '', +}; + +function SeverityIcon({ severity }: { severity: IncidentSeverity }) { + if (severity === 'info') { + return