Skip to content

A probe theme covering every theming target, and a PR-scoped visual job - #5396

Merged
cixzhang merged 6 commits into
mainfrom
visual-probe
Aug 25, 2026
Merged

A probe theme covering every theming target, and a PR-scoped visual job#5396
cixzhang merged 6 commits into
mainfrom
visual-probe

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

Both follow-ups from #5372.

The probe theme — "capture new targets when they're added"

Real themes style what their designer cared about, so most of the themeable surface was never exercised by any of them, and a newly added target starts life unverified by default. Nothing notices when it stops working, because nothing was styling it.

packages/themes/probe is generated from the same target enumeration the CLI uses, so a target added tomorrow is covered the moment its doc lands — nobody has to remember:

targets selectors
all seven real themes combined 25 keys ~60
probe theme 248 795

Each selector gets a distinct hashed colour, and text/background/border differ from each other. Two sub-targets that are supposed to be separate elements but actually resolve to the same one show up as a single colour — which a uniform hot-pink theme would hide. Private, never published. pnpm visual:probe-theme:check fails CI when a target is added without regenerating.

Effect on the gate: declared targets no shot could verify drop 52 → 8. It costs 128 shots, not 496: the probe tier greedily set-covers, so one story rendering twenty targets is one shot.

The PR-scoped job — component regression tests

pr-visual shoots every story of a touched component in every theme that styles it — deeper than the daily gate, which shoots one representative story per component. Scoped by the same analysis.json pr-a11y already uses.

PR shots time
median 16 ~10s
p90 90 ~40s
Button (worst realistic) 210 ~90s

Over 240 shots it declines and says why. A token or shared-hook change would put hundreds of diffs in front of a reviewer who cannot judge them one by one; the daily gate reviews that against the whole baseline instead. The skip states its reason in the PR comment rather than vanishing. Non-blocking while it earns trust.

Two signals corrected so they don't lie on every PR: a scoped run no longer reports the other 500 baseline shots as removed, and shots the daily baseline doesn't reach are reported as unbaselined, not as additions.

What this surfaced (flagging, not fixing)

69 visual props across 27 components are declared themeable but undocumentedStack, Stepper, ButtonGroup and others declare visualProps: ['direction','gap',…] in their theming targets while documenting no props at all. astryx component Stack shows a theming table naming direction, but nothing says what values it takes, so a theme author can't use it and the probe theme can't enumerate it. The generator lists every one.

The 8 still-uncovered targets and the 49 unexercised real-theme overrides share a single root cause: no story renders themHeading type="display-1", a selected TopNav item, Card variant="info", the empty states, hovercard, table-footer. Worth a stories pass; happy to take it.

Testing

77 unit tests across the gate (55) and the PR comment formatter (9) plus existing. Verified end to end on the Mac: 642-shot run passes clean, the median-PR path takes 50 shots in ~20s, and the over-budget path declines with 924 shots exceeds the 240-shot budget (5 components touched).

@vercel

vercel Bot commented Aug 24, 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 25, 2026 2:00am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 24, 2026
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Aug 24, 2026
…sual job

Two gaps the release gate left open.

THE PROBE THEME. Real themes style what their designer cared about, so most of
the themeable surface was never exercised by any of them — and a newly added
target starts life in that unverified set by default. Nothing notices when it
stops working, because nothing was styling it.

packages/themes/probe is GENERATED from the same target enumeration the CLI
uses, so a target added tomorrow is covered the moment its doc lands: 248
targets, 795 selectors, against 25 keys across all seven real themes. Each
selector gets a distinct hashed colour, and text/background/border differ, so
two sub-targets that collapse onto one element show as one colour instead of
two — which a uniform hot-pink theme would hide. Private, never published, and
visual:probe-theme:check fails CI when a target is added without covering it.

Effect on the gate: declared targets no shot could verify drop 52 -> 8. The
remaining 8, and the 49 unexercised real-theme overrides, share one root cause
— no story renders them at all.

THE PR-SCOPED JOB. pr-visual shoots every story of a touched component in every
theme that styles it (deeper than the daily gate, which shoots one
representative story), scoped by the same analysis.json pr-a11y uses: a median
PR is ~16 shots, ~10s. Over 240 shots it declines and says why — a token change
would put hundreds of diffs in front of a reviewer who cannot judge them one by
one, and the daily gate reviews that against the whole baseline instead.
Non-blocking while it earns trust.

A scoped run no longer reports the rest of the baseline as removed, and shots
the daily baseline does not reach are reported as unbaselined rather than as
additions — both would otherwise appear on every PR.
…ut of the CLI

Three things the first cut got wrong, all found by running the build rather
than reading it.

The package emitted no .d.ts, so Storybook's typecheck failed on the import.
It now runs tsc the way every shipped theme does.

scripts/check-fully-specified.test.mjs requires every @astryxdesign/theme-*
build to run the fully-specified gate, and it is right to: this package emits
dist ESM the same way, so it can regress the same way.

Worst of the three: generate-cli-themes.mjs discovers themes by scanning
packages/themes, so the probe fixture was bundled into the CLI's shipped
templates — a 5,000-line generated test fixture offered to users as a
selectable theme in `astryx theme add`. The generator now skips private
packages, and the bundle drift test grew a guard so it cannot come back. That
test's own discovery mirrored the generator's, so it had to learn the same
rule.
The review loop's second block: '52 → 8' measured the set-cover tier, not the
probe theme — the scout is theme-blind, and buildPlan with probeTheme:'neutral'
gives the same number. Worse, nothing anywhere asserted a probe colour ever
painted, so a target that had already stopped working would be captured broken
and promoted into the baseline as correct, permanently silent.

`gate.mjs reach` is the assertion a pixel diff cannot make. Each probe selector
has a unique deterministic colour, so 'did this override arrive' is an equality
test, not a diff: no baseline, no images, and it names the target instead of a
rectangle. paint() is now exported and shared, because two copies of the
seed→colour mapping would drift and the check would report the drift as a bug.

Three outcomes, not two, because the difference matters:
  reached    — the override painted
  shadowed   — another target on the SAME element won; a fact about the markup
  missed     — nothing probe-coloured won

First run over the whole index: 213 reached, 45 shadowed, 50 missed, 8 rendered
by no story. Rebased onto v0.5.0 and repinned; the generator picked up main's
new targets on its own, 248 → 268, which is the property that makes this worth
having.
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

No component packages changed.

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

github-actions Bot added a commit that referenced this pull request Aug 25, 2026
…lds it

    <Package>/<Component>/(Default | Theme Sheet | …)
    <Package>/Hooks/<hook>
    <Package>/Themes/<theme feature>

A reader could not guess where anything lived: six hooks sat beside components
under three different parents, five theme-level features were filed as if they
were components, and one story was under a 'Components' category that exists
nowhere else. Nothing but a reader could notice, which is why it drifted.

The test reads the BUILT index, so it judges what Storybook renders rather than
what the source appears to say. Charts/Chrome/* and Lab/3DChart/* stay as
deliberate sub-grouping.

Titles only — no story moved file, no render changed.

The README now carries the pattern, including the part that is easy to get
backwards: a Theme Sheet must NOT wrap itself in <Theme theme={…}>. Several
existing 'Themed*' stories do, which overrides the global theme, so the toolbar
cannot switch them and the visual gate can never probe them — they are
invisible to exactly the testing they look like they are helping with.
The reach check existed but nothing ran it, so the 50 standing misses would
have quietly come back. It is now a leg of the release gate, reported in the
job summary with its own artifact.

Reported, not enforced. Every one of today's misses traces to a single cause —
StyleX emits into @layer priority1-4, which sort AFTER astryx-theme, so
wherever a component sets a property the theme override loses. Failing the gate
on a known systemic issue teaches everyone to ignore the gate. Enforce it when
that is fixed and the count is zero.
@cixzhang
cixzhang merged commit 23fb7ca into main Aug 25, 2026
21 checks passed
github-actions Bot added a commit that referenced this pull request Aug 25, 2026
cixzhang added a commit that referenced this pull request Aug 25, 2026
The gate reads each theme's dist/source.mjs — a theme's component map is
what defineTheme returns, not a literal in its source — and pr-visual only
downloaded the Storybook artifact, so every run since #5396 died on 'Theme
butter is not built'. build-storybook already builds them, so it uploads
them rather than pr-visual paying for a second full build.
cixzhang added a commit that referenced this pull request Aug 25, 2026
The gate reads each theme's dist/source.mjs — a theme's component map is
what defineTheme returns, not a literal in its source — and pr-visual only
downloaded the Storybook artifact, so every run since #5396 died on 'Theme
butter is not built'. build-storybook already builds them, so it uploads
them rather than pr-visual paying for a second full build.
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. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant