deckhearth/.convoys/unify-glass-panel-surfaces/brief-1-upgrade-glass-surface-primitive.md
varutasu 66a4d7b721
docs(convoys): unify-glass-panel-surfaces + cleanup palette — conductor seed + architect plan (#119)
* 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>

* docs(convoys): architect plan + 7 briefs for unify-glass-panel-surfaces

Appends the ## Architecture section to .convoys/unify-glass-panel-surfaces.md
ratifying the 4 open Decisions, and writes 7 implementer-brief files under
.convoys/unify-glass-panel-surfaces/.

Decisions ratified:
  D1. <GlassSurface> grows a cornerLights='subtle'|'chrome'|'none' prop,
      default 'subtle' (4-layer gradient-border per PR #118 recipe).
  D2. Retire .card entirely; migrate all 8 consumers to .glass-panel.
  D3. Mobile drawer uses subtle tier (.glass-panel-strong).
  D4. CI gate scopes to JSX inline-style usage only; 3-entry allowlist
      for documented chrome exceptions.

Briefs (1-7, all <100 LOC each, disjoint files):
  1. Upgrade <GlassSurface> primitive (no deps, blocks 3+4)
  2. Migrate auth form cards (parallel-safe)
  3. Migrate floating popovers (sidebar dropdown, mobile drawer,
     TopSearchBar UserMenu)
  4. Migrate BulkSelectionToolbar + interior token sweep
  5. Retire .card class; migrate 8 consumers
  6. Migrate landing nav bar to .page-header-glass
  7. forbidden-bespoke-glass-surface CI gate (runs LAST)

Boot-the-brief check: all 4-layer recipes verified against post-PR-#118
styles/globals.css; all box-shadow preservation chains verified against
current Layout.js + TopSearchBar.js; .card deletion verified clean
(single rule at L729-733); CI gate shape modeled on existing
forbidden-modal-shell-without-primitive job.

Architecture is read-only output — no application code touched.
Awaiting human gate 1 (plan approval) before implementers run.

Co-authored-by: Cursor <cursoragent@cursor.com>

* 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>

---------

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

5.9 KiB

convoy brief_number depends_on files
unify-glass-panel-surfaces 1
components/ui/GlassSurface.js
test/components/ui-primitives.test.js

Brief 1: Upgrade <GlassSurface> primitive with cornerLights prop

Goal (1 sentence)

Add a cornerLights = 'subtle' | 'chrome' | 'none' prop to <GlassSurface> and compose the 4-layer gradient-border pattern (padding-box fill + two border-box corner radials + --chip-border-base base) on top of the existing tint / blur / rim / elevation props so every consumer (Modal, StatCard, landing feature cards) picks up corner catch-lights automatically.

Files in scope (do not edit anything else)

  • components/ui/GlassSurface.js — add the prop, compose the 4-layer background and the border: 1px solid transparent when the prop is not 'none'.
  • test/components/ui-primitives.test.js — add 3 corner-light assertions to the existing describe('GlassSurface', ...) block (or add the block if it doesn't exist; today the file covers Button / Input / SearchBar only, so a new top-level describe('GlassSurface', ...) block at the end of the file is correct).

Out of scope: every consumer of <GlassSurface>components/ui/Modal.js, components/ui/StatCard.js, pages/index.js landing cards. Their behavior changes via the new default; no edits required. Do NOT modify them in this PR.

Conventions to follow

  • Verbatim 4-layer recipe (mirrors .glass-panel-strong post-PR #118; see styles/globals.css):
    // When cornerLights === 'subtle' (default):
    background:
      `linear-gradient(var(--glass-surface-${tint}), var(--glass-surface-${tint})) padding-box,
       radial-gradient(at 0% 100%, var(--corner-light-warm-subtle) 0%, transparent 42%) border-box,
       radial-gradient(at 100% 0%, var(--corner-light-cool-subtle) 0%, transparent 42%) border-box,
       var(--chip-border-base) border-box`
    // When cornerLights === 'chrome':
    // Same recipe; swap --corner-light-warm-subtle → --corner-light-warm
    // and --corner-light-cool-subtle → --corner-light-cool.
    // When cornerLights === 'none':
    // Today's behavior — single layer background: var(--glass-surface-${tint}).
    // No border declaration.
    
  • Border declaration: when cornerLights !== 'none', also set border: '1px solid transparent' on the composed style. This is what reveals the border-box gradient layers. When 'none', omit the border entirely (preserve today's box-model).
  • Shadow stack: unchanged. The existing RIM_SHADOWS / ELEVATION_SHADOWS composition logic stays exactly as written. The corner-light layers live in background, not box-shadow.
  • Default value: cornerLights = 'subtle'. This is the new default for every consumer that doesn't pass the prop explicitly.
  • Backdrop filter: unchanged. backdropFilter / WebkitBackdropFilter continue to compose from blur / tint exactly as today. Do not touch this line.
  • style prop merging: today the composed style spreads ...style LAST so caller overrides win. Preserve that exactly — a caller passing style={{ border: '1px solid #f00' }} overrides the transparent border. This is the explicit escape hatch any consumer who needs a real border can use without setting cornerLights='none'.
  • AGENTS.md / no-go zones: styles/globals.css is untouched in this brief (Brief 5 owns the .card deletion; no other CSS edits in this convoy). The --corner-light-warm-subtle / --corner-light-cool-subtle / --corner-light-warm / --corner-light-cool / --chip-border-base tokens all exist in :root and [data-theme="dark"] per PR #118 — verified.

Acceptance criteria

  • <GlassSurface> accepts a cornerLights prop with values 'subtle' (default), 'chrome', 'none'. Any other value falls back to 'subtle' (mirror the existing RIM_SHADOWS[rim] ?? [] defensive fallback shape).
  • When cornerLights='subtle', rendered output includes:
    • background: containing both var(--corner-light-warm-subtle) and var(--corner-light-cool-subtle).
    • border: value 1px solid transparent.
  • When cornerLights='chrome', rendered output includes:
    • background: containing var(--corner-light-warm) and var(--corner-light-cool) (NOT the -subtle variants).
    • border: value 1px solid transparent.
  • When cornerLights='none', rendered output is identical to today: single-layer background: var(--glass-surface-${tint}), no border declaration in the composed style.
  • tint / blur / rim / elevation / as / style / className props all behave exactly as before. Existing consumers continue to compile + render without prop changes.
  • test/components/ui-primitives.test.js has 3 new assertions (one per cornerLights value) verifying the inline style attribute or the rendered element's style.background / style.border accordingly. Use the existing render + screen.getByRole / getByText / container patterns from the file; don't pull in new test deps.
  • npm run lint passes.
  • npm run test:run is 22+/22+ (today's count is 21; this brief adds at least 3 — exact count depends on whether you split assertions across multiple it(...) blocks).
  • No edits to files outside the two listed in files: above. No consumer migrations in this PR. Brief 3 + 4 will compose against the new default; Briefs 2 + 5 + 6 don't use the primitive.

Rationale (≤3 sentences)

The primitive is the single largest leverage point in the convoy — upgrading it ripples through <Modal>, <StatCard>, and the landing-page feature cards in one commit. Defaulting to 'subtle' matches the PR #118 token-tier decision and means downstream consumers don't need to opt in. The 'chrome' + 'none' values exist so the prop is future-proof: floating chrome can opt up without handrolling, and any GPU-constrained tile (e.g. a future -like surface) can opt out cleanly.