Skip to content

chore(website): lint guard against new static inline styles - #858

Merged
blove merged 1 commit into
mainfrom
blove/style-substrate-lint-guard
Aug 29, 2026
Merged

chore(website): lint guard against new static inline styles#858
blove merged 1 commit into
mainfrom
blove/style-substrate-lint-guard

Conversation

@blove

@blove blove commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

What

The final PR of the substrate migration (plan, Task 7): an ESLint guard so new code cannot quietly reintroduce static inline styles. Deliberately last — landing it before batch 6b would have meant hundreds of suppressions; landing it now, it only ever sees new code.

no-restricted-syntax (warn) on identifier-keyed members of a style object literal in apps/website/src/**/*.tsx. The dynamic-value escape hatch — style={{ '--x': value }} — uses string-literal keys and passes untouched.

Verified with raw eslint (nx's lint cache can serve stale results on rapid re-runs)

  • Identifier-key probe (style={{ color: "red" }}) → fires.
  • Custom-property probe (style={{ '--x': v }}) → silent.
  • Satori OG file → zero hits (note: [slug] in an ignore glob is a character class — matched by wildcard instead).
  • Baseline: 18 warnings, 0 errors — the 18 are exactly the documented dynamic-value sites from the batch reports (per-page accent props, unbounded size props, TIERS-driven grid templates, the toast's entrance animation). The warn list is the exception ledger.

Escalation to error is a deliberate follow-up one release later, per the plan.

The arc this closes

Project 2 is complete: ~877 style-prop sites and 12 embedded <style> tags across 88 files migrated in 7 PRs (#848#857), each verified element-by-element against production. Project 3 (the docs polish arc, from the findings audit) is now unblocked — every fix it needs (:hover, :focus-visible, :last-child, media queries) has a stylesheet to live in.

🤖 Generated with Claude Code

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>
@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview Aug 29, 2026 11:27pm

Request Review

@blove
blove enabled auto-merge (squash) August 29, 2026 23:24

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove merged commit ca08bf3 into main Aug 29, 2026
60 of 61 checks passed
blove added a commit that referenced this pull request Aug 30, 2026
#866)

* chore(website): delete the dev-only primitives route

Its own FAQ said 'this route gets deleted once the marketing pages have
migrated' - the substrate migration (#848-#858) finished that. Removes the
route, its e2e spec, and its lint-guard exemption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(website): escalate the inline-style guard to error, at zero suppressions

Second commit of the post-arc follow-ups. Rather than the planned 18
eslint-disable comments, every remaining dynamic-value site converts to the
rule's own patterns, so the guard enforces with no exceptions ledger at all:

- GlyphChip size, CardGroup columns, CompareTable's tier-driven CTA grid, the
  solutions pages' per-page accent, and BrowserFrame's rotate/maxWidth all
  become custom-property escape hatches with CSS consumers.
- AnnouncementToast's entrance animation becomes a data-mounted state pair.

Two cascade traps caught by prod-signature verification during the swap:
- The accent rule needed [data-ui="eyebrow"][data-accent-text] to tie the
  Eyebrow tone rules' 0-2-0 specificity (the inline color it replaced used to
  outrank everything).
- rotate(var(--x, 0deg)) computes to an identity MATRIX, not `none` - which
  silently creates a stacking context and fixed-position containing block on
  every un-rotated BrowserFrame. The tilt is now gated behind data-rotated.

Verified value-preserving: /pricing, /docs, and /solutions/customer-support
hash-identical to production at 1280 over 30 computed properties (including
transform and min/max-width).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant