Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion dashboard/src/__tests__/PipelinesPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: <span><span class="font-mono">2</span> steps</span>
const stepCount = screen.getByText((_content, element) =>
element?.textContent === "2 steps"
);
expect(stepCount).toBeDefined();
});
});

Expand Down
5 changes: 4 additions & 1 deletion dashboard/src/__tests__/a11y-css.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)', () => {
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/components/approvals/ApprovalBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? (
Expand All @@ -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 ? (
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/overview/SessionTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ export default React.memo(function SessionTable({ maxRows }: SessionTableProps =
if (isLoading && sessions.length === 0 && !loadError) {
return (
<div className="card-glass p-16 text-center flex flex-col items-center justify-center min-h-[400px]">
<div className="w-16 h-16 rounded-full border-2 border-[var(--color-accent-cyan)]/20 border-t-[var(--color-accent-cyan)] animate-spin mb-6 shadow-[0_0_15px_rgba(6,182,212,0.5)]" />
<div className="w-16 h-16 rounded-full border-2 border-[var(--color-accent)]/20 border-t-[var(--color-accent)] animate-spin mb-6 shadow-[0_0_15px_rgba(255,184,0,0.4)]" />
<h3 className="text-xl font-bold tracking-tight text-[var(--color-text-primary)] drop-shadow-md">{t('sessionTable.wakingAgents')}</h3>
<p className="mt-2 text-sm text-[var(--color-text-muted)]">{t('sessionTable.wakingAgentsDescription')}</p>
</div>
Expand Down
6 changes: 3 additions & 3 deletions dashboard/src/components/session/AcpApprovalModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 */}
<div className="flex items-start justify-between gap-3">
Expand Down Expand Up @@ -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 ? <Loader2 className="h-4 w-4 animate-spin" /> : <ShieldCheck className="h-4 w-4" />}
Expand All @@ -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 ? <Loader2 className="h-4 w-4 animate-spin" /> : <ShieldX className="h-4 w-4" />}
Expand Down
6 changes: 3 additions & 3 deletions dashboard/src/components/session/PermissionPromptSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
>
<div className="mx-auto mb-3 h-1.5 w-12 rounded-full bg-[var(--color-void-lighter)]" />

Expand Down Expand Up @@ -99,14 +99,14 @@ export function PermissionPromptSheet({
<button
type="button"
onClick={onApprove}
className="min-h-[48px] 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)]"
className="min-h-[48px] 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)]"
>
{t('sessionDetail.approve')}
</button>
<button
type="button"
onClick={onReject}
className="min-h-[48px] 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)]"
className="min-h-[48px] 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"
>
{t('sessionDetail.reject')}
</button>
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/session/TerminalDebugTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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")}
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/shared/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)]"
/>
<kbd className="shrink-0 rounded border border-[var(--color-overlay-border-strong)] bg-[var(--color-overlay-bg)] px-1.5 py-0.5 font-mono text-[10px] text-[var(--color-text-muted)]">
ESC
Expand Down
12 changes: 4 additions & 8 deletions dashboard/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/pages/ActivityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export default function ActivityPage() {
</span>
)}
</div>
<div className="overflow-x-auto rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] p-4">
<div className="overflow-x-auto rounded-lg border border-[var(--color-void-lighter)] bg-[var(--color-surface)] p-4">
{heatmapLoading ? (
<div className="flex h-20 items-center justify-center">
<div className="h-5 w-5 animate-spin rounded-full border-2 border-[var(--color-accent)] border-t-transparent" />
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/pages/AnalyticsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ export default function AnalyticsPage() {
{(data.topApiKeys ?? []).length > 0 ? (
<div className="space-y-3">
{(data.topApiKeys ?? []).map((key) => (
<div key={key.keyId} className="flex items-center justify-between rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2">
<div key={key.keyId} className="flex items-center justify-between rounded-lg border border-[var(--color-void-lighter)] bg-[var(--color-surface)] px-3 py-2">
<div>
<div className="text-sm font-medium text-[var(--color-text-primary)]">{key.keyName}</div>
<div className="text-xs text-[var(--color-text-muted)]"><span className="font-mono">{key.sessions}</span> session{key.sessions !== 1 ? 's' : ''} &middot; <span className="font-mono">{key.messages}</span> message{key.messages !== 1 ? 's' : ''}</div>
Expand Down Expand Up @@ -482,7 +482,7 @@ function EmptyChart() {

function MetricBox({ label, value }: { label: string; value: string }) {
return (
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2">
<div className="rounded-lg border border-[var(--color-void-lighter)] bg-[var(--color-surface)] px-3 py-2">
<div className="text-xs text-[var(--color-text-muted)]">{label}</div>
<div className="mt-1 text-lg font-bold font-mono text-[var(--color-text-primary)]">{value}</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/pages/AuditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)]"
>
<td className="whitespace-nowrap px-4 py-3 text-sm text-[var(--color-text-muted)]">
<td className="whitespace-nowrap px-4 py-3 font-mono text-sm text-[var(--color-text-muted)]">
{formatTimestamp(record.ts)}
</td>
<td className="max-w-[120px] truncate px-4 py-3 font-mono text-sm text-[var(--color-text-primary)]" title={record.actor}>
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/pages/CostPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import { getBudgetSettings, type BudgetSettings } from '../utils/budgetSettings'

const MODEL_COLORS: Record<string, string> = {
'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)',
Expand Down
6 changes: 3 additions & 3 deletions dashboard/src/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default function LoginPage() {

return (
<div className="flex min-h-screen items-center justify-center bg-[var(--color-void)]">
<div className="w-full max-w-sm rounded-xl border border-[var(--color-void-lighter)] bg-[var(--color-void)] p-4 sm:p-8">
<div className="w-full max-w-sm rounded-xl border border-[var(--color-void-lighter)] bg-[var(--color-surface)] p-4 sm:p-8">
{/* Logo / Title */}
<div className="mb-8 flex flex-col items-center gap-2">
<Shield className="h-10 w-10 text-[var(--color-cta-bg)]" />
Expand All @@ -83,7 +83,7 @@ export default function LoginPage() {
<button
type="button"
onClick={handleOidcLogin}
className="flex min-h-[44px] w-full items-center justify-center gap-2 rounded-lg bg-[var(--color-cta-bg)] px-4 py-2.5 text-sm font-medium text-white hover:bg-[var(--color-cta-bg)]"
className="flex min-h-[44px] w-full items-center justify-center gap-2 rounded-lg bg-[var(--color-cta-bg)] px-4 py-2.5 text-sm font-medium text-[var(--color-cta-text)] hover:bg-[var(--color-cta-bg-hover)]"
>
<LogIn className="h-4 w-4" />
<span>{t('login.signInWithSSO')}</span>
Expand Down Expand Up @@ -120,7 +120,7 @@ export default function LoginPage() {
<button
type="submit"
disabled={loading || !token.trim()}
className="min-h-[44px] rounded-lg bg-[var(--color-cta-bg)] px-4 py-2.5 text-sm font-medium text-white hover:bg-[var(--color-cta-bg)] disabled:cursor-not-allowed disabled:opacity-50"
className="min-h-[44px] rounded-lg bg-[var(--color-cta-bg)] px-4 py-2.5 text-sm font-medium text-[var(--color-cta-text)] hover:bg-[var(--color-cta-bg-hover)] disabled:cursor-not-allowed disabled:opacity-50"
>
{loading ? t('login.verifying') : t('login.signInButton')}
</button>
Expand Down
8 changes: 4 additions & 4 deletions dashboard/src/pages/MetricsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export default function MetricsPage() {

{/* Summary cards */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
<div className="rounded-[10px] border border-[var(--color-border)] bg-[var(--color-surface)] p-4">
<div className="rounded-[10px] border border-[var(--color-void-lighter)] bg-[var(--color-surface)] p-4">
<div
className="mb-1 flex items-center gap-1 text-[11px] uppercase text-[var(--color-text-muted)]"
style={{ fontWeight: 590, letterSpacing: '0.08em' }}
Expand All @@ -201,7 +201,7 @@ export default function MetricsPage() {
</div>
</div>

<div className="rounded-[10px] border border-[var(--color-border)] bg-[var(--color-surface)] p-4">
<div className="rounded-[10px] border border-[var(--color-void-lighter)] bg-[var(--color-surface)] p-4">
<div
className="mb-1 flex items-center gap-1 text-[11px] uppercase text-[var(--color-text-muted)]"
style={{ fontWeight: 590, letterSpacing: '0.08em' }}
Expand All @@ -214,7 +214,7 @@ export default function MetricsPage() {
</div>
</div>

<div className="rounded-[10px] border border-[var(--color-border)] bg-[var(--color-surface)] p-4">
<div className="rounded-[10px] border border-[var(--color-void-lighter)] bg-[var(--color-surface)] p-4">
<div
className="mb-1 flex items-center gap-1 text-[11px] uppercase text-[var(--color-text-muted)]"
style={{ fontWeight: 590, letterSpacing: '0.08em' }}
Expand All @@ -227,7 +227,7 @@ export default function MetricsPage() {
</div>
</div>

<div className="rounded-[10px] border border-[var(--color-border)] bg-[var(--color-surface)] p-4">
<div className="rounded-[10px] border border-[var(--color-void-lighter)] bg-[var(--color-surface)] p-4">
<div
className="mb-1 flex items-center gap-1 text-[11px] uppercase text-[var(--color-text-muted)]"
style={{ fontWeight: 590, letterSpacing: '0.08em' }}
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/pages/PipelinesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,8 @@ export default function PipelinesPage() {
<PipelineStatusBadge status={pipeline.status} />
</div>
<div className="flex items-center gap-4 text-xs text-[var(--color-text-muted)] shrink-0 ml-4">
<span>{pipeline.stages.length} step{pipeline.stages.length !== 1 ? 's' : ''}</span>
<span>{formatTimeAgo(pipeline.createdAt)}</span>
<span><span className="font-mono">{pipeline.stages.length}</span> step{pipeline.stages.length !== 1 ? 's' : ''}</span>
<span className="font-mono">{formatTimeAgo(pipeline.createdAt)}</span>
</div>
</div>
</Link>
Expand Down
Loading
Loading