diff --git a/.convoys/migrate-button-input-mobilenav-to-glass-primitive.md b/.convoys/migrate-button-input-mobilenav-to-glass-primitive.md index 5ec467e..841538b 100644 --- a/.convoys/migrate-button-input-mobilenav-to-glass-primitive.md +++ b/.convoys/migrate-button-input-mobilenav-to-glass-primitive.md @@ -1,10 +1,83 @@ --- -status: queued +status: closed classification: server-only-no-actually-just-frontend-styles-cleanup parent_convoy: unify-glass-panel-surfaces blocked_by: [] size: small budget_hours: 2-3 +created: 2026-06-04 +closed: 2026-06-05 +prs: + - 131 # Single-PR convoy — all 3 migrations + CI allowlist reduction +--- + +## Architect ratifications (2026-06-05) + +The seed posed 3 open questions. Ratified decisions: + +**D1 — Button secondary** → New `.btn-glass-secondary` utility class +in `styles/globals.css` (NOT ``). Reason: +`` sets `background` inline via `composedStyle`, which +CSS `:hover` rules cannot override without `!important`. A +purpose-built class with a pure-CSS `:hover` swap (high → mid fill +on the padding-box layer of the gradient-border composition) keeps +the hover semantics clean. The class composes the same high-tint +gradient-border that `.glass-panel-strong` uses. + +**D2 — Input** → New `.glass-input` utility class in +`styles/globals.css` (NOT `` and NOT a +wrapping ``). Reason: ``'s +gradient-border trick requires `border: 1px solid transparent` to +expose the border-box layers. That conflicts with ``'s +conditional error-state `1px solid #dc2626` red border swap. The +new class adopts only the tint + blur layer; the visible 1px +border + focus ring stay in JSX (class-controlled, not inline). + +**D3 — MobileNavigation** → Compose `` (NOT `.page-header-glass`). Reason: the +seed recommended `.page-header-glass` reuse, but that class uses +`var(--glass-surface-high)` (wrong tint — MobileNav uses mid) and +sets a bottom-border separator (wrong for a fixed-bottom-nav +where the bottom edge is the viewport edge). `` is +the better fit AND brings the chrome-tier corner-light bleed that +the parent convoy is unifying across all chrome surfaces. + +## Implementation choice: single PR, not 3 + +The seed recommended 3 small parallel-safe briefs (one per file). +On execution that's the wrong decomposition — D1 and D2 BOTH need +the same `styles/globals.css` to gain new utility classes, so +those 2 changes can't run truly in parallel without merge +conflicts. With the CI billing block still active, each PR also +requires an admin-merge cycle. Shipping all 3 migrations + the +CSS additions + the CI allowlist reduction in a single PR was +faster, simpler to review end-to-end, and the natural shape for a +small (2-3 hour) convoy with tightly-coupled artifacts. + +## Closeout + +All acceptance criteria from the seed met: + +- [x] `Button.js` secondary variant uses `.btn-glass-secondary`. +- [x] `Input.js` outer wrapper uses `.glass-input`. +- [x] `MobileNavigation.js` bottom-nav backdrop uses ``. +- [x] `grep -lE "var\(--glass-surface-(low|mid|high)\)" pages + components -r --include='*.js'` returns **only Layout.js + + TopSearchBar.js** (GlassSurface.js uses a template-literal `${tint}` + that doesn't match the static regex — intentional). +- [x] `.github/workflows/ci.yml`'s `GLASS_ALLOWLIST` reduced from + 6 entries to 3 (the 3 chrome blocks). +- [x] `npm run lint` passes (1 pre-existing unrelated warning). +- [x] `npm run test:run`: 118/118 tests pass. +- [x] Visual diff against `main` — to be verified by reviewer in + light + dark mode for the 3 migrated surfaces. + +The parent convoy `unify-glass-panel-surfaces` is now FULLY closed +— no residual handrolled glass-surface usage outside the 3 chrome +blocks, and the gate (Check 7/7 of `forbidden-patterns`) enforces +that contract going forward. + --- # migrate-button-input-mobilenav-to-glass-primitive diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99c168f..fc6a556 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -320,29 +320,16 @@ jobs: # - components/ui/GlassSurface.js # the primitive itself # - components/Layout.js # sidebar nav-chip chrome (L850 area) # - components/ui/TopSearchBar.js #
chrome - # --- Pending migrations (queued follow-up convoy) --- - # TODO: migrate-button-input-mobilenav-to-glass-primitive convoy. - # The 3 entries below currently handroll their own glass - # surfaces — they should compose (cornerLights - # default 'subtle' from Brief 1) or the appropriate - # .glass-panel-* class. They are allowlisted so this gate can - # ship NOW and prevent future regressions; the cleanup is - # tracked in `.convoys/migrate-button-input-mobilenav-to-glass-primitive.md`. - # When that convoy lands, remove the 3 entries below. - # - components/ui/Button.js # secondary variant inline + hover:bg-[var(...)] - # - components/ui/Input.js # input fill background - # - components/MobileNavigation.js # bottom-nav background # - # Adding a fourth chrome-tier entry is a design-system decision - # — open a new convoy. Removing an entry from the pending- - # migration tier requires the matching convoy to ship. + # The pending-migration entries (Button / Input / + # MobileNavigation) were removed by the + # migrate-button-input-mobilenav-to-glass-primitive convoy + # (2026-06-05). Adding a fourth chrome-tier entry is a + # design-system decision — open a new convoy. GLASS_ALLOWLIST=( "components/ui/GlassSurface.js" "components/Layout.js" "components/ui/TopSearchBar.js" - "components/ui/Button.js" - "components/ui/Input.js" - "components/MobileNavigation.js" ) GLASS_FOUND=() while IFS= read -r file; do diff --git a/components/MobileNavigation.js b/components/MobileNavigation.js index 5ce8772..abd0914 100644 --- a/components/MobileNavigation.js +++ b/components/MobileNavigation.js @@ -1,6 +1,7 @@ import { useRouter } from 'next/router'; import Link from 'next/link'; import { useState } from 'react'; +import { GlassSurface } from './ui'; export default function MobileNavigation({ onMenuOpen }) { const router = useRouter(); @@ -78,16 +79,21 @@ export default function MobileNavigation({ onMenuOpen }) { return (
- {/* Liquid Glass background — mid-tint surface, top rim-light to - define the bar's edge against the page content above. */} -
+ post-migrate-button-input-mobilenav-to-glass-primitive + convoy (2026-06-05) — `rim="subtle"` reproduces the previous + rim-light-inner + rim-light-outer halo; `cornerLights="chrome"` + brings in the corner-light bleed that the convoy is + unifying across all chrome surfaces. */} + {/* Navigation Content */} diff --git a/components/ui/Button.js b/components/ui/Button.js index 774fa56..0085542 100644 --- a/components/ui/Button.js +++ b/components/ui/Button.js @@ -37,16 +37,8 @@ const Button = forwardRef(function Button( }; variantClass = 'font-medium transition-transform duration-200 hover:scale-[1.02] active:scale-[0.98]'; } else if (variant === 'secondary') { - variantStyle = { - background: 'var(--glass-surface-high)', - backdropFilter: - 'blur(var(--glass-blur-low)) saturate(var(--glass-saturate))', - WebkitBackdropFilter: - 'blur(var(--glass-blur-low)) saturate(var(--glass-saturate))', - color: 'var(--text-primary)', - boxShadow: 'var(--rim-light-inner), var(--rim-light-outer)', - }; - variantClass = 'font-medium transition-all duration-200 hover:bg-[var(--glass-surface-mid)]'; + variantStyle = {}; + variantClass = 'btn-glass-secondary font-medium'; } else if (variant === 'danger') { variantStyle = { backgroundColor: '#dc2626', diff --git a/components/ui/Input.js b/components/ui/Input.js index e9c60e5..b4fae0e 100644 --- a/components/ui/Input.js +++ b/components/ui/Input.js @@ -54,7 +54,7 @@ const Input = forwardRef(function Input( aria-invalid={error ? 'true' : undefined} aria-describedby={describedBy.length > 0 ? describedBy.join(' ') : undefined} className={[ - 'w-full rounded-xl px-4 py-3 transition-all duration-200', + 'glass-input w-full rounded-xl px-4 py-3 transition-all duration-200', 'focus:outline-none focus-visible:ring-2', leadingIcon ? 'pl-10' : '', trailingIcon || trailingAction ? 'pr-10' : '', @@ -62,11 +62,6 @@ const Input = forwardRef(function Input( .filter(Boolean) .join(' ')} style={{ - background: 'var(--glass-surface-high)', - backdropFilter: - 'blur(var(--glass-blur-low)) saturate(var(--glass-saturate))', - WebkitBackdropFilter: - 'blur(var(--glass-blur-low)) saturate(var(--glass-saturate))', border: inputBorder, color: 'var(--text-primary)', '--tw-ring-color': error ? '#dc2626' : 'var(--accent-ember)', diff --git a/styles/globals.css b/styles/globals.css index 0b49d92..8828d13 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -859,6 +859,66 @@ body { var(--elevation-pronounced); } +/* Button — secondary (glass) variant. + Adopted by `