deckhearth/.convoys/unify-glass-panel-surfaces.md
Randall Stillwell b195395b82 docs(convoys): seed unify-glass-panel-surfaces + cleanup-card-item-list-and-share-modal-palette
Two convoy seeds opened as follow-ups to the 2026-06-04 design pass
(#116 corner-border-light → #117 site-wide sweep → #118 card vibrancy
reduction). Both were called out in #117's PR body as deferred and are
now formally tracked.

## unify-glass-panel-surfaces

Migrates remaining panel-shaped surfaces to the gradient-border
corner-light treatment so the app shares one surface vocabulary.

The audit's key insight: `<GlassSurface>` (`components/ui/GlassSurface.js`)
predates the corner-light pattern. Because `<Modal>`, `<StatCard>`,
and the landing-page feature/collection cards all delegate to it,
upgrading the primitive cascades to ~10 visible surfaces at once.

7 briefs, multitask-parallel after Brief 1 lands:

1. `<GlassSurface>` primitive upgrade — BLOCKING for 3, 4
2. Auth form cards (login.js, signup.js)
3. Floating popovers (mobile drawer, sidebar profile dropdown,
   UserMenu dropdown)
4. BulkSelectionToolbar (currently `bg-white border-gray-200` —
   invisible in dark mode)
5. `.card`-class consumers (4 pages); decision to ratify whether
   to retire `.card` entirely or keep as documented opaque fallback
6. Landing nav bar — wrong pattern; should use existing
   `.page-header-glass` class
7. `forbidden-bespoke-glass-surface` CI grep gate — prevents
   regression after the migration ships

## cleanup-card-item-list-and-share-modal-palette

Targeted palette cleanup for two files whose interiors weren't
addressed in #117:

1. `CardItem.js` list-mode (L183–284) — entirely hardcoded
   Tailwind palette (`bg-purple-50`, `border-gray-200`, `text-gray-{500-900}`,
   `bg-blue-100 text-blue-800` etc.); unreadable / off-brand in dark mode.
2. `ShareModal.js` interior rows — purple avatar circles, gray-50
   permission row (invisible in dark mode), blue-600 Copy-link button,
   gray text labels.

Token-only swap. 2 parallel briefs, no architect / IA / UX needed
(no design decisions — palette to design tokens).

## Sequencing note

The two convoys are independent and can run in parallel. The audit
agent's recommended sequencing (Brief 1 of `unify-glass-panel-surfaces`
first) is encoded in the multitask `slice_dependencies` blocks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 14:06:53 -05:00

15 KiB
Raw Blame History

name classification success_metric skip status created depends_on
unify-glass-panel-surfaces feature Every panel-shaped surface in the app (modals, popovers, form cards, dashboard widgets, page-level content cards) renders with the same gradient-border corner-light treatment that the floating chrome chips use — at the appropriate intensity tier for its surface class (full intensity for chrome, subtle for data cards, flat for opaque GPU-budget-constrained tiles like CardItem grid). Verified by visual diff + a forbidden grep gate that prevents reintroduction of bespoke `var(--glass-surface-*)` inline styles outside the documented exception list.
ia
open 2026-06-04
tone-down-card-corner-lights
design-sweep-pass

Convoy: unify-glass-panel-surfaces

Follow-on to the redesign-v2-from-mockups umbrella and the 2026-06-04 corner-border-light refinement (PR #116) + design-sweep (PR #117) + card-vibrancy reduction (PR #118).

Why

The gradient-border corner-light pattern (introduced in PR #116 and applied broadly in PR #117) is now the canonical surface treatment for the app. But the audit run on 2026-06-04 found that several panel-shaped surfaces still use the previous generation's "flat translucent fill" pattern — they predate the corner-light work and were never migrated:

  1. <GlassSurface> primitive — the most consequential gap. components/ui/GlassSurface.js uses a single flat var(--glass-surface-${tint}) background with no transparent border and no corner radials. Because <Modal> (and therefore every modal in the app), <StatCard> (dashboard stat tiles), and the public landing-page feature/collection cards all delegate to <GlassSurface>, the gap cascades broadly. Upgrading the primitive fixes ~10 visible surfaces in one move.

  2. Auth form cards (pages/login.js L8288, pages/signup.js L227232) — handrolled glass imitation using rgba(--bg-secondary-rgb, 0.85) + backdrop-blur-sm (Tailwind, not the system blur tokens). These are the first surfaces a new user sees; they should be canonical, not handrolled.

  3. Floating popovers / drawers

    • components/Layout.js mobile drawer (L698709)
    • components/Layout.js sidebar profile dropdown (L8896)
    • components/ui/TopSearchBar.js UserMenu dropdown (L214222)

    All three render a translucent panel over arbitrary page content. None has the gradient-border treatment. The visual cue that says "this is an elevated surface" relies entirely on box-shadow, not on light response.

  4. BulkSelectionToolbar (L47 and L117 dropdown) — uses hardcoded bg-white border-gray-200, invisible in dark mode. This is a floating toolbar over all content during bulk-select; should be .glass-panel-strong.

  5. .card-class consumerspages/profile.js (×3), pages/settings.js (×2), pages/community/collections.js, components/CollectionsPageView.js. The .card class (styles/globals.css L757) is a pre-redesign opaque solid-fill panel. Most consumers should migrate to .glass-panel; the class itself can either be retired or retained as a documented "opaque fallback" for special cases.

  6. Landing nav bar (pages/index.js L6172) — uses var(--glass-surface-mid) flat + an incorrectly double-wrapped inset 0 1px 0 boxShadow. Not a gradient-border candidate (full-bleed bar; corner lights would be at viewport edges, not visible). Should use the existing .page-header-glass class instead.

Unifying these means the entire app shares one surface vocabulary. Future work — new modals, new dashboards, new public pages — picks up the gradient-border treatment automatically because the primitives are correct.

Scope

In scope

Brief 1 — Upgrade <GlassSurface> primitive

Add the 4-layer gradient-border pattern to the <GlassSurface> component so its tint, blur, rim, elevation props compose with corner catch-lights. Default to the subtle corner-light tokens (--corner-light-{warm,cool}-subtle) since most consumers are data cards. Add a cornerLights="chrome" | "subtle" | "none" prop so floating chrome can opt up and special opaque tiles (CardItem grid mode) can opt out.

Consumers automatically upgraded:

  • <Modal> → corner lights on every modal
  • <StatCard> → corner lights on dashboard stat tiles
  • Landing feature cards (pages/index.js L132224, L278345)

Files: components/ui/GlassSurface.js, test/components/ui-primitives.test.js (add a corner-light assertion).

Risk: MED — the primitive's output structure changes (adds border: 1px solid transparent). Most consumers won't notice, but any consumer that set a custom border via style override could conflict. Audit needed.

Brief 2 — Auth form cards

pages/login.js L8288 + pages/signup.js L227232: replace the inline className="p-8 rounded-2xl shadow-2xl backdrop-blur-sm border border-opacity-20" style={{ backgroundColor: 'rgba(...)' }} shape with className="glass-panel-strong rounded-2xl p-8".

Files: pages/login.js, pages/signup.js. Risk: LOW.

Brief 3 — Floating popovers / drawers

Three independent floating surfaces:

  • components/Layout.js mobile drawer (L698709)
  • components/Layout.js sidebar profile dropdown (L8896)
  • components/ui/TopSearchBar.js UserMenu dropdown (L214222)

For each: drop the inline background: var(--glass-surface-*) + backdropFilter pair, add className="glass-panel-strong rounded-2xl" or rounded-xl to match existing dimensions. Preserve any additional boxShadow: 'var(--ember-rim-subtle)' adornments by chaining them onto the class's existing box-shadow (via inline style override).

Special handling for mobile drawer: the drawer takes ~1/3 of the viewport. At full corner-light intensity it would be noisy. Use .glass-panel-strong (which uses the subtle tokens).

Files: components/Layout.js, components/ui/TopSearchBar.js, test/components/Layout.test.js (regression assertions). Risk: LOWMED — popovers appear over arbitrary page content.

Brief 4 — BulkSelectionToolbar

Replace bg-white border-gray-200 on the toolbar (L47) and its "More actions" dropdown (L117) with .glass-panel-strong rounded-2xl. Sweep interior text-gray-700 hover:bg-gray-100, text-red-600 hover:bg-red-50 to use var(--text-primary) / nav-item-hover and tokenized red for destructive actions.

Files: components/BulkSelectionToolbar.js. Risk: MED — floats over all content during bulk-select mode; shadow/overflow bleed would be very visible.

Brief 5 — .card-class consumers

Audit every <div className="card"> usage. For each, pick the right migration:

  • Most likely: glass-panel rounded-3xl p-6 (preserves rounded-3xl
    • p-6 from the old class).
  • Some may need to stay opaque (e.g. screenshot-friendly profile card with photo overlay) — keep .card and document the exception in styles/globals.css's comment block.

Decision to ratify: retire .card entirely, OR keep as a documented opaque-panel alternative? Architect's call. If kept, add a comment to globals.css explaining when to use which.

Files: pages/profile.js, pages/settings.js, pages/community/collections.js, components/CollectionsPageView.js, optionally styles/globals.css (retire or document). Risk: LOW per page; sweep across 4 files.

Brief 6 — Landing nav bar

pages/index.js L6172: replace the handrolled translucent header with <header className="page-header-glass border-b ...">. The .page-header-glass class already exists in globals.css for exactly this "full-bleed top band" use case. The current bar also has a malformed boxShadow: 'inset 0 1px 0 var(--rim-light-inner)' (the token already contains inset 0 1px 0; double-wrapping breaks the cascade). Remove the malformed shadow.

Files: pages/index.js. Risk: LOW — public page, no auth dependencies.

Brief 7 — Forbidden grep gate

Add a forbidden-bespoke-glass-surface job to .github/workflows/ci.yml that fails the build if var(--glass-surface-(low|mid|high)) appears in JSX inline styles across components/** and pages/**, with a curated allowlist for the documented exceptions (intentional chrome treatment in Layout/TopSearchBar; <GlassSurface> component itself; any opaque-fallback .card consumers ratified in Brief 5).

Prevents regression: future PRs can't reintroduce handrolled glass.

Files: .github/workflows/ci.yml. Risk: LOW.

Out of scope

  • <CardItem> grid-mode (L290303) — intentionally opaque per AGENTS.md GPU-budget rule.
  • <CardItem> list-mode token cleanup (L183284) — covered by the parallel cleanup-card-item-list-and-share-modal-palette convoy.
  • Adding any NEW surfaces or panels.
  • .card rename to .opaque-panel — Brief 5 may retire the class entirely; rename is a separate polish if it survives.

Roles invoked

  1. role-architect — surface-by-surface migration plan; ratifies the cornerLights prop shape for <GlassSurface> and the .card retire-vs-keep decision; writes Briefs 17.
  2. role-design-system-auditor — visual regression review on each brief; confirms no chrome-vs-data-card hierarchy regressions.
  3. role-ux-reviewer — light pass; ensures auth form contrast + mobile drawer legibility hold up post-migration.
  4. role-implementer — one per brief; Briefs 2, 3, 4, 6 are parallel-safe (disjoint files, no shared component dependency chain). Brief 1 MUST land first because Briefs 3, 4 may end up simplifying their inline-style code by using the upgraded primitive instead.
  5. role-reviewer — single post-PR review per brief.
  6. role-a11y-auditor — focus-ring + keyboard nav for new floating surfaces (Brief 3 popovers especially).

Todos

  • Architect: surface-by-surface migration plan + <GlassSurface> API spec (the cornerLights prop)
  • Design-system auditor: confirm subtle-tokens are the right default for the upgraded <GlassSurface>
  • Brief 1 — <GlassSurface> primitive upgrade (BLOCKING for Briefs 3, 4)
  • Brief 2 — auth form cards
  • Brief 3 — floating popovers (drawer, sidebar profile, UserMenu)
  • Brief 4 — BulkSelectionToolbar
  • Brief 5 — .card consumers; Decision: retire or keep
  • Brief 6 — landing nav bar (.page-header-glass)
  • Brief 7 — forbidden-bespoke-glass-surface CI gate
  • Post-PR review per brief
  • Visual-diff baseline refresh after Brief 1 lands

Decisions to ratify

  1. <GlassSurface> API shape post-upgrade. Add cornerLights prop with values "chrome" | "subtle" | "none". Default "subtle". Architect confirms or proposes alternative.
  2. Retire .card class entirely after Brief 5, or keep as documented opaque fallback? Conservative: keep + document ("use when the surface must NOT have backdrop-filter — e.g. inside another modal, screen-reader-critical, or GPU-budget-constrained"). Aggressive: retire and migrate the handful of legitimate opaque cases to inline style.
  3. Mobile drawer corner-light intensity. The drawer is a large surface. Subtle tokens (matching .glass-panel-strong) are likely right, but the architect should sanity-check visually before locking it in.
  4. CI gate scope for forbidden-bespoke-glass-surface. Should it gate ALL var(--glass-surface-*) usage in JSX, or only inline style={{ background: ... }} usage? Recommended: only inline styles, since the tokens still need to be referenceable in styles/globals.css.

Acceptance criteria

  1. Every in-scope surface lists either glass-panel, glass-panel-strong, page-header-glass, or <GlassSurface> in its className (no inline var(--glass-surface-*) background).
  2. <GlassSurface> primitive's output includes the 4-layer gradient-border pattern and a border: 1px solid transparent.
  3. CI's forbidden-bespoke-glass-surface job passes; introducing a new bespoke var(--glass-surface-low) inline-style usage in a scratch commit makes it fail (negative test).
  4. Build + lint + 113/113 vitest + Playwright smoke green.
  5. Visual diff shows the expected differences (corner catch-lights appear on modals, dashboard stat tiles, auth cards, dropdowns) and no unexpected regressions on chrome/cards/CardItem grid.

CI impact

Workflow / job Behavior
preview-smoke.yml Fires per brief.
visual-diff.yml Fires + baseline refresh required after Brief 1 lands (the primitive upgrade ripples through Modal/StatCard/landing).
lint Fires + new forbidden-bespoke-glass-surface gate after Brief 7.
test: (vitest) Fires; Brief 1 adds a corner-light assertion to ui-primitives.test.js.

Known constraints

  • No-go zones honouredcomponents/Layout.js.backup, scripts/add-*.js graveyard untouched.
  • GPU budget for CardItem grid — AGENTS.md explicitly prohibits backdrop-filter per card thumbnail (it multiplies); Brief 1 must NOT default <GlassSurface> to any rendering that would pull CardItem into that prohibition.

Multitask dispatch

slice_dependencies:
  - brief: 1
    depends_on: []
    files:
      - components/ui/GlassSurface.js
      - test/components/ui-primitives.test.js
  - brief: 2
    depends_on: []
    files:
      - pages/login.js
      - pages/signup.js
  - brief: 3
    depends_on: [1]  # may simplify by using upgraded primitive
    files:
      - components/Layout.js
      - components/ui/TopSearchBar.js
      - test/components/Layout.test.js
  - brief: 4
    depends_on: [1]
    files:
      - components/BulkSelectionToolbar.js
  - brief: 5
    depends_on: []
    files:
      - pages/profile.js
      - pages/settings.js
      - pages/community/collections.js
      - components/CollectionsPageView.js
      - styles/globals.css  # if .card is retired / documented
  - brief: 6
    depends_on: []
    files:
      - pages/index.js
  - brief: 7
    depends_on: [1, 2, 3, 4, 5, 6]  # gate goes in LAST
    files:
      - .github/workflows/ci.yml

Briefs 2, 5, 6 can run in parallel with Brief 1. Briefs 3, 4 wait for Brief 1 to land so they can simplify by using the upgraded primitive. Brief 7 runs LAST so the grep gate doesn't fail the build on in-flight migrations.

Out of scope follow-ups (queued)

  • retire-or-formalize-card-class — if Brief 5 Decision 2 keeps .card as opaque fallback, a future small convoy can rename it .opaque-panel for clarity. P3.
  • storybook-adoption-for-glass-surface — once the primitive has the full prop matrix (tint, blur, rim, elevation, cornerLights), it's a natural Storybook candidate. P2 DX.