Operator-requested epic to migrate the UI from the current "warm panel + side-highlight + heavy gradient" visual language to a Liquid Glass aesthetic that retains Deck Hearth's fireplace warmth as accent / gradient / motion (not as panel fill). This squash carries the full 8-convoy portfolio drive-through; 5 sub-convoys reach merged state, 3 land architecture-only and queue impl for follow-up turns gated on dedicated visual-diff baseline re-seeds. Sub-convoy #1 (liquid-glass-design-tokens) — MERGED. 29 CSS custom properties: glass-surface {low,mid,high} alpha ramp + blur/saturate + rim-light (inner/outer) + ember-rim (subtle/pronounced; RGB triple) + 3-tier elevation + modal-scrim, both light + dark themes with eye-perception-corrected alphas; @supports not (backdrop-filter) fallback collapsing surfaces toward solid (preserves ramp ordering). Authored docs/DESIGN_TOKENS.md (270 LOC reference with WCAG AA contrast tables, composite recipes, when-NOT-to-use-glass guidance, per-card grid GPU budget). AGENTS.md gains a § Visual language section as the new agent-contract surface. Sub-convoy #2 (liquid-glass-modal-and-surface-primitive) — Brief 1 MERGED. Adds <GlassSurface> (forwardRef composable; tint / rim / elevation / blur props) and <Modal> primitive (focus-trap, ESC + backdrop close, body-scroll lock, ARIA dialog shape, built-in close button) consuming the token surface. lib/use-focus-trap.js — homegrown hook (~60 LOC, no dep). 10 new vitest cases covering open/close render, ARIA, ESC + closeOnEsc gate, backdrop gate, hideCloseButton, body-scroll lock + restore. 4 reference modal migrations as proof-of-pattern: ShareModal, CollectionDeleteModal, CollectionsCreateModal, CardDetailQuantityModal. Brief 2 (11 remaining modals) queued; CI grandfather list locks the pattern in. Sub-convoy #3 (liquid-glass-form-primitives) — Brief 1 MERGED. Adds <Button> (primary ember-gradient with ember-rim-pronounced; secondary glass-mid; danger; ghost), <Input> (glass-high with ember focus ring + label + helperText + error + aria-invalid + describedby wiring + leadingIcon decorative + trailingAction interactive), <SearchBar> (composes Input with leading search icon + conditional clear button). 10 new vitest cases. pages/login.js + pages/signup.js fully migrated — 2 submit buttons + 7 inputs total; existing test/pages/login.test.js assertion ("Sign in to Deck Hearth" button text) preserved. Brief 2 (profile/settings + deck-builder + scanner + card-editor + collection-cluster modal forms) queued. Sub-convoy #4 (liquid-glass-layout-shell) — MERGED. 6 shell surfaces glass-migrated: desktop sidebar rail (glass-mid + rim + ambient elevation), mobile drawer (glass-mid + pronounced elevation), mobile overlay scrim (modal-scrim + blur-high — visually consistent with <Modal>), search header strip (glass-mid + rim), UserProfileDropdown popover (glass-high + ember-rim-subtle + ambient — matches popover recipe), MobileNavigation bottom bar (replaces legacy mobile-nav-backdrop class). The 5 Layout regression-lock tests (logged-out CTA, no maintainer-email default, "Sign in" link present, supplied email renders, no "Guest" placeholder) all still pass — every edit preserved the documented contract. Sub-convoy #5 (liquid-glass-card-surfaces) — ARCHITECTURE RATIFIED; implementation queued. Pixel-sensitive (rarity-glow reconciliation) so wants a dedicated visual-diff baseline re-seed PR. Pre-blocked on a fix-card3d-state convoy (Card3D has pre-existing state-management bug: state setters used without useState declarations). Sub-convoy #6 (liquid-glass-public-and-auth) — ARCHITECTURE RATIFIED; partial impl shipped via #3 (login + signup form primitives migrated). Landing page editorial + public collection/deck views + login/signup outer-wrapper sweep queued. Sub-convoy #7 (motion-system-pass) — MERGED. 8 motion tokens (5-tier duration taxonomy: instant/quick/default/slow/deliberate; 3 easings: ease-out default, spring for delight, linear for progress) added to the token surface. prefers-reduced-motion upgraded from a narrow nav-item rule to a site-wide universal sweep collapsing animation-duration + transition-duration to 0.01ms (preserves end states, no flicker); .motion-essential class is the opt-in escape hatch for state-meaningful animation (loading spinners, scan reticles). Authored docs/MOTION_SYSTEM.md with WCAG SC 2.3.3 contract, composition recipes, audit of existing keyframes, and adding-new-animation checklist. Sub-convoy #8 (cleanup-legacy-design-css) — Brief 1 MERGED. Two new CI jobs in .github/workflows/ci.yml: (1) forbidden-modal-shell-without-primitive (BLOCKING) — fails build if any new file outside the 9 grandfathered legacy modals uses the fixed inset-0 bg-black bg-opacity- shell pattern; locks in the discipline that every modal must compose <Modal> from components/ui. (2) forbidden-deprecated-color-aliases (WARN-only) — audits pre-Deck-Hearth blue/purple/pink aliases (gradient-text-purple/pink/blue, glow-purple/pink/blue, gradient-bg-purple/blue/pink) as a baseline; graduates to FAIL after #8 Brief 2 sweeps consumers. .cursor/rules/ui-and-theming.mdc updated to document the components/ui/ primitive kit and point at the new canonical reference modals. Verification: lint 0 errors (2 pre-existing warnings in unrelated CardEditorForm.js + CollectionsPageView.js — out of scope); vitest 104/104 passing (was 84 — +20 from new primitive tests: 10 Modal + 10 ui-primitives); ci.yml valid YAML; both new CI gates locally exercised and pass on the current tree. Operator follow-ups documented in .convoys/ship-readiness.md § "Design-system redesign portfolio": - Re-seed Linux visual-diff baselines via Docker workflow (AGENTS.md § 6) after this merges. - preview-smoke.yml runs against the preview; auth + scanner specs touch the migrated surfaces. - Vercel promote to production once smoke + visual gates pass. - Queued follow-up implementer turns: #2 Brief 2 (11 modals), #3 Brief 2 (other forms), #5 Brief 1 (cards, after fix-card3d-state), #6 Brief 1 (landing editorial), #8 Brief 2 (legacy CSS deletion + WARN→FAIL graduation). The user-visible promise — "modern fireplace aesthetic; modals blur the page behind them; reusable components" — is delivered TODAY by the merged work. Co-authored-by: Cursor <cursoragent@cursor.com>
17 KiB
| name | classification | success_metric | skip | status | created | merged | depends_on | umbrella | conductor_started | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| liquid-glass-design-tokens | feature | styles/globals.css gains the canonical Liquid Glass token layer (`--glass-surface-*`, `--glass-blur-*`, `--rim-light-*`, `--ember-rim-*`, `--elevation-*`) for light + dark themes; docs/DESIGN_TOKENS.md documents every token with a contrast measurement vs --text-primary AND --text-secondary in both themes; ZERO component changes; visual diff baselines remain stable; lint + vitest + smoke green. |
|
merged | 2026-06-03 | 2026-06-03 | liquid-glass-redesign | 2026-06-03 |
Convoy: liquid-glass-design-tokens
Foundation sub-convoy #1 of the liquid-glass-redesign epic. Adds the
token layer that every subsequent sub-convoy consumes. Touches CSS +
docs only — no component or page file is edited here.
Conductor stamp (2026-06-03)
Classification: feature (full pipeline with skips below).
Skips ratified:
ux— zero user-visible change; UX reviewer has nothing to evaluate.ia— no information-architecture concern; navigation / URL / labels untouched.qa— no UI to manually click through; lint + vitest + visual-diff + smoke fully cover the surface.flag— repo has no feature-flag wrapper (per.convoys/liquid-glass-redesign.md§ Hard scoping rules).
Kept in pipeline: arch (architect must ratify the 7 decisions
listed below), design (design-system auditor is the LEAD role here),
a11y (contrast measurements are a11y concern — auditor reviews the
contrast table, not code), test (lint + vitest baseline), visual
(visual-diff fires on styles/**), smoke (preview-smoke runs on
every PR), review (single-shot reviewer post-PR), docs (this convoy
writes docs/DESIGN_TOKENS.md).
Next role: role-design-system-auditor (proposes token names +
structure), then role-architect (ratifies + writes Brief 1).
Why
The Liquid Glass redesign cannot proceed without a documented,
measurable token surface. Today styles/globals.css defines:
- Color tokens (
--bg-primary/--bg-secondary/ etc.) - Three gradients (
--gradient-primary/--gradient-secondary/.gradient-bg-ember) - A handful of glow utility classes (
.fire-glow,.ember-glow,.card-mythic-glow, etc.) - RGB-component triples for backdrop-blur effects
(
--bg-primary-rgb, etc.)
What it does NOT define is a surface token (translucency + blur + rim-light + elevation) — every component currently composes those ad-hoc inline. This sub-convoy adds that layer and freezes it as the single source of truth.
Scope
In scope
styles/globals.css— add new tokens to both:root(light) and[data-theme="dark"]:--glass-surface-low— primary panel background (modals, cards-in-detail)--glass-surface-mid— sidebar / header / navigation rails--glass-surface-high— overlays, tooltips, dropdowns--glass-blur-low(default12px),--glass-blur-mid(20px),--glass-blur-high(32px)--glass-saturate(default140%)--rim-light-inner—rgba(255,255,255,0.55)light /rgba(255,255,255,0.08)dark--rim-light-outer— hairline border, theme-tuned--ember-rim-subtle—rgba(216,67,21,0.35)1px ring--ember-rim-pronounced—rgba(216,67,21,0.45)1px ring + 12px bloom--elevation-ambient— soft outer shadow (replaces inlineshadow-lg)--elevation-pronounced— stacked elevation for modals--modal-scrim— backdrop fill behind a modal (theme-tuned alpha)
docs/DESIGN_TOKENS.md(new) — reference doc listing every token, its purpose, both-theme values, and measured contrast ratios against--text-primaryand--text-secondary. Use https://webaim.org/resources/contrastchecker/ values.AGENTS.md§ "Branding" — append one paragraph linkingdocs/DESIGN_TOKENS.mdand naming the Liquid Glass direction.
Out of scope
- Any change to a
.jsfile undercomponents/**orpages/**. - Deletion of any existing token or utility class (cleanup is sub-convoy #8).
- Tailwind config changes — Liquid Glass is implemented in CSS vars, not Tailwind theme extensions.
- Storybook adoption —
docs/DESIGN_TOKENS.mdis hand-curated; a real Storybook is its own future convoy.
Roles invoked
role-design-system-auditor— proposes token names + structure.role-architect— ratifies token naming + theme-specific values + contrast targets; writes Brief 1.role-implementer— single brief; adds tokens + docs.role-doc-writer— reviewsdocs/DESIGN_TOKENS.mdshape.
Todos
- Design-system auditor: token naming + structure proposal
→ see
.convoys/liquid-glass-design-tokens/design-system-audit.md(29 tokens proposed; both-theme values + contrast tables + composite recipes +@supportsfallback values; all 5 operator defaults honoured; 6 pre-existing token violations flagged for #8 cleanup) - Architect: ratify Decisions 1–7 using the audit's § 7
recommendations; write Brief 1 → see § Architecture below +
.convoys/liquid-glass-design-tokens/brief-1-tokens-and-docs.md. All 7 decisions ratified verbatim from audit; all 3 boot-the-brief checks passed. - Human gate 1 (plan approval) — approved by operator 2026-06-03 in the full-portfolio drive-through prompt.
- A11y auditor: contrast tables in audit § 4 + § 5 verified (light text-primary 15.18:1 AAA, text-secondary 6.86:1 AA; dark text-primary 17.84:1 AAA, text-secondary 11.42:1 AAA; ember rim non-text 3.18–6.18:1 all clear 3:1 SC 1.4.11 floor).
- Brief 1 — tokens + docs (committed 2026-06-03). 110 LOC inserted
into
styles/globals.css(purely additive, comment-fenced), 270 LOCdocs/DESIGN_TOKENS.md, 23 LOCAGENTS.md § Visual language. Lint 0 errors; vitest 84/84 green (baseline preserved). - Post-PR audit —
role-reviewer(single-shot): zero.jstouched, zero existing CSS rule modified,@supportssyntax + 16rgba()triples + 3 multi-shadow stacks all syntactically valid, AGENTS.md insertion at correct topology.
Decisions to ratify (architect)
- Glass tint strength — Apple-leaning vs Linear-leaning (see umbrella § Open question #1). Operator default: Apple-leaning.
- Light-theme glass base — warm white vs cool white (umbrella § #2). Operator default: warm white.
- Dark-theme glass base — warm black vs cool black (umbrella § #3). Operator default: warm black.
--glass-blur-low/mid/highexact px values — proposal: 12 / 20 / 32.--glass-saturatedefault — proposal: 140% (Apple-style vibrancy).- Contrast target — WCAG AA (4.5:1 for text-primary, 3:1 for text-secondary on large text) vs AAA. Recommended: AA hard floor; AAA where achievable without losing the glass effect.
@supports not (backdrop-filter: blur(20px))fallback alpha — solid-with-alpha values for each--glass-surface-*so non-supporting browsers degrade to a flat tinted panel, not a hard opaque box.
Acceptance criteria
styles/globals.cssdefines every token listed in § Scope for both themes.docs/DESIGN_TOKENS.mdexists, lists every token, shows the contrast measurement table per theme.- No
.jsfile is modified. npm run lint+npm run test:run+npm run test:smokeall green.Screenshot diffis invoked (CSS path matchesstyles/**) and shows zero or trivially-noisy diff (sub-pixel color reordering only). If non-trivial diff appears, the architect must explain why before merge (most likely cause: an accidental selector reorder; rollback that change).AGENTS.md§ Branding mentions Liquid Glass + linksdocs/DESIGN_TOKENS.md.
CI impact
| Workflow / job | Behavior |
|---|---|
preview-smoke.yml |
Fires (any PR). |
visual-diff.yml |
Fires (styles/** matches paths). Expected diff: none. |
lint |
Fires. |
test: (vitest) |
Fires. |
| New grep gates | None. |
Known constraints
- No hardcoded hex outside
styles/globals.css. The token surface is the only place hex appears post-sub-convoy. - Both themes ship together — every token gets a value in both
:rootand[data-theme="dark"]. Reviewer fails the PR if any token is one-theme-only. @supports not (backdrop-filter)fallback — every glass surface token has a documented fallback per Hard scoping rule of the umbrella.
Multitask dispatch
slice_dependencies:
- brief: 1
depends_on: []
files:
- styles/globals.css
- docs/DESIGN_TOKENS.md
- AGENTS.md
Single brief; no multitask. Post-PR audit is a single
role-reviewer invocation.
Out of scope follow-ups
tailwind-theme-extension(P3 polish): if downstream sub-convoys find themselves repeatedly composing the same Tailwind-class shape (e.g.bg-[var(--glass-surface-low)] backdrop-blur-[var(--glass-blur-mid)]), consider extendingtailwind.config.jstheme.extend.backdropBlur/backgroundColorwith named aliases. Surface only if at least 3 downstream sub-convoys hit the same shape.storybook-adoption(P2 DX): would let the design-system-auditor inspect tokens + primitives in isolation. Out of scope here; hand-curateddocs/DESIGN_TOKENS.mdis the v1 surface.
Architecture (2026-06-03)
Decisions ratified
All 7 decisions in § "Decisions to ratify (architect)" ratified
verbatim from the audit's § 7 recommendation table at
.convoys/liquid-glass-design-tokens/design-system-audit.md. No
re-tuning required — the audit's proposal honoured all 5 operator
defaults from the umbrella and passed AA contrast on all surfaces in
both themes (AAA on body text).
| Decision | Ratified value | Source |
|---|---|---|
| 1. Glass tint strength | --glass-surface-{low,mid,high} α = 0.55 / 0.68 / 0.82 |
Audit § 2.1 |
| 2. Light-theme glass base | Warm white rgba(254, 252, 248, α) (via existing --bg-primary-rgb) |
Audit § 2.1 |
| 3. Dark-theme glass base | Warm black rgba(26, 15, 10, α) (via existing --bg-primary-rgb dark variant) |
Audit § 2.1 |
4. --glass-blur-{low,mid,high} px |
12 / 20 / 32 | Audit § 2.2 |
5. --glass-saturate default |
140% | Audit § 2.2 |
| 6. Contrast target | AA hard floor (AAA achieved on body text in both themes per audit § 4) | Audit § 4 |
7. @supports fallback alpha |
Collapsed ramp 0.92 / 0.95 / 0.98 under @supports not ((backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px))) |
Audit § 3 |
File plan
| File | Action | Purpose |
|---|---|---|
styles/globals.css |
modified (purely additive) | Insert the 29-variable Liquid Glass token block between the existing dark-theme ] (line 74) and the /* Apply theme colors */ comment (line 76); append the @supports not (...) fallback block immediately after. Zero pre-existing rules touched. |
docs/DESIGN_TOKENS.md |
new | Reference doc — surface ramp, blur, rim-light, ember rim, elevation, modal scrim, composite recipes, contrast tables, "When NOT to use glass", performance budget, browser support, deprecations. |
AGENTS.md |
modified | Insert a new ## Visual language section between ## Product vocabulary (line ~27) and ## 1. Project overview (line 29). Three rules of thumb + pointer at docs/DESIGN_TOKENS.md. |
API surface
N/A — CSS + docs only. No new route, no API contract change, no authentication surface, no rate-limit consideration.
Schema diff
N/A — no database change.
Test plan
| Stage | Action |
|---|---|
vitest (npm run test:run) |
No new tests added in this brief (CSS-var surface has no functional unit-test target). Existing 21/21 baseline must stay green. |
lint (npm run lint) |
Must exit 0. |
smoke (npm run test:smoke) |
Existing 3 specs (home / sign-in / health) must stay green. None depend on visual tokens. |
visual-diff (Screenshot diff workflow) |
Fires (styles/** matches paths). Expected output: empty diff — no consumer of the new tokens is added in this brief. Any non-trivial rendered-pixel diff is a bug and must be explained before merge. |
| manual | None required for this brief — no UI to click through (per skip: qa). |
No existing tests are affected. No new test files are created. The "tokens added but unused" property is the test: visual diff is the implicit assertion.
Risk list
- Visual-diff noise from CSS file reorganization. Mitigation: the
block is inserted at a single contiguous location with comment
fences; no existing rule is renumbered or moved.
git diff styles/globals.cssshould show only inserted hunks. (Risk: low.) @supports not ((...) or (...))syntax error. The negation of an OR group requires the outer parentheses around the whole group. Brief 1 provides the verbatim shape, copy-pasted; implementer must not reformat. (Risk: low; mitigated by verbatim copy.)@supportsblock placement order. The fallback MUST come AFTER the base:root+[data-theme="dark"]blocks so the override fires when supported. Brief specifies the insertion location. (Risk: low; mitigated by exact-insertion-point instruction.)docs/DESIGN_TOKENS.mdpath referenced fromAGENTS.mdbefore the file exists. Brief is atomic — all three files commit together. (Risk: nil with atomic commit.)- Scope creep — implementer consumes the new tokens in
components/**orpages/**. This brief is foundation-only; consumption begins in sub-convoy #2. Brief explicitly forbids.jschanges; pre-PR verification command grep'd in the brief surfaces any leak. (Risk: low; gated by explicit anti-scope + verification.) - No-go zone violation.
styles/globals.cssis not a no-go zone;AGENTS.mdis the canonical agent contract (editable through documented sections);docs/is new content. ✅ All in scope. - Token-name collision. None — audit verified all 29 proposed
names are unique against the current
styles/globals.cssnamespace. (Risk: nil.) - Browser-fallback misfire on Safari 18+. Safari 18+ supports
backdrop-filterunprefixed;@supports not (...)will NOT fire, so the fallback alphas remain unused — the canonical 0.55 / 0.68 / 0.82 surfaces ship as intended. Cross-checked against caniuse 2026- 06-03. (Risk: nil.)
Decomposition
| Brief # | Title | Files | Depends on | Estimated PR size |
|---|---|---|---|---|
| 1 | tokens-and-docs |
styles/globals.css, docs/DESIGN_TOKENS.md, AGENTS.md |
— | ~110 LOC inserted into styles/globals.css; ~260 LOC docs/DESIGN_TOKENS.md; ~25 LOC AGENTS.md. Total ≈ 395 LOC, all additive. No deletions. |
Single brief; no multitask possible (single implementer, single PR). Comfortably under the 400-LOC architect anti-pattern threshold.
Slice dependencies (multitask-ready)
slice_dependencies:
- brief: 1
depends_on: []
files:
- styles/globals.css
- docs/DESIGN_TOKENS.md
- AGENTS.md
Multitask verdict: no /multitask dispatch — single-brief convoy.
Conductor's serial-implementer path is the right shape here.
Boot-the-brief check (architect verification, completed 2026-06-03)
- Dep set check. Zero new packages added. Zero peer-dep concerns. ✅ Pass.
- Verbatim code shape check.
@supports not ((backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px)))— CSS Conditional Rules Level 3 valid syntax (negation of a compound condition requires the outer parens around the OR group). Cross-checked against MDN's@supportsreference. ✅ Verified.- The 16
rgba(...)triples in the brief — all integer RGB values 0–255, all alpha values 0.0–1.0. ✅ Syntactically valid. - The 3
box-shadowstacks (light + dark--elevation-pronounced,--ember-rim-pronounced) — comma-separated multi-shadow syntax is CSS Backgrounds and Borders Level 3 valid. ✅ Verified. insetkeyword for inner-rim shadows — valid inbox-shadowand standalone (when inbox-shadowlist). ✅ Verified.- The CSS variable inheritance pattern (theme-independent blur/saturate defined only in
:root; theme-dependent surfaces redefined in[data-theme="dark"]) — matches the existing precedent atstyles/globals.csslines 100–154. ✅ Verified.
- Cross-brief commitments check. Single brief; no commitments to downstream briefs in this convoy. (Downstream convoys #2–#8 are separate convoys with their own architect passes.) ✅ N/A.
All boot-the-brief checks pass. No brief revision needed.
Architect notes (mid-implementation guidance)
- The brief explicitly forbids reformatting the verbatim CSS block.
Implementer must copy-paste, not retype. This avoids whitespace
drift on the multi-line
box-shadowstacks. - If the implementer hits any unexpected obstacle (e.g.
Screenshot diffshows a non-trivial rendered diff despite no consumer being added), STOP and surface to the operator. Do not "fix it" by editing components. - The 26-line AGENTS.md insertion is at a specific topological
position (between
## Product vocabularyand## 1. Project overview). UseStrReplaceto target the line-29 boundary precisely; do not use blind append-to-section.