milestone 23 s2: react aria primitives and their cluster

This commit is contained in:
2026-08-12 22:12:18 +02:00
parent 994bbf922c
commit 01e455c8af
32 changed files with 2316 additions and 532 deletions
+115
View File
@@ -347,6 +347,121 @@ Tailwind dependency pair and its vite plugin line and CSS import.
- [ ] `web/src/lib/types.ts` and `web/src/lib/contractSamples.gen.ts` are
byte-identical to HEAD.
## Recorded (implementation)
Written during S1 and the S2 hand-off. Each entry is a place the spec was wrong
or a decision the spec did not have.
### S1 could not put StyleX in the bundle, and its acceptance says otherwise
S1 owns no page file, so no production module imports StyleX and rollup emits
none of it. Two S1 boxes — "`npm run build` emits StyleX CSS into the `web/dist`
assets" and "`assert-bundled` passes with `@stylexjs/stylex` in the recorded
set" — are therefore unmeetable inside S1 as scoped, and the recorded bundled
set is unchanged at the S1 commit. Both were proven instead against a temporary
probe component, then deferred to S2, which converts real pages and does change
the bundled set. Ruling 8's "S1 adds `@stylexjs/stylex` ... to the runtime
closure and the bundled set" is half right: the closure moves in S1, the bundled
set in S2. The inventory entry for StyleX is anticipatory at the S1 commit,
which is deliberate — see the styleq entry for why it cannot wait.
### The probe results, recorded rather than left as a claim
Against the temporary probe: the built CSS carries `@layer priority1..N` and the
token variables, resolving to `#fafafa` light and `#09090b` dark with
lightningcss `lab()` fallbacks. The dev server needs no wiring of its own — the
plugin injects `<link rel="stylesheet" href="/virtual:stylex.css">` into
`index.html`, so the `DevStyleXInject` component in StyleX's Vite guide is for
frameworks that bypass Vite's HTML transform and is not used here. Editing a
token regenerates the virtual stylesheet and the dev server issues a page
reload.
### S1 wrote outside its owned files, and outside an anti-requirement
The licence guard forced it: `licenses/stylex-mit.txt`, `licenses/styleq-mit.txt`
and their registration in `licenses/licenses.zig`, plus the `npm_not_shipped`
array in `src/licenses_drift_test.zig`. The last contradicts the "no edits ...
under `src/`" anti-requirement below. The anti-requirement is amended, not
ignored: `src/licenses_drift_test.zig` is writable for its licence-fact arrays
and, from S2, for the per-package licence change described next. The rest of
`src/` stays closed.
### Apache-2.0 inbound: a decision, not a deduction
`react-aria-components` 1.20.0 brings eight Apache-2.0 packages
(react-aria-components, react-aria, react-stately, `@react-types/shared`, three
`@internationalized/*`, `@swc/helpers`) and `tslib` under 0BSD into an EUPL-1.2
project. The user accepted Apache-2.0 inbound on 2026-08-12 and authorised the
guard change it needs. This is recorded as their decision because nothing in the
licence settles it: EUPL-1.2's appendix does not list Apache-2.0 — it names GPL,
AGPL, OSL, EPL, CeCILL, MPL, LGPL, CC BY-SA, EUPL and LiLiQ — and that appendix
governs Article 5 outbound relicensing, not inbound consumption. Any claim that
the appendix permits this is false and must not be written into the inventory.
`src/licenses_drift_test.zig:122` hardcodes a single expected licence and
becomes per-package in S2; the guard stays strict, an unexpected licence still
fails.
### Ruling 8's picture of the React Aria closure is wrong
There are no `@react-aria/*` or `@react-stately/*` scoped packages. RAC 1.20.0
ships consolidated `react-aria` and `react-stately`. The runtime closure it adds
is the eleven packages named above plus `aria-hidden`, `clsx` and `client-only`
under MIT — not the "dozens" the ruling anticipated.
### Ruling 11's claim about the tab tests is wrong
`LocalDnsPage.tsx` already renders `role="tablist"` and `role="tab"` by hand, so
no test query moves from `getByRole("button")` to `getByRole("tab")`. What RAC
adds there is keyboard arrow-key navigation, which the hand-rolled switcher
lacks, and that is what the S2 acceptance box asserts.
### `formCard` is a layout change, not a port
`space-y-3` set sibling margins on a block container; the StyleX version is a
flex column with `gap`. StyleX cannot express the `> * + *` selector `space-y`
compiles to, so `gap` is the only mechanism available. Both call sites
(`RecordsTab`, `ZonesTab`) are plain vertical form stacks, where the difference
in child sizing and margin behaviour does not show. Recorded against ruling 7,
which pins layout structure.
### S2 needed two build-config changes the spec did not anticipate
Both are in files S1 and S3 own, both were forced, and neither is optional.
`web/vitest.setup.ts` (new) plus `setupFiles` in `web/vite.config.ts`: jsdom
implements no `CSS` interface at all, and React Aria's collection code calls
`CSS.escape` unguarded when it looks an item up by key
(`react-aria/dist/private/selection/utils.mjs:22`). Every RAC Select and Tabs
test threw `Cannot read properties of undefined (reading 'escape')` before this
landed. The setup file implements CSSOM's *serialize an identifier* algorithm
rather than approximating it, because a wrong escape would break the key lookups
silently instead of loudly.
The `@` alias passed to `stylex.vite()` in `web/vite.config.ts`: StyleX resolves
the `.stylex.ts` theme file itself and cannot see Vite's `resolve.alias`, so
`import { colors } from "@/ui/tokens.stylex"` in a feature file failed the build
with "Could not resolve the path to the imported file". Relative imports were the
alternative; passing the alias keeps the repo's `@/` convention instead.
### RAC Select changes the accessible name of a labelled control
`useSelect` labels the trigger button with `aria-labelledby="<value> <label>"`,
so a select labelled "Type" showing "A" has the accessible name "A Type", and
`getByLabelText("Type")` no longer finds it. Converted tests query
`getByRole("button", { name: /Type$/ })` and read the options by opening the
listbox. RAC also renders a hidden native `<select>` for form submission; it sits
inside an `aria-hidden` container, so role and label queries skip it.
### Two notes for S3
`textMuted` resolves to zinc-400 in dark mode. Several current `text-zinc-500`
call sites carry no dark override and stay zinc-500, so the token cannot replace
those without a shade shift — decide per call site rather than sweeping.
Vitest began reporting "Tests closed successfully but something prevents Vite
server from exiting" with the StyleX plugin in the pipeline. The suite passes
and exits 0. It should not be allowed to become the accepted baseline.
## Anti-requirements
- No router or query changes: TanStack Router and TanStack Query stay,