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