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>
15 KiB
| 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. |
|
open | 2026-06-04 |
|
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:
-
<GlassSurface>primitive — the most consequential gap.components/ui/GlassSurface.jsuses a single flatvar(--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. -
Auth form cards (
pages/login.jsL82–88,pages/signup.jsL227–232) — handrolled glass imitation usingrgba(--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. -
Floating popovers / drawers —
components/Layout.jsmobile drawer (L698–709)components/Layout.jssidebar profile dropdown (L88–96)components/ui/TopSearchBar.jsUserMenu 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.
-
BulkSelectionToolbar(L47 and L117 dropdown) — uses hardcodedbg-white border-gray-200, invisible in dark mode. This is a floating toolbar over all content during bulk-select; should be.glass-panel-strong. -
.card-class consumers —pages/profile.js(×3),pages/settings.js(×2),pages/community/collections.js,components/CollectionsPageView.js. The.cardclass (styles/globals.cssL757) 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. -
Landing nav bar (
pages/index.jsL61–72) — usesvar(--glass-surface-mid)flat + an incorrectly double-wrappedinset 0 1px 0boxShadow. Not a gradient-border candidate (full-bleed bar; corner lights would be at viewport edges, not visible). Should use the existing.page-header-glassclass 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.jsL132–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.jsmobile drawer (L698–709)components/Layout.jssidebar profile dropdown (L88–96)components/ui/TopSearchBar.jsUserMenu 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 <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
.cardand document the exception instyles/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 <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 (L290–303) — intentionally opaque per AGENTS.md GPU-budget rule.<CardItem>list-mode token cleanup (L183–284) — covered by the parallelcleanup-card-item-list-and-share-modal-paletteconvoy.- Adding any NEW surfaces or panels.
.cardrename to.opaque-panel— Brief 5 may retire the class entirely; rename is a separate polish if it survives.
Roles invoked
role-architect— surface-by-surface migration plan; ratifies thecornerLightsprop shape for<GlassSurface>and the.cardretire-vs-keep decision; writes Briefs 1–7.role-design-system-auditor— visual regression review on each brief; confirms no chrome-vs-data-card hierarchy regressions.role-ux-reviewer— light pass; ensures auth form contrast + mobile drawer legibility hold up post-migration.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.role-reviewer— single post-PR review per brief.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 (thecornerLightsprop) - 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 —
.cardconsumers; Decision: retire or keep - Brief 6 — landing nav bar (
.page-header-glass) - Brief 7 —
forbidden-bespoke-glass-surfaceCI gate - Post-PR review per brief
- Visual-diff baseline refresh after Brief 1 lands
Decisions to ratify
<GlassSurface>API shape post-upgrade. AddcornerLightsprop with values"chrome" | "subtle" | "none". Default"subtle". Architect confirms or proposes alternative.- Retire
.cardclass 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. - 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. - CI gate scope for
forbidden-bespoke-glass-surface. Should it gate ALLvar(--glass-surface-*)usage in JSX, or only inlinestyle={{ background: ... }}usage? Recommended: only inline styles, since the tokens still need to be referenceable instyles/globals.css.
Acceptance criteria
- Every in-scope surface lists either
glass-panel,glass-panel-strong,page-header-glass, or<GlassSurface>in its className (no inlinevar(--glass-surface-*)background). <GlassSurface>primitive's output includes the 4-layer gradient-border pattern and aborder: 1px solid transparent.- CI's
forbidden-bespoke-glass-surfacejob passes; introducing a new bespokevar(--glass-surface-low)inline-style usage in a scratch commit makes it fail (negative test). - Build + lint + 113/113 vitest + Playwright smoke green.
- 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-*.jsgraveyard untouched. - GPU budget for CardItem grid — AGENTS.md explicitly
prohibits
backdrop-filterper 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.cardas opaque fallback, a future small convoy can rename it.opaque-panelfor 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.