diff --git a/docs/superpowers/specs/2026-05-21-ui-shell-rework-design.md b/docs/superpowers/specs/2026-05-21-ui-shell-rework-design.md new file mode 100644 index 0000000..376f344 --- /dev/null +++ b/docs/superpowers/specs/2026-05-21-ui-shell-rework-design.md @@ -0,0 +1,198 @@ +# UI Shell Rework — Design Spec + +> Status: **design approved 2026-05-21**, awaiting user review of the spec before the implementation plan is written. + +## Context + +The Wild Dragon MAM web-ui currently ships 15 static HTML pages served by nginx, sharing a single hand-written `common.css`. The token system is already strong (oklch palette, brand hue 266, 5-step depth surfaces, semantic signal tokens, 4pt spacing). What does not work is the gap between tokens and execution: cards across pages have undifferentiated spacing, generic chrome, weak hierarchy, and identical shape regardless of role. The user feedback that prompted this work was "the UI still looks AI-designed." + +The rework adopts flyon-ui (Tailwind plugin) as the component primitive layer, ports the oklch palette into a custom flyon-ui theme so brand identity is preserved, and rebuilds every page that uses the standard shell against the new primitives. The personality target is *quiet pro tool* — closer to Sony Media Cloud and DaVinci Resolve than to a consumer SaaS dashboard. + +## Goals & non-goals + +**Goals** +- A coherent visual system across every shell page (15 pages minus 3 excluded). +- Higher information density at every screen — closer to Sony / DaVinci than to today's spacing. +- Four distinct card families so the eye reads role from shape. +- A theme port that preserves brand hue 266 and the existing oklch palette under flyon-ui. +- Accessibility floor: WCAG AA contrast, full keyboard nav, reduced-motion honored. + +**Non-goals** +- Mobile UX. Phones get an explicit "desktop only" splash. Tablet gets a collapsed icon-rail sidebar but no further accommodation. +- Replacing the brand color, the font stack, or the dark theme. +- Animations beyond functional state transitions (no celebrations, no page-fade, no sound design). +- Adding new pages or features. This is purely visual / structural. +- Rebuilding `edit.html`, `editor.html`, or `player.html` (deliberately excluded — see Rollout). + +## Personality, scene & color strategy + +- **Register:** product (app UI, design serves the product), not brand. +- **Theme scene sentence:** "MAM operator at a 27-inch monitor in a dim control room, scanning a grid of 100+ video assets at 2am while a live recording timer runs." Forces dark, low-chroma, tabular-numeric trust signals. +- **Color strategy:** restrained. Tinted neutrals (chroma 0.010–0.015, hue 266) plus a single amber accent used in ≤10% of surfaces — for active states, recording indicators, primary CTAs, focus rings. +- **Anti-patterns explicitly banned:** glassmorphism as default, gradient text, side-stripe borders (>1px on the side of cards/rows/callouts), hero-metric SaaS template, identical card grids across roles, em dashes in copy. Cliché category-reflex check passes: this design lands as DAW / NLE / NLE-adjacent operational tool, not "dark blue observability dashboard." + +## Architecture + +### 1. Build system & theme port + +The `services/web-ui` Docker image gains a Node build stage. During `docker build`, `tailwindcss --minify` runs once, scans `public/**/*.html` for class usage, and emits `public/dist/app.[hash].css`. The runtime stage stays nginx-static — no runtime Node, longer startup, or extra moving parts. + +A `tailwind.config.js` at `services/web-ui/` defines a custom flyon-ui theme that maps the existing oklch palette into flyon-ui's color slots. Brand hue 266 is preserved; the 5-step depth surfaces become Tailwind's `bg-deep` / `bg-base` / `bg-panel` / `bg-surface` / `bg-raised` / `bg-hover` utility chain. Signal tokens (`signal-good` / `signal-bad` / `signal-warn` / `signal-idle`) map directly. Spacing scale uses Tailwind's default 4pt scale, which already matches the existing `--sp-*` tokens — utilities like `p-3` and `gap-4` replace `var(--sp-3)` and `gap: var(--sp-4)`. + +Fonts (Inter + JetBrains Mono) move from Google CDN to self-hosted woff2 in `public/fonts/`. The four "legacy alias" entries in the current `:root` (`--status-amber`, `--status-amber-bg`, etc.) get cleaned up during the port. + +The custom theme also disables flyon-ui utility classes for the banned patterns: no `glass-*`, no `gradient-text`, no card-shadow defaults. + +### 2. Sidebar + +- **Dimensions:** 200px wide (down from 220px). Items 28px tall (down from ~36px), 8px horizontal padding, 4px vertical. +- **Type:** Inter 13px / 500 for items. Section labels 10px / 600 / 0.14em tracked / uppercase / `text-tertiary`. +- **Header:** 18px dragon logo + Inter 13px / 600 / -0.01em "Z-AMPP" wordmark. Total header height 48px to align with topbar. +- **Active state:** `bg-surface` background + `text-primary` text + 4px leading accent dot (8px tall, vertically centered). No side-stripe border (banned). No accent background fill. +- **Hover:** `bg-hover` fade-in over 120ms ease-out. No transform. +- **IN DEV badge** (injected by `auth-guard.js`): retained, restyled as 9px / 700 / 0.12em tracked amber pill. +- **Footer user widget:** 28px round avatar, name + role stacked, logout button reveals on row hover. + +### 3. Topbar + +- **Dimensions:** 48px tall. Padding 16px left / 12px right. Bottom border `border-faint`. +- **Left:** breadcrumb pattern, not flat title. Inter 13px / 500 / `text-secondary` for ancestor crumbs, 13px / 600 / `text-primary` for current. 10px chevron separator in `text-tertiary` with 8px gutters. +- **Center:** page-scoped search input on pages that have searchable content (Library, Recorders, Projects, Jobs, Cluster). 360px wide, 28px tall, leading magnifier, monospace placeholder. +- **Right:** primary CTA rightmost (28px button with 12px leading icon), 1px vertical divider, then 28px-square icon-only ghost buttons for secondary actions (filter, sort, view-toggle). +- **Sticky:** `position: sticky; top: 0; z-index: 30` inside `.main`. Sidebar does not scroll separately; topbar stays visible while content scrolls. + +### 4. Card families + +Four distinct card shapes, each with one clear job. Same-shape repetition is banned. + +**Asset card** — Library, Projects asset grid, Recorders recording cards +- 16:9 thumbnail, full-bleed. Duration chip bottom-right (JetBrains Mono 10px, `bg-deep` 70% opacity). Comment-count chip bottom-left (when >0). Selection checkbox top-left (only on hover or when any are selected). Version badge top-right when applicable. +- Metadata: filename (Inter 13px / 500), then `{author} · {date}` row (11px / `text-tertiary`, mono numerics). +- Role pill at bottom: full-width, light tint of role color, 10px / 600 / 0.08em tracked / uppercase. Dotted-border placeholder when unset. +- 1px `border-faint`, 6px radius, `bg-panel`. Hover: thumbnail +4% brightness, border lifts to `border`. No scale, no shadow. + +**Operational card** — Recorders cards, Cluster nodes, Jobs queue +- Header / content / footer rows. Wider than tall (min 8:3 ratio). 14px padding, 10px row gap. +- Header: 16px name + status pill (semantic signal tokens). +- Content: role-specific. Recorder gets live preview + signal strip + timer; cluster node gets CPU/mem mini-bars; job gets progress strip. +- Footer: 1px top hairline, metadata-left + actions-right. +- Border 1px `border-faint`, becomes 1px `accent-border` when active (recording / online / running). Color change only — no glow, no shadow, no animation. + +**Inline list row** — Containers, Users, Tokens, API tokens +- Not a card. Table row with extra breathing room. 44px tall, hairline divider (`border-faint`). +- Hover: `bg-hover` row tint. Selected: `bg-surface` tint + 4px leading accent dot (same indicator language as sidebar active state). + +**Empty state** +- Centered 28px line icon (`text-tertiary`), 14px / 600 title, 13px body, primary action button. No card chrome — the empty state IS the page. +- Declarative copy. No exclamation points, no emojis. + +### 5. Grids + +- Asset grid: `repeat(auto-fill, minmax(220px, 1fr))` with 12px gap. +- Operational grid: `repeat(auto-fill, minmax(380px, 1fr))` with 14px gap. +- Page content padding: 20px sides, 16px top, 32px bottom. + +### 6. Forms, slide-panels & inputs + +**Slide-panel structure (the codec-clipping bug fix codified as a primitive):** +- 460px wide. `height: 100vh; display: flex; flex-direction: column; overflow: hidden`. Header `flex-shrink: 0`. Body `flex: 1; min-height: 0; overflow-y: auto`. Footer `flex-shrink: 0; bg-deep`. +- Header 52px, 18px padding, title + close button. Bottom border `border-faint`. +- Body 18px padding, `display: flex; flex-direction: column; gap: 16px`. + +**Form primitives:** +- Label: 11px / 600 / 0.08em tracked / uppercase / `text-tertiary`. +- Input / select / textarea: 32px tall, 10px horizontal padding, 13px text, 1px `border` outline, 4px radius. Focus: `accent-border` outline + 2px `accent-subtle` ring. +- Form hint: 11px / `text-tertiary` / 1.5 line-height. JetBrains Mono code spans. +- Form row: `grid-template-columns: 1fr 1fr; gap: 14px`. + +**Field-group (tabbed sections, generalized from the codec-block pattern):** +- Titled header strip (36px, `bg-surface`) + tab row (32px, `bg-deep`) + tab panels (14px padding). +- Active tab: 2px `accent` bottom border, text shifts from `text-tertiary` to `accent`. Tab switches are instant — no animation. + +**Buttons:** +- Sizes: `sm` 28px / `md` 32px / `lg` 36px. +- Variants: `primary` (accent bg), `secondary` (`bg-surface` + border), `ghost` (transparent + secondary text, `bg-hover` on hover), `danger` (status-red bg). +- Leading icon: 12px svg, 6px gap. Disabled: 40% opacity. Active press: 60ms `opacity: 0.85`. No gradient, no shadow, no scale. + +**Toggle:** 34×18, track `bg-hover` → `accent`, 200ms ease-out on dot only. + +**Date / time inputs:** native `` styled to match the input primitive. No third-party picker library. + +### 7. States, motion & feedback + +**Loading:** skeleton blocks matched to content shape. Asset grid → 12 placeholder cards with 1.8s gradient shimmer (not opacity pulse). In-card actions get inline 12px ring spinner. In-button: label replaced by spinner, width preserved. + +**Empty states:** fade in 240ms on first load; instant when user-initiated. + +**Errors:** +- *Toast* (bottom-right, 320px): `bg-panel` + 1px `status-red` border + 4px `status-red` top strip. Auto-dismiss 4s success / 8s warning / manual error. Stack up to 3. +- *Inline*: red 11px text below offending field. No icon, no shake. +- *Page-level*: full-page card with icon + plain-English title + Retry + Get-help buttons. Never blocks the sidebar. + +**Success:** "Recorder saved" toast. Affected card briefly tints (200ms `accent-subtle` background, fades back over 1.2s). One-time. No checkmark celebrations. + +**Live / realtime (recording-in-progress):** +- Signal strip shimmer 1.8s ease-in-out (down from 2.4s linear). +- "LIVE" preview-stamp dot stutter pattern: bright 0.9s / dim 0.3s / bright 0.9s (broadcast tally light). +- Timer: 600 weight, `status-red` while recording. Already correct. + +**Hover / focus:** +- All transitions 120ms ease-out on `border-color` and `background-color` only. Never on `width`, `height`, `transform`. +- Focus ring: 2px `accent-subtle` outline, 1px offset, `:focus-visible` only. + +**Page transitions:** none. Click nav → page renders. Slide-panel keeps 240ms slide-in from right. + +**Notifications:** no bell, no global status banner. Failures surface inline on affected pages. + +### 8. Accessibility & responsive + +**A11y floor:** +- WCAG AA contrast on every text/background pair. `text-tertiary` lightness bumped from 52% to 56% to clear AA cleanly. +- `:focus-visible` ring on every interactive element. +- Full keyboard nav. Slide-panel traps focus while open; Esc closes overlays. +- Every icon-only button gets `aria-label`. Toasts use `role="status" aria-live="polite"`. +- `prefers-reduced-motion: reduce` honored: kills shimmer / pulse / slide-in, state changes become instant. + +**Responsive:** +- Desktop-first. Minimum supported viewport 1280×800. Below that, layout breaks accepted. +- 1280–1599px: standard layout. +- ≥1600px: content max-width 1440px, centered. +- Tablet (768–1279px): sidebar collapses to 56px icon-rail. Breadcrumb truncates to last crumb. +- Phone (<768px): explicit "Z-AMPP is desktop-only" splash. No fake mobile experience. + +**Browser support:** Chromium latest 2 versions + Safari latest 2. Firefox best-effort. No IE / legacy Edge. + +## Rollout + +Four waves, ordered by blast radius. Each wave is its own commit / deploy / verify cycle on zampp1. + +**Wave 1 — Foundation (zero user-visible change).** Build pipeline, Tailwind + flyon-ui config, theme port, primitive CSS components, shell markup migration. New primitives exist but no page uses them yet. *Validates the build system works end-to-end.* + +**Wave 2 — Shell + low-risk pages.** `login.html`, `home.html`, `settings.html`, `tokens.html`, `users.html`, `containers.html`. New shell, new card / list patterns, new forms. Low risk: no live data, simple flows. + +**Wave 3 — Content-heavy pages.** `index.html` (Library), `projects.html`, `upload.html`, `jobs.html`, `api-tokens.html`. Asset grid, project tree, upload queue, job queue. + +**Wave 4 — Operational pages.** `recorders.html`, `cluster.html`, `capture.html`. Live data, HLS preview, signal polling, BMD picker, codec slide-panel. Done last so the primitives are battle-tested before they meet the most fragile pages. + +**Excluded from rework:** `edit.html`, `editor.html` (in-development construction screen is the right treatment), `player.html` (standalone embed, no shell). + +**Definition of done per page:** new shell + new primitives + AA contrast verified + keyboard-nav check + responsive at 1280/1440/1920 widths + no JS regressions (live-recording flow on recorders.html is the canary). + +## Risks & mitigations + +| Risk | Mitigation | +|---|---| +| Tailwind build pipeline introduction breaks docker build | Wave 1 ships the build *without* migrating any page. If build fails, we revert without losing functionality. | +| Theme port loses brand hue 266 character | Custom flyon-ui theme explicitly maps existing oklch tokens; QA on wave 1 includes side-by-side color comparison vs. current. | +| Recorders rewrite (just stabilized) gets re-touched in wave 4 | Wave 4 is last on purpose — primitives are battle-tested by then. The codec-tab pattern from the recent recorders rewrite is generalized into the `.field-group` primitive in wave 1, so wave 4's recorders rewrite is mostly markup migration, not pattern reinvention. | +| Density target is too aggressive — 13px text / 28px rows feel cramped on smaller monitors | Wave 1 ships density + AA verified at 1280×800. If feedback says cramped, bump base text to 14px in a single token change. | +| Page-level skeleton loaders are extra implementation work | Acceptable cost. Spinners-only would feel cheaper than the rest of the design. | +| Native `` looks inconsistent across Chromium / Safari | Acceptable. Inconsistency is small; bundle weight savings of avoiding a date-picker library is real. | + +## Implementation plan handoff + +Once this spec is approved by the user, the next step is invoking the `superpowers:writing-plans` skill to produce a wave-by-wave implementation plan with concrete commit / deploy steps. The plan will live at `docs/superpowers/plans/2026-05-21-ui-shell-rework-plan.md` and reference this spec. + +## Open questions + +None. All seven design sections were approved by the user (Zac) during brainstorming on 2026-05-21. No placeholder values remain in this spec.