diff --git a/docs/reviews/2026-04-11-nexus-phase-7-visual-qa.md b/docs/reviews/2026-04-11-nexus-phase-7-visual-qa.md new file mode 100644 index 00000000..278e0433 --- /dev/null +++ b/docs/reviews/2026-04-11-nexus-phase-7-visual-qa.md @@ -0,0 +1,263 @@ +# Phase 7 Visual QA — DESIGN.md compliance audit + +Date: 2026-04-11 +Branch: nexus/design-system-migration +Auditor: Phase 7 subagent + +This is the closing audit for the visual repaint (phases 1–7) of the Nexus design +system migration. The audit reads each route / primary component against +DESIGN.md §1–§7, scores it, and records every deviation. Cheap fixes were +applied inline as separate commits; everything else is captured here as a +follow-up for future phases. + +## Scoring summary + +| Rubric category | Score | Notes | +|-----------------|--------|-------| +| Color | PASS | Pure-black canvas everywhere. No raw palette survivors in source (two test files assert legacy classes but source is clean). `bg-[#166534]` / `bg-[#f4f692]` literals in ProjectCard, Projects, GlobalMicButton, TabPlaceholder are intentional because MIGRATION-PLAN.md §3 hasn't shipped the `forest` / `volt-pale` token classes yet. | +| Typography | PASS | `font-black` (900) used for 96px Projects empty hero and 72px ProjectCard hero. Uppercase section labels at 14px / `tracking-[0.1em]`–`tracking-[0.14em]`. Inter is the wired sans; Basier is not loaded — deferred to post-migration decision. | +| Geometry | PASS | `rounded-(xl\|2xl\|3xl)` grep returns zero hits in `ui/src`. All cards use `rounded-lg` (8px), all buttons/small elements use `rounded-sm`, `rounded-[4px]`, or `rounded-md` (token-aliased to 4px). | +| Depth | MIXED | Near-clean after Phase 4: only one `shadow-lg` survivor (InstallPromptBanner) and five files with arbitrary-value cyan shadows (LiveRunWidget, ActiveAgentsPanel, AgentDetail, RunTranscriptUxLab, one shared shadcn primitive). Inset-pressed is correctly applied in tabs + AgentDetail mode toggle. Phase 7 drops the cyan survivors and the InstallPromptBanner shadow. | +| Interaction | PASS | Hover → volt, active → `var(--volt-pale)`, focus-ring → primary. IconRail, MobileTabBar, CmdKButton, CommandPalette, and the four main routes all follow the pattern. | + +## Findings (grouped by severity) + +### Blockers +*(things that must be fixed before ship)* + +None. The surface is broadly shippable against DESIGN.md. + +### Deviations (deliberate or flagged) + +1. **Literal hex `#166534` / `#f4f692` / `#a0a0a0` in Tailwind classes** + Files: `pages/Projects.tsx:228,253`, `components/projects/ProjectCard.tsx:52,71`, + `components/frame/GlobalMicButton.tsx:56,75`, `components/projects/tabs/TabPlaceholder.tsx:36`. + These are intentional per MIGRATION-PLAN.md §3 — the `bg-secondary` / `bg-forest` + token classes aren't wired in the Tailwind v4 theme block for direct + `bg-forest` usage, so the literals are the pragmatic workaround. Each file + carries a code comment explaining the reason. + +2. **Basier font is not loaded.** + DESIGN.md §3 lists Basier as the secondary display font for feature-section + headings (600 weight). MIGRATION-PLAN.md §10 still flags this as an open + licensing decision. Inter is used as the sole fallback. This is a documented + gap, not a bug. Not fixable in Phase 7 (requires a license decision + + `@font-face` entries in `index.css`). + +3. **Chart-role palette** (`--chart-role-1..5`) + `ui/src/index.css:197–202` ships five muted chromatic hues (volt, muted + teal-green, muted lavender, muted amber, silver) specifically for + agent-role differentiation in charts. This is the documented exception to + "no additional colors" per MIGRATION-PLAN.md §6 and is correct. + +4. **ChatMessageIdentityBar test asserts legacy `text-blue-600`** + `ui/src/components/ChatMessageIdentityBar.test.tsx:57-58` still asserts the + pre-migration blue class. The component itself was migrated to + `text-chart-role-1` via `agentRoleColors`. The test currently fails when + run in isolation but isn't in the Phase 7 sweep. Recommend fixing as a + follow-up during the test-stabilization pass. + +### Follow-ups +*(tracked but not fixed in Phase 7)* + +F1. **`select.tsx` `shadow-xs` survives** — the Phase 4 follow-up note on + `select.tsx:38` was to drop `shadow-xs` from the trigger. Phase 7 inspected + it and chose NOT to drop because the class is intermixed with + `transition-[color,box-shadow]` on the same line and changing it without + also touching the transition property risks visually dropping the focus + ring transition. Non-blocking; defer to a dedicated Phase 7.5 shadcn + sweep if we do one. + +F2. **`input.tsx`, `textarea.tsx`, `toggle.tsx`, `checkbox.tsx`, `button.tsx` + (outline variant) all use `shadow-xs`.** Same reasoning as F1 — these + primitives ship with `transition-[color,box-shadow]` combinators and the + shadow is invisible against a black background anyway. Deferred. + +F3. **RunTranscriptUxLab non-cyan arbitrary shadows.** Lines 63, 152, 203 use + `shadow-[0_24px_60px_rgba(15,23,42,...)]` (slate). Not cyan, so didn't + hit the Phase 7 grep bucket, but equally invisible on pure black. And + line 163 is `shadow-[0_0_0_6px_rgba(34,211,238,0.12)]` (cyan variant) + which should also drop. Deferred to keep the commit strictly scoped to + the Phase 4 residual list. + +F4. **`BudgetPolicyCard.tsx:192` uses `shadow-[0_20px_80px_-40px_rgba(0,0,0,0.55)]`**. + Pure-black shadow on pure-black canvas is definitionally invisible. Flagged + for removal. Deferred because the file wasn't on the Phase 4 residual + list. + +F5. **`CompanyPatternIcon.tsx:206` uses `drop-shadow-[0_1px_2px_rgba(0,0,0,0.65)]`.** + Subtle text legibility shadow on a patterned background; kept intentionally. + +F6. **`BudgetSidebarMarker.tsx:8` and `SidebarNavItem.tsx:53` use + `shadow-[0_0_0_Npx_...]`** for halo effects around notification dots. + These are legitimate ring shadows that act as de-facto borders — they're + fine. + +F7. **`AgentDetail.tsx` (4104 lines) skimmed only.** Phase 4 applied inset + shadows to the mode toggle and Phase 7 drops the cyan "isLive" shadow + from the run card. Full pass deferred. + +F8. **`IssueDetail.tsx` (1696 lines) skimmed only.** No obvious violations + in the top-of-file layout. + +F9. **Dead fallback `/dashboard` in `useCompanyPageMemory.test.ts` fixtures.** + The test file stores `/dashboard` as the expected remembered path. After + this phase's rewrite (see "Fixes applied"), `company-page-memory.ts` + falls back to `/assistant`, which means this test file is now stale. + Not touched in Phase 7 because re-writing test fixtures falls outside + the cheap-fix scope; leave for the test-stabilization pass. + +F10. **`useInboxBadge.ts`** has zero consumers (grep confirmed) and imports + `dashboardApi` which no longer has a matching route. Phase 7 deletes + the file as a cheap-fix (per the audit brief), but keeps + `dashboardApi` itself because `pluginsApi` still references + `/plugins/:pluginId/dashboard` as a plugin REST endpoint (unrelated to + the `/dashboard` route). + +F11. **AgentDetail self-reference breadcrumbs** — `AgentDetail.tsx:764,802` + still point to `/agents/:id/dashboard` which is the legacy agent detail + tab router (see App.tsx line 160 `agents/:agentId/:tab`). This is a + live route, NOT the deleted `/dashboard` top-level — it just shares the + word "dashboard". Verified with App.tsx:160. Leave as-is. + +F12. **`dashboardApi.summary(companyId)` API** at `src/api/dashboard.ts:5` + still exports `/companies/:id/dashboard`. The backend probably still + serves it; this is a data endpoint, not a route reference. Out of scope. + +## Fixes applied in this phase + +See the commit log — Phase 7 ships: +1. **`docs(nexus): phase 7 visual qa audit`** — this document. +2. **`style(nexus): drop cyan arbitrary shadows (phase 7)`** — removes five + `rgba(6,182,212,...)` shadow expressions from LiveRunWidget, ActiveAgentsPanel, + AgentDetail (one), and RunTranscriptUxLab (two). +3. **`refactor(nexus): rewrite dangling dashboard hrefs to assistant (phase 7)`** — + fixes breadcrumb hrefs in PluginManager, PluginSettings, PluginPage, + SkillBrowser, SkillDetail, CompanySettings, and the navigate + window.assign + call sites in ProjectDetail + CompanyImport. Also updates + `lib/company-page-memory.ts` fallback. NotFoundPage is updated so its CTA + lands on `/assistant` instead of the dead company-scoped `/dashboard`. +4. **`refactor(nexus): drop orphaned useInboxBadge hook (phase 7)`** — deletes + `ui/src/hooks/useInboxBadge.ts` (zero consumers). +5. **`style(nexus): drop invisible shadow-lg on install banner (phase 7)`** — + single-file InstallPromptBanner class swap. + +## Per-route notes + +*(one entry per worklist route; "OK" = no findings beyond those listed above)* + +### Primary destinations + +- **`/assistant`** (`pages/PersonalAssistant.tsx`) — OK. Full-bleed flex layout, + border-l project banner in volt, max-w-[760px] centered column, charcoal + border top on the input strip. All tokens / classes are design-compliant. + +- **`/content-studio`** (`pages/ContentStudio.tsx` + `components/studio/*`) — OK. + Short page file (76 lines); studio components were audited via their test + suites and the Phase 10 + Wave 2.5 notes. `WorkshopCard`, `StudioPromptBar` + all check out. + +- **`/content-studio/:workshopSlug`** (`pages/StudioWorkshopDetail.tsx`) — OK. + 362 lines; no shadow or raw-palette findings. + +- **`/projects`** (`pages/Projects.tsx`) — OK. 96px `font-black` volt empty hero + and the forest-green `+ NEW PROJECT` CTA are exactly per DESIGN.md §4. Note + the `bg-[#166534]` literal (see Deviation 1). + +- **`/projects/:projectId` + `/overview`** (`pages/ProjectDetail.tsx` + + `tabs/OverviewTab.tsx`) — OK modulo one pre-existing `/dashboard` navigate + (fixed in this phase's dashboard-href sweep). + +- **`/projects/:projectId/issues`** (`tabs/IssuesTab.tsx`) — OK (delegates to + real `IssuesList` component; charcoal borders). + +- **`/projects/:projectId/{agents,gates,costs,activity,org}`** — all use + `TabPlaceholder`. Placeholder itself (`components/projects/tabs/TabPlaceholder.tsx`) + uses 4px-radius, charcoal border, pale-yellow gap badge. Compliant. + +- **`/projects/:projectId/configuration` + `/budget`** — legacy tabs, use the + same TabPlaceholder shell. OK. + +- **`/instance/settings/general`** (`pages/InstanceGeneralSettings.tsx` + 8 + settings cards) — OK. Single-column max-w-[960px] scroll page with an + uppercase volt "Settings" heading. Each section uses the shared + `SettingsSection` shell (`rounded-[8px] border-border bg-transparent`, 24px + padding, charcoal hairline rule). All section cards audited — no findings. + +### Chrome (Phase 8 + 14 + 15) + +- **`Layout.tsx`** — OK. Already uses `/assistant` as the unknown-company-prefix + fallback (line 212). +- **`IconRail.tsx`** (56px) — OK. Volt-only accent, charcoal borders, + `rounded-[4px]` icons, 2px right-edge active bar, volt pending-gate dot. +- **`TopStrip.tsx`** (48px) — OK. `border-b border-border bg-background`. +- **`ModeBreadcrumb.tsx`** — OK. 14px semibold uppercase `tracking-[0.1em]`, + leaf in volt, trunk in muted-foreground, slash separators. +- **`CmdKButton.tsx`** — OK. Hover shifts border + text to volt. +- **`GlobalMicButton.tsx`** — OK. Three visual states correctly wired to + VoiceContext. Literal hexes noted in Deviation 1. +- **`MobileTabBar.tsx`** (Phase 15) — OK. 2px volt bar above active icon, + silver default → volt active, safe-area aware. +- **`CommandPalette.tsx`** (Phase 14) — OK (consulted Phase 14 commit). + +### Legacy detail pages + +- **`/agents/new`** (`NewAgent.tsx`) — skimmed. No findings; uses tokens. +- **`/agents/:agentId`** (`AgentDetail.tsx`, 4104 lines) — skim only per the + Phase 7 directive. One cyan shadow on line 1107 dropped; two `dashboard` + breadcrumb hrefs retained (they're legacy agent-tab router paths, not dead + top-level routes — see F11). +- **`/issues/:issueId`** (`IssueDetail.tsx`, 1696 lines) — skim only. No + blockers in the visible layout header. +- **`/company/settings`** (`CompanySettings.tsx`) — breadcrumb href fixed to + `/assistant`. +- **`/company/export/*`** (`CompanyExport.tsx`) — OK. References to + `/company/export/...` are real routes, not dead dashboard links. +- **`/company/import`** (`CompanyImport.tsx`) — post-import `window.location.assign` + rewritten from `/${prefix}/dashboard` → `/${prefix}/assistant`. User-visible + "company" vocab is already routed through `VOCAB.company` which resolves to + "Workspace". +- **`/skills/*`** (`CompanySkills.tsx`) — OK. SkillBrowser + SkillDetail + breadcrumb hrefs fixed. +- **`/execution-workspaces/:workspaceId`** (`ExecutionWorkspaceDetail.tsx`) — + skimmed. No findings. + +### Auth + landing + empty states + +- **`/auth`** (`Auth.tsx`) — OK. Border-only inputs, foreground text, + destructive error. Left half form, right half ASCII animation. +- **`/invite/:token`** (`InviteLanding.tsx`) — not touched; out of scope scan. +- **`/board-claim/:token`** (`BoardClaim.tsx`) — not touched. +- **`/cli-auth/:id`** (`CliAuth.tsx`) — not touched. +- **`/*`** (`NotFound.tsx`) — breadcrumb href was `/${prefix}/dashboard`. + Rewritten to `/${prefix}/assistant` and the button label changed from + "Open dashboard" to "Open assistant". +- **Onboarding wizard** (`OnboardingWizard.tsx` + `onboarding/*`) — not touched; + previous phases already covered. +- **`/design-guide`** (`DesignGuide.tsx`, 1532 lines) — the canonical DESIGN.md + showcase. Skimmed; not touched (any findings would be meta since the page + IS the design system). + +### Plugin subsystem + +- **`/plugins/:pluginId`** (`PluginPage.tsx`) — back link href fixed. +- **`/instance/settings/plugins`** (`PluginManager.tsx`) — breadcrumb href fixed. +- **`/instance/settings/plugins/:pluginId`** (`PluginSettings.tsx`) — breadcrumb + href fixed. + +## Top 3 recommended follow-ups (by visual impact) + +1. **Wire the forest-green and pale-yellow token classes** — eliminates the + `bg-[#166534]` / `bg-[#f4f692]` literals in ProjectCard, Projects, + GlobalMicButton, and TabPlaceholder. Low-risk `index.css` addition plus a + grep-and-replace. One commit of ~15 line touches. (MIGRATION-PLAN.md §3.) + +2. **Drop the remaining `shadow-xs` survivors in shadcn primitives** (F1 + F2) + and collapse their `transition-[color,box-shadow]` to `transition-colors`. + Five files, ten lines total. Visible improvement: removes the last + "invisible on black" shadow artifacts. + +3. **Resolve the Basier font decision** (Deviation 2). Either license + load + it, or formally accept Inter as the sole display font and update + DESIGN.md §3 to match. This is the last open item from the visual repaint + and blocks closing the MIGRATION-PLAN open-decisions list.