@ultimat3/ui 7.0.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -3
- package/package.json +6 -5
- package/src/jsx-probe.ts +8 -0
package/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ultimat3/ui — agent notes
|
|
2
2
|
|
|
3
|
-
Tier 5. Imports `@ultimat3/core`, `schema`, `i18n`, `money`, `time`. Never `http`, `action`, `render`, `admin
|
|
3
|
+
Tier 4 (moved 5 → 4 on 2026-08-19, when the `admin → ui` exception was deleted). Imports `@ultimat3/core`, `schema`, `i18n`, `money`, `time`. Never `http`, `action`, `render`, `admin` — `render` is tier 4 too, which is what keeps the static bundle graph out of the design system.
|
|
4
4
|
|
|
5
5
|
## Boundary
|
|
6
6
|
|
|
@@ -29,7 +29,7 @@ Tier 5. Imports `@ultimat3/core`, `schema`, `i18n`, `money`, `time`. Never `http
|
|
|
29
29
|
- **`inert-render.test.ts` must not assume which factory its `.tsx` compiled to.** `@ultimat3/render`'s `index.ts` installs a process-global `Bun.plugin` `onLoad` for `/\.tsx$/` at import, and `bun test` is one process — so any file in the run that imports render first makes every ui component after it compile to render's `h` instead of the file's own inert copy. The walker recognises both (`Symbol.for('ultimate.render.jsx')`, off the global registry, never an import), and the first test in the describe asserts a component returned a node it recognises. Without both halves the file silently rendered `"[object Object]"` and 26 assertions were decided by shard packing.
|
|
30
30
|
- **Components are not unit-tested through a renderer.** `.tsx` compiles to `@ultimat3/render`'s `h`, which this package may not import, so every rule lives in a pure module beside the component (`icon-glyph.ts`, `accordion-view.ts`, `combobox-filter.ts`, `infinite-scroll-view.ts`) and *that* is what the tests assert.
|
|
31
31
|
- Formatting logic lives in a pure `*-view.ts` next to the component (`money-view.ts`, `date-time-view.ts`) so it is testable with no renderer. Every other renderer-free core follows the same rule under its own name (`sort-state.ts`, `image-source.ts`) — the `.tsx` holds markup, never a rule.
|
|
32
|
-
- **The rule being pure is not enough — the WIRING has to be tested too.** `createRovingTabindex` was correct and `Menu` handed it `[role="menuitem"]`, so a disabled item made every item after it unreachable and every assertion in the package still passed. `src/jsx-probe.ts` reads the props an element actually carries (a `tabindex`, an `aria-live`, an `onKeyDown`, a `ref`) and `src/fake-dom.ts` gives it a DOM where a disabled control REFUSES focus, exactly as the real one does. Both are test-only and neither is in `index.ts`. `components/interaction.test.ts` is where a keyboard or form-participation claim gets proven; asserting the pure helper alone is how these shipped.
|
|
32
|
+
- **The rule being pure is not enough — the WIRING has to be tested too.** `createRovingTabindex` was correct and `Menu` handed it `[role="menuitem"]`, so a disabled item made every item after it unreachable and every assertion in the package still passed. `src/jsx-probe.ts` reads the props an element actually carries (a `tabindex`, an `aria-live`, an `onKeyDown`, a `ref`) and `src/fake-dom.ts` gives it a DOM where a disabled control REFUSES focus, exactly as the real one does. Both are test-only and neither is in `index.ts`. `jsx-probe`'s `probe`/`unprobe` are the one exception, reachable at the subpath `@ultimat3/ui/jsx-probe` and still absent from the barrel: `globalThis.React` is ONE property, so its install/restore counter has to be ONE counter — `@ultimat3/admin` (tier 5) kept a second pair, and interleaved installs restored the two harnesses in the wrong order, leaving the global holding a harness the run had already torn down. `components/interaction.test.ts` is where a keyboard or form-participation claim gets proven; asserting the pure helper alone is how these shipped.
|
|
33
33
|
- **A roving group excludes disabled items from both answers** — the set arrows walk and the one item holding the tab stop (`src/roving.ts`). `focus()` on a disabled control is a no-op, so a disabled item left in the list pins the reducer on its index forever. And a control that answers arrows itself (`handlesOwnArrowKeys`) keeps them: a `Toolbar` exists to hold a search field.
|
|
34
34
|
- **A single-winner state attribute is decided by POSITION, never by a missing prop.** `Breadcrumb` gives `aria-current="page"` to the last item and to nothing else; an href-less ancestor renders as plain text with no `aria-current` at all. Reading "no href" as "is the current page" put two of them in one `<nav>`. `Tabs.tsx` is the same rule for `tabindex`, and `Breadcrumb.test.ts` proves it through `jsx-probe` rather than through the pure helper.
|
|
35
35
|
- **Live semantics belong to the container that outlives the message.** `ToastRegion`'s `<ol>` carries `aria-live`; a `Toast` is a plain `<li>`. A region created with its content already inside it is not announced, and a `role="status"` on the `<li>` also strips its `listitem` semantics.
|
|
@@ -55,7 +55,7 @@ Tier 5. Imports `@ultimat3/core`, `schema`, `i18n`, `money`, `time`. Never `http
|
|
|
55
55
|
| `src/catalog/` | parses `components/*.tsx` into `CATALOG.md`; `bun run catalog` writes it, `catalog.test.ts` fails on drift |
|
|
56
56
|
| `src/roving.ts` | the pure rules of a keyboard group: navigable set, tab stop, who keeps their own arrows |
|
|
57
57
|
| `src/fake-dom.ts` | TEST-ONLY: a DOM where a disabled control refuses focus. Never exported from `index.ts` |
|
|
58
|
-
| `src/jsx-probe.ts` | TEST-ONLY: a component's node tree, so a test can assert the props and call the handlers an element carries |
|
|
58
|
+
| `src/jsx-probe.ts` | TEST-ONLY: a component's node tree, so a test can assert the props and call the handlers an element carries. `probe`/`unprobe` are the framework's ONE owner of `globalThis.React`, reached from `@ultimat3/admin` at `@ultimat3/ui/jsx-probe`; the walkers stay internal |
|
|
59
59
|
| `src/components/style-classes.test.ts` | the build error behind "every `styles['x']` a component names is declared in its own `.module.scss`" — under `bun test` a `.module.scss` import resolves to the file PATH, so no render can catch a dead class |
|
|
60
60
|
|
|
61
61
|
## Assumed peer contracts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "SolidJS design system: semantic design tokens, dark/RTL-ready SCSS modules, a11y primitives",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"exports": {
|
|
21
21
|
".": "./src/index.ts",
|
|
22
22
|
"./icons/*": "./src/icons/glyphs/*.ts",
|
|
23
|
+
"./jsx-probe": "./src/jsx-probe.ts",
|
|
23
24
|
"./tokens": "./src/tokens/_index.scss",
|
|
24
25
|
"./theme.scss": "./src/tokens/theme.scss",
|
|
25
26
|
"./reset.scss": "./src/tokens/reset.scss",
|
|
@@ -43,10 +44,10 @@
|
|
|
43
44
|
"icons": "bun run src/icons/build-icons.ts"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@ultimat3/core": "
|
|
47
|
-
"@ultimat3/i18n": "
|
|
48
|
-
"@ultimat3/money": "
|
|
49
|
-
"@ultimat3/time": "
|
|
47
|
+
"@ultimat3/core": "9.0.0",
|
|
48
|
+
"@ultimat3/i18n": "9.0.0",
|
|
49
|
+
"@ultimat3/money": "9.0.0",
|
|
50
|
+
"@ultimat3/time": "9.0.0"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
53
|
"solid-js": "^1.9.0"
|
package/src/jsx-probe.ts
CHANGED
|
@@ -35,6 +35,14 @@ function h(
|
|
|
35
35
|
* DELETED the property on the way out destroyed a binding it did not create, and a nested or
|
|
36
36
|
* repeated probe tore the factory out from under the suite still using it — the counter is what
|
|
37
37
|
* makes the last unprobe the only one that restores.
|
|
38
|
+
*
|
|
39
|
+
* There may be exactly ONE such counter in the framework, and it is this one — `globalThis.React`
|
|
40
|
+
* is a single property, so a second module counting its own depth over it restores in the wrong
|
|
41
|
+
* order. `@ultimat3/admin`'s `inert-jsx.ts` kept a second pair and the two interleaved: admin
|
|
42
|
+
* installs (saving the real binding), ui installs (saving ADMIN's factory), admin restores (both
|
|
43
|
+
* counters at 1 → the real binding is back), ui restores (its counter hits 0 → admin's factory is
|
|
44
|
+
* reinstalled over it). The global ended up holding a harness the run had already torn down.
|
|
45
|
+
* `admin` is tier 5 and `ui` is tier 4, so admin imports these rather than declaring them.
|
|
38
46
|
*/
|
|
39
47
|
let depth = 0;
|
|
40
48
|
let saved: PropertyDescriptor | undefined;
|