feat: mount the Astryx substrate behind the legacy cascade - #1662
Merged
Conversation
Slice PR 2 of #1565. Pins @astryxdesign/* 0.1.9, mounts Theme (mode follows the resolved .dark class) and the Astryx i18n adapter, and layers astryx.css under maka.legacy via the extended cascade order. reset.css and theme.css are deferred (bare-element reach no layer order can bury) with their layer slots contract-tested. styles/astryx-mount.css neutralizes the Theme wrapper's inheritance baseline and token shadowing. Harness: contract-routes.mjs single manifest, data-maka-contract hooks replace product-class selectors, box-tree path identity (display:contents generates no segment), declared-subtree mode (--scopes) with fail-closed validation, and a CSSOM probe so zero diff cannot mean Astryx never loaded. Gate: 20/20 cells zero diff against main.
Astro-Han
force-pushed
the
refactor/1565-pr2-astryx-mount
branch
from
July 30, 2026 15:36
571af41 to
7a99dfc
Compare
…closed Review findings on #1662 (independent subagent + Codex): - astryx.* sub-layers all collapse to the top-level astryx layer at its first occurrence, so astryx.components never actually sat above base and Tailwind preflight would beat every Astryx component rule from PR 3 on. Flatten to top-level astryx-reset/astryx-tokens/astryx-components names, pin the effective order and a no-dotted-sub-layers rule in the cascade contract test, and match probe layer names exactly (astryx.css nests its own @layer astryx-base, which a prefix match would double-count). - Declared-scope mode failed open: a changed record inherited the base side's scope, so a mistyped migrated selector waived every change inside the old legacy subtree, and selectors like '#root *' dodge the root floor while claiming the whole app. Changed records now require a current-side claim, and a scope claiming most of a side's boxes fails the run. - AstryxLocaleProvider memoises its overrides per locale; the wrapper neutralizer is scoped under #root (Theme mirrors data-astryx-theme onto <html>). Re-verified: visual contract 20/20 zero diff, harness unit tests 27, desktop 2993, ui 269, hit-test 5 routes, lint/format/typecheck clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Slice PR 2 of the Astryx migration (Refs #1565): everything the parallel fan-out depends on, gated on strict zero visual diff.
Mount Astryx behind the legacy cascade. Pinned
@astryxdesign/core/@astryxdesign/theme-neutral0.1.9 (@astryxdesign/cliarrives with its consumer in PR 3 — knip rightly rejects a dep nothing uses); versions, provider mount, and import strategy from the retired integration branch.Themesits at the renderer root and follows our already-resolved.darkmode through a MutationObserver (astryx-theme-mode.ts) — no second theme source.AstryxLocaleProvidersits insideLocaleProvider(mounting it at theThemelevel throws before first render) with the zh override map restricted to copy that exists today. The cascade order is extended toastryx-reset, astryx-tokens, theme, base, astryx-components, components, utilities, maka.legacy; Astryx ships its sheets unlayered, so each import is pinned into itsastryx-*layer — unwrapped, Astryx would outrankmaka.legacyand repaint the app on mount. The astryx layers are deliberately top-level names, notastryx.*sub-layers: a sub-layer collapses to its parent's first-occurrence position, which would silently dropastryx-componentsbelowbaseand let Tailwind preflight beat every Astryx component rule from PR 3 on (caught in review; the contract test now pins the effective order and rejects dotted sub-layers). The cascade contract test pins the exact layer per Astryx file, including files not imported yet.Two inertness measures the zero-diff gate forced, both instructive about what layer order cannot do:
styles/astryx-mount.cssneutralizes theThemewrapper's inheritance baseline (color/font-familyfrom Astryx tokens) and re-inherits--font-size-base: custom properties resolve by tree distance, not layer order, so a token declared on the wrapper shadows the product's:roottoken for the whole app. The neutralizer lives inmaka.legacyand dies with it in PR 11.astryx.css(class-scoped, zero product reach) is imported now.reset.cssandtheme-neutral/theme.cssboth style bare elements (theme.cssadditionally ships@scope'd element typography —:where(p),:where(h1..h6)take the Astryx font), and an element rule wins wherever no product rule competes, regardless of layers.theme.csslands with the first component consumer (PR 3) — preferably as anastryx theme buildof a Maka-named theme so the typography block is dropped at the source;reset.csslands with the Tailwind-preflight retirement (PR 12). Layer slots for both are reserved and contract-tested.Harness upgrade — the fan-out blocker. Without this, slices 8–10 would all edit the same harness files and re-couple through test infrastructure:
scripts/contract-routes.mjsis the single route manifest for both harnesses.data-maka-contractstructural hooks (12 attribute-only product edits). Ready selectors arehook, legacy-classcompounds so the base side of a comparison keeps working across the hook rollout and later class deletions.display: contentswrappers (whatThemerenders) generate no box and contribute no path segment, so mounting a provider does not re-key every element under it.--scopes <file>): a slice declares selectors for the legacy element it replaces and the migrated element it lands; diffs inside declared scopes are reported for review, any diff outside fails, and zero diff stays the default gate. The mode fails closed: a changed element must be claimed by the current side (a mistyped migrated selector cannot ride on the base side's legacy claim), a scope matchinghtml/body/#rootfails the run, and so does a scope claiming most of a side's captured boxes (#root *cannot dodge the root floor).astryx.*layers via CSSOM and fails when zero, so a zero-diff pass cannot mean "Astryx never loaded".Deliberate deviations from the issue text, with reasons:
LayerProvideris not mounted here: it renders a portal host/toast viewport, which would change the DOM underbodyand break the zero-diff gate. It lands with PR 5, which owns Layer behavior.theme.cssis deferred to PR 3 (above): the token sheet is inseparable from its element-typography block, which repaints unowned text on every route.@astryx.*keys whose copy sources exist onmain; the Markdown catalog keys land with PR 7 together with their copy.tailwind-theme.cssis not imported (the issue already allows this): the@theme inlinebridge stays the utility-token source until PR 12.Third-party notices: regenerated;
@astryxdesign/coreand@stylexjs/stylexship no LICENSE file and use version-pinned MIT text overrides;css-mediaquerydeclares legacy "BSD" and is pinned to its shipped BSD-3-Clause. Metadata overrides now take precedence over a present-but-wrong declaration.Verification
npm run check:visual-contractagainstmain: 20/20 comparisons (5 routes × light/dark × darwin/win32) zero diff, salvaged anchors present, Astryx-loaded probe non-zero. The gate caught four real leak classes during development (hook-only ready selectors, theThemewrapper's box-tree/inheritance effects,reset.cssbare-element reach, wrapper token shadowing) — it is doing the job the fan-out relies on.npm run check:hit-test: 5 routes clean.@maka/desktopmain tests 2993 pass (cascade contract 9/9),@maka/ui269 pass.npm run lint,npm run format:check, workspace typecheck,npm run check:third-party-noticesall clean.