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.
15 KiB
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, orplayer.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-surfacebackground +text-primarytext + 4px leading accent dot (8px tall, vertically centered). No side-stripe border (banned). No accent background fill. - Hover:
bg-hoverfade-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-secondaryfor ancestor crumbs, 13px / 600 /text-primaryfor current. 10px chevron separator intext-tertiarywith 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: 30inside.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-deep70% 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 toborder. 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 1pxaccent-borderwhen 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-hoverrow tint. Selected:bg-surfacetint + 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. Headerflex-shrink: 0. Bodyflex: 1; min-height: 0; overflow-y: auto. Footerflex-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
borderoutline, 4px radius. Focus:accent-borderoutline + 2pxaccent-subtlering. - 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
accentbottom border, text shifts fromtext-tertiarytoaccent. Tab switches are instant — no animation.
Buttons:
- Sizes:
sm28px /md32px /lg36px. - Variants:
primary(accent bg),secondary(bg-surface+ border),ghost(transparent + secondary text,bg-hoveron 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 <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+ 1pxstatus-redborder + 4pxstatus-redtop 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-redwhile recording. Already correct.
Hover / focus:
- All transitions 120ms ease-out on
border-colorandbackground-coloronly. Never onwidth,height,transform. - Focus ring: 2px
accent-subtleoutline, 1px offset,:focus-visibleonly.
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-tertiarylightness bumped from 52% to 56% to clear AA cleanly. :focus-visiblering on every interactive element.- Full keyboard nav. Slide-panel traps focus while open; Esc closes overlays.
- Every icon-only button gets
aria-label. Toasts userole="status" aria-live="polite". prefers-reduced-motion: reducehonored: 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.
- 1280–1599px: standard layout, no max-width cap.
- 768–1279px (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.