--- name: unify-glass-panel-surfaces classification: feature success_metric: | 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. skip: - ia status: open created: 2026-06-04 depends_on: - tone-down-card-corner-lights # PR #118 — establishes the subtle token tier - design-sweep-pass # PR #117 — applies .glass-panel broadly --- # 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. **`` 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 `` (and therefore every modal in the app), `` (dashboard stat tiles), and the public landing-page feature/collection cards all delegate to ``, the gap cascades broadly. Upgrading the primitive fixes ~10 visible surfaces in one move. 2. **Auth form cards** (`pages/login.js` L82–88, `pages/signup.js` L227–232) — 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 (L698–709) - `components/Layout.js` sidebar profile dropdown (L88–96) - `components/ui/TopSearchBar.js` UserMenu dropdown (L214–222) 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 consumers** — `pages/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` L61–72) — 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 `` primitive Add the 4-layer gradient-border pattern to the `` 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: - `` → corner lights on every modal - `` → corner lights on dashboard stat tiles - Landing feature cards (`pages/index.js` L132–224, L278–345) **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` L82–88 + `pages/signup.js` L227–232: 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 (L698–709) - `components/Layout.js` sidebar profile dropdown (L88–96) - `components/ui/TopSearchBar.js` UserMenu dropdown (L214–222) 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:** LOW–MED — 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 `
` 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` L61–72: replace the handrolled translucent header with `
`. 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; `` 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 - `` grid-mode (L290–303) — intentionally opaque per AGENTS.md GPU-budget rule. - `` list-mode token cleanup (L183–284) — 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 `` and the `.card` retire-vs-keep decision; writes Briefs 1–7. 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 + `` API spec (the `cornerLights` prop) - [ ] Design-system auditor: confirm subtle-tokens are the right default for the upgraded `` - [ ] Brief 1 — `` 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. **`` 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 `` in its className (no inline `var(--glass-surface-*)` background). 2. `` 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 honoured** — `components/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 `` to any rendering that would pull CardItem into that prohibition. ## Multitask dispatch ```yaml 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.