--- convoy: unify-glass-panel-surfaces brief_number: 7 depends_on: [1, 2, 3, 4, 5, 6] files: - .github/workflows/ci.yml --- # 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 `
` block, and the `` primitive itself). ## Files in scope (do not edit anything else) - `.github/workflows/ci.yml` — append a new job after the existing `forbidden-modal-shell-without-primitive` job (around L200) or alongside it in the `forbidden-*` 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-primitive` job** (`.github/workflows/ci.yml` L200-226). Same shape: - Single `runs-on: ubuntu-latest` step that runs a `grep` command. - Collects matches into a bash array. - Iterates and emits `::error file=${f}::` for each match. - Exits non-zero if any match remains. - The grep pattern is bounded to `pages/ components/ -r --include='*.js'`. - **Verbatim job shape** (drop into `ci.yml` at the bottom of the forbidden-* cluster): ```yaml 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 / . 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 #
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 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.js` is later refactored into its own sub-component (e.g. ``), the new file path replaces `components/Layout.js` in 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 on `main`. - **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.yml` contains a new `forbidden-bespoke-glass-surface` job 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.js` and 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.