@adia-ai/adia-ui-forge 0.8.39 → 0.8.41

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.
Files changed (32) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +21 -0
  3. package/package.json +1 -1
  4. package/scripts/release-pretag-docs-gate +1 -1
  5. package/skills/a2ui-maintenance/SKILL.md +26 -6
  6. package/skills/a2ui-maintenance/references/anti-patterns.md +1 -1
  7. package/skills/a2ui-maintenance/references/chunk-authoring.md +2 -2
  8. package/skills/a2ui-maintenance/references/eval-diagnostics.md +1 -1
  9. package/skills/a2ui-maintenance/references/mcp-pipeline-ops.md +2 -2
  10. package/skills/a2ui-maintenance/references/pipeline-overview.md +2 -2
  11. package/skills/demo-audit/scripts/analyze.mjs +1 -1
  12. package/skills/package-release/SKILL.md +1 -1
  13. package/skills/package-release/references/cut-procedure.md +14 -4
  14. package/skills/package-release/references/gates-catalog.md +7 -1
  15. package/skills/package-release/scripts/gate-roster.mjs +28 -1
  16. package/skills/package-release/scripts/release-pack.mjs +43 -4
  17. package/skills/primitive-authoring/SKILL.md +1 -0
  18. package/skills/primitive-authoring/references/INDEX.md +2 -1
  19. package/skills/primitive-authoring/references/anti-patterns.md +27 -0
  20. package/skills/primitive-authoring/references/api-contract.md +39 -0
  21. package/skills/primitive-authoring/references/composite-demo-protocol.md +1 -1
  22. package/skills/primitive-authoring/references/css-patterns.md +7 -1
  23. package/skills/primitive-authoring/references/shell-patterns.md +1 -1
  24. package/skills/primitive-authoring/references/svg-authoring.md +277 -0
  25. package/skills/primitive-authoring/references/token-contract.md +1 -1
  26. package/skills/primitive-authoring/references/yaml-contract.md +165 -5
  27. package/skills/ssr-compatibility/SKILL.md +10 -5
  28. package/skills/ssr-compatibility/references/consumer-workarounds.md +14 -6
  29. package/skills/ssr-compatibility/references/failure-shapes.md +30 -7
  30. package/skills/ssr-compatibility/references/guard-patterns.md +28 -0
  31. package/skills/ssr-compatibility/references/status-ledger.md +13 -0
  32. package/skills/ssr-compatibility/references/test-without-linkedom.md +29 -14
@@ -19,14 +19,37 @@ or a write to `document.adoptedStyleSheets`.
19
19
  not a partial/quirky implementation, an absence. Any unconditional call throws
20
20
  `TypeError` or `ReferenceError` (undefined global) immediately.
21
21
 
22
- **Status: FIXED** (gh#285, PR #292, merged 2026-07-17). `UIElement`'s constructor
23
- (`packages/web-components/core/element.js`) plus a sweep of component/trait/module
24
- files had this shape — the per-file tally is
22
+ **Status: FIXED, twice — and now GATED.** First wave gh#285 (PR #292, merged
23
+ 2026-07-17): `UIElement`'s constructor (`packages/web-components/core/element.js`)
24
+ plus a sweep of component/trait/module files — the per-file tally is
25
25
  [`status-ledger.md`](status-ledger.md)'s #285 row (the ledger, not this line, is
26
- the count of record). The fix pattern (feature-detect + fallback matched to how the reference is
27
- used downstream) is [`guard-patterns.md`](guard-patterns.md) apply that pattern to
28
- any NEW file that construct-calls one of these APIs; don't re-derive the shape from
29
- scratch.
26
+ the count of record). Second wave gh#1430 + gh#1436 (2026-08-17, found by adiav2's
27
+ first SSR admission trial at 0.8.40): the sweep had missed
28
+ - **environment-detection instead of feature-detection at MODULE scope**
29
+ `core/responsive.js` guarded `window.matchMedia(...)` behind `typeof window !==
30
+ 'undefined'`; linkedom HAS a `window`, just no `matchMedia` and no numeric
31
+ `innerWidth`, so six components (`block`/`col`/`grid`/`row`/`text`/`demo-toggle`)
32
+ could not be IMPORTED server-side at all;
33
+ - **bare `instanceof Node|Element|HTMLElement`** (15 sites — `core/template.js`
34
+ `applyValue`, table/list-window renderer results, event-target checks) — a
35
+ `ReferenceError`, not `false`, because custom-elements-ssr installs ONLY
36
+ `HTMLElement` as a global (never `Node`, `Element`, `Text`, `DocumentFragment`);
37
+ now `core/dom.js` `isNode()`/`isElement()` (structural `nodeType` tests);
38
+ - **`requestAnimationFrame` / `MutationObserver` / `getBoundingClientRect` at
39
+ connect** in feed-item/toast, noodles, preview, toolbar (rAF), nav-group,
40
+ stepper (MutationObserver — the #292 sweep guarded four Observer sites, not
41
+ these two) and nav-ui (`getBoundingClientRect` is absent, not zero, on linkedom).
42
+
43
+ **Why happy-dom could not see any of it:** it implements every one of those APIs.
44
+ The gate that closes the class is `scripts/dev/ssr-linkedom-smoke.mjs`, run by
45
+ `packages/web-components/test/ssr-linkedom-smoke.test.js` — a real `linkedom`
46
+ devDependency installing EXACTLY the six globals `custom-elements-ssr/server-shim.js`
47
+ installs, then importing every `components/*/*.js` entry and constructing +
48
+ connecting every registered tag (124/124 import, every tag renders). A new
49
+ shape-1 instance fails that test, not a consumer's build. The fix pattern
50
+ (feature-detect + fallback matched to how the reference is used downstream) is
51
+ [`guard-patterns.md`](guard-patterns.md) — apply it to any NEW call site; don't
52
+ re-derive the shape from scratch, and don't guard on `typeof window`.
30
53
 
31
54
  ## 2 · `connectedCallback` destructively re-stamps existing DOM → content loss
32
55
 
@@ -51,6 +51,34 @@ const observer = typeof MutationObserver !== 'undefined'
51
51
  // every later use: observer?.observe(...) / bootstrap() no-ops via the same check
52
52
  ```
53
53
 
54
+ **(d) Feature, not environment — `typeof window` is NOT a guard (gh#1430).**
55
+ linkedom defines `window` (an object) but neither `matchMedia` nor a numeric
56
+ `innerWidth`, so `typeof window !== 'undefined'` passes and the call still throws —
57
+ at MODULE scope that takes every importer off the server. Test the member:
58
+ ```js
59
+ // packages/web-components/core/responsive.js
60
+ const w = typeof window !== 'undefined' ? window.innerWidth : undefined;
61
+ if (typeof w !== 'number') return 'lg'; // fallback
62
+ if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') { … }
63
+ ```
64
+
65
+ **(e) `instanceof <DOM constructor>` — use `core/dom.js` (gh#1436).** Only
66
+ `HTMLElement` is a global under custom-elements-ssr; `v instanceof Node` /
67
+ `Element` / `Text` / `DocumentFragment` is a `ReferenceError`, not `false`.
68
+ `isNode(v)` / `isElement(v)` test `nodeType` structurally (and also accept nodes
69
+ from another realm, which the constructor identity check never did):
70
+ ```js
71
+ import { isNode, isElement } from '../../core/dom.js';
72
+ if (isNode(result)) cell.replaceChildren(result); // renderer output
73
+ const row = isElement(e.target) ? e.target.closest('…') : null; // event target
74
+ ```
75
+ `isElement` is deliberately the SUPERSET of a former `instanceof HTMLElement` (it
76
+ admits SVG elements too) — every swept site only needed "an element", none needed
77
+ "an HTML element specifically". Absent-method form of the same idea:
78
+ `typeof this.getBoundingClientRect === 'function' && …` (nav-ui) — linkedom has no
79
+ rect API at all, so an unguarded call is a TypeError, not a zero rect (that zero-rect
80
+ case is §3).
81
+
54
82
  **The `this.internals` special case — a shim, not `undefined`.** `UIElement`'s
55
83
  constructor (`packages/web-components/core/element.js`) needed a DIFFERENT answer
56
84
  than "guard and leave unset," because ~12 files across the framework call
@@ -1,5 +1,15 @@
1
1
  # SSR support — status ledger
2
2
 
3
+ **Filing a new downstream SSR-seam report?** Use the `ssr` label plus
4
+ [`.github/ISSUE_TEMPLATE/ssr-seam-report.md`](../../../../../../.github/ISSUE_TEMPLATE/ssr-seam-report.md)
5
+ (gh#1384) — it captures the resolved `@adia-ai/web-components` version, the
6
+ downstream kit version, the component + fixture, an SSR-vs-CSR diff or
7
+ minimal repro, and a cosmetic/behavioral/blocked-upstream classification, so
8
+ a report lands triageable instead of free-form (the archaeology the #284/#288
9
+ rows below needed). `docs/ops/spec/spec-ssr-kit.md` is referenced by some
10
+ downstream reports but does not exist in this repo — it lives in adiav2's own
11
+ repo (`@adiav2/ssr-kit`'s spec); do not look for it here.
12
+
3
13
  **This file drifts fast — re-verify against the live issue tracker
4
14
  (`gh issue view <n>`) before trusting it for anything beyond a quick orientation.**
5
15
  Every row was true as of 2026-07-17; a closed issue below may have re-opened, a PR
@@ -9,6 +19,9 @@ the territory — the territory is `gh issue list` / `gh issue view`.
9
19
  | Issue | Failure shape | Status | Shipped in | Notes |
10
20
  |---|---|---|---|---|
11
21
  | [#285](https://github.com/adiahealth/gen-ui-kit/issues/285) | §1 — browser-only API called unconditionally | **CLOSED** | PR #292 (merged 2026-07-17) | `UIElement` constructor + `adoptStyles()` + 22 component/trait/module files guarded; 10 more already correct from an earlier pass |
22
+ | [#1430](https://github.com/adiahealth/gen-ui-kit/issues/1430) | §1 — browser-only API called unconditionally (module scope: `window.matchMedia` behind a `typeof window` guard) | **FIXED 2026-08-17** | `core/responsive.js` feature-detects `matchMedia` + `innerWidth`; `swiper.class.js` `play()` guarded; `scripts/dev/ssr-linkedom-smoke.mjs` + `test/ssr-linkedom-smoke.test.js` gate the whole catalog under linkedom | 6 components (`block`/`col`/`grid`/`row`/`text`/`demo-toggle`) were un-importable server-side; 118→124 of 124. Side findings on the issue: gh#285's "shim no longer necessary" over-reached (two of the consumer's shim sections patch `custom-elements-ssr` itself, not this library — see `consumer-workarounds.md`); `audit-template-child-conflict.mjs` isn't shipped in the package. |
23
+ | [#1436](https://github.com/adiahealth/gen-ui-kit/issues/1436) | §1 — bare `instanceof Node/Element/HTMLElement` (ReferenceError under linkedom, only `HTMLElement` is global) | **FIXED 2026-08-17** | `core/dom.js` `isNode()`/`isElement()`; 15 sites swept; plus rAF/MutationObserver/getBoundingClientRect guards in feed, noodles, preview, toolbar, nav-group, stepper, nav found by the same gate's render sweep | Same PR as #1430 — one root-cause class. |
24
+ | [#1435](https://github.com/adiahealth/gen-ui-kit/issues/1435) | reported as §2 — check/radio/switch "lose" a `<span slot="label">` child | **NOT an SSR seam** (2026-08-17) | yaml/a2ui `slots` descriptions clarified for the three (separate follow-up PR — a docs-contract change, not a shape-1 guard) | The fixture is not a supported form in CSR either: `label` on all three is ATTRIBUTE-driven (`label="…"` → `aria-label` + `::after content: attr(label)` / template-owned span); a late CSR upgrade replaces the foreign child the same way. The yaml `slots:` entries name template-OWNED anatomy parts (`box`/`dot`/`track`/`thumb`/`label`), which a downstream reimplementation of the audit read as consumer insertion points — the audit's own definition (`slots.default` + non-null template) is what "consumer children" means here. |
12
25
  | [#286](https://github.com/adiahealth/gen-ui-kit/issues/286) | §3 — connect-time measurement treated as confirmed | **CLOSED** (for `admin-sidebar` specifically) | PR #290 (merged 2026-07-17) | The GENERAL pattern (any other component reading a rect/size synchronously at connect) is NOT swept — only this one instance is fixed |
13
26
  | [#284](https://github.com/adiahealth/gen-ui-kit/issues/284) | §2 — destructive `stamp()` on connect | **CLOSED 2026-07-18 — narrowed AND separately fixed** | `scripts/dev/audit-template-child-conflict.mjs` (PR #295) + `packages/web-components/core/element.js` connectedCallback resync (PR #309) | Two distinct findings. (1) The ORIGINAL diagnosis (destructive `stamp()`) was narrowed 2026-07-17: every component the issue names has `static template = () => null`, so `stamp()` never runs for them — zero shipped components exposed; a static audit gate catches a future regression instead of a lifecycle rewrite. (2) A SEPARATE, real, live bug was found in the same investigation area and fixed 2026-07-18: happy-dom/linkedom don't replay `attributeChangedCallback` for attributes already present at custom-element upgrade (spec §4.13.5 step 6) — any `reflect: true` property seeded only from pre-parsed/SSR HTML stayed at its class default after upgrade (e.g. `<nav-item-ui text="Profile">` rendered with an empty label). `connectedCallback` now re-syncs every declared property from its live attribute before `connected()` runs. See `failure-shapes.md` §2 for the full narrative. |
14
27
  | [#288](https://github.com/adiahealth/gen-ui-kit/issues/288) | §4 — property-only components can't seed from SSR HTML | **CLOSED 2026-07-18** | `table.class.js` `data="[…]"` attribute hydration | Never actually blocked on #284 (table-ui/chart-ui/select-ui all use `static template = () => null`, so the stamp() question never applied). Scope was narrower than filed: `select-ui` already parsed declarative `<option>` children, `chart-ui` already hydrated `.data` from a `data="[…]"` attribute — only `table-ui`'s `.data` had no declarative form. Fixed in the same attribute-hydration shape as chart-ui, not the JSON-script-child form originally proposed. |
@@ -1,10 +1,24 @@
1
- # Testing an SSR gap without a real linkedom install
1
+ # Testing an SSR gap the linkedom gate first, then the deletion pattern
2
2
 
3
- This repo has **no `linkedom` devDependency** (checked 2026-07-17: not in
4
- `package.json` at any workspace level). The test suite runs on `happy-dom`/`jsdom`
5
- (whichever `vitest` is configured with), and BOTH of those implement most of the APIs
6
- linkedom lacks so a naive test using the default environment will not reproduce an
7
- SSR-shaped bug. Two consequences:
3
+ **UPDATE 2026-08-17 (gh#1430/#1436): this repo now HAS a `linkedom` root
4
+ devDependency and a shipped shim gate.** `scripts/dev/ssr-linkedom-smoke.mjs`
5
+ installs exactly the six globals `custom-elements-ssr/server-shim.js` installs
6
+ (document, window, customElements, HTMLElement, Event, CustomEvent and nothing
7
+ else: no Node/Element/Text/DocumentFragment, no matchMedia, no
8
+ requestAnimationFrame, no getBoundingClientRect), imports every
9
+ `components/*/*.js` entry, and constructs + connects every registered tag the way
10
+ `CustomElementRender` does; `packages/web-components/test/ssr-linkedom-smoke.test.js`
11
+ runs it as a Node child process and fails on any import or render throw. **For a
12
+ shape-1 question ("does this crash under SSR"), run that first** — `node
13
+ scripts/dev/ssr-linkedom-smoke.mjs` — it is the consumer's environment, not an
14
+ approximation. Add a row to its `FIXTURES` table when a guard lands on a branch only
15
+ some attribute value reaches (`swiper-ui[autoplay]` is the model). The rest of this
16
+ file is the UNIT-level pattern for a focused regression test in the happy-dom
17
+ suite; it stays valid, but it is no longer the only proof available.
18
+
19
+ The main test suite still runs on `happy-dom` (`vitest.config.js`), which
20
+ implements most of the APIs linkedom lacks — so a naive test using the default
21
+ environment will not reproduce an SSR-shaped bug. Two consequences:
8
22
 
9
23
  1. **You cannot trust "the tests pass" as proof an SSR fix works** unless the test
10
24
  itself removes the API under test. A guard around `attachInternals` that's never
@@ -118,12 +132,13 @@ actually present. Both gh#285 and gh#286's fixes were verified this second way b
118
132
  shipping (a real `ElementInternals` instance still used and functional; a real
119
133
  `ResizeObserver` tick still corrects state) — do both, never one instead of the other.
120
134
 
121
- ## If a deeper simulation becomes worth the cost
135
+ ## The deeper simulation DONE 2026-08-17
122
136
 
123
- A real `linkedom` install (as a devDependency, used only in a dedicated SSR-simulation
124
- test file, never in the main suite) would let a test actually import `linkedom`,
125
- create a document with it, and run REAL component code against REAL absent APIs
126
- end-to-end closer to the consumer's actual failure mode than deletion, and the only
127
- way to test shape 2/4 meaningfully. This has not been done as of 2026-07-17 — it's a
128
- tooling investment, not a quick addition; raise it as its own decision if shape 2's
129
- investigation (gh#284) needs it.
137
+ The real-`linkedom` simulation this section used to propose is now the shipped
138
+ gate described at the top of this file (`scripts/dev/ssr-linkedom-smoke.mjs`,
139
+ `linkedom` pinned as a root devDependency, used only there the main suite stays
140
+ on happy-dom). It sweeps import + connect for the whole catalog; a shape-2/4
141
+ question (content preserved across a late upgrade, declarative data seeding) is
142
+ still answered by a targeted test extend the script's `FIXTURES` table with the
143
+ attrs/children in question, or write the assertion in
144
+ `test/ssr-upgrade-adoption-contract.test.js`'s late-upgrade shape.