From d0ab5b6e7f434d247fcfe7cd8ec0a082e01df991 Mon Sep 17 00:00:00 2001 From: m4 Date: Mon, 10 Aug 2026 14:06:13 +0800 Subject: [PATCH] docs(webui): implementation plan for UI polish --- .../plans/2026-08-10-ui-polish-bright.md | 627 ++++++++++++++++++ 1 file changed, 627 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-10-ui-polish-bright.md diff --git a/docs/superpowers/plans/2026-08-10-ui-polish-bright.md b/docs/superpowers/plans/2026-08-10-ui-polish-bright.md new file mode 100644 index 0000000..488350d --- /dev/null +++ b/docs/superpowers/plans/2026-08-10-ui-polish-bright.md @@ -0,0 +1,627 @@ +# UI Polish — Bright/Premium Theme 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:** Restyle the WebUI to a bright, premium (Stripe-like) theme: zinc-neutral surfaces, vivid cyan accents, Inter font, layered shadows — across the main UI, all dialogs, and the login page, in both light and dark mode. + +**Architecture:** Token-first, three independently shippable layers: (1) rewrite CSS design tokens in `globals.css` + wire Inter via `next/font/local`; (2) restyle the `src/components/ui/` primitives so every consumer inherits; (3) targeted screen-level class edits (chat area, thread list, login). Purely visual — no logic changes. + +**Tech Stack:** Tailwind CSS 3 (config `tailwind.config.mjs`, `darkMode: "class"`), shadcn-style Radix primitives, `next/font/local`, Vitest. + +**Spec:** `docs/superpowers/specs/2026-08-10-ui-polish-bright-design.md` + +## Global Constraints + +- This is a purely visual change: `npx tsc --noEmit`, `npx eslint .`, and `npm test` must stay green after every task. Any test breakage means logic was touched by accident — revert it. +- Commit style: `feat(webui): …` / `style(webui): …`. +- The working tree contains **unrelated uncommitted user changes**, including in `src/app/components/ChatInterface.tsx` and `src/app/components/ChatMessage.tsx`. Tasks that edit those files must NOT `git add` them wholesale — the controller stages only the task's hunks. Never use `git add -A` / `git add .` anywhere. +- `src/app/components/ContextUsageIndicator.tsx` is untracked user work-in-progress — do not touch it. (Spec's `tnum` application is descoped to shipping the `.tnum` utility only; see Self-Review Notes.) +- Dark mode is driven by the `.dark` class on ``; both `:root` and `.dark` token sets are updated in the same task. +- WCAG AA for text: cyan-600 `#0891b2` on white; cyan-700 `#0e7490` on cyan-50 `#ecfeff`; dark-mode equivalents. + +--- + +### Task 1: Design tokens + Inter font + +**Files:** +- Create: `src/app/fonts/InterVariable.woff2` (downloaded font file) +- Modify: `src/app/globals.css` (token blocks, body font, heading tracking, `.tnum` utility) +- Modify: `src/app/layout.tsx` (font wiring, viewport themeColor) + +**Interfaces:** +- Produces (consumed by Tasks 2–4): + - Color tokens: `--brand` `#0891b2` (light) / `#22d3ee` (dark); `--brand-solid`; `--color-active-bg` `#ecfeff` / `#083344`; `--color-active-text` `#0e7490` / `#67e8f9`; remapped shadcn HSL tokens (`--background`, `--muted`, `--accent`, `--ring`, …) + - Shadow tokens: `--shadow-sm` / `--shadow-lg` (popover) / `--shadow-xl` (dialog), overridden in `.dark` + - `--radius: 0.625rem` (so Tailwind `rounded-md` = 8px, `rounded-lg` = 10px) + - Font: `--font-inter` CSS variable on ``; `.tnum` utility class + +- [ ] **Step 1: Download the Inter variable font** + +Run: + +```bash +mkdir -p src/app/fonts +curl -fL -o src/app/fonts/InterVariable.woff2 "https://rsms.me/inter/font-files/InterVariable.woff2?v=4.1" +``` + +Verify: `ls -la src/app/fonts/InterVariable.woff2` shows a file of roughly 800 KB – 1.5 MB, and `file src/app/fonts/InterVariable.woff2` reports `Web/OpenType font` (or `data` — woff2 has no magic string in some `file` builds; size is the real check). + +Fallback if rsms.me is unreachable: + +```bash +curl -fL -o /tmp/inter.zip https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip +unzip -l /tmp/inter.zip | grep -i "InterVariable.woff2" # locate the exact path inside +unzip -j -o /tmp/inter.zip "" -d src/app/fonts/ +``` + +If both fail (offline environment), STOP and report BLOCKED — do not substitute a different font or a CDN link. + +- [ ] **Step 2: Wire the font in `src/app/layout.tsx`** + +Add the import after the existing imports (line 8 area): + +```ts +import localFont from "next/font/local"; +``` + +Add the font definition after the `viewport` export (after line 22): + +```ts +// Self-hosted Inter variable font; exposes --font-inter, consumed by globals.css. +// No CJK glyphs — Chinese text falls through to PingFang/YaHei in the stack. +const inter = localFont({ + src: "./fonts/InterVariable.woff2", + variable: "--font-inter", + display: "swap", +}); +``` + +Change the `` tag (line 41) from: + +```tsx + +``` + +to: + +```tsx + +``` + +Update the stale viewport theme colors (lines 17-20) from: + +```ts + { media: "(prefers-color-scheme: light)", color: "#f9f9f9" }, + { media: "(prefers-color-scheme: dark)", color: "#212121" }, +``` + +to: + +```ts + { media: "(prefers-color-scheme: light)", color: "#fafafa" }, + { media: "(prefers-color-scheme: dark)", color: "#09090b" }, +``` + +- [ ] **Step 3: Replace the light `:root` token block in `src/app/globals.css`** + +In `src/app/globals.css`, inside `@layer base { :root { … } }`, replace everything from the comment `/* App-specific color variables */` (line 13) through `--chart-5: 330 26% 48%;` (line 144) with the block below. Keep: the alias block (`--text-primary: var(--color-text-primary);` … `--scrollbar-thumb-hover`) and the spacing/font-size/line-height/weight/radius-sm..full/layout variable sections exactly as they are — the replacement block below re-includes only what it replaces; anything not shown here stays untouched. (The old `--radius-sm/md/lg/full` and `--sidebar-width` etc. sections sit between the color section and the Tailwind variables section — leave them in place.) + +```css + /* App-specific colors — bright neutral palette. Light mode: zinc-50 app + background, white surfaces, near-black zinc text, vivid cyan accent. */ + --color-primary: #18181b; + --color-user-message: #0e7490; + --color-user-message-bg: #ecfeff; + --color-avatar-bg: #cffafe; + --color-secondary: #52525b; + --color-success: #16a34a; + --color-warning: #d97706; + --color-error: #dc2626; + --color-background: #fafafa; + --color-subagent-hover: #f4f4f5; + --color-surface: #ffffff; + --color-border: #e4e4e7; + --color-border-light: #f4f4f5; + --color-text-primary: #18181b; + --color-text-secondary: #52525b; + --color-text-tertiary: #a1a1aa; + --color-file-button: #ffffff; + --color-file-button-hover: #f4f4f5; + + /* Active/selected surface (sidebar items, tabs, selections). */ + --color-active-bg: #ecfeff; + --color-active-text: #0e7490; + + /* Brand accent — EvoScientist cyan. cyan-600 passes WCAG AA on white. */ + --brand: #0891b2; + --brand-hover: #0e7490; + /* Flat fill for badges/marks; the primary Button adds its own gradient. */ + --brand-solid: #0891b2; + --brand-foreground: #ffffff; +``` + +Then, still inside `:root`, replace the shadow variables block (lines 102-106, `/* Shadow variables */` through the `--shadow-xl` line) with: + +```css + /* Shadow variables — lighter, layered. lg = popover/dropdown, xl = dialog. */ + --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.05); + --shadow-lg: 0 4px 12px -2px rgb(0 0 0 / 0.08), + 0 2px 4px -2px rgb(0 0 0 / 0.04); + --shadow-xl: 0 20px 40px -12px rgb(0 0 0 / 0.18), + 0 4px 12px -4px rgb(0 0 0 / 0.06); +``` + +And replace the Tailwind/Radix HSL variables (lines 118-144, from `--radius: 0.5rem;` through `--chart-5`) with: + +```css + /* Tailwind/Radix UI component variables */ + --radius: 0.625rem; + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 189 90% 36%; + --primary-foreground: 0 0% 100%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 10% 3.9%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 5% 34%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 10% 3.9%; + --destructive: 0 72% 51%; + --destructive-foreground: 0 0% 100%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 189 90% 36%; + --sidebar: 0 0% 98%; + --chart-1: 189 90% 36%; + --chart-2: 173 80% 40%; + --chart-3: 38 92% 50%; + --chart-4: 199 89% 48%; + --chart-5: 350 89% 60%; +``` + +- [ ] **Step 4: Replace the `.dark` token block** + +Inside the same file's `.dark { … }` (lines 151-206), replace everything from `/* App-specific colors — warm charcoal dark mode …` through `--chart-5: 332 39% 64%;` with: + +```css + /* App-specific colors — cool neutral dark (zinc), cyan accents. */ + --color-primary: #fafafa; + --color-user-message: #67e8f9; + --color-user-message-bg: #083344; + --color-avatar-bg: #083344; + --color-secondary: #a1a1aa; + --color-success: #4ade80; + --color-warning: #fbbf24; + --color-error: #f87171; + --color-background: #09090b; + --color-subagent-hover: #27272a; + --color-surface: #18181b; + --color-border: #27272a; + --color-border-light: #1f1f22; + --color-text-primary: #fafafa; + --color-text-secondary: #a1a1aa; + --color-text-tertiary: #71717a; + --color-file-button: #18181b; + --color-file-button-hover: #27272a; + --color-active-bg: #083344; + --color-active-text: #67e8f9; + --brand: #22d3ee; + --brand-hover: #67e8f9; + /* Deep, saturated cyan fill: colorful but low-lightness so buttons don't + glow on the dark surface; white text passes AA. */ + --brand-solid: #0e7490; + --brand-foreground: #ffffff; + + /* Shadows need more contrast on dark surfaces. */ + --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4); + --shadow-lg: 0 4px 12px -2px rgb(0 0 0 / 0.5), + 0 2px 4px -2px rgb(0 0 0 / 0.3); + --shadow-xl: 0 20px 40px -12px rgb(0 0 0 / 0.6), + 0 4px 12px -4px rgb(0 0 0 / 0.4); + + /* Tailwind/Radix UI component variables for dark mode */ + --radius: 0.625rem; + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 192 86% 29%; + --primary-foreground: 0 0% 100%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 72% 51%; + --destructive-foreground: 0 0% 100%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 187 85% 53%; + --sidebar: 240 6% 8%; + --chart-1: 187 85% 53%; + --chart-2: 172 66% 50%; + --chart-3: 43 96% 56%; + --chart-4: 198 93% 60%; + --chart-5: 351 95% 71%; +``` + +- [ ] **Step 5: Body font stack, heading tracking, `.tnum` utility** + +In the same file, change the `body` rule's `font-family` (lines 234-235) from: + +```css + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, sans-serif; +``` + +to: + +```css + font-family: var(--font-inter), -apple-system, BlinkMacSystemFont, + "Segoe UI", "PingFang SC", "Microsoft YaHei", Roboto, "Helvetica Neue", + Arial, sans-serif; +``` + +Change the `h1, h2, h3, h4, h5, h6` rule (lines 244-252) from: + +```css +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 600; + line-height: 1.25; + color: var(--color-text-primary); +} +``` + +to: + +```css +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 600; + line-height: 1.25; + letter-spacing: -0.02em; + color: var(--color-text-primary); +} +``` + +Append at the end of the file: + +```css +/* Tabular numerals for token/usage readouts. */ +.tnum { + font-feature-settings: "tnum"; +} +``` + +- [ ] **Step 6: Verify** + +Run: `npx tsc --noEmit && npx eslint src/app/layout.tsx && npm test` +Expected: no type errors, no lint errors, all tests pass (310 passed / 1 skipped at plan time). + +- [ ] **Step 7: Commit** + +These files are NOT in the user's uncommitted set — stage them directly: + +```bash +git add src/app/globals.css src/app/layout.tsx src/app/fonts/InterVariable.woff2 +git commit -m "style(webui): bright zinc+cyan design tokens and Inter font" +``` + +- [ ] **Step 8 (controller, not implementer):** Hand to the user for the Layer-1 visual gate (light + dark overall palette/feel) per the spec. + +--- + +### Task 2: `ui/` primitives restyle + +**Files:** +- Modify: `src/components/ui/button.tsx` +- Modify: `src/components/ui/input.tsx` +- Modify: `src/components/ui/textarea.tsx` +- Modify: `src/components/ui/select.tsx` +- Modify: `src/components/ui/dialog.tsx` +- Modify: `src/components/ui/dropdown-menu.tsx` + +**Interfaces:** +- Consumes (from Task 1): `--brand`, `--brand-solid`, `--brand-hover`, `--brand-foreground`, `--shadow-lg`, `--shadow-xl`. +- Produces: every existing consumer of Button/Input/Select/Dialog/DropdownMenu inherits the new look without edits (Task 3–4 rely on this). + +All edits are exact string replacements inside `cn(...)`/cva class strings. No logic changes. + +- [ ] **Step 1: Button — gradient primary variant** + +In `src/components/ui/button.tsx`, replace the `default` variant string (line 13): + +```ts + default: + "bg-[var(--brand-solid)] text-[var(--brand-foreground)] shadow-xs hover:opacity-90", +``` + +with: + +```ts + default: + "bg-gradient-to-b from-[#06b6d4] to-[var(--brand-solid)] text-[var(--brand-foreground)] shadow-sm hover:from-[#0891b2] hover:to-[var(--brand-hover)] dark:bg-none dark:bg-[var(--brand-solid)] dark:hover:bg-[var(--brand-hover)]", +``` + +(Light: subtle vertical cyan-500→cyan-600 gradient deepening on hover. Dark: flat `--brand-solid` — a gradient would glow.) + +- [ ] **Step 2: Input — cyan focus treatment** + +In `src/components/ui/input.tsx`, replace (line 12): + +```ts + "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50", +``` + +with: + +```ts + "focus-visible:border-[var(--brand)] focus-visible:ring-[3px] focus-visible:ring-[var(--brand)]/20", +``` + +- [ ] **Step 3: Textarea — same focus treatment** + +In `src/components/ui/textarea.tsx`, replace (line 12): + +```ts + "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", +``` + +with: + +```ts + "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:border-[var(--brand)] focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--brand)]/20 disabled:cursor-not-allowed disabled:opacity-50", +``` + +- [ ] **Step 4: Select — trigger focus + content/item polish** + +In `src/components/ui/select.tsx`: + +a) Trigger (line 22) — replace: + +```ts + "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1", +``` + +with: + +```ts + "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm focus:border-[var(--brand)] focus:outline-none focus:ring-[3px] focus:ring-[var(--brand)]/20 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1", +``` + +b) Content (line 78) — replace `rounded-md border bg-popover text-popover-foreground shadow-md` with `rounded-xl border bg-popover text-popover-foreground shadow-[var(--shadow-lg)]` (keep every other class in that string untouched). + +c) Item (line 121) — replace `rounded-sm` with `rounded-md` in the SelectItem class string. + +- [ ] **Step 5: Dialog — blur overlay, 16px radius, layered shadow** + +In `src/components/ui/dialog.tsx`: + +a) Overlay (line 61) — replace: + +```ts + "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", +``` + +with: + +```ts + "fixed inset-0 z-50 bg-black/40 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", +``` + +b) Content (line 83) — replace `rounded-lg border bg-background p-6 shadow-lg` with `rounded-2xl border bg-background p-6 shadow-[var(--shadow-xl)]` (keep every other class untouched, including the max-height/translate/animation classes). + +- [ ] **Step 6: Dropdown — rounded content + items, layered shadow** + +In `src/components/ui/dropdown-menu.tsx`: + +a) Content (line 63) — replace `rounded-md border bg-popover p-1 text-popover-foreground shadow-md` with `rounded-xl border bg-popover p-1 text-popover-foreground shadow-[var(--shadow-lg)]`. + +b) Item (line 96) — replace `rounded-sm` with `rounded-md` in the DropdownMenuItem class string. + +- [ ] **Step 7: Verify** + +Run: `npx tsc --noEmit && npx eslint src/components/ui/ && npm test` +Expected: clean, all tests pass. + +- [ ] **Step 8: Commit** + +```bash +git add src/components/ui/button.tsx src/components/ui/input.tsx src/components/ui/textarea.tsx src/components/ui/select.tsx src/components/ui/dialog.tsx src/components/ui/dropdown-menu.tsx +git commit -m "style(webui): restyle ui primitives (gradient primary, cyan focus, layered shadows)" +``` + +- [ ] **Step 9 (controller, not implementer):** Hand to the user for the Layer-3 visual gate (dialogs, buttons, inputs, dropdowns — hover/focus/disabled, both modes). + +--- + +### Task 3: Chat area polish + +**Files:** +- Modify: `src/app/components/ChatInterface.tsx:2048` (composer container) +- Modify: `src/app/components/ChatMessage.tsx:274` (user bubble) +- Modify: `src/app/components/ToolCallBox.tsx:305` (container radius) + +**Interfaces:** +- Consumes (from Task 1): `--brand`, `--shadow-sm`, `--color-user-message-bg` (now `#ecfeff` light / `#083344` dark — applied via the existing inline style, no code change needed). +- Consumes (from Task 2): the composer Send/Stop `Button` already renders the gradient primary variant — no edit. + +**Commit discipline (unusual — read first):** `ChatInterface.tsx` and `ChatMessage.tsx` contain unrelated uncommitted user changes. The implementer makes the edits and runs verification but does NOT commit; the controller stages only the task's hunks (`git apply --cached` with a filtered diff) and commits. `ToolCallBox.tsx` is clean and committed normally by the controller too. + +- [ ] **Step 1: Composer container — 16px radius, subtle shadow, cyan focus-within ring** + +In `src/app/components/ChatInterface.tsx`, find (line 2048, first element of a `cn(` call): + +```ts + "relative z-10 mx-auto mb-2 flex w-[calc(100%-16px)] max-w-[960px] flex-shrink-0 flex-col overflow-hidden rounded-xl border border-border bg-background transition-colors duration-200 ease-in-out sm:mb-4 sm:w-[calc(100%-24px)]", +``` + +Replace with: + +```ts + "relative z-10 mx-auto mb-2 flex w-[calc(100%-16px)] max-w-[960px] flex-shrink-0 flex-col overflow-hidden rounded-2xl border border-border bg-background shadow-[var(--shadow-sm)] transition-colors duration-200 ease-in-out focus-within:border-[var(--brand)] focus-within:ring-2 focus-within:ring-[var(--brand)]/20 sm:mb-4 sm:w-[calc(100%-24px)]", +``` + +- [ ] **Step 2: User message bubble — 16px radius, borderless** + +In `src/app/components/ChatMessage.tsx`, find (line 274, inside the `isUser` ternary): + +```ts + ? "rounded-xl rounded-br-none border border-border px-3 py-2 text-foreground" +``` + +Replace with: + +```ts + ? "rounded-2xl rounded-br-md px-3.5 py-2 text-foreground" +``` + +(The bubble's background keeps coming from the inline `backgroundColor: "var(--color-user-message-bg)"` a few lines below — Task 1 changed that token to cyan-50/dark-cyan, so the bubble restyles with no further edit.) + +- [ ] **Step 3: ToolCallBox container radius** + +In `src/app/components/ToolCallBox.tsx`, find (line 305): + +```ts + className="rounded-sm border border-border" +``` + +Replace with: + +```ts + className="rounded-lg border border-border" +``` + +- [ ] **Step 4: Verify (implementer)** + +Run: `npx tsc --noEmit && npx eslint src/app/components/ChatInterface.tsx src/app/components/ChatMessage.tsx src/app/components/ToolCallBox.tsx && npm test` +Expected: clean, all tests pass. + +- [ ] **Step 5: Selective commit (controller, not implementer)** + +The controller extracts only the hunks containing the three replacement strings above from the working-tree diff, stages them with `git apply --cached`, and commits: + +```bash +git commit -m "style(webui): polish composer, user bubble, and tool-call box" +``` + +- [ ] **Step 6 (controller):** Hand to the user for the chat-area visual gate (both modes). + +--- + +### Task 4: Thread list + login page polish + +**Files:** +- Modify: `src/app/components/ThreadList.tsx` (active states ×4) +- Modify: `src/app/login/page.tsx` (login card) + +**Interfaces:** +- Consumes (from Task 1): `--color-active-bg`, `--color-active-text`, `--shadow-xl`. + +Both files are clean of user changes — normal staging. + +- [ ] **Step 1: Thread list — active conversation uses the cyan active surface** + +In `src/app/components/ThreadList.tsx`, find (line 462, the active branch of a ternary): + +```ts + ? "border border-primary bg-accent hover:bg-accent" +``` + +Replace with: + +```ts + ? "border border-transparent bg-[var(--color-active-bg)] text-[var(--color-active-text)] hover:bg-[var(--color-active-bg)]" +``` + +- [ ] **Step 2: Sidebar view tabs — active state uses the cyan active surface** + +In the same file, replace each of these three strings: + +```ts + view === "skills" && "bg-accent" +``` + +```ts + view === "memory" && "bg-accent" +``` + +```ts + view === "schedule" && "bg-accent" +``` + +with, respectively: + +```ts + view === "skills" && + "bg-[var(--color-active-bg)] text-[var(--color-active-text)]" +``` + +```ts + view === "memory" && + "bg-[var(--color-active-bg)] text-[var(--color-active-text)]" +``` + +```ts + view === "schedule" && + "bg-[var(--color-active-bg)] text-[var(--color-active-text)]" +``` + +- [ ] **Step 3: Login card — 16px radius, dialog shadow, more padding** + +In `src/app/login/page.tsx`, find (line 79): + +```ts + className="w-full max-w-sm rounded-lg border border-border bg-[var(--color-surface)] p-6 shadow-sm" +``` + +Replace with: + +```ts + className="w-full max-w-sm rounded-2xl border border-border bg-[var(--color-surface)] p-8 shadow-[var(--shadow-xl)]" +``` + +(The sign-in button already uses the shared `Button` default variant → gradient from Task 2; inputs inherit Task 2's focus treatment.) + +- [ ] **Step 4: Verify** + +Run: `npx tsc --noEmit && npx eslint src/app/components/ThreadList.tsx src/app/login/page.tsx && npm test` +Expected: clean, all tests pass. + +- [ ] **Step 5: Commit** + +```bash +git add src/app/components/ThreadList.tsx src/app/login/page.tsx +git commit -m "style(webui): cyan active states in sidebar, premium login card" +``` + +- [ ] **Step 6 (controller):** Hand to the user for the final visual gate (thread list active states, login page, both modes). + +--- + +## Self-Review Notes (already applied) + +- **Spec coverage:** Layer 1 → Task 1 (tokens, font, typography, shadows, radii, both modes). Layer 2 → Task 1 Step 2/5 (font wiring, tracking) + Task 1 Step 5 (`.tnum` utility). Layer 3 → Task 2 (all six primitives). Layer 4 → Tasks 3–4 (chat, thread list, login). Workspace panel needs **no** edits: its checkboxes use `accent-[var(--brand)]` (= cyan-600 after Task 1) and its category labels already match the 11px uppercase micro style — token remap carries it, verified by reading `WorkspacePanel.tsx`. +- **Descoped from spec, deliberately:** (a) blanket "more breathing room between messages" — no concrete receiver could be pinned without redesigning message layout; the lighter tokens already open the UI up, and the user's per-layer visual gates are the right place to request specific spacing follow-ups. (b) Applying `.tnum` to specific numeric readouts — the natural target (`ContextUsageIndicator.tsx`) is untracked user WIP and must not be swept into a commit; the utility ships ready to use. +- **Placeholder scan:** every edit step contains exact old/new code strings with line anchors. +- **Type/consistency:** token names referenced in Tasks 2–4 (`--brand`, `--brand-solid`, `--brand-hover`, `--color-active-bg`, `--color-active-text`, `--shadow-sm/lg/xl`) are all produced by Task 1. +- **Risk:** Task 1 Step 3 instructs replacing a large token block while *keeping* the alias/spacing/layout sections that interleave it — the implementer must read the current file and preserve everything not explicitly replaced. The brief tells them exactly which line ranges are replaced.