From 2fca7bccb0e8a41bf77006ce0b2097b979d55492 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 9 Oct 2026 05:41:29 +0000 Subject: [PATCH 1/6] Add design audit against Emil Kowalski's Apple-design and motion skills Co-Authored-By: Claude Opus 5.5 --- plans/2026-10-09-design-audit.md | 223 +++++++++++++++++++++++++++++++ plans/README.md | 1 + 2 files changed, 224 insertions(+) create mode 100644 plans/2026-10-09-design-audit.md diff --git a/plans/2026-10-09-design-audit.md b/plans/2026-10-09-design-audit.md new file mode 100644 index 000000000..cbe7d16a9 --- /dev/null +++ b/plans/2026-10-09-design-audit.md @@ -0,0 +1,223 @@ +# Design audit: Apple design and Emil Kowalski standards + +**Status:** Backlog. Audit of `main` at `56a94d7` on 2026-10-09. Nothing here is implemented. Recheck paths and line numbers before acting on any row. + +## What this is + +A full UI audit of DebateLab against Emil Kowalski's design-engineering skills (`apple-design`, `emil-design-eng` and `review-animations` from [emilkowalski/skills](https://github.com/emilkowalski/skills) at `e8a175d`). `apple-design` is Apple's WWDC guidance on fluid interfaces, materials, typography and the eight design principles, translated for the web. The other two supply the motion rules and values: easing curves, duration budgets, press feedback, transform origins and interruptibility. + +Four Opus reviewers read every page, every component in `components/ui/` and elsewhere, and the whole of `src/index.css`. Then the real app ran locally (`verify-debatelab`, fake provider). A Playwright sweep covered 20 routes on desktop (1440×900) and iPhone 13, in light and dark. A probe opened every overlay type and read real durations, easings and transform origins from `document.getAnimations()`. Further checks covered reduced motion, `prefers-contrast: more`, and Hungarian at 375px. Evidence is in `.verify/artifacts/audit-20261009T052123-design/` (gitignored): 118 screenshots, `metrics.json`, `probe.json`, and five screen recordings. + +Verdict under `review-animations`: **Block.** Two of the feel-breaking issues are system-wide bugs, not taste: toasts can't stack or leave, and secondary text has no colour hierarchy. Both are cheap to fix. + +## The short version + +The product structure is sound. The motion tokens are good (`--motion-ease-fluid` is a strong ease-out), `MotionConfig reducedMotion="user"` wraps the app, routes prefetch on intent, and the voice, Sparring and Crossfire work is careful. The weak spots are five systemic problems: + +1. **A global CSS layer overrides the shared UI components.** About 600 unlayered lines in `src/index.css` (roughly 491–847) target ARIA roles, Radix `data-state` attributes and Lucide icon class names. They override Sonner, Select, Progress, Tabs, Switch, focus rings and Tailwind's own `.tabular-nums`. Most of the motion bugs below come from this layer. Deleting most of it and moving what's worth keeping into each component's `cva` classes fixes the largest share of findings for the least work. +2. **The colour tokens are miswired.** `--muted-foreground` equals `--foreground` in both themes, so 379 uses of `text-muted-foreground` render at full body contrast. Light `--muted` is a mid grey, `--accent` (the ghost-button hover) is 1.05:1 against the page, the light popover is darker than the page, dark shadows are invisible, and the light primary button label is 3.85:1. +3. **Nothing is built for touch.** Hover motion isn't gated, there's no tap-highlight reset, nothing reads the safe-area insets despite `viewport-fit=cover`, and most controls on every mobile page are under 44px. +4. **Wayfinding and agency.** On all eight tool routes, nothing in the header shows as active. There are five different delete patterns and no undo anywhere, even though the toast adapter supports an action button. Streaming tools can't be stopped. +5. **Too much decoration.** The landing page loops an aurora, orbs, a drifting grid and CTA shimmer forever across five sections. The app replays staggers on tab switches, animates the nav underline from `scale(0)` on every navigation, and stacks five looping indicators on the AI loading panel. + +## Confirmed in the browser + +These rows were observed in the running app, not only inferred from code. + +| Finding | What the browser showed | +| --- | --- | +| Toasts can't stack, expand or exit | With two toasts open, both sat at the same position (top 823 and 825px) with an identity transform. Hovering didn't expand them, and only the newest was visible. 150ms after `toast.dismiss()` both were marked removed but still fully opaque and unmoved. Cause: `[data-sonner-toast]{animation: toast-slide-in .3s … both !important}` at `src/index.css:622-624` holds the transform Sonner uses for stacking, swipe and exit. Screenshots: `probe-toast-stacked.png`, `probe-toast-expanded.png`, `probe-toast-exit-150ms.png`. | +| Landing card hovers never render | `.feature-card`, `.tilt-card` and every `hover:-translate-y-1` card keep an inline `transform: none` from framer's `whileInView` after the reveal, so hovering changes nothing. | +| Select closes with `ease-in` | Measured: the Select exit is 150ms `ease-in`, leaked from `src/index.css:709-711`. | +| Popovers scale from their centre | Measured: Select content origin is `94.5px 37px` and the debate menu popover is `176px 151px`, the centres of their boxes, not the trigger. | +| `duration-200` on Dialog is dead | Measured: the dialog enters in 150ms and exits in 150ms with `ease`, so enter and exit are symmetric. | +| Press feedback is too faint | Measured: hover lifts 1px, and `:active` scales to 0.985 over 160ms in both directions. | +| Timer ring eases every second | Measured: `clip-path 1000ms cubic-bezier(0.4,0,0.2,1)` per tick, so the ring surges and stops each second. Digits are Space Mono at 72px with −3.6px tracking. | +| Mobile menu is slow and has no scrim | Measured: a 420ms `motion-reveal` on a plain `