Nothing in the guard tier can SEE a material leak — the cascade model skips every combinator by construction, so the style axis needs a rendered isolation guard
MOTIR-3947 shipped 10 of 11 style previews wearing the wrong material for months with a full green board. This card is about why nothing caught it.
The gap, quoted from the instrument itself
tests/theme/paletteCascade.ts — the cascade model scopedPreviewIsolation.test.ts resolves every assertion through — states its own limit in its header:
The model deliberately does NOT understand descendant/child combinators or class selectors: any rule carrying one is component-scoped (
[data-style='glass'] .card), not part of the root token layer, and is skipped.
That is a correct design decision for a TOKEN-layer model and it means the suite that proves the palette and type axes isolated is structurally incapable of seeing the style axis's defect. The style axis does not break in the token layer; it breaks in the selector layer. So there is no assertion to add to that lane — the guard has to render.
What to do
Add a spec to the browser lane, beside tests/e2e/appearance-sync.spec.ts and tests/e2e/shell-a11y-tokens.spec.ts. app/tokens/page.tsx already renders the eleven scoped StyleVignette tiles (its style gallery, ~line 349) and is not behind auth, so the surface exists.
The oracle is the stylesheet, never a table of expected values — the rule scopedPreviewIsolation.test.ts already establishes for the palette axis, and for the same reason: a hard-coded expectation needs re-typing every time a style is tuned, and the first person to skip that turns the guard into a test of a stale table.
- For each of the eleven styles, read the computed material properties of the tile's
data-surfaceelements (card,modal,sidebar,input) plus its.sv-canvas:background-color,background-image,backdrop-filter,box-shadow,border-color, and the::afterreads thathand-drawn-indieandretrofuturismpaint with. - Compare each against what that same tile renders when
<html>carries its own style — identical DOM in both arms, so the only variable is the ancestor. Drive<html data-style>through the page's own style control rather than by injecting an attribute. - Assert over more than one ancestor. Under
warm-editorialthe matrix is 11/11 even with the defect present, because the base ships no block and has nothing to leak — a spec that only ever runs under the default style passes on a broken page. Exercise at leastglassmorphismandneumorphism(1/11 each before the fix) andwarm-editorialas the control.
Acceptance criteria
Every criterion is checkable on this card's own branch, before it merges.
- The spec FAILS against the pre-fix stylesheet — check out the commit before [the
@scoperewrite], run the spec there, and quote the failure and its count in the pull-request body. A guard authored after its fix and never run against the defect asserts a tautology; this criterion is the whole reason the card exists and it is not discharged by reasoning about it. - It passes on this branch, whose base already carries [the
@scoperewrite] — reported as the matrix rather than a bare boolean. The failing output must name WHICH tile wore WHICH ancestor's material, because that is what makes a future regression diagnosable from the CI log alone. The pull request's own green check is the evidence; do not write a criterion that can only be read after this card has merged. - At least three ancestors are exercised, one of which is a non-material style used as the control.
- It runs in the pull request's ordinary lane — no
e2e-at-scalelabel, no new CI job, no new test project invitest.config.ts. tests/theme/paletteCascade.ts's header gains one sentence naming where the style axis's guard now lives, so the next reader of that limit is pointed at the answer instead of concluding the axis is unguarded.- Ships in
motir-coreonly.