deckhearth/.convoys/migrate-button-input-mobilenav-to-glass-primitive.md
Randall Stillwell e3ba58139d ci(forbidden-patterns): add Check 7/7 — no bespoke var(--glass-surface-*)
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>
2026-06-04 16:43:50 -05:00

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 — the secondary variant carries style={{ background: 'var(--glass-surface-high)', backdropFilter: '...' }} and a Tailwind arbitrary class hover:bg-[var(--glass-surface-mid)].
  • components/ui/Input.js — the input fill is style={{ background: 'var(--glass-surface-high)', backdropFilter: '...' }} on the outer wrapper of the input control.
  • components/MobileNavigation.js — the bottom-nav background layer is style={{ 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 — the secondary variant block only; leave primary, 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 — the forbidden-bespoke-glass-surface check (now Check 7/7 of the consolidated forbidden-patterns job). Delete the three pending entries from GLASS_ALLOWLIST, leaving only the 3 chrome blocks.

Open questions for the architect

  1. <Button variant="secondary"><GlassSurface> or class? The button uses a complex backdropFilter + boxShadow stack matching glass-panel-strong's look. Composing <GlassSurface tint="high" blur="low" cornerLights="subtle"> keeps it tokenized and means cornerLights ripples in for free. The hover variant (hover:bg-[var(--glass-surface-mid)]) needs a different solution — either a hover prop on <GlassSurface>, or wrap the hover state in a separate utility class. Recommend pulling the hover into a CSS variable swap on the :hover pseudo-class of a new utility class (.glass-surface-hover-shift or similar), authored in styles/globals.css.

  2. <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-input utility class to styles/globals.css mirroring .glass-panel-strong's shape but with border-radius: 8px and the input-specific focus ring.

  3. <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-nav mirror. 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.js secondary variant uses <GlassSurface> or a documented .glass-* class.
  • Input.js outer wrapper uses <GlassSurface> or a documented .glass-input class.
  • MobileNavigation.js bottom-nav backdrop uses .page-header-glass (or .glass-bottom-nav if 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's GLASS_ALLOWLIST is reduced from 6 entries to 3.
  • Visual diff against main shows no regression in the secondary button, the <Input> control, or the bottom-nav surface in both themes.
  • npm run lint + npm run test:run both green.

Out of scope

  • Other Button variants (primary, danger, ghost) — they don't use var(--glass-surface-*).
  • The <GlassSurface> primitive itself — Brief 1 already shipped the cornerLights prop; 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-surface gate'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.