dragonflight/docs/superpowers/specs/2026-05-21-ui-shell-rework-design.md
Zac Gaetano b77a370eb7 docs: clarify responsive viewport tiers in UI rework spec
Self-review caught an ordering ambiguity in the responsive section: 1280x800
is the fully-supported minimum, tablet 768-1279 is best-effort. Rewording
so the tiers list top-down by viewport size.
2026-05-21 10:45:59 -04:00

15 KiB
Raw Blame History

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.0100.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-hoveraccent, 200ms ease-out on dot only.

Date / time inputs: native <input type="date"> 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 tool. Fully supported minimum viewport: 1280×800. Anything narrower is best-effort only.
  • ≥1600px: standard layout, content max-width 1440px, centered.
  • 12801599px: standard layout, no max-width cap.
  • 7681279px (tablet, best-effort): sidebar collapses to a 56px icon-rail, breadcrumb truncates to last crumb. Functional but not polished.
  • <768px (phone): 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 <input type="date"> 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.