diff --git a/dashboard/src/components/tour/FirstRunTour.tsx b/dashboard/src/components/tour/FirstRunTour.tsx
index 3945a9d8..de1ad150 100644
--- a/dashboard/src/components/tour/FirstRunTour.tsx
+++ b/dashboard/src/components/tour/FirstRunTour.tsx
@@ -220,7 +220,7 @@ export function FirstRunTour({ onComplete }: FirstRunTourProps) {
@@ -229,7 +229,7 @@ export function FirstRunTour({ onComplete }: FirstRunTourProps) {
complete: {
title: 'Tour complete!',
description: 'You now know the basics: create sessions, handle permissions, and manage cleanup. Happy orchestrating!',
- icon:
,
},
};
@@ -286,7 +286,7 @@ export function FirstRunTour({ onComplete }: FirstRunTourProps) {
{error && (
-
+
{error}
)}
diff --git a/dashboard/src/index.css b/dashboard/src/index.css
index bc4609ed..361cb0b0 100644
--- a/dashboard/src/index.css
+++ b/dashboard/src/index.css
@@ -87,6 +87,11 @@
--color-metrics-purple: #8b5cf6;
--color-success-bg: #064e3b;
--color-error-bg: #4c1d1f;
+ --color-success-glow: #4ade80; /* emerald-400 — text on dark surfaces */
+ --color-danger-glow: #fca5a5; /* red-300 — text on dark surfaces */
+ --color-warning-glow: #fcd34d; /* amber-300 — text on dark surfaces */
+ --color-info-glow: #93c5fd; /* blue-300 — text on dark surfaces */
+ --color-accent-purple-glow: #c4b5fd; /* purple-300 — text on dark surfaces */
/* ------------------------------------------------------------------ */
/* Motion tokens (mirror of dashboard/src/design/tokens.ts). */
@@ -205,6 +210,11 @@
--color-success: #047857; /* emerald-700 on white ≈ 6.3:1 */
--color-warning: #92400e; /* amber-800 on tinted light surfaces clears AA */
--color-danger: #dc2626; /* red-600 on white ≈ 4.8:1 */
+ --color-success-glow: #059669; /* emerald-600 on white */
+ --color-danger-glow: #b91c1c; /* red-700 on white */
+ --color-warning-glow: #92400e; /* amber-800 on white */
+ --color-info-glow: #2563eb; /* blue-600 on white */
+ --color-accent-purple-glow: #7c3aed; /* purple-600 on white */
/* Primary CTA: emerald-600 on white ≈ 4.6:1 (AA normal). */
--color-cta-bg: #059669;
@@ -615,23 +625,23 @@ body::before {
[data-theme="light-aaa"] .text-cyan-400 {
color: var(--color-accent-cyan) !important;
}
-[data-theme="light"] .text-emerald-300,
-[data-theme="light"] .text-emerald-400,
+[data-theme="light"] .text-[var(--color-success-glow)],
+[data-theme="light"] .text-[var(--color-success-glow)],
[data-theme="light"] .text-green-400,
-[data-theme="light-paper"] .text-emerald-300,
-[data-theme="light-paper"] .text-emerald-400,
+[data-theme="light-paper"] .text-[var(--color-success-glow)],
+[data-theme="light-paper"] .text-[var(--color-success-glow)],
[data-theme="light-paper"] .text-green-400,
-[data-theme="light-aaa"] .text-emerald-300,
-[data-theme="light-aaa"] .text-emerald-400,
+[data-theme="light-aaa"] .text-[var(--color-success-glow)],
+[data-theme="light-aaa"] .text-[var(--color-success-glow)],
[data-theme="light-aaa"] .text-green-400 {
color: var(--color-success) !important;
}
-[data-theme="light"] .text-amber-300,
-[data-theme="light"] .text-amber-400,
-[data-theme="light-paper"] .text-amber-300,
-[data-theme="light-paper"] .text-amber-400,
-[data-theme="light-aaa"] .text-amber-300,
-[data-theme="light-aaa"] .text-amber-400 {
+[data-theme="light"] .text-[var(--color-warning-glow)],
+[data-theme="light"] .text-[var(--color-warning-glow)],
+[data-theme="light-paper"] .text-[var(--color-warning-glow)],
+[data-theme="light-paper"] .text-[var(--color-warning-glow)],
+[data-theme="light-aaa"] .text-[var(--color-warning-glow)],
+[data-theme="light-aaa"] .text-[var(--color-warning-glow)] {
color: var(--color-warning) !important;
}
/* ------------------------------------------------------------------
@@ -640,14 +650,14 @@ body::before {
* amber-200 (#fde68a) has ~1.4:1 contrast on white — fails WCAG AA.
* bg-white/5 and border-white/5 are invisible on light backgrounds.
* ------------------------------------------------------------------ */
-[data-theme="light"] .text-amber-200,
-[data-theme="light-paper"] .text-amber-200,
-[data-theme="light-aaa"] .text-amber-200 {
+[data-theme="light"] .text-[var(--color-warning-glow)],
+[data-theme="light-paper"] .text-[var(--color-warning-glow)],
+[data-theme="light-aaa"] .text-[var(--color-warning-glow)] {
color: var(--color-warning) !important;
}
-[data-theme="light"] .text-amber-200\/80,
-[data-theme="light-paper"] .text-amber-200\/80,
-[data-theme="light-aaa"] .text-amber-200\/80 {
+[data-theme="light"] .text-[var(--color-warning-glow)]\/80,
+[data-theme="light-paper"] .text-[var(--color-warning-glow)]\/80,
+[data-theme="light-aaa"] .text-[var(--color-warning-glow)]\/80 {
color: var(--color-warning) !important;
}
[data-theme="light"] .border-amber-500\/20,
@@ -799,22 +809,22 @@ body::before {
* fail WCAG contrast on light backgrounds.
* Placed AFTER body::before closes so rules are top-level.
* ------------------------------------------------------------------ */
-[data-theme="light"] .text-rose-300,
-[data-theme="light"] .text-rose-400,
-[data-theme="light-paper"] .text-rose-300,
-[data-theme="light-paper"] .text-rose-400,
-[data-theme="light-aaa"] .text-rose-300,
-[data-theme="light-aaa"] .text-rose-400 {
+[data-theme="light"] .text-[var(--color-danger-glow)],
+[data-theme="light"] .text-[var(--color-danger-glow)],
+[data-theme="light-paper"] .text-[var(--color-danger-glow)],
+[data-theme="light-paper"] .text-[var(--color-danger-glow)],
+[data-theme="light-aaa"] .text-[var(--color-danger-glow)],
+[data-theme="light-aaa"] .text-[var(--color-danger-glow)] {
color: var(--color-danger) !important;
}
-[data-theme="light"] .text-red-300,
-[data-theme="light"] .text-red-400,
+[data-theme="light"] .text-[var(--color-danger-glow)],
+[data-theme="light"] .text-[var(--color-danger-glow)],
[data-theme="light"] .text-red-500,
-[data-theme="light-paper"] .text-red-300,
-[data-theme="light-paper"] .text-red-400,
+[data-theme="light-paper"] .text-[var(--color-danger-glow)],
+[data-theme="light-paper"] .text-[var(--color-danger-glow)],
[data-theme="light-paper"] .text-red-500,
-[data-theme="light-aaa"] .text-red-300,
-[data-theme="light-aaa"] .text-red-400,
+[data-theme="light-aaa"] .text-[var(--color-danger-glow)],
+[data-theme="light-aaa"] .text-[var(--color-danger-glow)],
[data-theme="light-aaa"] .text-red-500 {
color: var(--color-danger) !important;
}
diff --git a/dashboard/src/pages/AnalyticsPage.tsx b/dashboard/src/pages/AnalyticsPage.tsx
index 2ecb865a..a2e0e082 100644
--- a/dashboard/src/pages/AnalyticsPage.tsx
+++ b/dashboard/src/pages/AnalyticsPage.tsx
@@ -221,7 +221,7 @@ export default function AnalyticsPage() {
⚠
@@ -533,8 +533,8 @@ function MetricBox({ label, value }: { label: string; value: string }) {
function RateBar({ label, value, detail, color }: { label: string; value: number; detail: string; color: 'red' | 'green' }) {
const pct = Math.min(value * 100, 100);
- const barColor = color === 'red' ? 'bg-[var(--color-danger)]' : 'bg-emerald-500';
- const textColor = color === 'red' ? 'text-[var(--color-danger)]' : 'text-emerald-400';
+ const barColor = color === 'red' ? 'bg-[var(--color-danger)]' : 'bg-[var(--color-success)]';
+ const textColor = color === 'red' ? 'text-[var(--color-danger)]' : 'text-[var(--color-success-glow)]';
return (
diff --git a/dashboard/src/pages/AuditPage.tsx b/dashboard/src/pages/AuditPage.tsx
index 4beddd6a..c9cc3d3d 100644
--- a/dashboard/src/pages/AuditPage.tsx
+++ b/dashboard/src/pages/AuditPage.tsx
@@ -131,13 +131,13 @@ function hasActiveFilters(filters: AuditFilterState): boolean {
function actionBadgeClass(action: string): string {
if (action.includes('deny') || action.includes('kill')) {
- return 'border border-rose-500/30 bg-rose-500/10 text-rose-300';
+ return 'border border-[var(--color-danger)]/30 bg-[var(--color-danger)]/10 text-[var(--color-danger-glow)]';
}
if (action.includes('reject')) {
- return 'border border-[var(--color-warning)]/30 bg-[var(--color-warning)]/10 text-amber-300';
+ return 'border border-[var(--color-warning)]/30 bg-[var(--color-warning)]/10 text-[var(--color-warning-glow)]';
}
if (action.includes('approve') || action.includes('allowed')) {
- return 'border border-emerald-500/30 bg-emerald-500/10 text-emerald-300';
+ return 'border border-[var(--color-success)]/30 bg-[var(--color-success)]/10 text-[var(--color-success-glow)]';
}
if (action.includes('create') || action.includes('authenticated')) {
return 'border border-[var(--color-accent-cyan)]/30 bg-[var(--color-accent-cyan)]/10 text-[var(--color-accent-cyan-glow)]';
@@ -222,8 +222,8 @@ function MetadataField({
function ExportMetadataCard({ result }: { result: AuditExportResult }) {
const t = useT();
const integrityTone = result.integrity?.valid
- ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-300'
- : 'border-rose-500/30 bg-rose-500/10 text-rose-300';
+ ? 'border-[var(--color-success)]/30 bg-[var(--color-success)]/10 text-[var(--color-success-glow)]'
+ : 'border-[var(--color-danger)]/30 bg-[var(--color-danger)]/10 text-[var(--color-danger-glow)]';
const integrityLabel = result.integrity?.valid ? 'Integrity verified' : 'Integrity check failed';
return (
@@ -259,7 +259,7 @@ function ExportMetadataCard({ result }: { result: AuditExportResult }) {
{result.integrity && result.integrity.brokenAt !== undefined ? (
-
+
Chain verification failed at line {result.integrity.brokenAt}.
) : null}
@@ -281,7 +281,7 @@ function ChainIntegrityBadge({ state }: { state: IntegrityState }) {
return (
- Integrity check failed: {state.error}
+ Integrity check failed: {state.error}
);
}
@@ -297,11 +297,11 @@ function ChainIntegrityBadge({ state }: { state: IntegrityState }) {
if (state.integrity && !state.integrity.valid) {
return (
-
-
-
Chain broken
+
+
+ Chain broken
{state.integrity.brokenAt !== undefined && (
- (at seq {state.integrity.brokenAt})
+ (at seq {state.integrity.brokenAt})
)}
);
@@ -309,9 +309,9 @@ function ChainIntegrityBadge({ state }: { state: IntegrityState }) {
if (state.chain) {
return (
-
-
-
+
+
+
Chain verified ({state.chain.count} records)
@@ -662,7 +662,7 @@ export default function AuditPage() {
disabled={page !== 1}
className={`flex min-h-[44px] items-center gap-1.5 rounded border px-3 py-2 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${
liveTail
- ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-300 hover:bg-emerald-500/20'
+ ? 'border-[var(--color-success)]/30 bg-[var(--color-success)]/10 text-[var(--color-success-glow)] hover:bg-[var(--color-success)]/20'
: 'border-[var(--color-void-lighter)] bg-[var(--color-void-light)] text-[var(--color-text-primary)] hover:bg-[var(--color-void-lighter)]'
}`}
aria-label={liveTail ? 'Pause live tail' : 'Start live tail'}
@@ -797,10 +797,10 @@ export default function AuditPage() {
{filterError ? (
-
{filterError}
+
{filterError}
) : null}
{exportError ? (
-
{exportError}
+
{exportError}
) : null}
diff --git a/dashboard/src/pages/AuthKeysPage.tsx b/dashboard/src/pages/AuthKeysPage.tsx
index c7fc3640..ae141097 100644
--- a/dashboard/src/pages/AuthKeysPage.tsx
+++ b/dashboard/src/pages/AuthKeysPage.tsx
@@ -285,11 +285,11 @@ export default function AuthKeysPage() {
{createdKey ? (
-
+
-
{t('authKeys.storeKeyNow')}
-
+
{t('authKeys.storeKeyNow')}
+
{t('authKeys.secretShownOnce')}
@@ -299,7 +299,7 @@ export default function AuthKeysPage() {
setCreatedKey(null);
setSecretVisible(false);
}}
- className="text-xs font-medium text-emerald-200/80 transition-colors hover:text-emerald-200"
+ className="text-xs font-medium text-[var(--color-success-glow)]/80 transition-colors hover:text-[var(--color-success-glow)]"
aria-label={t('authKeys.dismiss')}
>
{t('authKeys.dismiss')}
@@ -401,7 +401,7 @@ export default function AuthKeysPage() {
onClick={() => void handleRevoke(key.id, key.name)}
disabled={revokingId === key.id}
aria-label={`Revoke auth key ${key.name}`}
- className="flex min-h-[40px] items-center justify-center gap-2 rounded border border-[var(--color-danger)]/20 bg-[var(--color-danger)]/05 px-3 py-2 text-xs font-medium text-[var(--color-danger)] dark:text-red-300 transition-colors hover:bg-[var(--color-danger)]/10 disabled:cursor-not-allowed disabled:opacity-60"
+ className="flex min-h-[40px] items-center justify-center gap-2 rounded border border-[var(--color-danger)]/20 bg-[var(--color-danger)]/05 px-3 py-2 text-xs font-medium text-[var(--color-danger)] dark:text-[var(--color-danger-glow)] transition-colors hover:bg-[var(--color-danger)]/10 disabled:cursor-not-allowed disabled:opacity-60"
>
{revokingId === key.id ? t('authKeys.revoking') : t('authKeys.revoke')}
diff --git a/dashboard/src/pages/CostPage.tsx b/dashboard/src/pages/CostPage.tsx
index 0009ebbd..75b47f08 100644
--- a/dashboard/src/pages/CostPage.tsx
+++ b/dashboard/src/pages/CostPage.tsx
@@ -120,13 +120,13 @@ function BudgetOverview({ dailyData, budgetSettings, navigateToSettings }: Budge
-
{t('cost.budgetAlertSection.title')}
+
{t('cost.budgetAlertSection.title')}
{t('cost.budgetAlertSection.description')}{' '}
diff --git a/dashboard/src/pages/MetricsPage.tsx b/dashboard/src/pages/MetricsPage.tsx
index 6c516acb..7fa3b1ee 100644
--- a/dashboard/src/pages/MetricsPage.tsx
+++ b/dashboard/src/pages/MetricsPage.tsx
@@ -254,7 +254,7 @@ export default function MetricsPage() {
-
+
Anomalous Sessions ({data.anomalies?.length})
@@ -263,7 +263,7 @@ export default function MetricsPage() {
{data.anomalies.map((a) => (
-
+
{a.sessionId.slice(0, 12)}
diff --git a/dashboard/src/pages/SessionHistoryPage.tsx b/dashboard/src/pages/SessionHistoryPage.tsx
index 207cc150..481c408c 100644
--- a/dashboard/src/pages/SessionHistoryPage.tsx
+++ b/dashboard/src/pages/SessionHistoryPage.tsx
@@ -48,8 +48,8 @@ function formatTimestamp(ts?: number): string {
}
function statusClass(status: SessionHistoryRecord['finalStatus']): string {
- if (status === 'active') return 'text-emerald-300 bg-emerald-500/10 border-emerald-500/25';
- if (status === 'killed') return 'text-rose-300 bg-rose-500/10 border-rose-500/25';
+ if (status === 'active') return 'text-[var(--color-success-glow)] bg-[var(--color-success)]/10 border-[var(--color-success)]/25';
+ if (status === 'killed') return 'text-[var(--color-danger-glow)] bg-[var(--color-danger)]/10 border-[var(--color-danger)]/25';
return 'text-[var(--color-text-muted)] dark:text-[var(--color-text-primary)] bg-[var(--color-void-lighter)]/40 border-[var(--color-void-lighter)]';
}
@@ -613,7 +613,7 @@ export default function SessionHistoryPage() {
) : error ? (
-
+
{t('templates.loadErrorTitle')}
{error}
setDeleteTarget({ id: template.id, name: template.name })}
aria-label={`Delete template ${template.name}`}
disabled={deletingId === template.id}
- className="flex min-h-[40px] items-center justify-center gap-1.5 rounded border border-[var(--color-danger)]/20 bg-[var(--color-danger)]/05 px-3 py-2 text-xs font-medium text-[var(--color-danger)] dark:text-red-300 transition-colors hover:bg-[var(--color-danger)]/10 disabled:cursor-not-allowed disabled:opacity-60"
+ className="flex min-h-[40px] items-center justify-center gap-1.5 rounded border border-[var(--color-danger)]/20 bg-[var(--color-danger)]/05 px-3 py-2 text-xs font-medium text-[var(--color-danger)] dark:text-[var(--color-danger-glow)] transition-colors hover:bg-[var(--color-danger)]/10 disabled:cursor-not-allowed disabled:opacity-60"
>
{deletingId === template.id ? t('templates.deleting') : t('templates.deleteButton')}
diff --git a/dashboard/src/types/acp-approval.ts b/dashboard/src/types/acp-approval.ts
index 94e62a6f..a6ee2ef5 100644
--- a/dashboard/src/types/acp-approval.ts
+++ b/dashboard/src/types/acp-approval.ts
@@ -63,8 +63,8 @@ export interface AcpApprovalActionResult {
/** Risk level styling config. */
export const RISK_LEVEL_CONFIG: Record = {
- low: { label: 'Low Risk', bg: 'bg-green-500/10', text: 'text-green-400', border: 'border-green-500/30' },
- medium: { label: 'Medium Risk', bg: 'bg-amber-500/10', text: 'text-amber-400', border: 'border-amber-500/30' },
+ low: { label: 'Low Risk', bg: 'bg-[var(--color-success)]/10', text: 'text-[var(--color-success-glow)]', border: 'border-[var(--color-success)]/30' },
+ medium: { label: 'Medium Risk', bg: 'bg-[var(--color-warning)]/10', text: 'text-[var(--color-warning-glow)]', border: 'border-[var(--color-warning)]/30' },
high: { label: 'High Risk', bg: 'bg-orange-500/10', text: 'text-orange-400', border: 'border-orange-500/30' },
- critical: { label: 'Critical Risk', bg: 'bg-red-500/10', text: 'text-red-400', border: 'border-red-500/30' },
+ critical: { label: 'Critical Risk', bg: 'bg-[var(--color-danger)]/10', text: 'text-[var(--color-danger-glow)]', border: 'border-[var(--color-danger)]/30' },
};
diff --git a/dashboard/src/types/acp-driver-observer.ts b/dashboard/src/types/acp-driver-observer.ts
index df44d93e..0301a5d3 100644
--- a/dashboard/src/types/acp-driver-observer.ts
+++ b/dashboard/src/types/acp-driver-observer.ts
@@ -63,8 +63,8 @@ export interface AcpSessionParticipants {
/** Role color mapping for UI badges. */
export const ROLE_COLORS: Record = {
- driver: { bg: 'bg-blue-500/20', text: 'text-blue-400' },
+ driver: { bg: 'bg-[var(--color-info)]/20', text: 'text-[var(--color-info-glow)]' },
observer: { bg: 'bg-zinc-500/20', text: 'text-zinc-400' },
- operator: { bg: 'bg-amber-500/20', text: 'text-amber-400' },
- admin: { bg: 'bg-red-500/20', text: 'text-red-400' },
+ operator: { bg: 'bg-[var(--color-warning)]/20', text: 'text-[var(--color-warning-glow)]' },
+ admin: { bg: 'bg-[var(--color-danger)]/20', text: 'text-[var(--color-danger-glow)]' },
};