--- convoy: unify-glass-panel-surfaces brief_number: 1 depends_on: [] files: - components/ui/GlassSurface.js - test/components/ui-primitives.test.js --- # Brief 1: Upgrade `` primitive with `cornerLights` prop ## Goal (1 sentence) Add a `cornerLights = 'subtle' | 'chrome' | 'none'` prop to `` 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 `` — `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 - [ ] `` 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 ``, ``, 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.