refactor(website): substrate migration batch 1 — ui primitives off inline styles - #848
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
blove
enabled auto-merge (squash)
August 29, 2026 19:49
Extends the data-ui/data-mdx hook pattern already shipping in 21 places rather
than introducing CSS Modules; splits the CSS into six files so each batch maps
1:1 to a file and can be reverted alone.
Corrects a count carried through project 1: the real figure is 908 style-prop
sites, not 802. `grep 'style={{'` sees only object literals at the call site and
misses 106 sites that pass a named CSSProperties variable — Button.tsx does
this and was miscounted as already migrated. The 1,188 token-reference figure is
unaffected, having been counted from `tokens.*` directly.
Records the no-visual-harness decision as an explicitly accepted risk, with two
cheap non-harness mitigations: a text-level value-equality check per batch, and
a one-page computed-style spot check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seven migration PRs plus an ESLint guard, each batch mapping 1:1 to a CSS file so it reverts alone. Locks the conventions every batch follows: data-ui hooks for primitives, component-prefixed classes elsewhere, four worked shape archetypes (static literal, variant map, hover handler, reused variable), and the custom-property escape hatch for dynamic values. Planning corrected the spec in four places, each verified rather than assumed: - The @layer components suggestion was backwards. Tailwind v4 layers everything and unlayered author CSS beats all layers - the precedence inline styles had. Migrated rules stay unlayered, like global.css today. - A third substrate: 12 components embed <style> tags interpolating tokens.*. - Satori OG images and the dev-only primitives route are excluded (inline styles are load-bearing there); in-scope is ~877 sites, not 908. - 33 onMouseEnter presentation handlers (23 in Footer) become :hover rules. Ships an advisory value-equality checker and a computed-style snapshot snippet as the agreed non-harness mitigations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Card, Container, Eyebrow, Pill, Section move static presentation from inline style props into unlayered rules in ui.css, keyed off the existing data-ui/data-variant/data-surface hooks (plus new data-padding on Card and data-tight on Section). Also cut-pastes the existing [data-ui="card"] and [data-ui="faq-item"] blocks out of global.css into ui.css unchanged. Zero visual change: every value moved verbatim, token references resolved via CSS_VAR_BY_PATH. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Button, FAQ, LogoMark move static presentation from inline style props (VARIANT_STYLES/SIZE_STYLES records, per-item inline objects) into unlayered rules in ui.css, keyed off data-ui/data-variant/data-size (new data-size on LogoMark, new data-ui="logo-mark-icon" hook). Caller `style` passthrough is preserved on Button and LogoMark. Zero visual change: every value moved verbatim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BrowserFrame, ClipPlayer, TabGroup move static presentation from inline style props into unlayered rules in ui.css. BrowserFrame gains data-elevation plus new data-ui hooks on its title bar/dots/url pill/spacer (transform-rotate and max-width stay inline, genuinely dynamic per-instance values); ClipPlayer and TabGroup get their first data-ui hooks (data-ui="clip-player", data-ui="tab-group"/"tab-group- list"/"tab-group-tab" with data-active). TabGroup's hardcoded `Inter, sans-serif` font stack and `8px` radius are copied verbatim rather than mapped to the (different-valued) --font-inter/--radius-md tokens. Zero visual change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s the token vars layout.tsx loads fonts via next/font with variable names identical to the theme.css font vars, so var(--font-inter) resolves to next/font's stack at runtime, not tokens.typography.fontSans. The batch-1 migration onto the vars changed Button's rendered font (widths shifted ~2.5px, measured by computed-style diff against the pre-migration baseline). Sites whose inline value was the raw stack keep it as a literal; Eyebrow, whose inline value was already 'var(--font-mono)', keeps the var. After this fix the baseline diff is 0 real changes across 143 elements x 26 properties (4 remaining diffs are the hero clip's lazy-mount heights). Recorded as findings §10 and as a locked exception in the migration plan. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
force-pushed
the
blove/style-substrate-batch-1
branch
from
August 29, 2026 19:50
f49247d to
ed81369
Compare
Contributor
blove
added a commit
that referenced
this pull request
Aug 29, 2026
Lands only now that all seven migration batches (#848-#857) are merged, so it never sees legacy code. Flags identifier-keyed members of a style object literal in apps/website/src; the style={{ '--x': value }} escape hatch uses string-literal keys and passes. Excluded: the two Satori OG-image files (inline-only by design - note the [slug] segment must be matched by wildcard, brackets are a glob character class), the dev-only primitives route, and specs. Verified with raw eslint: fires on an identifier-key probe, silent on a custom-property probe, zero hits on the OG file. Baseline is 18 warnings - exactly the documented dynamic-value sites from the batch reports - and 0 errors, so CI (which fails on errors only) stays green. Ships as 'warn'; the escalation to 'error' is a deliberate follow-up one release later. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Batch 1 of the inline-style substrate migration (spec · plan in this PR): the 11
components/uiprimitives move their static presentation from inlinestyleprops intosrc/styles/ui.css, plus the migration infrastructure (six scope files imported byglobal.css, an advisory value-equality checker, a computed-style snapshot snippet). Deletes the unusedpage.module.cssstub.This PR is the pattern-setter — its review calibrates batches 2–7. The conventions it establishes:
@layer): Tailwind v4 layers everything, unlayered author CSS beats all layers — the precedence inline styles had.data-*modifiers:data-variant/data-size(Button),data-padding(Card),data-tight(Section),data-active(TabGroup),data-elevation(BrowserFrame).stylepassthroughs stay, so callers still win.data-uihooks.Verification — zero visual change, measured
Before migrating, a computed-style baseline of the homepage was captured: 143 hooked elements × 26 properties. After migration the diff is 0 real changes (4 diffs remain, all heights of the hero clip's lazy-mounted video — no height property was migrated).
Also: vitest exactly at the pre-existing baseline (
5 failed | 341 passed— PostCard/Differentiator/thanks content drift, untouched), 0 lint errors, production build green.The bug the measurement caught
The first migration pass moved
fontFamily: tokens.typography.fontSansontovar(--font-inter)per the token↔CSS parity map — and the diff showed button widths shifting ~2.5px. Cause:layout.tsxloads fonts via next/font with the same variable names as the token vars, sovar(--font-inter)resolves to next/font's stack at runtime, not the token value. The parity guard is value-true againsttheme.cssbut runtime-false for the three font vars.Fix in the final commit: sites whose inline value was the raw stack keep it as a verbatim literal; only sites already using a
.familyvalue (which is'var(--font-*)') keep the var. Recorded as findings §10 and as a locked exception in the migration plan — batches 2–6 inherit the rule. Unifying fonts onto next/font would be a real (arguably desirable) visual change and belongs to the polish arc as its own decision.Two verbatim oddities preserved on purpose
Inter, sans-serifandborder-radius: 8pxare hardcoded literals that match no token (--radius-mdis 10px). Copied through unchanged with a comment — fixing them is restyling.global.cssfor now; only resting styles moved.🤖 Generated with Claude Code