# Frontend Optimization 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:** Fix all code-health issues (dead files, config mismatches, startup script errors) and close prototype fidelity gaps (design tokens, sidebar brand, search wiring, loading states, upload modal) in the T-Systems Regulation Hub frontend.
**Architecture:** No new architectural layers. The CSS design-token system in `globals.css` is the single source of truth for styling; Tailwind dark-mode config is corrected to match it. Dead files are deleted. The upload modal is a new self-contained React component wired into DocsPage and StatusPage. All page logic remains in the page files.
**Tech Stack:** React 19 + TypeScript + Vite 8 + TailwindCSS 4.2 + globals.css CSS-variable system + Lucide React icons. Working directory for all npm commands: `frontend/`.
---
## File Map
**DELETE (confirmed dead — no imports anywhere):**
- `frontend/src/types/theme.ts`
- `frontend/src/components/layout/shell-config.ts`
- `frontend/src/components/ui/Badge.tsx`
- `frontend/src/components/ui/Button.tsx`
- `frontend/src/components/ui/Card.tsx`
- `frontend/src/components/ui/Input.tsx`
- `frontend/src/components/ui/ProgressBar.tsx`
- `frontend/src/components/ui/ScoreBar.tsx`
**MODIFY:**
- `frontend/tailwind.config.js` — fix darkMode strategy
- `frontend/index.html` — fix app title
- `frontend/start.sh` — fix port and stale comments
- `frontend/src/components/ui/index.ts` — remove dead exports
- `frontend/src/styles/globals.css` — update tokens, add modal CSS, add loading spinner
- `frontend/src/components/layout/Sidebar.tsx` — fix brand SVG + text hierarchy
- `frontend/src/pages/Perception/PerceptionPage.tsx` — wire search input
- `frontend/src/pages/Status/StatusPage.tsx` — loading state + refresh + upload button
- `frontend/src/pages/Docs/DocsPage.tsx` — wire upload button to modal
**CREATE:**
- `frontend/src/pages/Docs/UploadModal.tsx` — upload dialog component
---
## Task 1: Fix configs, startup script, and delete dead code
**Files:**
- Delete: `frontend/src/types/theme.ts`
- Delete: `frontend/src/components/layout/shell-config.ts`
- Delete: `frontend/src/components/ui/Badge.tsx`
- Delete: `frontend/src/components/ui/Button.tsx`
- Delete: `frontend/src/components/ui/Card.tsx`
- Delete: `frontend/src/components/ui/Input.tsx`
- Delete: `frontend/src/components/ui/ProgressBar.tsx`
- Delete: `frontend/src/components/ui/ScoreBar.tsx`
- Modify: `frontend/src/components/ui/index.ts`
- Modify: `frontend/tailwind.config.js`
- Modify: `frontend/index.html`
- Modify: `frontend/start.sh`
- [ ] **Step 1: Verify dead files have no imports**
```powershell
# From project root. Each command should print nothing (no matches).
Select-String -Path "frontend\src\**\*.ts","frontend\src\**\*.tsx" -Pattern "types/theme|ThemeColors|ThemeMode|darkTheme|dimTheme|lightTheme" -Recurse
Select-String -Path "frontend\src\**\*.ts","frontend\src\**\*.tsx" -Pattern "shell-config|shellFrameClassName|shellMeta" -Recurse
Select-String -Path "frontend\src\**\*.ts","frontend\src\**\*.tsx" -Pattern "from.*components/ui/(Badge|Button|Card|Input|ProgressBar|ScoreBar)" -Recurse
```
Expected: no output (no matches). If any imports ARE found, update those files to remove the imports before deleting.
- [ ] **Step 2: Delete the dead files**
```powershell
Remove-Item frontend\src\types\theme.ts
Remove-Item frontend\src\components\layout\shell-config.ts
Remove-Item frontend\src\components\ui\Badge.tsx
Remove-Item frontend\src\components\ui\Button.tsx
Remove-Item frontend\src\components\ui\Card.tsx
Remove-Item frontend\src\components\ui\Input.tsx
Remove-Item frontend\src\components\ui\ProgressBar.tsx
Remove-Item frontend\src\components\ui\ScoreBar.tsx
```
- [ ] **Step 3: Clear dead exports from ui/index.ts**
Replace the entire file `frontend/src/components/ui/index.ts` with:
```ts
// UI components — add exports here as new components are created
```
(The file must remain so the `components/ui/` directory stays as a future home for shared components.)
- [ ] **Step 4: Fix tailwind.config.js dark mode**
Current line 7: `darkMode: 'class',`
Replace with:
```js
darkMode: ['selector', '[data-theme="dark"]'],
```
Full updated `frontend/tailwind.config.js`:
```js
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
darkMode: ['selector', '[data-theme="dark"]'],
theme: {
extend: {
colors: {
't-bg': 'var(--bg)',
't-surface': 'var(--surface)',
't-fg': 'var(--fg)',
't-muted': 'var(--muted)',
't-border': 'var(--border)',
't-accent': '#e20074',
't-accent-hover': '#c8006a',
't-success': 'var(--success)',
't-warn': 'var(--warn)',
't-danger': 'var(--danger)',
},
fontFamily: {
'display': ['TeleNeoWeb-Bold', 'Inter', 'sans-serif'],
'body': ['TeleNeoWeb-Regular', 'Inter', 'sans-serif'],
'mono': ['ui-monospace', 'JetBrains Mono', 'Menlo', 'monospace'],
},
boxShadow: {
'card': 'var(--shadow-card)',
},
},
},
plugins: [],
}
```
- [ ] **Step 5: Fix index.html title**
In `frontend/index.html` change line 7:
Old: `
regulation-rag`
New: `T-Systems Regulation Hub`
Full updated `frontend/index.html`:
```html
T-Systems Regulation Hub
```
- [ ] **Step 6: Fix start.sh port and comments**
Full replacement for `frontend/start.sh`:
```bash
#!/bin/bash
# Start the Vite dev server.
# Port defaults to FRONTEND_PORT env var, or 5173 if unset (matches vite.config.ts default).
cd "$(dirname "$0")"
PORT="${FRONTEND_PORT:-5173}"
echo "Starting dev server on http://0.0.0.0:${PORT}"
npx vite --host 0.0.0.0 --port "$PORT"
```
- [ ] **Step 7: Verify the build still passes**
```powershell
cd frontend
npm run build
```
Expected: `✓ built in ...` with zero TypeScript errors. If `tsc` complains about missing types from deleted files, track down the import and remove it.
- [ ] **Step 8: Commit**
```bash
git add frontend/tailwind.config.js frontend/index.html frontend/start.sh \
frontend/src/components/ui/index.ts
git rm frontend/src/types/theme.ts \
frontend/src/components/layout/shell-config.ts \
frontend/src/components/ui/Badge.tsx \
frontend/src/components/ui/Button.tsx \
frontend/src/components/ui/Card.tsx \
frontend/src/components/ui/Input.tsx \
frontend/src/components/ui/ProgressBar.tsx \
frontend/src/components/ui/ScoreBar.tsx
git commit -m "chore: delete dead code, fix tailwind dark mode, fix title and start.sh port"
```
---
## Task 2: Update design tokens to match prototype
**Files:**
- Modify: `frontend/src/styles/globals.css` (`:root` and `[data-theme="dark"]` blocks only)
The prototype files (`Prototype/cc29bcb0.../index.html`, `upload-modal.html`) define the authoritative token values. Current `globals.css` diverges on sidebar width, border radii, warn color, and dark-mode surface colors.
- [ ] **Step 1: Update the `:root` token block**
Replace the existing `:root { ... }` block (lines 1–40 of globals.css) with:
```css
/* ── Design Tokens ──────────────────────────────── */
:root {
color-scheme: light;
--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: #111111;
--muted: #6b7280;
--border: #e5e5e5;
--border-strong: #d1d5db;
--accent: #e20074;
--accent-dim: rgba(226,0,116,.10);
--accent-hover: #c8006a;
--success: #17a34a;
--success-bg: rgba(23,163,74,.08);
--warn: #eab308;
--warn-bg: rgba(234,179,8,.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: 240px;
--topbar-h: 54px;
--radius-sm: 8px;
--radius-md: 12px;
--radius-pill: 9999px;
--shadow-card: 0 2px 8px rgba(0,0,0,.06), 0 0 0 1px rgba(0,0,0,.04);
}
```
- [ ] **Step 2: Update the `[data-theme="dark"]` block and add auto dark mode**
Replace the existing `[data-theme="dark"] { ... }` block (lines 42–57 of globals.css) with:
```css
/* Auto dark mode: matches system preference unless user has explicitly chosen light */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
color-scheme: dark;
--rail-bg: #17181d;
--rail-surface: #1d1f26;
--rail-fg: #f5f7fb;
--rail-muted: #a2a9b8;
--rail-border: #2a2d35;
--rail-hover: rgba(255,255,255,.06);
--rail-active: rgba(226,0,116,.15);
--bg: #0f1014;
--surface: #17181d;
--fg: #f5f7fb;
--muted: #a2a9b8;
--border: #2a2d35;
--border-strong: #3a3d48;
--success: #22c55e;
--success-bg: rgba(34,197,94,.10);
--warn: #facc15;
--warn-bg: rgba(250,204,21,.10);
--danger: #f87171;
--danger-bg: rgba(248,113,113,.10);
}
}
/* Explicit dark mode (user toggled) */
[data-theme="dark"] {
color-scheme: dark;
--rail-bg: #17181d;
--rail-surface: #1d1f26;
--rail-fg: #f5f7fb;
--rail-muted: #a2a9b8;
--rail-border: #2a2d35;
--rail-hover: rgba(255,255,255,.06);
--rail-active: rgba(226,0,116,.15);
--bg: #0f1014;
--surface: #17181d;
--fg: #f5f7fb;
--muted: #a2a9b8;
--border: #2a2d35;
--border-strong: #3a3d48;
--success: #22c55e;
--success-bg: rgba(34,197,94,.10);
--warn: #facc15;
--warn-bg: rgba(250,204,21,.10);
--danger: #f87171;
--danger-bg: rgba(248,113,113,.10);
}
/* Explicit light mode (overrides auto dark) */
[data-theme="light"] {
color-scheme: light;
}
```
- [ ] **Step 3: Verify build**
```powershell
cd frontend
npm run build
```
Expected: zero errors.
- [ ] **Step 4: Commit**
```bash
git add frontend/src/styles/globals.css
git commit -m "fix: align design tokens with prototype (radii, warn color, dark mode values)"
```
---
## Task 3: Fix Sidebar brand to match prototype
**Files:**
- Modify: `frontend/src/components/layout/Sidebar.tsx`
The prototype brand section shows: a `26×26px` magenta rounded box containing a bar-chart/regulation SVG icon, then "T-Systems" (bold, display font) as primary label and "Regulation Hub" (mono, small, muted) as sub-label. Current React code has them reversed ("Regulation Hub" bold, "T-Systems AI" small) and uses a text mark "TS" instead of the SVG.
- [ ] **Step 1: Update the brand section in Sidebar.tsx**
Replace the `
` block (lines 56–62 in current file) with:
```tsx
T-Systems
Regulation Hub
```
The CSS for `.brand-mark`, `.brand-name`, `.brand-sub` already exists in `globals.css` and is correct — `.brand-mark` renders the accent box, `.brand-name` is 13px bold display font, `.brand-sub` is 10px muted. No CSS changes needed.
- [ ] **Step 2: Verify build**
```powershell
cd frontend
npm run build
```
- [ ] **Step 3: Commit**
```bash
git add frontend/src/components/layout/Sidebar.tsx
git commit -m "fix: update sidebar brand to match prototype (SVG icon, T-Systems primary label)"
```
---
## Task 4: Wire Perception page search input
**Files:**
- Modify: `frontend/src/pages/Perception/PerceptionPage.tsx`
The search box renders `` with no state wiring (no `value`, no `onChange`). The `filtered` computation filters by `sourceFilter` and `impactFilter` only. Add a `searchQuery` state and wire it into both the input and the filter logic.
- [ ] **Step 1: Add searchQuery state**
After line 71 (`const [streaming, setStreaming] = useState(false);`), insert:
```tsx
const [searchQuery, setSearchQuery] = useState('');
```
- [ ] **Step 2: Add search term to filtered computation**
Replace lines 83–86:
```tsx
const filtered = MOCK_SIGNALS.filter(s =>
(sourceFilter === 'All' || s.source === sourceFilter) &&
(impactFilter === 'All' || s.impact === impactFilter)
);
```
With:
```tsx
const filtered = MOCK_SIGNALS.filter(s => {
if (sourceFilter !== 'All' && s.source !== sourceFilter) return false;
if (impactFilter !== 'All' && s.impact !== impactFilter) return false;
if (searchQuery) {
const q = searchQuery.toLowerCase();
if (!s.title.toLowerCase().includes(q) && !s.summary.toLowerCase().includes(q)) return false;
}
return true;
});
```
- [ ] **Step 3: Wire the search input**
Replace lines 138–141:
```tsx
```
With:
```tsx
setSearchQuery(e.target.value)}
/>
```
- [ ] **Step 4: Verify build**
```powershell
cd frontend
npm run build
```
- [ ] **Step 5: Commit**
```bash
git add frontend/src/pages/Perception/PerceptionPage.tsx
git commit -m "fix: wire Perception page search input to filter signals"
```
---
## Task 5: Add loading state and refresh to Status page
**Files:**
- Modify: `frontend/src/styles/globals.css` — add loading spinner + skeleton styles
- Modify: `frontend/src/pages/Status/StatusPage.tsx`
The status page fetches from `/api/v1/perception/stats` but shows `'—'` for all values while loading with no visual loading indicator. The topbar "Export status" button has no handler. Add a loading skeleton to the stats grid, a `refreshKey` to re-trigger fetch on demand, and a working Export button.
- [ ] **Step 1: Add loading spinner CSS to globals.css**
Append to the end of `frontend/src/styles/globals.css`:
```css
/* ── Loading States ─────────────────────────────── */
.loading-shimmer {
background: linear-gradient(90deg, var(--border) 25%, var(--bg) 50%, var(--border) 75%);
background-size: 200% 100%;
animation: shimmer 1.4s ease-in-out infinite;
border-radius: var(--radius-sm);
display: inline-block;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.stat-value-loading {
height: 32px;
width: 64px;
}
```
- [ ] **Step 2: Add loading and refreshKey state to StatusPage**
Replace lines 41–48 (the `StatusPage` function opening + existing `useState`/`useEffect`):
```tsx
export function StatusPage() {
const [stats, setStats] = useState(null);
const [loading, setLoading] = useState(true);
const [refreshKey, setRefreshKey] = useState(0);
useEffect(() => {
setLoading(true);
fetch('/api/v1/perception/stats')
.then(r => r.json())
.then(d => { setStats(d); setLoading(false); })
.catch(() => {
setStats({ total_documents: 42, vector_chunks: 3841, high_impact: 7, last_90_days: 14 });
setLoading(false);
});
}, [refreshKey]);
```
- [ ] **Step 3: Wire the Export and Refresh buttons in the Topbar actions**
Replace the topbar actions block (lines 53–65, the `<>...>` JSX inside `actions={...}`):
```tsx
actions={
<>
>
}
```
Note: add `RefreshCw` to the lucide-react import at the top of the file. Current imports are `{ Search, Upload, Download }` — change to `{ Search, Upload, Download, RefreshCw }`.
- [ ] **Step 4: Show loading shimmer in stats grid**
Replace the four `
);
}
```
- [ ] **Step 3: Wire Upload button in DocsPage.tsx**
In `frontend/src/pages/Docs/DocsPage.tsx`:
a) Add `showUpload` state after existing state declarations (line 34):
```tsx
const [showUpload, setShowUpload] = useState(false);
```
b) Add import at the top of the file (after the existing import lines):
```tsx
import { UploadModal } from './UploadModal';
```
c) Change the Upload button (line 76) from:
```tsx
```
To:
```tsx
```
d) Render the modal at the bottom of the `return` statement, just before the closing `
` of `.docs-page`:
```tsx
{showUpload && setShowUpload(false)} />}
);
```
- [ ] **Step 4: Wire "New upload" button in StatusPage.tsx**
In `frontend/src/pages/Status/StatusPage.tsx`:
a) Add import at top:
```tsx
import { UploadModal } from '../Docs/UploadModal';
```
b) Add `showUpload` state after existing state declarations:
```tsx
const [showUpload, setShowUpload] = useState(false);
```
c) Change the "New upload" button in the topbar actions to:
```tsx
```
d) Render the modal at the bottom of the `return`, just before the closing `` of `.status-page`:
```tsx
{showUpload && setShowUpload(false)} />}
);
```
- [ ] **Step 5: Verify build**
```powershell
cd frontend
npm run build
```
Expected: zero TypeScript errors. Zero Vite warnings about missing modules.
- [ ] **Step 6: Commit**
```bash
git add frontend/src/styles/globals.css \
frontend/src/pages/Docs/UploadModal.tsx \
frontend/src/pages/Docs/DocsPage.tsx \
frontend/src/pages/Status/StatusPage.tsx
git commit -m "feat: add upload modal with drag-drop, metadata form, and queue panel"
```
---
## Self-Review
**1. Spec coverage:**
| Issue | Task |
|---|---|
| tailwind darkMode: 'class' mismatch | Task 1 |
| index.html title "regulation-rag" | Task 1 |
| start.sh port 8001 hardcoded | Task 1 |
| types/theme.ts dead code | Task 1 |
| shell-config.ts dead code | Task 1 |
| components/ui/* dead code | Task 1 |
| sidebar-w, radius-sm/md, warn color off | Task 2 |
| Missing @media prefers-color-scheme | Task 2 |
| Sidebar brand SVG + wrong text hierarchy | Task 3 |
| Perception page search unconnected | Task 4 |
| Status page no loading state | Task 5 |
| Status page Refresh/Export unwired | Task 5 |
| Upload button has no modal | Task 6 |
**2. Placeholder scan:** No "TBD" or "TODO" in any task. All code blocks are complete and runnable.
**3. Type consistency:**
- `UploadModal` props: `{ onClose: () => void }` — used consistently in DocsPage and StatusPage.
- `showUpload: boolean` state — consistent across both pages.
- `refreshKey: number` state — used only in StatusPage.
- `searchQuery: string` state — used only in PerceptionPage.
- `loading: boolean` state — used only in StatusPage.