deckhearth/.convoys/unify-glass-panel-surfaces/brief-1-upgrade-glass-surface-primitive.md

83 lines
5.9 KiB
Markdown
Raw Normal View History

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>
2026-06-04 14:45:32 -04:00
---
convoy: unify-glass-panel-surfaces
brief_number: 1
depends_on: []
files:
- 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`):
```js
// 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 <CardItem>-like surface) can opt out cleanly.