Skip to content

feat(cli): doctor detects CSS theming escapes and swizzled components - #5260

Open
josephfarina wants to merge 1 commit into
docs/agent-theme-guide-linkfrom
feat/doctor-theme-drift
Open

feat(cli): doctor detects CSS theming escapes and swizzled components#5260
josephfarina wants to merge 1 commit into
docs/agent-theme-guide-linkfrom
feat/doctor-theme-drift

Conversation

@josephfarina

Copy link
Copy Markdown
Contributor

Stacked on #5226. Review that one first; this diff is only the two new checks.

Summary

Two read-only, text-only astryx doctor checks for the cases where an app provably steps outside its theme. No modules are imported or evaluated and the scan is bounded, so both are cheap enough for the default run (~250ms added) and safe in CI.

CSS theming escapes covers three cases:

  • A write to a private --_* var. astryx theme build already rejects these in a theme; they are no more valid in raw CSS, so this is a fail to match.
  • A system token redefined in :root/html/:host. A theme applies inside @scope ([data-astryx-theme=...]), so a global definition sits outside it and overrides every theme at once.
  • The deprecated bare prop classes (.astryx-button.primary) in place of the reflected data attributes. The fix names the exact replacement selector.

Swizzled components fails on exactly one combination: ejected source that imports StyleX while no StyleX compiler is configured. That is not a build error — the component renders completely unstyled, with no warning — which makes it the most expensive thing here to find by hand. Otherwise it is informational, noting that swizzled copies stop tracking upstream fixes and no longer respond to theme component overrides.

Three details that are load-bearing

Each of these produced a false positive before it was handled, found while testing the approach against this repo:

  • Generated theme CSS is skipped. The pipeline emits private vars and bare prop classes itself. Judging the tool's own output as consumer error accounted for 6 of the first 7 findings against apps/docsite.
  • Comments are blanked, not stripped. Removing them collapses lines, which shifts every reported line number — one finding pointed at line 104 when the code was at 174. For a diagnostic an agent edits from, a wrong line is worse than no line.
  • Bare-class detection matches only enumerated prop/state values. Otherwise .astryx-card.my-highlight — a consumer's own class — trips it.

Against this repo the pair reports exactly one finding, and it is a true positive: a hand-written .astryx-pagination-dot.active in apps/docsite/src/app/globals.css:174.

Test plan

  • 46 tests pass, roughly half of them negative controls: clean consumer CSS, a consumer class chained onto an Astryx class, a token name inside a comment, generated output, node_modules/dist, a project with nothing swizzled, and an unrelated directory named astryx
  • Line-number accuracy pinned by a test with a multi-line banner
  • check:cli-structure, readme:check, check:changesets, golden suite
  • Typecheck: 6 strict errors, all pre-existing in assets/templates/blocks/
  • Verified against apps/docsite — 1 finding, manually confirmed genuine

Made with Cursor

Two read-only, text-only checks for the cases where an app provably steps
outside its theme.

CSS escapes covers three: a write to a private --_* var, which theme build
already rejects and which is no more valid in raw CSS; a system token
redefined in :root/html/:host, which sits outside the theme's @scope and so
beats every theme at once; and the deprecated bare prop classes
(.astryx-button.primary) in place of the reflected data attributes.

Swizzled components fails on exactly one combination — ejected source that
imports StyleX with no StyleX compiler configured. That is not a build
error. The component renders completely unstyled, with no warning, which
makes it the most expensive thing here to discover by hand.

Three details are load-bearing, each of which produced a false positive
before it was handled. Generated theme CSS is skipped, because the pipeline
emits private vars and bare classes itself and judging its own output as
consumer error dominated the early findings. Comments are blanked while
preserving newlines, because stripping them collapses lines and shifts
every reported line number. And bare-class detection matches only the
enumerated prop and state values, so a consumer's own class chained onto an
Astryx class is left alone.

Half the tests are negative controls. A detector that only proves it finds
things says nothing about whether it will bury the reader in noise, and
every false positive here is one an agent would act on.

Co-authored-by: Cursor <cursoragent@cursor.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 20, 2026
@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview Aug 20, 2026 5:51pm

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant