Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
144
planned
1,362
shipped

Motir · Roadmap

MOTIR-3997Done

Move the style axis's 69 material rules under `@scope (…) to ([data-style])` — and re-point `styleRegistry.test.ts`'s parser, which cannot survive the move

Opened by Zhu Yue ·

Builds the mechanism MOTIR-3947 decided. Read docs/decisions/scoped-preview-isolation.md first — it carries the measurement, the two rejected candidates, and the reason a base token block is the wrong remedy.

What to do

packages/design-system/theme.css carries 69 rules of the form [data-style='X'] <descendant>. A descendant combinator does not stop at a nested data-style, so all 69 match through a scoped StyleVignette. Rewrite each one as:

@scope ([data-style='X']) to ([data-style]) {
  <the rest of the selector> { … }
}

The rewrite is mechanical and scriptable; the judgement is in the two things that ride with it.

Do NOT touch: the 10 bare [data-style='…'] token blocks, the compound [data-style='neumorphism'][data-theme='dark'] block, or the 4 already-anchored .style-vignette[data-style='…'] > .sv-canvas rules. Those 15 are the exact complement of the 69 — a rewriter that converts 70 or 68 has mis-parsed something.

tests/theme/styleRegistry.test.ts must land in the SAME commit. Its material-layer finder is a text regex — /\[data-style='[^']+'\]\s+[^{};]+\{/ — which stops matching the moment the anchor moves into an @scope prelude, so materialRulesChecked drops to 0 against its own toBeGreaterThanOrEqual(4) floor. Re-point it at the @scope form, keep both existing assertions (palette-derived: var(--color-*|--el-*) or currentColor; and no raw hex hue) asserting over the same population, and ratchet the count floor to 69 so a material rule authored outside a scope in future fails here instead of going uncounted.

And correct the two comments that state the leak as open, both of which become false with this commit:

  • packages/design-system/src/components/theme/StyleVignette.tsx — the ⚠️ header's third bullet (SHAPE / MATERIAL (styleId) — NOT ISOLATED, and this is open). Rewrite it to say what the axis IS isolated by, and point at the ADR. Keep the instruction not to add a [data-style='warm-editorial'] base block, and keep it as a MEASURED statement rather than a claim about the current consumer set — that framing is what MOTIR-3933 corrected and it should not regress.
  • packages/design-system/src/components/theme/AppearancePickers.tsx (~line 285) — the PalettePicker comment cites "the StyleVignette nested-base caveat" as a live reason for the hardcoded swatch. The swatch stays hardcoded (it is also theme-independent, which is the other half of the reason); the citation needs updating.

Acceptance criteria

  1. packages/design-system/theme.css contains 69 @scope ([data-style='…']) to ([data-style]) blocks and zero remaining [data-style='X'] <descendant> rules outside the four .style-vignette … > .sv-canvas ones. Show the count from a script, not from a grep -c over a tree you have been editing.

  2. tests/theme/styleRegistry.test.ts passes, reads the @scope form, and its material-rule count floor is 69. Removing one @scope wrapper in the working tree makes it fail — demonstrate that, then restore.

    ⚠️ AMENDED ON THE RECORD (2026-08-30, MOTIR-3997's own run). The 69 could not go on materialRulesChecked, which this card's What to do section names. Measured at origin/main 00e949a68 with a postcss classifier: there are 69 material rule BLOCKS (88 selectors across them — 14 blocks carry a selector list sharing one anchor), and only 32 of the 69 carry a colour-bearing declaration. materialRulesChecked counts that colour-bearing subset — it is what the two palette-derivation assertions iterate — so toBeGreaterThanOrEqual(69) is unpassable on a correct tree. The 69 therefore went on the population this card's own REASON describes ("so a material rule authored outside a scope in future fails here instead of going uncounted", which quantifies over all material rules): the test now carries MATERIAL_RULES.length >= 69 as the ratchet and the matcher guard, with the two existing assertions unchanged over the same colour-bearing population and their floor still 4. Both sentences of this card hold; only the counter the 69 attaches to moved. Demonstrated red at 68 and restored, per this criterion. Evidence: motir-core#2482.

  3. pnpm test is not run locally over the full suite (the PR's CI is the authority); run the theme lane's changed files only: tests/theme/styleRegistry.test.ts, tests/theme/scopedPreviewIsolation.test.ts, tests/theme/designSystemParity.test.ts, packages/design-system/test/themeCssExport.test.ts.

  4. pnpm lint, pnpm typecheck and prettier --check pass on the changed files.

  5. The app shape is unchanged. State in the PR body which check establishes it — CI's theme suite is the gate; the ADR's own 5610-declaration prototype comparison is prior art to cite, not a substitute.

  6. No consumer changes. StyleVignette, TokensSpecimen, app/tokens/page.tsx and components/onboarding/DesignStep.tsx keep their current markup — the wrapper's existing data-style IS the scope limit. A diff that touches a call site means the mechanism was mis-applied.

  7. Ships in motir-core only. No motir-marketing change and no npm publish — that is the release card.

Discussion

No comments yet.

Adding to this discussion signs you in on app.motir.co and brings you back to this request.

Add a comment