Brief 7 (final brief) of unify-glass-panel-surfaces convoy. Adds the regression gate that prevents reintroduction of bespoke var(--glass-surface-low|mid|high) inline styles outside the documented allowlist. IMPLEMENTATION DEVIATION FROM BRIEF (DOCUMENTED): Brief 7 was authored before PR #126 (slash-ci-minutes convoy) consolidated the 6 grep-only forbidden-* jobs into a single forbidden-patterns job with sequential ::group:: sections. Adding Brief 7 as a standalone forbidden-bespoke-glass-surface job (the brief's verbatim shape) would partially undo PR #126's checkout amortization win. Instead, this PR adds the check as Check 7/7 inside the existing forbidden-patterns job — semantics, allowlist, and grep pattern are exactly as Brief 7 specifies; only the wrapper changes. Job display name updated: "Forbidden patterns (6 checks)" → "Forbidden patterns (7 checks)". ALLOWLIST EXPANSION (DOCUMENTED): Brief 7's planned 3-entry allowlist (the 3 chrome blocks) turned out to undercount the residual surface area. Three additional files still handroll their own var(--glass-surface-*) inline styles: - components/ui/Button.js (secondary variant) - components/ui/Input.js (input wrapper) - components/MobileNavigation.js (bottom-nav background) Per Brief 7's own note ("If you need to add a fourth allowlist entry, that's a design-system decision — open a new convoy"), the right call is to ship the gate NOW with a 6-entry allowlist (3 chrome + 3 pending-migration) and track the cleanup in a follow-up. This PR therefore also seeds `.convoys/migrate-button-input-mobilenav-to-glass-primitive.md` with the migration plan, open questions for the architect, and acceptance criteria. The 3 pending entries are tagged with inline `# TODO:` comments in ci.yml referencing the follow-up convoy. Local verification (per Brief 7's pre-merge negative test): - Positive test: grep on clean main → 0 violations outside the 6-entry allowlist. ✅ - Negative test: injected a scratch `const scratch = { background: 'var(--glass-surface-low)' }` line at EOF of pages/profile.js; grep correctly flagged it. ✅ - Revert verified: removing the scratch line returns the grep to 0 violations. ✅ The scratch change was NOT committed (per Brief 7's instructions). Convoy closeout: - `.convoys/unify-glass-panel-surfaces.md` status moved from `open` to `closing`; queued_followup field names the new convoy. The convoy lands fully when this PR merges. Verification: - npm run lint passes (1 pre-existing unrelated warning). - npm run test:run: 118/118 tests pass. - YAML parses (python3 yaml.safe_load). Files: - .github/workflows/ci.yml: +~70 lines (Check 7/7 step + final-exit copy edit). - .convoys/migrate-button-input-mobilenav-to-glass-primitive.md: new file, 113 lines. - .convoys/unify-glass-panel-surfaces.md: +2 lines (status + queued_followup fields). Co-authored-by: Cursor <cursoragent@cursor.com>
5.8 KiB
| status | classification | parent_convoy | blocked_by | size | budget_hours |
|---|---|---|---|---|---|
| queued | server-only-no-actually-just-frontend-styles-cleanup | unify-glass-panel-surfaces | small | 2-3 |
migrate-button-input-mobilenav-to-glass-primitive
Why
The unify-glass-panel-surfaces convoy's Brief 7 added a CI gate that
forbids bespoke var(--glass-surface-*) inline-style usage outside a
documented allowlist. When the gate was being added, three files
turned out to still handroll their own glass surfaces and had to be
admitted to the allowlist to ship the gate now:
components/ui/Button.js— thesecondaryvariant carriesstyle={{ background: 'var(--glass-surface-high)', backdropFilter: '...' }}and a Tailwind arbitrary classhover:bg-[var(--glass-surface-mid)].components/ui/Input.js— the input fill isstyle={{ background: 'var(--glass-surface-high)', backdropFilter: '...' }}on the outer wrapper of the input control.components/MobileNavigation.js— the bottom-nav background layer isstyle={{ background: 'var(--glass-surface-mid)', backdropFilter: '...' }}.
The pattern (inline background: var(--glass-surface-X) + inline
backdropFilter) is exactly what the convoy spent six briefs
eliminating elsewhere. These three are the residual.
Goal (1 sentence)
Migrate components/ui/Button.js's secondary variant,
components/ui/Input.js, and components/MobileNavigation.js to
compose <GlassSurface> (with the right cornerLights + blur +
tint props from Brief 1) or the appropriate .glass-panel-* /
.page-header-glass class, then delete the three entries from
the forbidden-bespoke-glass-surface allowlist in
.github/workflows/ci.yml so the gate covers them too.
Files in scope
components/ui/Button.js— thesecondaryvariant block only; leaveprimary,danger,ghost, etc. as-is unless they legitimately need the same migration (they don't today).components/ui/Input.js— the outer wrapper style only.components/MobileNavigation.js— the bottom-nav backdrop layer only..github/workflows/ci.yml— theforbidden-bespoke-glass-surfacecheck (now Check 7/7 of the consolidatedforbidden-patternsjob). Delete the three pending entries fromGLASS_ALLOWLIST, leaving only the 3 chrome blocks.
Open questions for the architect
-
<Button variant="secondary">—<GlassSurface>or class? The button uses a complexbackdropFilter+boxShadowstack matchingglass-panel-strong's look. Composing<GlassSurface tint="high" blur="low" cornerLights="subtle">keeps it tokenized and meanscornerLightsripples in for free. The hover variant (hover:bg-[var(--glass-surface-mid)]) needs a different solution — either ahoverprop on<GlassSurface>, or wrap the hover state in a separate utility class. Recommend pulling the hover into a CSS variable swap on the:hoverpseudo-class of a new utility class (.glass-surface-hover-shiftor similar), authored instyles/globals.css. -
<Input>—<GlassSurface as="div">wrapping the native<input>? That's the most consistent shape, but the current<Input>API takes inline-style props the wrapper would have to forward. Easier alternative: add.glass-inpututility class tostyles/globals.cssmirroring.glass-panel-strong's shape but withborder-radius: 8pxand the input-specific focus ring. -
<MobileNavigation>—.page-header-glass? That class was designed for the desktop top-of-page strip; the bottom-nav has the same "full-bleed translucent chrome" semantics inverted vertically. Either reuse the class (simplest), or introduce a.glass-bottom-navmirror. Recommend reuse since the visual contract is identical aside from vertical anchoring (controlled by the consumer's<div className="fixed bottom-0 ...">).
Acceptance criteria (draft — architect to ratify)
Button.jssecondary variant uses<GlassSurface>or a documented.glass-*class.Input.jsouter wrapper uses<GlassSurface>or a documented.glass-inputclass.MobileNavigation.jsbottom-nav backdrop uses.page-header-glass(or.glass-bottom-navif the architect decides on a mirror).grep -lE "var\(--glass-surface-(low|mid|high)\)" pages components -r --include='*.js'returns only the 3 chrome files (Layout, TopSearchBar, GlassSurface)..github/workflows/ci.yml'sGLASS_ALLOWLISTis reduced from 6 entries to 3.- Visual diff against
mainshows no regression in thesecondarybutton, the<Input>control, or the bottom-nav surface in both themes. npm run lint+npm run test:runboth green.
Out of scope
- Other
Buttonvariants (primary, danger, ghost) — they don't usevar(--glass-surface-*). - The
<GlassSurface>primitive itself — Brief 1 already shipped thecornerLightsprop; this convoy just adopts it in 3 places. - Any other component the grep doesn't currently flag — if a new
file appears in the grep result after this convoy lands, that's
a separate convoy (per the
forbidden-bespoke-glass-surfacegate's own friction principle).
Pre-work the conductor should verify
- Brief 1 (
<GlassSurface>cornerLights prop) has merged. ✅ — PR #123. - Brief 7 (this convoy's parent gate) has merged.
- The 3 target files still contain
var(--glass-surface-*)inline styles (re-run the grep at kickoff).
Notes for future agents
The 3 files are independent — there's no shared abstraction across
them. Recommend treating this as 3 small briefs (one per file) the
architect can dispatch in parallel after deciding the migration
shape per file in the open questions above. If the architect chooses
the "add .glass-input and .glass-bottom-nav mirror utility
classes" path, those style additions belong in a 4th brief that
ships first.