Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/doctor-theme-drift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@astryxdesign/cli': patch
---

[feat] `astryx doctor` gains two theme-drift checks, both read-only and text-only.

**CSS theming escapes** flags the three cases where CSS provably leaves the theme behind: writes to private `--_*` vars (already a hard error in `theme build`), system tokens redefined in `:root`/`html`/`:host` — which sit outside the theme's `@scope` and so override every theme at once — and the deprecated bare prop classes (`.astryx-button.primary`) in place of the reflected data attributes. Generated theme CSS is skipped, since the pipeline emits private vars and bare classes itself.

**Swizzled components** reports ejected components, and fails when their source imports StyleX while no StyleX compiler is configured. That case is not a build error: the component renders completely unstyled, with no warning.
@josephfarina
16 changes: 16 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ No failures — but review the ⚠ warnings above when you can.
| Version alignment | pass / warn / info | Installed `@astryxdesign/core` is in step with `@astryxdesign/cli` |
| Theme packages | pass / warn | An `@astryxdesign/theme-*` package is installed and a theme is wired |
| Built theme freshness | pass / fail / warn / info | Built theme output is in step with its `defineTheme()` source |
| CSS theming escapes | pass / fail / warn / info | Your own CSS does not step outside the theme |
| Swizzled components | pass / fail / info | Ejected components, and whether their StyleX source can compile |
| astryx.config.mjs | pass / fail / info | Config (if present) loads cleanly with a valid shape |
| AI agent docs | pass / warn / info | Agent docs exist and contain the Astryx section markers |
| Peer dependencies | pass / warn / info | `@astryxdesign/core`'s peer deps (react, …) are installed |
Expand All @@ -490,6 +492,20 @@ directly (runtime injection), which is supported. Only drift counts, and it is
reported as `info` rather than `fail` when a `predev`/`prebuild` script already
rebuilds the theme, since in that case nothing ever consumes the stale output.

**CSS theming escapes** looks for the three cases where CSS provably leaves the
theme behind: a write to a private `--_*` var (a hard error in `theme build`,
so a `fail` here too), a system token redefined in `:root`/`html`/`:host` (which
sits outside the theme's `@scope` and so overrides every theme at once), and the
deprecated bare prop classes (`.astryx-button.primary`) in place of the
reflected data attributes. Generated theme CSS is skipped — the pipeline emits
private vars and bare classes itself.

**Swizzled components** is `fail` only in one situation: the ejected source
imports StyleX and no StyleX compiler is configured. That combination is not a
build error — the component renders completely unstyled, with no warning.
Otherwise it is informational, noting that swizzled copies stop receiving
upstream fixes and no longer respond to theme component overrides.

### CI gate

The exit code is the contract: `astryx doctor` exits `0` when there are no
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/api/doctor/doctor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {CLI_ROOT, findCoreDir} from '../../foundation/fs/paths.mjs';
import {detectPackageManager, getCliInvocation} from '../../foundation/env/package-manager.mjs';
import {findConfigPath, Project} from '../../foundation/config/project.mjs';
import {semverCompare, isValidSemver, satisfiesRange} from '../../foundation/env/semver.mjs';
import {checkCssEscapes, checkSwizzled} from './theme-drift.mjs';

const _require = createRequire(import.meta.url);

Expand Down Expand Up @@ -830,6 +831,8 @@ export const CHECKS = [
checkVersionAlignment,
checkThemes,
checkThemeBuilt,
checkCssEscapes,
checkSwizzled,
checkConfig,
checkAgentDocs,
checkPeerDeps,
Expand Down
Loading