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>
6.8 KiB
| convoy | brief_number | depends_on | files | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| unify-glass-panel-surfaces | 7 |
|
|
Brief 7: forbidden-bespoke-glass-surface CI gate
Goal (1 sentence)
Add a forbidden-bespoke-glass-surface job to .github/workflows/ci.yml that fails the build if var(--glass-surface-(low|mid|high)) appears in JSX inline-style usage under components/** or pages/**, with a curated allowlist for the documented chrome exceptions (Layout sidebar nav-chip block, TopSearchBar <header> block, and the <GlassSurface> primitive itself).
Files in scope (do not edit anything else)
.github/workflows/ci.yml— append a new job after the existingforbidden-modal-shell-without-primitivejob (around L200) or alongside it in theforbidden-*cluster. Don't reorder existing jobs.
Out of scope: every other file in the repo. This brief is pure CI surface; no application code changes.
Conventions to follow
-
Model after the existing
forbidden-modal-shell-without-primitivejob (.github/workflows/ci.ymlL200-226). Same shape:- Single
runs-on: ubuntu-lateststep that runs agrepcommand. - Collects matches into a bash array.
- Iterates and emits
::error file=${f}::<reason>for each match. - Exits non-zero if any match remains.
- The grep pattern is bounded to
pages/ components/ -r --include='*.js'.
- Single
-
Verbatim job shape (drop into
ci.ymlat the bottom of the forbidden-* cluster):forbidden-bespoke-glass-surface: name: No bespoke var(--glass-surface-*) inline styles runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Fail if JSX inline styles handroll glass surfaces run: | # unify-glass-panel-surfaces convoy (PR sequence after #118 + # #117). Once Briefs 1-6 land, every panel-shaped surface in # the app composes via .glass-panel / .glass-panel-strong / # .page-header-glass / <GlassSurface>. Inline-style usage of # var(--glass-surface-low|mid|high) under pages/ or # components/ JSX is the regression vector this gate prevents. # # Allowlist (explicit, documented exceptions): # - components/ui/GlassSurface.js # the primitive itself # - components/Layout.js # sidebar nav-chip chrome block (L858 area) # - components/ui/TopSearchBar.js # <header> chrome block # These three files intentionally compose handrolled chrome # surfaces ratified in PR #116 (gradient borders) + the # unify-glass-panel-surfaces architect decision D4. If you # need to add a fourth allowlist entry, that's a design-system # decision — open a new convoy. ALLOWLIST=( "components/ui/GlassSurface.js" "components/Layout.js" "components/ui/TopSearchBar.js" ) FOUND=() while IFS= read -r file; do skip=false for allowed in "${ALLOWLIST[@]}"; do if [ "$file" = "$allowed" ]; then skip=true break fi done if [ "$skip" = true ]; then continue fi FOUND+=("$file") done < <(grep -lE "var\\(--glass-surface-(low|mid|high)\\)" \ pages components -r --include='*.js' 2>/dev/null \ | sort -u || true) if [ ${#FOUND[@]} -gt 0 ]; then echo "::error::Bespoke var(--glass-surface-*) inline styles detected outside the documented allowlist." echo "Use .glass-panel / .glass-panel-strong / .page-header-glass or compose <GlassSurface> from components/ui/ instead." for f in "${FOUND[@]}"; do echo "::error file=${f}::Replace inline var(--glass-surface-*) with the appropriate class or primitive." done exit 1 fi echo "OK: no bespoke glass-surface inline styles outside the allowlist." -
Allowlist hygiene: the 3 allowlist entries are exact paths. If the relevant chrome block in
components/Layout.jsis later refactored into its own sub-component (e.g.<SidebarNavChip>), the new file path replacescomponents/Layout.jsin the allowlist — that's the kind of edit a future PR would carry. -
Pre-merge negative test: before opening this PR, run the allowlist locally — verify that adding a scratch
style={{ background: 'var(--glass-surface-low)' }}to a non-allowlisted file (e.g.pages/profile.js) and re-running the grep produces a match. Then revert the scratch change. Document the negative test in the PR description (don't commit the scratch change). -
CI ordering: Brief 7 depends on Briefs 1-6 ALL landing first. If this job is added before any of the prior briefs ships, the build will fail on the in-flight migrations (every site this convoy is migrating IS currently a bespoke
var(--glass-surface-*)inline-style usage). Conductor MUST hold dispatch until 1-6 are all onmain. -
Boundaries: do NOT touch any other CI job. Do NOT edit any application file. Do NOT update AGENTS.md or other docs in this PR (a separate AGENTS.md update can land alongside Brief 1 if the architect wants — Brief 7 is pure CI).
Acceptance criteria
.github/workflows/ci.ymlcontains a newforbidden-bespoke-glass-surfacejob matching the verbatim shape above (job name,runs-on, checkout step, grep-and-allowlist shell block).- The allowlist has exactly 3 entries:
components/ui/GlassSurface.js,components/Layout.js,components/ui/TopSearchBar.js. - On a clean post-Briefs-1-through-6
main, the new CI job is GREEN — no false positives. (Verify by running the grep locally before opening the PR.) - Negative test: adding a scratch
style={{ background: 'var(--glass-surface-low)' }}to e.g.pages/profile.jsand re-running the same grep command produces a match. (Documented in PR description; not committed.) - No other CI jobs are reordered, renamed, or modified.
- PR description links back to this brief and to the convoy file (
.convoys/unify-glass-panel-surfaces.md).
Rationale (≤3 sentences)
The convoy's success metric is unification, but unification without a regression gate is half a fix — a future PR can re-introduce a bespoke var(--glass-surface-*) inline style and undo the work. Modeling the gate on the existing forbidden-modal-shell-without-primitive job keeps it consistent with the repo's CI vocabulary and makes the failure message actionable. The 3-entry allowlist is small and intentional; growing it requires an explicit design-system decision, which is the right friction for a convention-enforcing gate.