UX/UI improvements: accessibility, polish, and responsiveness (10 items)
Some checks failed
CI / lint (pull_request) Failing after 1m4s
CI / test (3.10) (pull_request) Failing after 43s
CI / test (3.11) (pull_request) Failing after 42s
CI / test (3.12) (pull_request) Successful in 57s
CI / docker (pull_request) Has been skipped

1. WCAG AA contrast fixes - --text-muted increased to #8b8b95 for 4.5:1+ ratio
2. ARIA roles - tabs, avatars, status cards, live regions, alerts across all pages
3. Unique head colors - 12 distinct colors per head via data-head CSS selectors
4. Toast notification system - ToastProvider with success/error/info/warning types
5. Structured per-head response cards - colored dot indicators, head summaries
6. Status visualization - colored status dots (healthy/degraded/offline) with glow
7. Collapsible avatar grid - toggle button on mobile, persists collapsed state
8. System color scheme detection - prefers-color-scheme media query + JS fallback
9. Markdown rendering - lightweight parser for code, lists, headings, links, bold/italic
10. Mobile touch targets - 44px minimum on all interactive elements per WCAG AAA

Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
This commit is contained in:
Devin AI
2026-05-02 02:47:30 +00:00
parent a63e8505fa
commit 08b5ea7c9a
12 changed files with 560 additions and 113 deletions

View File

@@ -1,11 +1,16 @@
import { useState, useEffect, useCallback } from 'react'
import type { SystemStatus, VoiceProfile } from '../types'
function StatusCard({ label, value, unit }: { label: string; value: string | number | null; unit?: string }) {
function StatusCard({ label, value, unit, statusClass }: {
label: string; value: string | number | null; unit?: string; statusClass?: string
}) {
return (
<div className="status-card">
<div className="status-card" role="status" aria-label={`${label}: ${value ?? 'N/A'}${unit && value != null ? unit : ''}`}>
<span className="status-label">{label}</span>
<span className="status-value">{value ?? 'N/A'}{unit && value != null ? unit : ''}</span>
<span className={`status-value ${statusClass || ''}`}>
{statusClass && <span className={`status-dot ${statusClass}`} aria-hidden="true" />}
{value ?? 'N/A'}{unit && value != null ? unit : ''}
</span>
</div>
)
}
@@ -63,25 +68,34 @@ export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, s
return `${h}h ${m}m`
}
if (loading) return <div className="page-loading">Loading admin dashboard...</div>
const statusClass = status?.status === 'healthy' ? 'healthy' : status?.status === 'degraded' ? 'degraded' : status?.status === 'offline' ? 'offline' : ''
if (loading) return <div className="page-loading" role="status" aria-live="polite">Loading admin dashboard...</div>
return (
<div className="admin-page">
<div className="admin-tabs">
<div className="admin-page" role="main" aria-label="Admin Dashboard">
<div className="admin-tabs" role="tablist" aria-label="Admin sections">
{(['overview', 'voices', 'agents', 'governance'] as const).map((t) => (
<button key={t} className={tab === t ? 'active' : ''} onClick={() => setTab(t)}>
<button
key={t}
className={tab === t ? 'active' : ''}
onClick={() => setTab(t)}
role="tab"
aria-selected={tab === t}
aria-controls={`panel-${t}`}
>
{t.charAt(0).toUpperCase() + t.slice(1)}
</button>
))}
</div>
{error && <div className="error-banner" onClick={() => setError(null)}>{error}</div>}
{error && <div className="error-banner" role="alert" onClick={() => setError(null)}>{error}</div>}
{tab === 'overview' && (
<div className="admin-section">
<div className="admin-section" role="tabpanel" id="panel-overview" aria-label="System Overview">
<h2>System Overview</h2>
<div className="status-grid">
<StatusCard label="Status" value={status?.status ?? 'unknown'} />
<div className="status-grid" role="group" aria-label="System metrics">
<StatusCard label="Status" value={status?.status ?? 'unknown'} statusClass={statusClass} />
<StatusCard label="Uptime" value={status ? formatUptime(status.uptime_seconds) : 'N/A'} />
<StatusCard label="Active Tasks" value={status?.active_tasks ?? 0} />
<StatusCard label="Active Agents" value={status?.active_agents ?? 0} />
@@ -93,11 +107,13 @@ export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, s
)}
{tab === 'voices' && (
<div className="admin-section">
<div className="admin-section" role="tabpanel" id="panel-voices" aria-label="Voice Library">
<h2>Voice Library</h2>
<div className="add-form">
<input placeholder="Voice name" value={newVoiceName} onChange={(e) => setNewVoiceName(e.target.value)} />
<select value={newVoiceLang} onChange={(e) => setNewVoiceLang(e.target.value)}>
<div className="add-form" role="form" aria-label="Add voice">
<label htmlFor="voice-name" className="sr-only">Voice name</label>
<input id="voice-name" placeholder="Voice name" value={newVoiceName} onChange={(e) => setNewVoiceName(e.target.value)} />
<label htmlFor="voice-lang" className="sr-only">Language</label>
<select id="voice-lang" value={newVoiceLang} onChange={(e) => setNewVoiceLang(e.target.value)}>
<option value="en-US">English (US)</option>
<option value="en-GB">English (UK)</option>
<option value="es-ES">Spanish</option>
@@ -107,10 +123,10 @@ export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, s
</select>
<button onClick={addVoice}>Add Voice</button>
</div>
<div className="voice-list">
<div className="voice-list" role="list" aria-label="Voice profiles">
{voices.length === 0 && <p className="muted">No voice profiles configured</p>}
{voices.map((v) => (
<div key={v.id} className="voice-card">
<div key={v.id} className="voice-card" role="listitem">
<strong>{v.name}</strong>
<span className="muted">{v.language} | {v.provider}</span>
<span className="muted">Pitch: {v.pitch}x | Speed: {v.speed}x</span>
@@ -121,13 +137,13 @@ export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, s
)}
{tab === 'agents' && (
<div className="admin-section">
<div className="admin-section" role="tabpanel" id="panel-agents" aria-label="Agent Configuration">
<h2>Agent Configuration</h2>
<div className="agent-grid">
<div className="agent-grid" role="list" aria-label="Active agents">
{['Planner', 'Reasoner', 'Executor', 'Critic', '12 Heads', 'Witness'].map((a) => (
<div key={a} className="agent-card">
<div key={a} className="agent-card" role="listitem">
<strong>{a}</strong>
<span className="status-badge active">Active</span>
<span className="status-badge active" role="status">Active</span>
</div>
))}
</div>
@@ -135,10 +151,10 @@ export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, s
)}
{tab === 'governance' && (
<div className="admin-section">
<div className="admin-section" role="tabpanel" id="panel-governance" aria-label="Governance Mode">
<h2>Governance Mode</h2>
<div className="governance-info">
<div className="governance-mode">
<div className="governance-mode" role="status" aria-label="Current governance mode: Advisory">
<span className="mode-label">Current Mode:</span>
<span className="mode-value advisory">ADVISORY</span>
</div>