deckhearth/.convoys/unify-glass-panel-surfaces/brief-7-forbidden-bespoke-glass-surface-ci-gate.md
varutasu 66a4d7b721
docs(convoys): unify-glass-panel-surfaces + cleanup palette — conductor seed + architect plan (#119)
* docs(convoys): seed unify-glass-panel-surfaces + cleanup-card-item-list-and-share-modal-palette

Two convoy seeds opened as follow-ups to the 2026-06-04 design pass
(#116 corner-border-light → #117 site-wide sweep → #118 card vibrancy
reduction). Both were called out in #117's PR body as deferred and are
now formally tracked.

## unify-glass-panel-surfaces

Migrates remaining panel-shaped surfaces to the gradient-border
corner-light treatment so the app shares one surface vocabulary.

The audit's key insight: `<GlassSurface>` (`components/ui/GlassSurface.js`)
predates the corner-light pattern. Because `<Modal>`, `<StatCard>`,
and the landing-page feature/collection cards all delegate to it,
upgrading the primitive cascades to ~10 visible surfaces at once.

7 briefs, multitask-parallel after Brief 1 lands:

1. `<GlassSurface>` primitive upgrade — BLOCKING for 3, 4
2. Auth form cards (login.js, signup.js)
3. Floating popovers (mobile drawer, sidebar profile dropdown,
   UserMenu dropdown)
4. BulkSelectionToolbar (currently `bg-white border-gray-200` —
   invisible in dark mode)
5. `.card`-class consumers (4 pages); decision to ratify whether
   to retire `.card` entirely or keep as documented opaque fallback
6. Landing nav bar — wrong pattern; should use existing
   `.page-header-glass` class
7. `forbidden-bespoke-glass-surface` CI grep gate — prevents
   regression after the migration ships

## cleanup-card-item-list-and-share-modal-palette

Targeted palette cleanup for two files whose interiors weren't
addressed in #117:

1. `CardItem.js` list-mode (L183–284) — entirely hardcoded
   Tailwind palette (`bg-purple-50`, `border-gray-200`, `text-gray-{500-900}`,
   `bg-blue-100 text-blue-800` etc.); unreadable / off-brand in dark mode.
2. `ShareModal.js` interior rows — purple avatar circles, gray-50
   permission row (invisible in dark mode), blue-600 Copy-link button,
   gray text labels.

Token-only swap. 2 parallel briefs, no architect / IA / UX needed
(no design decisions — palette to design tokens).

## Sequencing note

The two convoys are independent and can run in parallel. The audit
agent's recommended sequencing (Brief 1 of `unify-glass-panel-surfaces`
first) is encoded in the multitask `slice_dependencies` blocks.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(convoys): architect plan + 7 briefs for unify-glass-panel-surfaces

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>

* docs(convoys): fix Brief 3 TopSearchBar UserMenu elevation token

Self-review recheck against current TopSearchBar.js (L211-222) found
that the UserMenu dropdown uses var(--elevation-pronounced), not
var(--elevation-ambient) as Brief 3's initial sample showed. Brief 3
now documents the verbatim shape (pronounced + role="menu" +
aria-label + overflow-hidden) and the convoy's risk-list note is
updated to reflect all three popovers' verbatim chains.

No semantic change to the plan; just an accuracy fix the
implementer of Brief 3 needs to ship the right code on the first try.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 14:09:40 -05:00

6.8 KiB

convoy brief_number depends_on files
unify-glass-panel-surfaces 7
1
2
3
4
5
6
.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 <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 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}::<reason> 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):

      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.js is later refactored into its own sub-component (e.g. <SidebarNavChip>), 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.