diff --git a/dashboard/src/__tests__/PipelinesPage.test.tsx b/dashboard/src/__tests__/PipelinesPage.test.tsx index aad0e9e3..9c3374c7 100644 --- a/dashboard/src/__tests__/PipelinesPage.test.tsx +++ b/dashboard/src/__tests__/PipelinesPage.test.tsx @@ -118,7 +118,12 @@ describe('PipelinesPage', () => { mockGetPipelines.mockResolvedValue(mockPipelines); renderPage(); await waitFor(() => { - expect(screen.getByText(/2 steps/)).toBeDefined(); + // After Command Center redesign, the number is wrapped in font-mono span + // so text is split across elements: 2 steps + const stepCount = screen.getByText((_content, element) => + element?.textContent === "2 steps" + ); + expect(stepCount).toBeDefined(); }); }); diff --git a/dashboard/src/__tests__/a11y-css.test.ts b/dashboard/src/__tests__/a11y-css.test.ts index 62b4d4f5..8064c783 100644 --- a/dashboard/src/__tests__/a11y-css.test.ts +++ b/dashboard/src/__tests__/a11y-css.test.ts @@ -23,10 +23,13 @@ describe('index.css — focus ring tokens', () => { expect(css).toContain('--focus-ring-offset:'); }); - it('applies focus ring via :focus-visible', () => { + it('applies an amber focus ring via :focus-visible', () => { + // DESIGN §1: the focus ring is brand amber (--color-accent), applied as a + // 2px outline with a 1px offset (see index.css :focus-visible rule). expect(css).toContain(':focus-visible'); // Command Center redesign uses outline-based focus ring (DESIGN.md §1) expect(css).toContain('outline: 2px solid var(--color-accent)'); + expect(css).toContain('outline-offset: 1px'); }); it('removes outline for :focus:not(:focus-visible)', () => { diff --git a/dashboard/src/components/approvals/ApprovalBanner.tsx b/dashboard/src/components/approvals/ApprovalBanner.tsx index 2d73f020..3f82d6f6 100644 --- a/dashboard/src/components/approvals/ApprovalBanner.tsx +++ b/dashboard/src/components/approvals/ApprovalBanner.tsx @@ -87,7 +87,7 @@ export function ApprovalBanner({ sessionId, sessionName }: ApprovalBannerProps) type="button" onClick={handleApprove} disabled={isApproving || isRejecting} - className="min-h-[44px] inline-flex items-center gap-1.5 rounded-lg bg-[var(--color-success)] px-4 py-2 text-sm font-medium text-white transition-colors hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-success)] disabled:cursor-not-allowed disabled:opacity-50" + className="min-h-[44px] inline-flex items-center gap-1.5 rounded border border-[var(--color-cta-bg)] bg-[var(--color-cta-bg)] px-4 py-2 text-[13px] font-semibold text-[var(--color-cta-text)] transition-colors hover:bg-[var(--color-cta-bg-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-cta-bg)] disabled:cursor-not-allowed disabled:opacity-50" aria-label={`Approve session ${sessionName ?? sessionId}`} > {isApproving ? ( @@ -101,7 +101,7 @@ export function ApprovalBanner({ sessionId, sessionName }: ApprovalBannerProps) type="button" onClick={handleReject} disabled={isApproving || isRejecting} - className="min-h-[44px] inline-flex items-center gap-1.5 rounded-lg border border-[var(--color-danger)]/30 bg-[var(--color-error-bg)]/20 px-4 py-2 text-sm font-medium text-[var(--color-danger)] transition-colors hover:bg-[var(--color-error-bg)]/35 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-danger)] disabled:cursor-not-allowed disabled:opacity-50" + className="min-h-[44px] inline-flex items-center gap-1.5 rounded border border-[var(--color-danger)] bg-transparent px-4 py-2 text-[13px] font-semibold text-[var(--color-danger)] transition-colors hover:bg-[var(--color-danger)]/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-danger)] disabled:cursor-not-allowed disabled:opacity-50" aria-label={`Reject session ${sessionName ?? sessionId}`} > {isRejecting ? ( diff --git a/dashboard/src/components/overview/SessionTable.tsx b/dashboard/src/components/overview/SessionTable.tsx index 639beccd..f1affa47 100644 --- a/dashboard/src/components/overview/SessionTable.tsx +++ b/dashboard/src/components/overview/SessionTable.tsx @@ -481,7 +481,7 @@ export default React.memo(function SessionTable({ maxRows }: SessionTableProps = if (isLoading && sessions.length === 0 && !loadError) { return (
-
+

{t('sessionTable.wakingAgents')}

{t('sessionTable.wakingAgentsDescription')}

diff --git a/dashboard/src/components/session/AcpApprovalModal.tsx b/dashboard/src/components/session/AcpApprovalModal.tsx index 15e75648..0f169222 100644 --- a/dashboard/src/components/session/AcpApprovalModal.tsx +++ b/dashboard/src/components/session/AcpApprovalModal.tsx @@ -108,7 +108,7 @@ export function AcpApprovalModal({ ref={trapRef} aria-modal="true" aria-label={t("aria.toolApprovalRequired")} - className="flex flex-col gap-3 rounded-xl border border-[var(--color-warning)]/35 bg-[var(--color-surface)] p-4 shadow-2xl" + className="flex flex-col gap-3 rounded-xl border border-[var(--color-border)] border-l-[3px] border-l-[var(--color-warning)] bg-[var(--color-surface)] p-4 shadow-2xl" > {/* Header */}
@@ -181,7 +181,7 @@ export function AcpApprovalModal({ type="button" onClick={handleApprove} disabled={isLoading} - className="flex flex-1 items-center justify-center gap-2 rounded-xl border border-[var(--color-success)]/30 bg-[var(--color-success-bg)] px-4 py-3 text-sm font-semibold text-[var(--color-success)] transition-colors hover:bg-[var(--color-success-bg-hover)] disabled:opacity-50 disabled:cursor-not-allowed" + className="flex flex-1 items-center justify-center gap-2 rounded-xl border border-[var(--color-cta-bg)] bg-[var(--color-cta-bg)] px-4 py-3 text-sm font-semibold text-[var(--color-cta-text)] transition-colors hover:bg-[var(--color-cta-bg-hover)] disabled:opacity-50 disabled:cursor-not-allowed" aria-label={t("aria.approveTool")} > {isLoading ? : } @@ -191,7 +191,7 @@ export function AcpApprovalModal({ type="button" onClick={() => setShowRejectReason(true)} disabled={isLoading} - className="flex flex-1 items-center justify-center gap-2 rounded-xl border border-[var(--color-error)]/30 bg-[var(--color-error-bg)] px-4 py-3 text-sm font-semibold text-[var(--color-error)] transition-colors hover:bg-[var(--color-error-bg-hover)] disabled:opacity-50 disabled:cursor-not-allowed" + className="flex flex-1 items-center justify-center gap-2 rounded-xl border border-[var(--color-danger)] bg-transparent px-4 py-3 text-sm font-semibold text-[var(--color-danger)] transition-colors hover:bg-[var(--color-danger)]/10 disabled:opacity-50 disabled:cursor-not-allowed" aria-label={t("aria.rejectTool")} > {isLoading ? : } diff --git a/dashboard/src/components/session/PermissionPromptSheet.tsx b/dashboard/src/components/session/PermissionPromptSheet.tsx index b2af3292..3aa3e4f3 100644 --- a/dashboard/src/components/session/PermissionPromptSheet.tsx +++ b/dashboard/src/components/session/PermissionPromptSheet.tsx @@ -65,7 +65,7 @@ export function PermissionPromptSheet({ ref={trapRef} aria-modal="true" aria-label={t("aria.permissionPrompt")} - className="rounded-t-2xl border border-[var(--color-warning)]/35 bg-[var(--color-surface)] p-4 shadow-2xl" + className="rounded-t-2xl border border-[var(--color-border)] border-l-[3px] border-l-[var(--color-warning)] bg-[var(--color-surface)] p-4 shadow-2xl" >
@@ -99,14 +99,14 @@ export function PermissionPromptSheet({ diff --git a/dashboard/src/components/session/TerminalDebugTab.tsx b/dashboard/src/components/session/TerminalDebugTab.tsx index 98a7a8fe..b5c42cc0 100644 --- a/dashboard/src/components/session/TerminalDebugTab.tsx +++ b/dashboard/src/components/session/TerminalDebugTab.tsx @@ -235,7 +235,7 @@ export function TerminalDebugTab({ onChange={(e) => setInputValue(e.target.value)} onKeyDown={handleKeyDown} placeholder={t('sessionDetail.commandPlaceholder')} - className="flex-1 bg-transparent font-mono text-xs text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)] focus-visible:outline-none" + className="flex-1 rounded-sm bg-transparent font-mono text-xs text-[var(--color-text-primary)] placeholder-[var(--color-text-muted)]" disabled={!isConnected} autoFocus aria-label={t("aria.terminalInput")} diff --git a/dashboard/src/components/shared/CommandPalette.tsx b/dashboard/src/components/shared/CommandPalette.tsx index b6bb02a0..e0da8f57 100644 --- a/dashboard/src/components/shared/CommandPalette.tsx +++ b/dashboard/src/components/shared/CommandPalette.tsx @@ -221,7 +221,7 @@ export default function CommandPalette({ open, onClose }: CommandPaletteProps) { onChange={(e) => { setQuery(e.target.value); setActiveIndex(0); }} onKeyDown={handleInputKeyDown} placeholder={t('commandPalette.searchPlaceholder')} - className="min-h-8 flex-1 bg-transparent text-sm text-white placeholder:text-[var(--color-text-muted)] outline-none" + className="min-h-8 flex-1 rounded-sm bg-transparent text-sm text-white placeholder:text-[var(--color-text-muted)]" /> ESC diff --git a/dashboard/src/index.css b/dashboard/src/index.css index 4f83de7c..d590c595 100644 --- a/dashboard/src/index.css +++ b/dashboard/src/index.css @@ -937,15 +937,11 @@ button:active:not(:disabled) { box-shadow: 0 0 10px currentColor; /* Smooth bleed */ } .status-dot--idle { color: var(--color-success); background-color: var(--color-success); } -.status-dot--working { color: var(--color-warning); background-color: var(--color-warning); animation: pulse-intense 1.5s infinite; } -.status-dot--permission_prompt { color: var(--color-danger); background-color: var(--color-danger); animation: pulse-intense 1s infinite; } .status-dot--unknown { color: #475569; background-color: #475569; box-shadow: none; } - -@keyframes pulse-intense { - 0% { transform: scale(1); opacity: 1; box-shadow: 0 0 0 0 rgba(var(--color-warning), 0.6); } - 70% { transform: scale(1.2); opacity: 0.7; box-shadow: 0 0 0 6px rgba(var(--color-warning), 0); } - 100% { transform: scale(1); opacity: 1; } -} +/* Live session/agent status dots — including the calm amber "working" pulse + * (opacity 1→0.4→1, DESIGN §5) — are owned by the canonical StatusDot + * component (components/overview/StatusDot.tsx). The old `.status-dot--working` + * / `--permission_prompt` modifiers used a bouncy scale pulse and were unused. */ /* ── Syntax Highlighting Tokens ────────────────────────────── */ .syn-keyword { color: var(--color-syn-keyword, #ff7b72); } diff --git a/dashboard/src/pages/ActivityPage.tsx b/dashboard/src/pages/ActivityPage.tsx index e589f43e..93185629 100644 --- a/dashboard/src/pages/ActivityPage.tsx +++ b/dashboard/src/pages/ActivityPage.tsx @@ -95,7 +95,7 @@ export default function ActivityPage() { )}
-
+
{heatmapLoading ? (
diff --git a/dashboard/src/pages/AnalyticsPage.tsx b/dashboard/src/pages/AnalyticsPage.tsx index 894ee67c..1c843769 100644 --- a/dashboard/src/pages/AnalyticsPage.tsx +++ b/dashboard/src/pages/AnalyticsPage.tsx @@ -407,7 +407,7 @@ export default function AnalyticsPage() { {(data.topApiKeys ?? []).length > 0 ? (
{(data.topApiKeys ?? []).map((key) => ( -
+
{key.keyName}
{key.sessions} session{key.sessions !== 1 ? 's' : ''} · {key.messages} message{key.messages !== 1 ? 's' : ''}
@@ -482,7 +482,7 @@ function EmptyChart() { function MetricBox({ label, value }: { label: string; value: string }) { return ( -
+
{label}
{value}
diff --git a/dashboard/src/pages/AuditPage.tsx b/dashboard/src/pages/AuditPage.tsx index e33e69a2..aaa7d063 100644 --- a/dashboard/src/pages/AuditPage.tsx +++ b/dashboard/src/pages/AuditPage.tsx @@ -190,7 +190,7 @@ function AuditRow({ record, index, onClick }: { record: AuditRecord; index: numb }} className="border-b border-[var(--color-void-lighter)] transition-colors hover:bg-[var(--color-void-light)]/40 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-accent)]" > - + {formatTimestamp(record.ts)} diff --git a/dashboard/src/pages/CostPage.tsx b/dashboard/src/pages/CostPage.tsx index a89ed08d..af1b401f 100644 --- a/dashboard/src/pages/CostPage.tsx +++ b/dashboard/src/pages/CostPage.tsx @@ -44,7 +44,7 @@ import { getBudgetSettings, type BudgetSettings } from '../utils/budgetSettings' const MODEL_COLORS: Record = { 'claude-sonnet-4.6': 'var(--color-accent-cyan)', - 'claude-opus-4.7': 'var(--color-accent-purple)', + 'claude-opus-4.7': 'var(--color-accent)', 'claude-haiku-4.5': 'var(--color-success)', 'gpt-5.4': 'var(--color-warning)', 'gpt-4.1': 'var(--color-info)', diff --git a/dashboard/src/pages/LoginPage.tsx b/dashboard/src/pages/LoginPage.tsx index c5e048cf..9083b4c1 100644 --- a/dashboard/src/pages/LoginPage.tsx +++ b/dashboard/src/pages/LoginPage.tsx @@ -65,7 +65,7 @@ export default function LoginPage() { return (
-
+
{/* Logo / Title */}
@@ -83,7 +83,7 @@ export default function LoginPage() { diff --git a/dashboard/src/pages/MetricsPage.tsx b/dashboard/src/pages/MetricsPage.tsx index 763eafaa..6e907c33 100644 --- a/dashboard/src/pages/MetricsPage.tsx +++ b/dashboard/src/pages/MetricsPage.tsx @@ -188,7 +188,7 @@ export default function MetricsPage() { {/* Summary cards */}
-
+
-
+
-
+
-
+
- {pipeline.stages.length} step{pipeline.stages.length !== 1 ? 's' : ''} - {formatTimeAgo(pipeline.createdAt)} + {pipeline.stages.length} step{pipeline.stages.length !== 1 ? 's' : ''} + {formatTimeAgo(pipeline.createdAt)}
diff --git a/dashboard/src/pages/RoutinesPage.tsx b/dashboard/src/pages/RoutinesPage.tsx index dcb730cc..dd30e29e 100644 --- a/dashboard/src/pages/RoutinesPage.tsx +++ b/dashboard/src/pages/RoutinesPage.tsx @@ -91,7 +91,7 @@ export default function RoutinesPage() {