# Frontend Redesign Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Completely replace the existing frontend UI layer with a new design strictly following the HTML prototypes in `Prototype/`, building a 6-page app with a permanent 232px left sidebar. **Architecture:** Delete all existing layout/page components and CSS. Rebuild with React 19 + TypeScript using a CSS-first approach (design tokens in globals.css), a permanent Sidebar component, and 6 dedicated page components. Preserve api/, data/, types/ unchanged. **Tech Stack:** React 19, TypeScript, Vite, TailwindCSS 4, React Router 7, Lucide React icons. --- ## File Structure Map **Delete (existing UI layer):** - `frontend/src/styles/globals.css` — rewrite entirely - `frontend/src/components/layout/AppShell.tsx` — rewrite - `frontend/src/components/layout/FooterLayout.tsx` — delete - `frontend/src/components/layout/HeaderBrand.tsx` — delete - `frontend/src/components/layout/HeaderLayout.tsx` — delete - `frontend/src/components/layout/KeepAliveViewport.tsx` — delete - `frontend/src/components/layout/TabNav.tsx` — delete - `frontend/src/components/layout/ContentLayout.tsx` — delete - `frontend/src/components/common/TLogo.tsx` — delete - `frontend/src/components/common/TPattern.tsx` — delete - `frontend/src/components/common/ThemeToggle.tsx` — delete - `frontend/src/router/tabs.tsx` — delete - `frontend/src/pages/Status/StatusPage.tsx` — rewrite - `frontend/src/pages/Perception/PerceptionPage.tsx` — rewrite - `frontend/src/pages/Perception/EventFeed.tsx` — rewrite - `frontend/src/pages/Perception/AnalysisPanel.tsx` — rewrite (if exists) - `frontend/src/pages/Docs/DocsPage.tsx` — rewrite - `frontend/src/pages/Compliance/CompliancePage.tsx` — rewrite - `frontend/src/pages/RagChat/RagChatPage.tsx` — rewrite - `frontend/src/pages/RagChat/CitedAnswer.tsx` — delete (inline into RagChatPage) **Create (new UI layer):** - `frontend/src/styles/globals.css` — design tokens + base reset - `frontend/src/components/layout/AppShell.tsx` — CSS grid: sidebar(232px) + content-area - `frontend/src/components/layout/Sidebar.tsx` — brand + nav groups + footer + theme toggle - `frontend/src/components/layout/Topbar.tsx` — sticky topbar per page - `frontend/src/router/AppRouter.tsx` — 6 routes with AppShell layout - `frontend/src/pages/Overview/OverviewPage.tsx` — launcher index - `frontend/src/pages/Status/StatusPage.tsx` — system status dashboard - `frontend/src/pages/Perception/PerceptionPage.tsx` — regulatory signals two-pane - `frontend/src/pages/Docs/DocsPage.tsx` — document management table - `frontend/src/pages/Compliance/CompliancePage.tsx` — three-column workspace - `frontend/src/pages/RagChat/RagChatPage.tsx` — three-column chat **Preserve (no changes):** - `frontend/src/api/` — all files - `frontend/src/data/` — all files - `frontend/src/types/` — all files - `frontend/src/App.tsx` — keep as-is (ThemeProvider wrapper) - `frontend/src/contexts/ThemeContext.tsx` — update to use `data-theme` on `` --- ## Task 1: Rewrite globals.css with design tokens **Files:** - Modify: `frontend/src/styles/globals.css` - [ ] **Step 1: Delete all existing CSS content and write new design tokens** Replace the entire file content: ```css /* ── Design Tokens ──────────────────────────────── */ :root { --rail-bg: #ffffff; --rail-surface: #f7f8fa; --rail-fg: #111827; --rail-muted: #8b929e; --rail-border: #e8eaed; --rail-hover: rgba(0,0,0,.04); --rail-active: rgba(226,0,116,.07); --bg: #f2f4f7; --surface: #ffffff; --fg: #111827; --muted: #6b7280; --border: #e5e7eb; --border-strong: #d1d5db; --accent: #e20074; --accent-dim: rgba(226,0,116,.10); --accent-hover: #c8006a; --success: #16a34a; --success-bg: rgba(22,163,74,.08); --warn: #d97706; --warn-bg: rgba(217,119,6,.08); --danger: #dc2626; --danger-bg: rgba(220,38,38,.08); --info: #2563eb; --info-bg: rgba(37,99,235,.08); --font-display: "TeleNeoWeb-Bold", "Inter", -apple-system, sans-serif; --font-body: "TeleNeoWeb-Regular", "Inter", -apple-system, sans-serif; --font-mono: ui-monospace, "JetBrains Mono", Menlo, monospace; --sidebar-w: 232px; --topbar-h: 54px; --radius-sm: 6px; --radius-md: 10px; --radius-pill: 9999px; --shadow-card: 0 1px 4px rgba(0,0,0,.06), 0 0 0 1px rgba(0,0,0,.04); } [data-theme="dark"] { --rail-bg: #1a1a2e; --rail-surface: #16213e; --rail-fg: #f0f0f0; --rail-muted: #8b929e; --rail-border: #2d3748; --rail-hover: rgba(255,255,255,.06); --rail-active: rgba(226,0,116,.15); --bg: #0f0f1a; --surface: #1a1a2e; --fg: #f0f0f0; --muted: #9ca3af; --border: #2d3748; --border-strong: #4a5568; } /* ── Base Reset ─────────────────────────────────── */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } html { height: 100%; } body { height: 100%; font-family: var(--font-body); background: var(--bg); color: var(--fg); line-height: 1.5; -webkit-font-smoothing: antialiased; } #root { height: 100%; } /* ── Utility Classes ────────────────────────────── */ .status { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: var(--radius-pill); } .status::before { content: ''; width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; } .status.ok { color: var(--success); background: var(--success-bg); } .status.ok::before { background: var(--success); } .status.warn { color: var(--warn); background: var(--warn-bg); } .status.warn::before { background: var(--warn); } .status.risk { color: var(--danger); background: var(--danger-bg); } .status.risk::before { background: var(--danger); } .status.info { color: var(--info); background: var(--info-bg); } .status.info::before { background: var(--info); } .card { background: var(--surface); border-radius: var(--radius-md); box-shadow: var(--shadow-card); padding: 16px 18px; } .btn { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px; border-radius: var(--radius-sm); font-size: 13px; font-family: var(--font-body); font-weight: 500; cursor: pointer; border: 1px solid var(--border); background: var(--surface); color: var(--fg); transition: background 0.15s, color 0.15s; white-space: nowrap; } .btn:hover { background: var(--bg); } .btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); } .btn.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); } .btn.sm { height: 28px; font-size: 12px; padding: 0 10px; } ``` - [ ] **Step 2: Verify CSS loads without errors** Run: `cd frontend && npm run build 2>&1 | head -30` Expected: No CSS parse errors. - [ ] **Step 3: Commit** ```bash git add frontend/src/styles/globals.css git commit -m "feat: rewrite globals.css with prototype design tokens and base utilities" ``` --- ## Task 2: Update ThemeContext to use data-theme on html element **Files:** - Modify: `frontend/src/contexts/ThemeContext.tsx` - [ ] **Step 1: Read the current ThemeContext** Read `frontend/src/contexts/ThemeContext.tsx` to understand its current implementation. - [ ] **Step 2: Update theme application to use data-theme attribute** Replace the theme application logic so that toggling dark mode sets `document.documentElement.setAttribute('data-theme', 'dark')` instead of adding a CSS class. The full replacement: ```tsx import React, { createContext, useContext, useEffect, useState } from 'react'; type Theme = 'light' | 'dark'; interface ThemeContextValue { theme: Theme; toggleTheme: () => void; } const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {}, }); export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState(() => { const saved = localStorage.getItem('theme'); return (saved === 'dark' || saved === 'light') ? saved : 'light'; }); useEffect(() => { if (theme === 'dark') { document.documentElement.setAttribute('data-theme', 'dark'); } else { document.documentElement.removeAttribute('data-theme'); } localStorage.setItem('theme', theme); }, [theme]); const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light'); return ( {children} ); } export function useTheme() { return useContext(ThemeContext); } ``` - [ ] **Step 3: Commit** ```bash git add frontend/src/contexts/ThemeContext.tsx git commit -m "feat: update ThemeContext to use data-theme attribute on html element" ``` --- ## Task 3: Delete old layout/common components and router tabs **Files:** - Delete: `frontend/src/components/layout/FooterLayout.tsx` - Delete: `frontend/src/components/layout/HeaderBrand.tsx` - Delete: `frontend/src/components/layout/HeaderLayout.tsx` - Delete: `frontend/src/components/layout/KeepAliveViewport.tsx` - Delete: `frontend/src/components/layout/TabNav.tsx` - Delete: `frontend/src/components/layout/ContentLayout.tsx` - Delete: `frontend/src/components/common/TLogo.tsx` - Delete: `frontend/src/components/common/TPattern.tsx` - Delete: `frontend/src/components/common/ThemeToggle.tsx` - Delete: `frontend/src/router/tabs.tsx` - [ ] **Step 1: Delete old component files** ```bash cd frontend rm src/components/layout/FooterLayout.tsx rm src/components/layout/HeaderBrand.tsx rm src/components/layout/HeaderLayout.tsx rm src/components/layout/KeepAliveViewport.tsx rm src/components/layout/TabNav.tsx rm src/components/layout/ContentLayout.tsx rm src/components/common/TLogo.tsx rm src/components/common/TPattern.tsx rm src/components/common/ThemeToggle.tsx rm src/router/tabs.tsx ``` - [ ] **Step 2: Commit** ```bash git add -A git commit -m "chore: delete old layout/common/tabs components before redesign" ``` --- ## Task 4: Build Sidebar component **Files:** - Create: `frontend/src/components/layout/Sidebar.tsx` - [ ] **Step 1: Create Sidebar.tsx** ```tsx import { NavLink, useLocation } from 'react-router-dom'; import { LayoutDashboard, Radio, Monitor, FileText, Shield, MessageSquare, Sun, Moon } from 'lucide-react'; import { useTheme } from '../../contexts/ThemeContext'; interface NavItem { to: string; icon: React.ReactNode; label: string; badge?: number; } const mainNav: NavItem[] = [ { to: '/', icon: , label: 'Overview' }, { to: '/signals', icon: , label: 'Regulatory Signals' }, { to: '/status', icon: , label: 'System Status' }, ]; const workbenchNav: NavItem[] = [ { to: '/documents', icon: , label: 'Documents' }, { to: '/compliance', icon: , label: 'Compliance Analysis' }, ]; const chatNav: NavItem[] = [ { to: '/chat', icon: , label: 'Regulation Q&A' }, ]; function NavGroup({ title, items }: { title: string; items: NavItem[] }) { return (
{title}
{items.map(item => ( `nav-item${isActive ? ' active' : ''}`} > {item.icon} {item.label} {item.badge !== undefined && item.badge > 0 && ( {item.badge} )} ))}
); } export function Sidebar() { const { theme, toggleTheme } = useTheme(); return ( ); } ``` - [ ] **Step 2: Add sidebar CSS to globals.css** Append to `frontend/src/styles/globals.css`: ```css /* ── Sidebar ────────────────────────────────────── */ .sidebar { width: var(--sidebar-w); min-height: 100vh; background: var(--rail-bg); border-right: 1px solid var(--rail-border); display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh; overflow: hidden; flex-shrink: 0; } .sidebar-brand { display: flex; align-items: center; gap: 10px; padding: 18px 16px 14px; border-bottom: 1px solid var(--rail-border); } .brand-mark { width: 32px; height: 32px; background: var(--accent); color: #fff; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; font-family: var(--font-display); flex-shrink: 0; } .brand-name { font-size: 13px; font-weight: 700; font-family: var(--font-display); color: var(--rail-fg); } .brand-sub { font-size: 10px; color: var(--rail-muted); } .sidebar-nav { flex: 1; overflow-y: auto; padding: 12px 0; } .nav-group { margin-bottom: 4px; } .nav-group-label { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--rail-muted); padding: 8px 16px 4px; } .nav-item { display: flex; align-items: center; gap: 9px; height: 36px; padding: 0 14px 0 16px; text-decoration: none; color: var(--rail-fg); font-size: 13px; border-left: 3px solid transparent; transition: background 0.12s, color 0.12s; position: relative; } .nav-item:hover { background: var(--rail-hover); } .nav-item.active { background: var(--rail-active); color: var(--accent); border-left-color: var(--accent); font-weight: 600; } .nav-icon { display: flex; align-items: center; color: inherit; flex-shrink: 0; } .nav-label { flex: 1; } .nav-badge { font-size: 10px; font-weight: 700; font-family: var(--font-mono); background: var(--accent-dim); color: var(--accent); padding: 1px 6px; border-radius: var(--radius-pill); } .sidebar-footer { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-top: 1px solid var(--rail-border); } .sidebar-user { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; } .user-avatar { width: 28px; height: 28px; background: var(--accent-dim); color: var(--accent); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; flex-shrink: 0; } .user-name { font-size: 12px; font-weight: 600; color: var(--rail-fg); } .user-role { font-size: 10px; color: var(--rail-muted); } .theme-btn { width: 28px; height: 28px; border: 1px solid var(--rail-border); background: var(--rail-surface); color: var(--rail-muted); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: background 0.12s; } .theme-btn:hover { background: var(--rail-hover); color: var(--rail-fg); } ``` - [ ] **Step 3: Commit** ```bash git add frontend/src/components/layout/Sidebar.tsx frontend/src/styles/globals.css git commit -m "feat: add Sidebar component with nav groups, badges, and theme toggle" ``` --- ## Task 5: Build AppShell + Topbar + rewrite AppRouter **Files:** - Modify: `frontend/src/components/layout/AppShell.tsx` - Create: `frontend/src/components/layout/Topbar.tsx` - Modify: `frontend/src/router/AppRouter.tsx` - [ ] **Step 1: Rewrite AppShell.tsx** ```tsx import { Outlet } from 'react-router-dom'; import { Sidebar } from './Sidebar'; export function AppShell() { return (
); } ``` Append to `frontend/src/styles/globals.css`: ```css /* ── App Shell ──────────────────────────────────── */ .app-shell { display: flex; height: 100vh; overflow: hidden; } .content-area { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; } ``` - [ ] **Step 2: Create Topbar.tsx** ```tsx interface TopbarProps { title: string; subtitle?: string; actions?: React.ReactNode; } export function Topbar({ title, subtitle, actions }: TopbarProps) { return (

{title}

{subtitle && {subtitle}}
{actions &&
{actions}
}
); } ``` Append to `frontend/src/styles/globals.css`: ```css /* ── Topbar ─────────────────────────────────────── */ .topbar { height: var(--topbar-h); display: flex; align-items: center; justify-content: space-between; padding: 0 22px; border-bottom: 1px solid var(--border); background: var(--surface); flex-shrink: 0; position: sticky; top: 0; z-index: 10; } .topbar-left { display: flex; align-items: baseline; gap: 10px; min-width: 0; } .topbar-title { font-size: 15px; font-weight: 700; font-family: var(--font-display); color: var(--fg); } .topbar-sub { font-size: 11px; color: var(--muted); font-family: var(--font-mono); } .topbar-actions { display: flex; align-items: center; gap: 8px; } ``` - [ ] **Step 3: Rewrite AppRouter.tsx with 6 routes** ```tsx import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { AppShell } from '../components/layout/AppShell'; import { OverviewPage } from '../pages/Overview/OverviewPage'; import { StatusPage } from '../pages/Status/StatusPage'; import { PerceptionPage } from '../pages/Perception/PerceptionPage'; import { DocsPage } from '../pages/Docs/DocsPage'; import { CompliancePage } from '../pages/Compliance/CompliancePage'; import { RagChatPage } from '../pages/RagChat/RagChatPage'; export function AppRouter() { return ( }> } /> } /> } /> } /> } /> } /> ); } ``` - [ ] **Step 4: Create stub page files so the build doesn't fail** Create each page as a minimal placeholder: `frontend/src/pages/Overview/OverviewPage.tsx`: ```tsx export function OverviewPage() { return

Overview

; } ``` `frontend/src/pages/Status/StatusPage.tsx` (overwrite): ```tsx export function StatusPage() { return

Status

; } ``` `frontend/src/pages/Perception/PerceptionPage.tsx` (overwrite): ```tsx export function PerceptionPage() { return

Signals

; } ``` `frontend/src/pages/Docs/DocsPage.tsx` (overwrite): ```tsx export function DocsPage() { return

Documents

; } ``` `frontend/src/pages/Compliance/CompliancePage.tsx` (overwrite): ```tsx export function CompliancePage() { return

Compliance

; } ``` `frontend/src/pages/RagChat/RagChatPage.tsx` (overwrite): ```tsx export function RagChatPage() { return

Chat

; } ``` Also delete now-unused files: ```bash cd frontend rm -f src/pages/Perception/EventFeed.tsx src/pages/Perception/AnalysisPanel.tsx rm -f src/pages/RagChat/CitedAnswer.tsx ``` Append to `frontend/src/styles/globals.css`: ```css /* ── Page Content ───────────────────────────────── */ .page-content { flex: 1; overflow-y: auto; padding: 24px; } ``` - [ ] **Step 5: Verify build passes** ```bash cd frontend && npm run build 2>&1 | tail -20 ``` Expected: Build succeeds with no TypeScript or import errors. - [ ] **Step 6: Commit** ```bash git add -A git commit -m "feat: add AppShell + Topbar + 6-route AppRouter with stub pages" ``` --- ## Task 6: Implement Overview page **Files:** - Modify: `frontend/src/pages/Overview/OverviewPage.tsx` - [ ] **Step 1: Write OverviewPage with hero, summary card, workflow steps, screen grid** ```tsx import { useNavigate } from 'react-router-dom'; import { ArrowRight, BarChart2, Eye, FileText, Shield, MessageSquare, Monitor } from 'lucide-react'; const SCREENS = [ { id: 'status', label: 'System Status', icon: , to: '/status', desc: 'Live health and workflow queue' }, { id: 'signals', label: 'Regulatory Signals', icon: , to: '/signals', desc: 'AI-detected regulatory changes' }, { id: 'documents', label: 'Document Management', icon: , to: '/documents', desc: 'Upload and inspect documents' }, { id: 'compliance', label: 'Compliance Analysis', icon: , to: '/compliance', desc: 'Three-column compliance workspace' }, { id: 'chat', label: 'Regulation Q&A', icon: , to: '/chat', desc: 'Chat with cited regulation sources' }, { id: 'analytics', label: 'Analytics', icon: , to: '/status', desc: 'KPIs and coverage metrics' }, ]; const STEPS = [ { num: '01', label: 'Upload', desc: 'Ingest regulation documents' }, { num: '02', label: 'Process', desc: 'Embed and chunk via vector DB' }, { num: '03', label: 'Monitor', desc: 'Watch regulatory signal feed' }, { num: '04', label: 'Analyze', desc: 'Run compliance gap analysis' }, { num: '05', label: 'Review', desc: 'Inspect findings with AI assist' }, { num: '06', label: 'Chat', desc: 'Ask questions with cited answers' }, ]; export function OverviewPage() { const navigate = useNavigate(); return (

T-Systems · AI Regulation Hub

AI Compliance,
Automated end-to-end

Monitor global AI regulations, analyze document compliance gaps, and get cited answers — all in one platform.

6 Screens
5 Backend-aware flows
AI Review posture

How it works

{STEPS.map(s => (
{s.num}
{s.label}
{s.desc}
))}

Screens

{SCREENS.map(s => ( ))}
); } ``` - [ ] **Step 2: Add Overview page CSS to globals.css** Append: ```css /* ── Overview Page ──────────────────────────────── */ .overview-page { padding: 32px; max-width: 900px; display: flex; flex-direction: column; gap: 32px; } .overview-hero { display: flex; flex-direction: column; gap: 12px; } .hero-eyebrow { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent); } .hero-title { font-size: 32px; font-weight: 700; font-family: var(--font-display); line-height: 1.15; } .hero-desc { font-size: 14px; color: var(--muted); max-width: 480px; line-height: 1.6; } .hero-actions { display: flex; gap: 10px; padding-top: 4px; } .overview-summary { display: flex; align-items: center; gap: 0; } .summary-item { display: flex; flex-direction: column; align-items: center; gap: 2px; flex: 1; padding: 14px; } .summary-num { font-size: 22px; font-weight: 700; font-family: var(--font-display); color: var(--accent); } .summary-label { font-size: 11px; color: var(--muted); } .summary-divider { width: 1px; height: 40px; background: var(--border); flex-shrink: 0; } .section-title { font-size: 13px; font-weight: 700; font-family: var(--font-display); color: var(--fg); margin-bottom: 14px; } .workflow-steps { display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px; } .workflow-step { display: flex; flex-direction: column; gap: 4px; padding: 12px; background: var(--surface); border-radius: var(--radius-md); box-shadow: var(--shadow-card); } .step-num { font-size: 10px; font-weight: 700; font-family: var(--font-mono); color: var(--accent); } .step-label { font-size: 13px; font-weight: 700; font-family: var(--font-display); } .step-desc { font-size: 11px; color: var(--muted); line-height: 1.4; } .screen-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; } .screen-card { text-align: left; cursor: pointer; border: none; transition: box-shadow 0.15s; display: flex; flex-direction: column; gap: 6px; } .screen-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.10), 0 0 0 2px var(--accent-dim); } .screen-icon { width: 36px; height: 36px; background: var(--accent-dim); color: var(--accent); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; margin-bottom: 4px; } .screen-label { font-size: 13px; font-weight: 700; font-family: var(--font-display); } .screen-desc { font-size: 12px; color: var(--muted); line-height: 1.4; } ``` - [ ] **Step 3: Commit** ```bash git add frontend/src/pages/Overview/OverviewPage.tsx frontend/src/styles/globals.css git commit -m "feat: implement Overview launcher page with hero, workflow steps, screen grid" ``` --- ## Task 7: Implement System Status page **Files:** - Modify: `frontend/src/pages/Status/StatusPage.tsx` Refer to `Prototype/dashboard-sidebar.html` for layout details. - [ ] **Step 1: Read the prototype to confirm grid/row structure** Read `Prototype/dashboard-sidebar.html` lines 1-100 to confirm `.stats-grid`, `.panel-grid`, task row, and KPI strip structure. - [ ] **Step 2: Write StatusPage.tsx** ```tsx import { useState, useEffect } from 'react'; import { Topbar } from '../../components/layout/Topbar'; import { Search, Upload, Download } from 'lucide-react'; interface Stats { total_documents: number; vector_chunks: number; high_impact: number; last_90_days: number; } const TASKS = [ { name: 'EU AI Act — Article 13 check', status: 'ok', progress: 88, cta: 'View report' }, { name: 'GB/T 42118 compliance scan', status: 'warn', progress: 54, cta: 'Continue' }, { name: 'MIIT Draft — automotive AI embedding', status: 'info', progress: 12, cta: 'Start' }, ]; const PROGRAMS = [ { name: 'EU AI Act Readiness', status: 'ok', coverage: 88 }, { name: 'China MIIT Compliance', status: 'warn', coverage: 54 }, { name: 'ISO/SAE 21434 Audit', status: 'info', coverage: 32 }, ]; const KPIS = [ { label: 'Retrieval hit rate', value: 94, unit: '%' }, { label: 'Evidence coverage', value: 78, unit: '%' }, { label: 'Reviewer SLA', value: 91, unit: '%' }, ]; const SERVICES = [ { name: 'Vector store (Chroma)', status: 'ok' }, { name: 'LLM gateway (Claude)', status: 'ok' }, { name: 'Document parser', status: 'ok' }, { name: 'SSE stream endpoint', status: 'ok' }, { name: 'Regulation feed sync', status: 'warn' }, ]; const EVENTS = [ { date: '2025-11-18', title: 'EU AI Act — Delegated acts published', summary: 'European Commission releases implementing rules for high-risk AI classification under Annex III.' }, { date: '2025-10-30', title: 'MIIT Draft — automotive AI', summary: 'New draft regulation covers in-vehicle AI training data provenance and OTA update governance.' }, { date: '2025-10-05', title: 'ISO/SAE 21434 amendment', summary: 'Amendment 1 clarifies cybersecurity management system scope for software-only updates.' }, ]; export function StatusPage() { const [stats, setStats] = useState(null); useEffect(() => { fetch('/api/v1/perception/stats') .then(r => r.json()) .then(d => setStats(d)) .catch(() => setStats({ total_documents: 42, vector_chunks: 3841, high_impact: 7, last_90_days: 14 })); }, []); return (
} />
{stats?.total_documents ?? '—'}
Documents indexed
{stats?.vector_chunks?.toLocaleString() ?? '—'}
Vector chunks
{stats?.high_impact ?? '—'}
High-impact signals
{stats?.last_90_days ?? '—'}
Last 90 days
Workflow queue
{TASKS.map(t => (
{t.name}
{t.status === 'ok' ? 'Complete' : t.status === 'warn' ? 'In progress' : 'Pending'}
))}
Active compliance programs
{PROGRAMS.map(p => (
{p.name} {p.coverage}%
))}
{KPIS.map(k => (
{k.label}
{k.value}{k.unit}
))}
System health
{SERVICES.map(s => (
{s.name} {s.status === 'ok' ? 'Online' : 'Degraded'}
))}
Regulatory watch
{EVENTS.map(e => (
{e.date}
{e.title}
{e.summary}
))}
Regulation Hub · T-Systems AI · Online
); } ``` - [ ] **Step 3: Add Status page CSS to globals.css** Append: ```css /* ── Status Page ────────────────────────────────── */ .status-page { display: flex; flex-direction: column; height: 100%; } .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; background: var(--surface); box-shadow: var(--shadow-card); margin-bottom: 20px; } .stat-cell { padding: 18px 22px; border-right: 1px solid var(--border); display: flex; flex-direction: column; gap: 4px; } .stat-cell:last-child { border-right: none; } .stat-value { font-size: 26px; font-weight: 700; font-family: var(--font-display); } .stat-label { font-size: 11px; color: var(--muted); } .stat-cell.danger .stat-value { color: var(--danger); } .panel-grid { display: grid; grid-template-columns: 1.4fr 0.9fr; gap: 16px; } .panel-left, .panel-right { display: flex; flex-direction: column; gap: 16px; } .mb-16 { margin-bottom: 0; } .card-header { font-size: 12px; font-weight: 700; font-family: var(--font-display); color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 12px; } .task-row { display: flex; align-items: center; gap: 12px; padding: 8px 0; border-top: 1px solid var(--border); } .task-info { flex: 1; min-width: 0; } .task-name { font-size: 13px; font-weight: 500; margin-bottom: 4px; } .task-progress-bar { height: 3px; background: var(--border); border-radius: 2px; overflow: hidden; } .task-progress-fill { height: 100%; background: var(--accent); border-radius: 2px; } .program-row { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-top: 1px solid var(--border); } .program-pct { font-size: 12px; font-weight: 700; font-family: var(--font-mono); color: var(--muted); } .kpi-strip { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 10px; } .kpi-item { display: flex; align-items: center; gap: 10px; } .kpi-label { font-size: 11px; color: var(--muted); width: 130px; flex-shrink: 0; } .kpi-bar { flex: 1; height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; } .kpi-fill { height: 100%; background: var(--accent); border-radius: 2px; } .kpi-value { font-size: 11px; font-weight: 700; font-family: var(--font-mono); color: var(--fg); width: 36px; text-align: right; flex-shrink: 0; } .service-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-top: 1px solid var(--border); } .service-name { font-size: 13px; } .event-row { padding: 10px 0; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 3px; } .event-date { font-size: 10px; font-family: var(--font-mono); color: var(--muted); } .event-title { font-size: 13px; font-weight: 600; } .event-summary { font-size: 12px; color: var(--muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .page-footer { padding: 12px 22px; border-top: 1px solid var(--border); display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--muted); background: var(--surface); flex-shrink: 0; } .live-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--success); box-shadow: 0 0 0 2px var(--success-bg); animation: pulse 2s ease-in-out infinite; } @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } } .search-box { display: flex; align-items: center; gap: 6px; height: 32px; padding: 0 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg); font-size: 13px; color: var(--muted); } .search-box input { border: none; background: transparent; outline: none; font-size: 13px; color: var(--fg); width: 160px; } ``` - [ ] **Step 4: Commit** ```bash git add frontend/src/pages/Status/StatusPage.tsx frontend/src/styles/globals.css git commit -m "feat: implement System Status page with stats grid, panel grid, KPI strip" ``` --- ## Task 8: Implement Regulatory Signals page **Files:** - Modify: `frontend/src/pages/Perception/PerceptionPage.tsx` Refer to `Prototype/perception.html` for exact stats-bar, filter chips, feed cards, and analysis pane. - [ ] **Step 1: Write PerceptionPage.tsx** ```tsx import { useState, useEffect, useRef } from 'react'; import { Topbar } from '../../components/layout/Topbar'; import { RefreshCw, Play, Square, ExternalLink } from 'lucide-react'; interface Signal { id: string; source: string; standard: string; status: 'ok'|'warn'|'risk'|'info'; title: string; summary: string; date: string; tags: string[]; impact: 'High'|'Medium'|'Low'; } interface Stats { total: number; high_impact: number; medium_impact: number; last_90_days: number; } interface DocResult { score: number; name: string; clause: string; snippet: string; } const SOURCES = ['All', 'MIIT', 'UN-ECE', 'ISO', 'GB Comm.', 'EUR-Lex', 'IATF']; const IMPACTS = ['All', 'High', 'Medium', 'Low']; const MOCK_SIGNALS: Signal[] = [ { id: '1', source: 'EUR-Lex', standard: 'EU/2024/1689', status: 'risk', title: 'EU AI Act — High-risk AI in vehicles', summary: 'Article 9 mandates risk management systems for automotive AI classifying as high-risk under Annex III point 3.', date: '2025-11-18', tags: ['automotive', 'GDPR', 'certification'], impact: 'High' }, { id: '2', source: 'MIIT', standard: 'Draft-2025-08', status: 'warn', title: 'MIIT Draft — in-vehicle AI training data', summary: 'Draft regulation requires OEM data provenance documentation and OTA audit trails for AI systems.', date: '2025-10-30', tags: ['OTA', 'data-governance', 'China'], impact: 'High' }, { id: '3', source: 'ISO', standard: 'ISO/SAE 21434:2021/Amd1', status: 'info', title: 'ISO/SAE 21434 Amendment 1', summary: 'Amendment clarifies CSMS scope for software-only updates and vulnerability disclosure timelines.', date: '2025-10-05', tags: ['cybersecurity', 'CSMS', 'ISO'], impact: 'Medium' }, { id: '4', source: 'UN-ECE', standard: 'UNECE WP.29 R155', status: 'ok', title: 'UNECE R155 Corrigendum', summary: 'Editorial corrections to cybersecurity management system requirements. No substantive changes.', date: '2025-09-12', tags: ['type-approval', 'UNECE'], impact: 'Low' }, ]; const MOCK_DOCS: DocResult[] = [ { score: 94, name: 'Vehicle AI Safety Manual v3.2', clause: '§4.2.1', snippet: 'The risk management process shall identify and evaluate risks arising from AI system decisions in safety-critical scenarios...' }, { score: 87, name: 'ADAS System Requirements', clause: '§7.1', snippet: 'Automated driving functions must document training data lineage and model performance envelopes prior to deployment.' }, { score: 71, name: 'Type Approval Documentation', clause: 'Annex B', snippet: 'Cybersecurity management system certification requires third-party audit of AI decision audit logs retention policy.' }, ]; export function PerceptionPage() { const [stats, setStats] = useState(null); const [sourceFilter, setSourceFilter] = useState('All'); const [impactFilter, setImpactFilter] = useState('All'); const [selected, setSelected] = useState(null); const [streaming, setStreaming] = useState(false); const [aiOutput, setAiOutput] = useState(''); const abortRef = useRef(null); useEffect(() => { fetch('/api/v1/perception/stats') .then(r => r.json()) .then(setStats) .catch(() => setStats({ total: 47, high_impact: 7, medium_impact: 18, last_90_days: 14 })); }, []); const filtered = MOCK_SIGNALS.filter(s => (sourceFilter === 'All' || s.source === sourceFilter) && (impactFilter === 'All' || s.impact === impactFilter) ); function runAnalysis() { if (!selected) return; setStreaming(true); setAiOutput(''); const ctrl = new AbortController(); abortRef.current = ctrl; const url = `/api/v1/perception/analyze?signal_id=${selected.id}`; fetch(url, { signal: ctrl.signal }) .then(async res => { if (!res.body) { setAiOutput('No stream available.'); setStreaming(false); return; } const reader = res.body.getReader(); const dec = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = dec.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') break; try { const j = JSON.parse(data); setAiOutput(p => p + (j.text || '')); } catch { setAiOutput(p => p + data); } } } } setStreaming(false); }) .catch(e => { if (e.name !== 'AbortError') { setAiOutput('Analysis failed. Check API connection.'); } setStreaming(false); }); } function stopAnalysis() { abortRef.current?.abort(); setStreaming(false); } return (
} />
{stats?.total ?? '—'}Total signals
{stats?.high_impact ?? '—'}High impact
{stats?.medium_impact ?? '—'}Medium impact
{stats?.last_90_days ?? '—'}Last 90 days
{SOURCES.map(s => ( ))}
{IMPACTS.map(i => ( ))}
{filtered.map(sig => (
{ setSelected(sig); setAiOutput(''); }}>
{sig.source} {sig.standard} {sig.status === 'ok' ? 'Final' : sig.status === 'warn' ? 'Draft' : sig.status === 'risk' ? 'Urgent' : 'Published'}
{sig.title}
{sig.summary}
{sig.date}
{sig.tags.map(t => {t})}
{sig.impact}
))}
{!selected ? (

Select a signal to run impact analysis

) : ( <>
{selected.source} {selected.standard} {selected.status === 'risk' ? 'Urgent' : 'Published'}
{selected.title}

{selected.summary}

{!streaming ? : }
Affected documents
{MOCK_DOCS.map(d => (
{d.score}%
{d.name} {d.clause}
{d.snippet}
))}
{(aiOutput || streaming) && (
AI Impact Analysis
{aiOutput} {streaming && }
)} )}
Live feed · Regulation Hub
); } ``` - [ ] **Step 2: Add Perception page CSS to globals.css** Append: ```css /* ── Perception Page ────────────────────────────── */ .perception-page { display: flex; flex-direction: column; height: 100%; } .stats-bar { display: flex; border-bottom: 1px solid var(--border); background: var(--surface); flex-shrink: 0; } .sbar-cell { flex: 1; padding: 14px 22px; border-right: 1px solid var(--border); display: flex; flex-direction: column; gap: 3px; } .sbar-cell:last-child { border-right: none; } .sbar-val { font-size: 22px; font-weight: 700; font-family: var(--font-display); } .sbar-lbl { font-size: 11px; color: var(--muted); } .sbar-cell.danger .sbar-val { color: var(--danger); } .sbar-cell.warn .sbar-val { color: var(--warn); } .sbar-cell.accent .sbar-val { color: var(--accent); } .filter-bar { display: flex; align-items: center; gap: 12px; padding: 10px 22px; border-bottom: 1px solid var(--border); background: var(--surface); flex-shrink: 0; } .chip-group { display: flex; gap: 6px; flex-wrap: wrap; } .chip { height: 26px; padding: 0 10px; border-radius: var(--radius-pill); border: 1px solid var(--border); background: transparent; font-size: 11px; font-weight: 500; cursor: pointer; color: var(--muted); transition: all 0.12s; } .chip:hover { border-color: var(--accent); color: var(--accent); } .chip.active { background: var(--accent-dim); border-color: var(--accent); color: var(--accent); font-weight: 600; } .filter-sep { width: 1px; height: 20px; background: var(--border); flex-shrink: 0; } .perception-split { display: grid; grid-template-columns: 360px 1fr; flex: 1; overflow: hidden; } .feed-pane { overflow-y: auto; border-right: 1px solid var(--border); display: flex; flex-direction: column; gap: 0; } .ev-card { padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer; transition: background 0.12s; border-left: 3px solid transparent; } .ev-card:hover { background: var(--bg); } .ev-card.selected { border-left-color: var(--accent); background: var(--accent-dim); box-shadow: inset 0 0 0 1px var(--accent-dim); } .ev-top { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; } .ev-std { font-size: 10px; font-family: var(--font-mono); color: var(--muted); } .ev-title { font-size: 13px; font-weight: 600; line-height: 1.35; margin-bottom: 5px; } .ev-summary { font-size: 12px; color: var(--muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 8px; } .ev-bottom { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .ev-date { font-size: 10px; font-family: var(--font-mono); color: var(--muted); } .ev-tags { display: flex; gap: 4px; flex-wrap: wrap; } .ev-tag { font-size: 10px; padding: 1px 6px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-pill); color: var(--muted); } .impact-dot { font-size: 10px; font-weight: 700; margin-left: auto; } .impact-high { color: var(--danger); } .impact-medium { color: var(--warn); } .impact-low { color: var(--success); } .source-tag { font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: var(--radius-pill); background: var(--accent-dim); color: var(--accent); } .analysis-pane { overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; } .analysis-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; color: var(--muted); font-size: 13px; } .empty-ring { width: 56px; height: 56px; border-radius: 50%; border: 2px dashed var(--border); } .detail-card { display: flex; flex-direction: column; gap: 10px; } .detail-header { display: flex; align-items: center; gap: 8px; } .detail-title { font-size: 15px; font-weight: 700; font-family: var(--font-display); } .detail-summary { font-size: 13px; color: var(--muted); line-height: 1.6; } .detail-actions { display: flex; gap: 8px; padding-top: 4px; } .docs-card { display: flex; flex-direction: column; gap: 0; } .doc-row { display: flex; gap: 10px; padding: 10px 0; border-top: 1px solid var(--border); } .doc-score { font-size: 11px; font-weight: 700; font-family: var(--font-mono); color: var(--success); width: 34px; flex-shrink: 0; padding-top: 1px; } .doc-name { font-size: 12px; font-weight: 600; margin-bottom: 3px; } .doc-clause { font-size: 10px; font-family: var(--font-mono); color: var(--muted); margin-left: 5px; } .doc-snippet { font-size: 11px; color: var(--muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .ai-card .ai-output { font-size: 13px; line-height: 1.7; white-space: pre-wrap; font-family: var(--font-mono); } .blink-cursor { animation: blink 1s step-end infinite; } @keyframes blink { 0%,100% { opacity: 1; } 50% { opacity: 0; } } ``` - [ ] **Step 3: Commit** ```bash git add frontend/src/pages/Perception/PerceptionPage.tsx frontend/src/styles/globals.css git commit -m "feat: implement Regulatory Signals page with two-pane split and SSE streaming" ``` --- ## Task 9: Implement Documents page **Files:** - Modify: `frontend/src/pages/Docs/DocsPage.tsx` Refer to `Prototype/cc29bcb0-.../document-management.html` for table columns and filter bar. - [ ] **Step 1: Write DocsPage.tsx** ```tsx import { useState, useEffect } from 'react'; import { Topbar } from '../../components/layout/Topbar'; import { Upload, Search } from 'lucide-react'; interface Doc { id: string; name: string; status: 'ok'|'warn'|'risk'|'info'; uploadedAt: string; chunks: number; type: string; } const STATUS_FILTERS = ['All', 'Ready', 'Embedding', 'Failed', 'Pending']; const TYPE_OPTS = ['All types', 'EU Regulation', 'ISO Standard', 'National Draft', 'Internal Policy']; const MOCK_DOCS: Doc[] = [ { id: '1', name: 'EU AI Act — Full text (EN)', status: 'ok', uploadedAt: '2025-11-10', chunks: 842, type: 'EU Regulation' }, { id: '2', name: 'MIIT Draft 2025-08 (ZH)', status: 'ok', uploadedAt: '2025-11-01', chunks: 320, type: 'National Draft' }, { id: '3', name: 'ISO/SAE 21434:2021', status: 'ok', uploadedAt: '2025-10-15', chunks: 614, type: 'ISO Standard' }, { id: '4', name: 'Vehicle AI Safety Manual v3.2', status: 'ok', uploadedAt: '2025-10-08', chunks: 198, type: 'Internal Policy' }, { id: '5', name: 'ADAS System Requirements', status: 'warn', uploadedAt: '2025-09-22', chunks: 0, type: 'Internal Policy' }, { id: '6', name: 'UNECE R155 Corrigendum', status: 'info', uploadedAt: '2025-09-12', chunks: 87, type: 'EU Regulation' }, { id: '7', name: 'GB/T 42118-2022', status: 'risk', uploadedAt: '2025-08-30', chunks: 0, type: 'National Draft' }, ]; const STATUS_LABEL: Record = { ok: 'Ready', warn: 'Embedding', risk: 'Failed', info: 'Pending' }; const STATUS_MAP: Record = { All: 'All', Ready: 'ok', Embedding: 'warn', Failed: 'risk', Pending: 'info' }; export function DocsPage() { const [search, setSearch] = useState(''); const [statusF, setStatusF] = useState('All'); const [typeF, setTypeF] = useState('All types'); const [selected, setSelected] = useState>(new Set()); const [docs, setDocs] = useState(MOCK_DOCS); useEffect(() => { fetch('/api/v1/documents') .then(r => r.json()) .then(d => { if (Array.isArray(d?.documents)) setDocs(d.documents); }) .catch(() => {}); }, []); const filtered = docs.filter(d => { const matchSearch = !search || d.name.toLowerCase().includes(search.toLowerCase()); const matchStatus = statusF === 'All' || d.status === STATUS_MAP[statusF]; const matchType = typeF === 'All types' || d.type === typeF; return matchSearch && matchStatus && matchType; }); function toggleAll() { if (selected.size === filtered.length) setSelected(new Set()); else setSelected(new Set(filtered.map(d => d.id))); } return (
setSearch(e.target.value)} />
} />
{STATUS_FILTERS.map(f => ( ))}
{selected.size > 0 && (
{selected.size} document{selected.size > 1 ? 's' : ''} selected
)}
0} onChange={toggleAll} /> Document name Status Uploaded Chunks Type Actions
{filtered.map(d => (
{ const s = new Set(selected); s.has(d.id) ? s.delete(d.id) : s.add(d.id); setSelected(s); }} /> {d.name} {STATUS_LABEL[d.status]} {d.uploadedAt} {d.chunks || '—'} {d.type} {d.status === 'risk' && }
))}
); } ``` - [ ] **Step 2: Add Documents page CSS to globals.css** Append: ```css /* ── Documents Page ─────────────────────────────── */ .docs-page { display: flex; flex-direction: column; height: 100%; } .docs-controls { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; flex-wrap: wrap; } .select-input { height: 28px; padding: 0 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); font-size: 12px; color: var(--fg); outline: none; cursor: pointer; } .batch-bar { display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: var(--accent-dim); border: 1px solid var(--accent); border-radius: var(--radius-sm); margin-bottom: 12px; font-size: 13px; color: var(--accent); font-weight: 600; } .risk-btn { color: var(--danger); border-color: var(--danger-bg); } .docs-table { background: var(--surface); border-radius: var(--radius-md); box-shadow: var(--shadow-card); overflow: hidden; } .table-header { display: grid; grid-template-columns: 28px 1.4fr 0.8fr 0.85fr 0.85fr 0.75fr 0.75fr; gap: 12px; padding: 10px 14px; background: var(--bg); border-bottom: 1px solid var(--border); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); align-items: center; } .table-row { display: grid; grid-template-columns: 28px 1.4fr 0.8fr 0.85fr 0.85fr 0.75fr 0.75fr; gap: 12px; padding: 11px 14px; border-bottom: 1px solid var(--border); font-size: 13px; align-items: center; transition: background 0.1s; } .table-row:last-child { border-bottom: none; } .table-row:hover { background: var(--bg); } .table-row.row-selected { background: var(--accent-dim); } .doc-name-cell { font-weight: 500; } .cell-mono { font-family: var(--font-mono); font-size: 11px; color: var(--muted); } .cell-muted { font-size: 12px; color: var(--muted); } .row-actions { display: flex; gap: 10px; } .text-link { background: none; border: none; font-size: 12px; color: var(--accent); cursor: pointer; font-weight: 500; padding: 0; } .text-link:hover { text-decoration: underline; } .danger-link { color: var(--danger); } ``` - [ ] **Step 3: Commit** ```bash git add frontend/src/pages/Docs/DocsPage.tsx frontend/src/styles/globals.css git commit -m "feat: implement Document Management page with filterable table and batch actions" ``` --- ## Task 10: Implement Compliance Analysis page **Files:** - Modify: `frontend/src/pages/Compliance/CompliancePage.tsx` Refer to `Prototype/cc29bcb0-.../compliance-analysis.html` for three-column layout. - [ ] **Step 1: Write CompliancePage.tsx** ```tsx import { Topbar } from '../../components/layout/Topbar'; import { Search, Plus } from 'lucide-react'; const SOURCES = [ { standard: 'EU AI Act', helper: 'Art. 9 — Risk management', scores: ['Art. 9.1', 'Art. 9.2'], status: 'risk' }, { standard: 'MIIT Draft 2025-08', helper: '§3 — Training data provenance', scores: ['§3.1', '§3.4'], status: 'warn' }, { standard: 'ISO/SAE 21434:2021', helper: 'Clause 9 — CSMS', scores: ['9.3', '9.4'], status: 'ok' }, ]; const STAGES = [ { label: 'Clause retrieval', pct: 100, status: 'ok' }, { label: 'Requirement extraction', pct: 100, status: 'ok' }, { label: 'Gap analysis', pct: 78, status: 'warn' }, { label: 'Recommendation synthesis', pct: 30, status: 'info' }, ]; const FINDINGS = [ { title: 'Missing risk management documentation', desc: 'No formal risk management system found for the described AI system scope under Art. 9.', status: 'risk' }, { title: 'Training data lineage incomplete', desc: 'MIIT §3.1 requires traceable provenance for training datasets. Current documentation lacks data source registry.', status: 'warn' }, { title: 'CSMS audit trail present', desc: 'ISO 21434 audit log requirements are met. Retention policy documented in Annex B.', status: 'ok' }, ]; const PARA = `The AI system described in Section 4.2.1 of the Vehicle AI Safety Manual performs real-time classification of driving scenarios to support Level 3 automated driving decisions. The system ingests sensor fusion data from cameras, LIDAR, and radar arrays, processes it through a deep neural network trained on 2.4M annotated driving scenarios, and outputs driving mode recommendations with associated confidence scores. The model was trained using data collected between 2022 and 2024 across European and Chinese road environments.`; export function CompliancePage() { return (
} />

Compliance Workspace

Document Paragraph Review

Three-column AI-assisted compliance gap analysis with regulation retrieval, paragraph review, and findings synthesis.

Retrieved Regulations
{SOURCES.map(s => (
{s.standard} {s.status === 'ok' ? 'Covered' : s.status === 'warn' ? 'Gap' : 'Critical'}
{s.helper}
{s.scores.map(sc => {sc})}
))}
Paragraph Under Review

{PARA.split('AI system').map((part, i, arr) => i < arr.length - 1 ? {part}AI system : {part} )}

Analysis stages
{STAGES.map(st => (
{st.label} {st.pct}%
))}
Findings
{FINDINGS.map(f => (
{f.title} {f.status === 'ok' ? 'OK' : f.status === 'warn' ? 'Gap' : 'Critical'}

{f.desc}

))}
Conclusion

The document requires a formal risk management section documenting the AI system classification, risk identification methodology, and mitigation measures per EU AI Act Art. 9 before compliance can be certified.

Next action Draft risk management annex
Escalation Legal review required
); } ``` - [ ] **Step 2: Add Compliance page CSS to globals.css** Append: ```css /* ── Compliance Page ────────────────────────────── */ .compliance-page { display: flex; flex-direction: column; height: 100%; overflow: hidden; } .compliance-hero { padding: 20px 24px 0; flex-shrink: 0; } .compliance-title { font-size: 18px; font-weight: 700; font-family: var(--font-display); margin-bottom: 4px; } .compliance-desc { font-size: 13px; color: var(--muted); } .compliance-workspace { display: grid; grid-template-columns: 0.95fr 1.25fr 0.9fr; gap: 14px; padding: 16px 24px 24px; flex: 1; overflow: hidden; min-height: 0; } .comp-col { display: flex; flex-direction: column; gap: 12px; overflow-y: auto; } .col-header { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); padding: 0 2px 4px; flex-shrink: 0; } .source-item { display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; } .source-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; } .source-std { font-size: 13px; font-weight: 700; font-family: var(--font-display); } .source-helper { font-size: 11px; color: var(--muted); } .source-scores { display: flex; gap: 5px; flex-wrap: wrap; } .score-pill { font-size: 10px; font-family: var(--font-mono); padding: 2px 6px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-pill); color: var(--muted); } .para-card { flex-shrink: 0; } .para-text { font-size: 13px; line-height: 1.7; color: var(--fg); } .para-text mark { background: rgba(226,0,116,.15); color: var(--accent); padding: 0 2px; border-radius: 2px; } .stages-card { flex-shrink: 0; } .stage-row { padding: 8px 0; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 5px; } .stage-label-row { display: flex; justify-content: space-between; align-items: center; } .stage-label { font-size: 12px; } .stage-pct { font-size: 11px; font-family: var(--font-mono); color: var(--muted); } .stage-bar { height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; } .stage-fill { height: 100%; border-radius: 2px; } .stage-fill.stage-ok { background: var(--success); } .stage-fill.stage-warn { background: var(--warn); } .stage-fill.stage-info { background: var(--info); } .finding-item { display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; } .finding-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; } .finding-title { font-size: 13px; font-weight: 600; line-height: 1.3; } .finding-desc { font-size: 12px; color: var(--muted); line-height: 1.5; } .conclusion-box { display: flex; flex-direction: column; gap: 10px; flex-shrink: 0; } .conclusion-text { font-size: 12px; line-height: 1.6; color: var(--fg); } .action-items { display: flex; flex-direction: column; gap: 8px; padding-top: 8px; border-top: 1px solid var(--border); } .action-item { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-size: 12px; } .action-label { color: var(--muted); } .action-value { font-weight: 600; } .risk-text { color: var(--danger); } ``` - [ ] **Step 3: Commit** ```bash git add frontend/src/pages/Compliance/CompliancePage.tsx frontend/src/styles/globals.css git commit -m "feat: implement Compliance Analysis three-column workspace with findings and stages" ``` --- ## Task 11: Implement Regulation Q&A chat page **Files:** - Modify: `frontend/src/pages/RagChat/RagChatPage.tsx` Refer to `Prototype/cc29bcb0-.../regulation-chat.html` for chat layout. - [ ] **Step 1: Write RagChatPage.tsx** ```tsx import { useState, useRef, useEffect } from 'react'; import { Topbar } from '../../components/layout/Topbar'; import { Send, Download } from 'lucide-react'; interface Message { id: string; role: 'user'|'assistant'; text: string; } interface Citation { score: number; name: string; clause: string; snippet: string; } const HISTORY = [ { id: 'h1', title: 'EU AI Act Article 9 scope', date: '2025-11-18' }, { id: 'h2', title: 'MIIT training data requirements', date: '2025-11-15' }, { id: 'h3', title: 'ISO 21434 CSMS audit scope', date: '2025-11-10' }, ]; const QUICK = [ 'What does EU AI Act Art. 9 require for risk management?', 'Which documents need CSMS certification?', 'Summarize MIIT training data rules', 'What are high-risk AI categories under Annex III?', ]; const MOCK_CITATIONS: Citation[] = [ { score: 94, name: 'EU AI Act', clause: 'Art. 9(1)', snippet: 'Providers of high-risk AI systems shall establish a risk management system consisting of a continuous iterative process run throughout the entire lifecycle.' }, { score: 87, name: 'Vehicle AI Safety Manual', clause: '§4.2.1', snippet: 'All AI systems classified as high-risk must maintain a documented risk register with quarterly review cadence.' }, { score: 72, name: 'ISO/SAE 21434', clause: 'Clause 9.3', snippet: 'The cybersecurity management system shall include AI model update governance procedures and audit log retention policy.' }, ]; export function RagChatPage() { const [messages, setMessages] = useState([ { id: 'init', role: 'assistant', text: 'Hello! I can answer questions about your indexed regulations and compliance documents. Try asking about EU AI Act requirements, MIIT rules, or ISO/SAE 21434 scope.' } ]); const [input, setInput] = useState(''); const [streaming, setStreaming] = useState(false); const [citations, setCitations] = useState(MOCK_CITATIONS); const bottomRef = useRef(null); const abortRef = useRef(null); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); async function send(text?: string) { const q = (text ?? input).trim(); if (!q || streaming) return; setInput(''); const userMsg: Message = { id: Date.now().toString(), role: 'user', text: q }; setMessages(m => [...m, userMsg]); const assistantId = (Date.now() + 1).toString(); setMessages(m => [...m, { id: assistantId, role: 'assistant', text: '' }]); setStreaming(true); const ctrl = new AbortController(); abortRef.current = ctrl; try { const res = await fetch('/api/v1/rag/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question: q }), signal: ctrl.signal, }); if (!res.body) throw new Error('No stream'); const reader = res.body.getReader(); const dec = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += dec.decode(value); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') break; try { const j = JSON.parse(data); if (j.text) setMessages(m => m.map(msg => msg.id === assistantId ? { ...msg, text: msg.text + j.text } : msg)); if (j.citations) setCitations(j.citations); } catch { setMessages(m => m.map(msg => msg.id === assistantId ? { ...msg, text: msg.text + data } : msg)); } } } } } catch (e: unknown) { if (e instanceof Error && e.name !== 'AbortError') { setMessages(m => m.map(msg => msg.id === assistantId ? { ...msg, text: 'Could not reach the RAG API. Please check the backend.' } : msg)); } } finally { setStreaming(false); } } return (
Export chat} />
Chat history
{HISTORY.map(h => (
{h.title}
{h.date}
))}
Quick prompts
{QUICK.map(q => ( ))}
{messages.map(msg => (
{msg.role === 'assistant' &&
AI
}
{msg.text} {streaming && msg.role === 'assistant' && msg.id === messages[messages.length - 1].id && ( )}
{msg.role === 'user' &&
You
}
))}
{QUICK.slice(0, 3).map(q => ( ))}