Skip to content

feat: mount the Astryx substrate behind the legacy cascade - #1662

Merged
Astro-Han merged 2 commits into
mainfrom
refactor/1565-pr2-astryx-mount
Jul 30, 2026
Merged

feat: mount the Astryx substrate behind the legacy cascade#1662
Astro-Han merged 2 commits into
mainfrom
refactor/1565-pr2-astryx-mount

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

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-neutral 0.1.9 (@astryxdesign/cli arrives with its consumer in PR 3 — knip rightly rejects a dep nothing uses); versions, provider mount, and import strategy from the retired integration branch. Theme sits at the renderer root and follows our already-resolved .dark mode through a MutationObserver (astryx-theme-mode.ts) — no second theme source. AstryxLocaleProvider sits inside LocaleProvider (mounting it at the Theme level throws before first render) with the zh override map restricted to copy that exists today. The cascade order is extended to astryx-reset, astryx-tokens, theme, base, astryx-components, components, utilities, maka.legacy; Astryx ships its sheets unlayered, so each import is pinned into its astryx-* layer — unwrapped, Astryx would outrank maka.legacy and repaint the app on mount. The astryx layers are deliberately top-level names, not astryx.* sub-layers: a sub-layer collapses to its parent's first-occurrence position, which would silently drop astryx-components below base and 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.css neutralizes the Theme wrapper's inheritance baseline (color/font-family from 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 :root token for the whole app. The neutralizer lives in maka.legacy and dies with it in PR 11.
  • Only astryx.css (class-scoped, zero product reach) is imported now. reset.css and theme-neutral/theme.css both style bare elements (theme.css additionally 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.css lands with the first component consumer (PR 3) — preferably as an astryx theme build of a Maka-named theme so the typography block is dropped at the source; reset.css lands 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.mjs is the single route manifest for both harnesses.
  • Ready/anchor selectors moved from product classes to data-maka-contract structural hooks (12 attribute-only product edits). Ready selectors are hook, legacy-class compounds so the base side of a comparison keeps working across the hook rollout and later class deletions.
  • Element identity is now box-tree paths: display: contents wrappers (what Theme renders) generate no box and contribute no path segment, so mounting a provider does not re-key every element under it.
  • Declared-subtree mode (--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 matching html/body/#root fails the run, and so does a scope claiming most of a side's captured boxes (#root * cannot dodge the root floor).
  • Non-vacuous mount proof: the capture counts rules under 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:

  • LayerProvider is not mounted here: it renders a portal host/toast viewport, which would change the DOM under body and break the zero-diff gate. It lands with PR 5, which owns Layer behavior.
  • theme.css is deferred to PR 3 (above): the token sheet is inseparable from its element-typography block, which repaints unowned text on every route.
  • The i18n override map covers only @astryx.* keys whose copy sources exist on main; the Markdown catalog keys land with PR 7 together with their copy.
  • tailwind-theme.css is not imported (the issue already allows this): the @theme inline bridge stays the utility-token source until PR 12.

Third-party notices: regenerated; @astryxdesign/core and @stylexjs/stylex ship no LICENSE file and use version-pinned MIT text overrides; css-mediaquery declares 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-contract against main: 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, the Theme wrapper's box-tree/inheritance effects, reset.css bare-element reach, wrapper token shadowing) — it is doing the job the fan-out relies on.
  • npm run check:hit-test: 5 routes clean.
  • Harness unit tests: 27 pass (including the new declared-scope, fail-closed attribution, scope-coverage, and hook-anchor suites — the synthetic-scope demonstration required by the issue).
  • @maka/desktop main tests 2993 pass (cascade contract 9/9), @maka/ui 269 pass.
  • npm run lint, npm run format:check, workspace typecheck, npm run check:third-party-notices all clean.

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
Astro-Han force-pushed the refactor/1565-pr2-astryx-mount branch from 571af41 to 7a99dfc Compare July 30, 2026 15:36
…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.
@Astro-Han
Astro-Han merged commit 6b27c99 into main Jul 30, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the refactor/1565-pr2-astryx-mount branch July 30, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant