deckhearth/.convoys/unify-glass-panel-surfaces.md
Randall Stillwell 6812c5c101 docs(convoys): fix Brief 3 TopSearchBar UserMenu elevation token
Self-review recheck against current TopSearchBar.js (L211-222) found
that the UserMenu dropdown uses var(--elevation-pronounced), not
var(--elevation-ambient) as Brief 3's initial sample showed. Brief 3
now documents the verbatim shape (pronounced + role="menu" +
aria-label + overflow-hidden) and the convoy's risk-list note is
updated to reflect all three popovers' verbatim chains.

No semantic change to the plan; just an accuracy fix the
implementer of Brief 3 needs to ship the right code on the first try.

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

34 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.

Architecture

Run date: 2026-06-04. Architect: role-architect (this convoy). Reads: this convoy file, AGENTS.md, .cursor/rules/*.mdc, components/ui/GlassSurface.js, components/ui/Modal.js, components/ui/StatCard.js, components/Layout.js, components/ui/TopSearchBar.js, components/BulkSelectionToolbar.js, pages/login.js, pages/signup.js, pages/index.js, pages/profile.js, pages/settings.js, pages/community/collections.js, components/CollectionsPageView.js, styles/globals.css (.card, .glass-panel, .glass-panel-strong, .page-header-glass, the --corner-light-*-subtle tokens added in PR #118, the --bg-secondary-rgb token used by today's auth cards), .github/workflows/ci.yml, test/components/ui-primitives.test.js, test/components/Layout.test.js.

Decisions ratified

The convoy file's four open Decisions are resolved as follows. Implementer briefs cite these and MUST NOT renegotiate them mid-flight (if a brief discovers a reason to revisit, escalate via the mid-convoy scope-expansion process in role-architect.md).

D1. <GlassSurface> cornerLights prop shape. Add cornerLights = 'subtle' | 'chrome' | 'none', default 'subtle'.

  • 'subtle' → applies the 4-layer gradient with --corner-light-warm-subtle / --corner-light-cool-subtle (matches what .glass-panel-strong ships today; PR #118).
  • 'chrome' → applies the 4-layer gradient with full-intensity --corner-light-warm / --corner-light-cool. Used by the Layout sidebar nav-chip and TopSearchBar header — those keep their inline styles for now; this prop value exists so future floating chrome doesn't have to re-handroll the gradient.
  • 'none' → no transparent border, no radials, no --chip-border-base layer. Identical to today's primitive output. Used for the CardItem grid path and any other GPU-budget-constrained tile that legitimately must skip the gradient-border treatment.

The gradient-border technique is the verbatim 4-layer recipe from .glass-panel-strong (styles/globals.css post-PR-#118): a padding-box solid linear-gradient of the fill, two border-box radial gradients for the corner catch-lights (warm 0%↔100%, cool 100%↔0%), and a border-box var(--chip-border-base) base layer. Combined with border: 1px solid transparent so the gradient renders through the border.

D2. .card class retire-or-keep. Retire the class entirely. All 8 consumers migrate to glass-panel rounded-3xl p-{6|4}. The .card block in styles/globals.css is deleted. Rationale: a single glass vocabulary across the app is the convoy's success metric; keeping .card as a documented opaque fallback creates two parallel panel languages for future agents to choose between, which is exactly the kind of design-system bifurcation the convoy was scoped to eliminate. The GPU-budget concern that motivated keeping .card is specific to <CardItem> grid-mode (a non-.card consumer) — none of the 8 .card sites face that constraint (they're profile cards, settings panels, and a single community-page list tile, not a multiplied-per-thumbnail surface).

Aggressive path; risk = one of the 8 sites has a hidden reason to be opaque that the architect didn't catch. Mitigation: Brief 5 includes a "if any consumer breaks visually post-migration, revert that one consumer to inline style={{ background: 'var(--bg-secondary)' }} and document the exception in the brief's post-merge note" escape hatch. That keeps the class deletion in the PR while leaving an explicit per-site fallback. If that escape hatch fires for more than 1 of the 8 consumers, the brief's reviewer is expected to push back and propose holding .card after all.

D3. Mobile-drawer corner-light intensity. Use .glass-panel-strong (subtle tier). The drawer is a ~256px-wide fixed-position surface that covers ~1/3 of a mobile viewport. At full-intensity ('chrome') the corner radials would dominate the drawer's interior nav text; at subtle they read as "elevated panel" without competing with content. The drawer is also a hidden surface most of the time — full intensity buys nothing for the rare moments it's open.

D4. CI gate scope. Gate inline-style usage onlyvar(--glass-surface-(low|mid|high)) appearing inside a JSX style={{ background: ... }} (or backgroundColor:, background: in template-string form) under components/** or pages/**. CSS class definitions in styles/globals.css (where the tokens are LEGITIMATELY chained to compose .glass-panel / .glass-panel-strong / .page-header-glass) are not gated. Allowlist entries: the Layout sidebar nav-chip block (components/Layout.js L858-861) and the TopSearchBar header block (the equivalent inline-style block in components/ui/TopSearchBar.js) — both intentionally retain 'chrome'-tier handrolled gradients and were ratified as such in PR #116. The <GlassSurface> primitive itself is also allowlisted (it sets the background internally; that's its job). Brief 7 ships this gate as a separate forbidden-bespoke-glass-surface job in ci.yml, modeled on the existing forbidden-modal-shell-without-primitive gate's grep-and-allowlist shape.

File plan

File Action Purpose Brief
components/ui/GlassSurface.js modified Add cornerLights prop ('subtle' | 'chrome' | 'none', default 'subtle'); compose 4-layer gradient + border: 1px solid transparent when not 'none' 1
test/components/ui-primitives.test.js modified Add 3 corner-light assertions covering the 3 cornerLights values 1
pages/login.js modified Replace L82-88 inline glass imitation with <div className="glass-panel-strong rounded-2xl p-8"> 2
pages/signup.js modified Replace L227-232 inline glass imitation with <div className="glass-panel-strong rounded-2xl p-8"> 2
components/Layout.js modified Sidebar profile dropdown (L88-96): drop inline background+blur+shadow, add className="glass-panel-strong rounded-xl". Mobile drawer (L698-709): drop inline background+blur+shadow, add className="glass-panel-strong" and keep the slide-in transform classes. Sidebar nav-chip block (L858-861) untouched (chrome tier). 3
components/ui/TopSearchBar.js modified UserMenu dropdown (L214-222): drop inline background+blur+shadow, add className="glass-panel-strong rounded-xl". Header block (chrome tier) untouched. 3
test/components/Layout.test.js modified Add regression-lock assertion: sidebar profile dropdown and mobile drawer carry .glass-panel-strong class. 3
components/BulkSelectionToolbar.js modified L47 toolbar: replace bg-white border-gray-200 with glass-panel-strong rounded-2xl. L117 dropdown: same swap on rounded-lgrounded-xl glass-panel-strong. Sweep interior text-gray-{600,700} to var(--text-primary/secondary); hover:bg-gray-{50,100} to nav-item-hover; text-red-600 hover:bg-red-50 to var(--accent-danger) + ember-tinted hover. 4
pages/profile.js modified 3 .cardglass-panel rounded-3xl p-6 swaps. 5
pages/settings.js modified 2 .cardglass-panel rounded-3xl swaps (one p-4, one p-6). 5
pages/community/collections.js modified 1 .cardglass-panel rounded-3xl swap (preserve group cursor-pointer hover:shadow-lg transition-all). 5
components/CollectionsPageView.js modified 1 .cardglass-panel rounded-3xl swap (preserve hover:shadow-xl transition-all cursor-pointer group). 5
styles/globals.css modified Delete the .card { ... } rule (the class block that lives near L757). No other CSS changes. 5
pages/index.js modified Landing nav bar (L61-72): replace handrolled inline style with <nav className="page-header-glass border-b">. Drop the malformed boxShadow: 'inset 0 1px 0 var(--rim-light-inner)' (the .page-header-glass class already sets the correct inset rim). 6
.github/workflows/ci.yml modified Add forbidden-bespoke-glass-surface job per D4. 7

API surface

N/A. This convoy is pure UI/CSS surface composition. No new routes, no Zod schema additions, no rate-limit changes.

Schema diff

N/A. No DB changes. No migrations/ files added; no scripts/add-*.js follow-up. The schema-map-fresh CI job will not fire on any of these PRs (the conditional in ci.yml L47-52 checks migrations/ / scripts/add-* / scripts/fix-* / scripts/setup-neon-db.js / docs/SCHEMA_MAP.md — none touched).

Test plan

Brief Unit Component Smoke Visual diff Notes
1 3 new assertions in test/components/ui-primitives.test.js covering cornerLights='subtle' (default, expects border: 1px solid transparent in the rendered style attribute), cornerLights='chrome' (expects --corner-light-warm token reference, not -subtle), cornerLights='none' (expects no border declaration, no gradient layers — same shape as today). Existing smoke tests cover Modal open/close; nothing new. Baseline refresh required after Brief 1 lands — Modal/StatCard/landing feature cards all gain corner lights. Plan: queue the refresh as a single Linux-baselined commit immediately after Brief 1 merges, before Briefs 3/4 ship. The most consequential brief; the assertions are the regression lock.
2 None needed (presentational class swap). Existing tests/smoke/auth.spec.js covers login/signup happy path; nothing new. Auth pages diff expected.
3 Regression-lock in test/components/Layout.test.js: render Layout with isMobileMenuOpen={true} (mocked) and assert the drawer container's className includes glass-panel-strong. Render with <UserProfileDropdown> open (mocked) and assert its dropdown container's className includes glass-panel-strong. Existing smoke covers nav clicks. Layout dropdown/drawer visual diff expected.
4 None needed (rare to bulk-select in a smoke test). Expected visual diff on the bulk-select toolbar — but only fires when one of the visual-diff tests/visual/* specs actually triggers bulk-select. Probably won't surface in the diff at all; rely on manual QA + dark-mode side-by-side screenshot for review. The dark-mode invisibility is the regression we're closing; verify manually post-merge.
5 None needed (className swap). 4 pages worth of visual diff expected.
6 None needed. Existing smoke tests/smoke/landing.spec.js covers landing nav rendering. Expected on landing.
7 Negative test documented in the brief's acceptance criteria: an implementer dry-run that adds a scratch style={{ background: 'var(--glass-surface-low)' }} to a components/ file should fail the new forbidden-bespoke-glass-surface job. Verified once by the architect via the boot-the-brief check below; not committed. The grep gate runs in CI; no test code needed.

Reference test files for the brief authors:

  • test/components/ui-primitives.test.js (Button/Input/SearchBar test patterns, jsdom env, cleanup pattern; Brief 1 extends in-file).
  • test/components/Layout.test.js (5 regression-lock assertions established by fix-layout-default-user PR #15; Brief 3 extends).

Risk list

  1. Brief 1 ripple effect (MED). The <GlassSurface> primitive feeds <Modal>, <StatCard>, and 4-5 landing-page feature cards. Adding border: 1px solid transparent to every consumer changes the rendered geometry by 2px in both axes — most consumers don't notice (their padding/gap absorbs it), but any consumer that relied on a 0-border box-model for pixel-exact layout will shift. The <Modal> panel sets maxWidth in pixels and is centered via flex — it's not sensitive. <StatCard> is in a 4-up grid — also not sensitive. The landing feature cards use grid with gap — not sensitive. But the visual-diff baseline refresh is non-negotiable; queue it before Briefs 3 + 4 ship.
  2. Brief 1 + GPU-budget constraint (LOW-MED). AGENTS.md forbids backdrop-filter per CardItem thumbnail. <CardItem> does NOT use <GlassSurface> today, so adding gradient-border to <GlassSurface> doesn't pull it in. The 'none' cornerLights value exists as a defensive escape hatch for any future tile surface that DOES want <GlassSurface> without the gradient layers. Brief 1's brief explicitly forbids changing CardItem anywhere in this convoy.
  3. Brief 3 popovers' z-index + box-shadow chain (LOW-MED). .glass-panel-strong ships with var(--rim-light-inner), var(--elevation-ambient). Today's inline-style on the profile dropdown adds var(--ember-rim-subtle) as a third shadow for ember emphasis; today's mobile drawer uses var(--elevation-pronounced) (heavier elevation than ambient). The brief MUST preserve these via an inline-style override chained onto the class's box-shadow — DON'T just drop them. Verbatim shape documented in Brief 3.
  4. Brief 4 dark-mode regressions (LOW). The bulk-select toolbar is currently bg-white — invisible in dark mode. The migration FIXES that regression but Brief 4's reviewer should capture before/after dark-mode screenshots so the fix is visible in PR review.
  5. Brief 5 .card deletion risk (LOW-MED, conditional). Per D2, all 8 consumers migrate to .glass-panel. If any one of them breaks visually (the architect doesn't expect this; the 8 sites are all "panel with content inside" surfaces), the brief includes an escape hatch to revert that one site to inline style={{ background: 'var(--bg-secondary)' }} rather than holding the whole class deletion. If MORE than 1 of 8 needs the escape hatch, the brief's reviewer is expected to push back and propose keeping .card.
  6. Brief 6 .page-header-glass already-applied check (LOW). The implementer must verify pages/index.js's nav bar isn't already inside another .page-header-glass ancestor (it isn't — landing has no shared Layout); otherwise nested backdrop-filters would compound.
  7. Brief 7 false-positive risk (LOW). The grep gate must correctly distinguish JSX inline-style usage of var(--glass-surface-*) from CSS class definitions in styles/globals.css. The architect-prescribed approach is to restrict the grep to files under components/** and pages/** (excluding .css), which by construction means JSX inline-style only. Brief 7 includes a verbatim grep command shape and the 2-entry allowlist.
  8. PR #117 + PR #118 not yet merged (LOW, dependency). This convoy's depends_on: frontmatter lists both. If either fails review, the briefs may need to refactor against a different baseline. Recommended: hold Brief 1 dispatch until PR #117 + PR #118 are both squash-merged to main.

Decomposition

Brief # Title Files Depends on LOC est.
1 Upgrade <GlassSurface> primitive with cornerLights prop components/ui/GlassSurface.js, test/components/ui-primitives.test.js ~80
2 Migrate auth form cards to .glass-panel-strong pages/login.js, pages/signup.js ~20
3 Migrate floating popovers to .glass-panel-strong components/Layout.js, components/ui/TopSearchBar.js, test/components/Layout.test.js 1 ~90
4 Migrate BulkSelectionToolbar to .glass-panel-strong + token sweep components/BulkSelectionToolbar.js 1 ~70
5 Retire .card class; migrate 8 consumers to .glass-panel pages/profile.js, pages/settings.js, pages/community/collections.js, components/CollectionsPageView.js, styles/globals.css ~30
6 Migrate landing nav bar to .page-header-glass pages/index.js ~10
7 forbidden-bespoke-glass-surface CI gate .github/workflows/ci.yml 1, 2, 3, 4, 5, 6 ~40

All briefs target <400 LOC; the largest (Brief 3) is ~90 LOC. Files across briefs are disjoint — verified against the slice_dependencies block above. No two parallel writers on the same file.

Parallel dispatch recommended for Briefs 1, 2, 5, 6 (no deps, disjoint files). Briefs 3 + 4 wait for Brief 1 to land (per D1 — they may simplify by adopting the upgraded primitive's cornerLights='subtle' default rather than handrolling). Brief 7 runs LAST so the grep gate doesn't fail the build on in-flight migrations.

Boot-the-brief check

Read-only verification against the post-PR-#117 + post-PR-#118 tree.

  1. Dep-set check. No new npm dependencies added by any brief. The gradient-border recipe is pure CSS; the cornerLights prop is pure React composition; the CI gate is a shell grep invocation in a YAML job. No pnpm view / package.json audit needed.
  2. Verbatim code-shape check.
    • Brief 1's 4-layer recipe matches .glass-panel-strong's current shape verbatim — verified against styles/globals.css post-PR #118 (the background: block uses linear-gradient(var(--glass-surface-high), ...) padding-box, radial-gradient(at 0% 100%, var(--corner-light-warm-subtle) ...) border-box, radial-gradient(at 100% 0%, var(--corner-light-cool-subtle) ...) border-box, var(--chip-border-base) border-box). The primitive's 'chrome' value uses the non--subtle tokens; both pairs exist in :root + [data-theme="dark"] (verified).
    • Brief 3's box-shadow preservation: the sidebar profile dropdown ships var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-ambient); the mobile drawer ships var(--rim-light-inner), var(--rim-light-outer), var(--elevation-pronounced); the TopSearchBar UserMenu dropdown ships var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-pronounced) (verified during boot-the-brief recheck — uses -pronounced, not -ambient, because it floats higher in the viewport). All three are preserved as inline style={{ boxShadow: '...' }} overrides on the .glass-panel-strong element (the class's default var(--rim-light-inner), var(--elevation-ambient) would otherwise lose the ember-rim and either heavier elevation). Brief 3 documents the verbatim chain.
    • Brief 5's class deletion: the .card block in styles/globals.css is a single rule; deleting it is a clean diff (verified by reading L750-L770 area — no other .card compound selectors exist).
    • Brief 7's grep command shape: modeled on the existing forbidden-modal-shell-without-primitive job (ci.yml L200-L226), which uses grep -lE 'pattern' pages components -r --include='*.js'. Same shape works here. Allowlist is implemented via ! -path 'components/ui/GlassSurface.js' and an explicit line-range exclusion for components/Layout.js and components/ui/TopSearchBar.js (the chrome blocks).
  3. Cross-brief commitments. None. Each brief lands a complete migration of its scope; no brief introduces a stub or forward declaration that another brief resolves. Brief 7's grep gate passes ONLY AFTER Briefs 1-6 land — that's a dispatch ordering constraint (encoded in depends_on:), not a cross-brief commitment in the role-architect sense.

All checks passed. No briefs need revision.