@adia-ai/adia-ui-forge 0.8.62 → 0.8.64

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 (143) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +3 -3
  3. package/CHANGELOG.md +317 -275
  4. package/README.md +54 -18
  5. package/__init__.py +8 -8
  6. package/agents/a2ui-maintenance-agent.corpus.json +7 -7
  7. package/agents/a2ui-maintenance-agent.md +6 -6
  8. package/agents/a2ui-planner-agent.md +11 -11
  9. package/agents/demo-audit-agent.corpus.json +4 -4
  10. package/agents/demo-audit-agent.md +8 -9
  11. package/agents/framework-checker.md +10 -11
  12. package/agents/framework-planner-agent.md +9 -10
  13. package/agents/gen-ui-review-agent.corpus.json +4 -4
  14. package/agents/gen-ui-review-agent.md +8 -8
  15. package/agents/package-release-agent.corpus.json +4 -4
  16. package/agents/package-release-agent.md +9 -10
  17. package/agents/primitive-authoring-agent.corpus.json +1 -1
  18. package/agents/primitive-authoring-agent.md +5 -5
  19. package/agents/routing-corpus.json +46 -46
  20. package/codex.interface.json +1 -1
  21. package/commands/gen-ui-review.md +1 -1
  22. package/commands/package-release.md +2 -2
  23. package/package.json +1 -1
  24. package/plugin.yaml +2 -2
  25. package/prompts/gen-ui-review.md +1 -1
  26. package/prompts/package-release.md +2 -2
  27. package/references/contracts/a2ui-mcp-surface.md +7 -7
  28. package/references/contracts/migration-guide-format.md +6 -7
  29. package/references/shared/content-trust.md +3 -5
  30. package/skills/a2ui-maintenance/SKILL.md +13 -16
  31. package/skills/a2ui-maintenance/evals/evals.json +2 -2
  32. package/skills/a2ui-maintenance/evals/routing-corpus.json +1 -1
  33. package/skills/a2ui-maintenance/references/anti-patterns.md +10 -10
  34. package/skills/a2ui-maintenance/references/chunk-authoring.md +11 -12
  35. package/skills/a2ui-maintenance/references/corpus-discipline.md +13 -14
  36. package/skills/a2ui-maintenance/references/data-model-reactivity.md +22 -25
  37. package/skills/a2ui-maintenance/references/eval-diagnostics.md +21 -21
  38. package/skills/a2ui-maintenance/references/format-extension-decisions.md +12 -13
  39. package/skills/a2ui-maintenance/references/leverage-rules.md +6 -6
  40. package/skills/a2ui-maintenance/references/mcp-pipeline-ops.md +9 -10
  41. package/skills/a2ui-maintenance/references/mcp-tool-reference.md +11 -12
  42. package/skills/a2ui-maintenance/references/pipeline-overview.md +33 -38
  43. package/skills/a2ui-maintenance/references/semantic-fail-lifting.md +13 -14
  44. package/skills/a2ui-maintenance/references/strategy-engines.md +14 -16
  45. package/skills/a2ui-maintenance/references/surface-lifecycle.md +10 -11
  46. package/skills/a2ui-maintenance/references/zettel-calibration.md +14 -16
  47. package/skills/component-md-authoring/SKILL.md +22 -22
  48. package/skills/component-md-authoring/agents/openai.yaml +1 -1
  49. package/skills/cross-harness-authoring-standards/SKILL.md +19 -21
  50. package/skills/cross-harness-authoring-standards/agents/openai.yaml +1 -1
  51. package/skills/cross-harness-compatibility-standards/SKILL.md +35 -34
  52. package/skills/cross-harness-compatibility-standards/agents/openai.yaml +1 -1
  53. package/skills/demo-audit/SKILL.md +38 -38
  54. package/skills/demo-audit/evals/routing-corpus.json +1 -1
  55. package/skills/demo-audit/references/admin-shell-anatomy.md +21 -25
  56. package/skills/demo-audit/references/app-shell-pitfalls.md +11 -13
  57. package/skills/demo-audit/references/auto-fix-allowlist.md +3 -3
  58. package/skills/demo-audit/references/card-anatomy-sweep.md +10 -11
  59. package/skills/demo-audit/references/chat-shell-anatomy.md +25 -26
  60. package/skills/demo-audit/references/editor-shell-anatomy.md +25 -28
  61. package/skills/demo-audit/references/html-attr-sweep.md +12 -13
  62. package/skills/demo-audit/references/mode7-status-battery.md +4 -4
  63. package/skills/demo-audit/references/native-leak-annotations.md +8 -8
  64. package/skills/demo-audit/references/output-contract-worked-example.md +8 -8
  65. package/skills/demo-audit/references/probe-discipline-and-escalation.md +2 -2
  66. package/skills/demo-audit/references/visual-probe-triage.md +21 -23
  67. package/skills/gen-ui-review/SKILL.md +28 -28
  68. package/skills/gen-ui-review/agents/openai.yaml +1 -1
  69. package/skills/gen-ui-review/evals/evals.json +1 -1
  70. package/skills/gen-ui-review/evals/routing-corpus.json +1 -1
  71. package/skills/gen-ui-review/references/corpus-html-patterns.md +28 -29
  72. package/skills/gen-ui-review/references/exit-gate-mechanics.md +5 -5
  73. package/skills/gen-ui-review/references/lookup-maintenance.md +2 -2
  74. package/skills/gen-ui-review/references/loop-protocol.md +25 -25
  75. package/skills/gen-ui-review/references/rubric-cosmetic.md +10 -10
  76. package/skills/gen-ui-review/references/rubric-decompose.md +15 -16
  77. package/skills/gen-ui-review/references/rubric-score.md +23 -24
  78. package/skills/gen-ui-review/references/scorecard-worked-examples.md +12 -12
  79. package/skills/gen-ui-review/references/scores.schema.json +2 -2
  80. package/skills/llm-client-maintenance/SKILL.md +12 -12
  81. package/skills/llm-client-maintenance/evals/routing-corpus.json +1 -1
  82. package/skills/llm-client-maintenance/references/adapter-contract.md +27 -27
  83. package/skills/llm-client-maintenance/references/add-a-provider.md +25 -25
  84. package/skills/llm-client-maintenance/references/bridge-facade.md +31 -31
  85. package/skills/llm-client-maintenance/references/browser-proxy-boundary.md +17 -17
  86. package/skills/llm-client-maintenance/references/model-registry.md +14 -14
  87. package/skills/llm-client-maintenance/references/streaming-sse.md +21 -21
  88. package/skills/package-release/SKILL.md +21 -23
  89. package/skills/package-release/evals/evals.json +2 -2
  90. package/skills/package-release/evals/routing-corpus.json +2 -2
  91. package/skills/package-release/references/authorization-model.md +6 -8
  92. package/skills/package-release/references/changelog-discipline.md +25 -27
  93. package/skills/package-release/references/cut-procedure.md +129 -136
  94. package/skills/package-release/references/gates-catalog.md +42 -43
  95. package/skills/package-release/references/independent-package-release.md +14 -15
  96. package/skills/package-release/references/invariants-detail.md +9 -9
  97. package/skills/package-release/references/mechanization.md +11 -12
  98. package/skills/package-release/references/migration-guide-authoring.md +20 -20
  99. package/skills/package-release/references/notes-authoring.md +20 -20
  100. package/skills/package-release/references/recovery-paths.md +28 -28
  101. package/skills/primitive-authoring/SKILL.md +19 -19
  102. package/skills/primitive-authoring/agents/openai.yaml +1 -1
  103. package/skills/primitive-authoring/evals/evals.json +2 -2
  104. package/skills/primitive-authoring/evals/routing-corpus.json +2 -2
  105. package/skills/primitive-authoring/references/INDEX.md +22 -22
  106. package/skills/primitive-authoring/references/anti-patterns.md +76 -77
  107. package/skills/primitive-authoring/references/api-contract.md +63 -65
  108. package/skills/primitive-authoring/references/authoring-cycle.md +47 -50
  109. package/skills/primitive-authoring/references/canonical-pattern-index.md +118 -118
  110. package/skills/primitive-authoring/references/code-style.md +50 -52
  111. package/skills/primitive-authoring/references/common-gotchas.md +23 -23
  112. package/skills/primitive-authoring/references/composite-demo-protocol.md +42 -45
  113. package/skills/primitive-authoring/references/css-patterns.md +97 -97
  114. package/skills/primitive-authoring/references/for-attribute-event-contract.md +12 -12
  115. package/skills/primitive-authoring/references/form-control-sizing.md +16 -18
  116. package/skills/primitive-authoring/references/lifecycle-patterns.md +42 -42
  117. package/skills/primitive-authoring/references/llm-bridge.md +21 -21
  118. package/skills/primitive-authoring/references/module-promotion.md +51 -51
  119. package/skills/primitive-authoring/references/primitive-audit.md +16 -16
  120. package/skills/primitive-authoring/references/shell-patterns.md +81 -81
  121. package/skills/primitive-authoring/references/svg-authoring.md +62 -66
  122. package/skills/primitive-authoring/references/token-contract.md +45 -49
  123. package/skills/primitive-authoring/references/trait-pages.md +8 -8
  124. package/skills/primitive-authoring/references/worked-example.md +39 -39
  125. package/skills/primitive-authoring/references/yaml-contract.md +139 -143
  126. package/skills/site-deployment/SKILL.md +25 -26
  127. package/skills/site-deployment/agents/openai.yaml +1 -1
  128. package/skills/site-deployment/evals/routing-corpus.json +1 -1
  129. package/skills/site-deployment/references/deploy-playbooks.md +51 -53
  130. package/skills/site-docs-authoring/SKILL.md +14 -14
  131. package/skills/site-docs-authoring/agents/openai.yaml +1 -1
  132. package/skills/site-docs-authoring/evals/evals.json +1 -1
  133. package/skills/site-docs-authoring/evals/routing-corpus.json +1 -1
  134. package/skills/site-docs-authoring/intent.md +18 -18
  135. package/skills/ssr-compatibility/SKILL.md +24 -25
  136. package/skills/ssr-compatibility/agents/openai.yaml +1 -1
  137. package/skills/ssr-compatibility/evals/evals.json +1 -1
  138. package/skills/ssr-compatibility/evals/routing-corpus.json +1 -1
  139. package/skills/ssr-compatibility/references/consumer-workarounds.md +22 -23
  140. package/skills/ssr-compatibility/references/failure-shapes.md +84 -91
  141. package/skills/ssr-compatibility/references/guard-patterns.md +54 -58
  142. package/skills/ssr-compatibility/references/status-ledger.md +17 -17
  143. package/skills/ssr-compatibility/references/test-without-linkedom.md +26 -27
@@ -1,206 +1,200 @@
1
- # SSR failure shapes symptom → root cause → status
1
+ # SSR failure shapes, symptom → root cause → status
2
2
 
3
- The §-numbered root-cause classes below (count the `##` headings the list
3
+ The §-numbered root-cause classes below (count the `##` headings, the list
4
4
  grows) have surfaced from real SSR consumers (adiav2's
5
5
  `admin-portal-fe` and `factory-dashboard`, server-rendering AdiaUI via Astro 5 +
6
- `custom-elements-ssr`, which runs on linkedom a DOM shim with no layout engine and
6
+ `custom-elements-ssr`, which runs on linkedom, a DOM shim with no layout engine and
7
7
  missing many browser APIs). A new SSR bug report almost always maps onto one of
8
8
  them; misclassifying it (e.g. treating a measurement-timing bug as a missing-API
9
9
  bug) sends the fix to the wrong place. Check symptom against this table first.
10
10
 
11
11
  ## 1 · Browser-only API called unconditionally → crash
12
12
 
13
- **Symptom:** the SSR pass throws often at construction, before any component-specific
13
+ **Symptom:** the SSR pass throws, often at construction, before any component-specific
14
14
  code runs. Stack trace points at `attachInternals`, `new ResizeObserver(...)`, `new
15
15
  IntersectionObserver(...)`, `new MutationObserver(...)`, `new PerformanceObserver(...)`,
16
16
  or a write to `document.adoptedStyleSheets`.
17
17
 
18
- **Root cause:** the call site assumes the API exists. linkedom implements none of these
19
- not a partial/quirky implementation, an absence. Any unconditional call throws
18
+ **Root cause:** the call site assumes the API exists. linkedom implements none of these, not a partial/quirky implementation, an absence. Any unconditional call throws
20
19
  `TypeError` or `ReferenceError` (undefined global) immediately.
21
20
 
22
- **Status: FIXED, twice and now GATED.** First wave gh#285 (PR #292, merged
21
+ **Status: FIXED, twice, and now GATED.** First wave gh#285 (PR #292, merged
23
22
  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
23
+ plus a sweep of component/trait/module files: the per-file tally is
25
24
  [`status-ledger.md`](status-ledger.md)'s #285 row (the ledger, not this line, is
26
25
  the count of record). Second wave gh#1430 + gh#1436 (2026-08-17, found by adiav2's
27
26
  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 !==
27
+ - **environment-detection instead of feature-detection at MODULE scope**, `core/responsive.js` guarded `window.matchMedia(...)` behind `typeof window !==
30
28
  'undefined'`; linkedom HAS a `window`, just no `matchMedia` and no numeric
31
29
  `innerWidth`, so six components (`block`/`col`/`grid`/`row`/`text`/`demo-toggle`)
32
30
  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
31
+ - **bare `instanceof Node|Element|HTMLElement`** (15 sites, `core/template.js`
32
+ `applyValue`, table/list-window renderer results, event-target checks), a
35
33
  `ReferenceError`, not `false`, because custom-elements-ssr installs ONLY
36
34
  `HTMLElement` as a global (never `Node`, `Element`, `Text`, `DocumentFragment`);
37
35
  now `core/dom.js` `isNode()`/`isElement()` (structural `nodeType` tests);
38
36
  - **`requestAnimationFrame` / `MutationObserver` / `getBoundingClientRect` at
39
37
  connect** in feed-item/toast, noodles, preview, toolbar (rAF), nav-group,
40
- stepper (MutationObserver the #292 sweep guarded four Observer sites, not
38
+ stepper (MutationObserver, the #292 sweep guarded four Observer sites, not
41
39
  these two) and nav-ui (`getBoundingClientRect` is absent, not zero, on linkedom).
42
40
 
43
41
  **Why happy-dom could not see any of it:** it implements every one of those APIs.
44
42
  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`
43
+ `packages/web-components/test/ssr-linkedom-smoke.test.js`, a real `linkedom`
46
44
  devDependency installing EXACTLY the six globals `custom-elements-ssr/server-shim.js`
47
45
  installs, then importing every `components/*/*.js` entry and constructing +
48
46
  connecting every registered tag (124/124 import, every tag renders). A new
49
47
  shape-1 instance fails that test, not a consumer's build. The fix pattern
50
48
  (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
49
+ [`guard-patterns.md`](guard-patterns.md), apply it to any NEW call site; don't
52
50
  re-derive the shape from scratch, and don't guard on `typeof window`.
53
51
 
54
52
  ## 2 · `connectedCallback` destructively re-stamps existing DOM → content loss
55
53
 
56
54
  **Symptom (as originally reported):** the component renders in the SSR HTML response
57
- as an EMPTY or STRUCTURALLY WRONG tag not a crash, a silent loss. Two named
55
+ as an EMPTY or STRUCTURALLY WRONG tag, not a crash, a silent loss. Two named
58
56
  variants:
59
57
  - A **container** (`admin-shell`, `admin-sidebar`, `nav-ui`) loses its nested custom-element
60
- children `<nav-item-ui>` inside `<nav-ui>` simply isn't in the response.
58
+ children, `<nav-item-ui>` inside `<nav-ui>` simply isn't in the response.
61
59
  - A **projected-text** leaf (`<text-ui>Adia Admin</text-ui>`, `<avatar-ui>A</avatar-ui>`,
62
- `<badge-ui>warning</badge-ui>`) goes out as an empty tag the authored text is gone.
60
+ `<badge-ui>warning</badge-ui>`) goes out as an empty tag: the authored text is gone.
63
61
 
64
62
  **Root cause (still real):** `UIElement.connectedCallback` (`packages/web-components/core/element.js`)
65
63
  runs `stamp(result, this)` (the `stamp` function in `packages/web-components/core/template.js`,
66
- currently at line 194) with the OWN template's output this OVERWRITES the element's
64
+ currently at line 194) with the OWN template's output, this OVERWRITES the element's
67
65
  existing children rather than adopting/patching them. In a real browser this is
68
66
  correct and invisible: nothing exists inside the element yet at first connect. Under
69
67
  SSR, `connectedCallback` runs a SECOND TIME against the server-parsed DOM (which
70
- already has real, meaningful children from the HTML response) the stamp silently
68
+ already has real, meaningful children from the HTML response), the stamp silently
71
69
  replaces them.
72
70
 
73
- **Status: NARROWED, downgraded 2026-07-17** (gh#284). Verified against 0.8.4 the
71
+ **Status: NARROWED, downgraded 2026-07-17** (gh#284). Verified against 0.8.4: the
74
72
  mechanism is real (a synthetic repro, a component with a non-null template AND
75
73
  projected text, does lose it), but **every named example in the issue currently
76
74
  has `static template = () => null`**: `admin-shell`, `admin-sidebar`, `nav-ui`,
77
75
  `text-ui`, `badge-ui`, `avatar-ui` all skip `stamp()` entirely (`if (result)
78
- stamp(result, this)` `null` never enters the branch). A framework-wide survey
76
+ stamp(result, this)`, `null` never enters the branch). A framework-wide survey
79
77
  (150 components at the time) found every component with a NON-null template derives its
80
78
  visible content from properties/attributes only (`check-ui`'s `label=`,
81
- `switch-ui`'s `label=`/`hint=`, `skip-nav`'s `text=`) never from light-DOM
82
- children so the destructive replace, where it does fire, only ever regenerates
79
+ `switch-ui`'s `label=`/`hint=`, `skip-nav`'s `text=`), never from light-DOM
80
+ children, so the destructive replace, where it does fire, only ever regenerates
83
81
  identical, template-owned content. Zero shipped components are exposed to the
84
82
  originally-reported symptom right now.
85
83
 
86
84
  **What shipped instead of a `stamp()` fix:** the two candidate directions the issue
87
85
  named (adopt-and-patch, or skip-on-marker) were explicitly rejected as
88
- disproportionate "changes the render lifecycle of every primitive in the
89
- framework (127+), not a mechanical guard sweep" for a risk with zero current
86
+ disproportionate, "changes the render lifecycle of every primitive in the
87
+ framework (127+), not a mechanical guard sweep", for a risk with zero current
90
88
  instances. Shipped a forward-looking STATIC AUDIT instead:
91
- `scripts/dev/audit-template-child-conflict.mjs` flags (critical) any future
89
+ `scripts/dev/audit-template-child-conflict.mjs`, flags (critical) any future
92
90
  component pairing a non-null `static template` with a yaml `slots.default` entry
93
91
  (the container shape), and (advisory) a non-null template paired with a body-text
94
92
  usage example (the projected-text shape, no mechanical yaml signal to check). Wired
95
93
  into `primitive-authoring`'s structural-gate sequence. This is an operator ruling (not a
96
- unilateral call) see gh#284's comment thread for the full reasoning and the
94
+ unilateral call), see gh#284's comment thread for the full reasoning and the
97
95
  empirical survey it's based on.
98
96
 
99
97
  **Current consumer workaround still exists but may be over-conservative:**
100
98
  `adiav2`'s SSR component registration is restricted to attribute-only-content
101
- components (`button-ui text="…"`, `icon-ui name="…"`) per this narrowing, EVERY
99
+ components (`button-ui text="…"`, `icon-ui name="…"`), per this narrowing, EVERY
102
100
  currently-shipped component (container or leaf) with real content already
103
101
  qualifies as attribute/property-driven, so the restriction may no longer be
104
102
  necessary for content-loss reasons. Don't assume this without the consumer
105
- confirming it via the issue thread see [`consumer-workarounds.md`](consumer-workarounds.md).
103
+ confirming it via the issue thread, see [`consumer-workarounds.md`](consumer-workarounds.md).
106
104
 
107
- **UPDATE 2026-07-18 a DIFFERENT, real bug was found and fixed in the same
105
+ **UPDATE 2026-07-18, a DIFFERENT, real bug was found and fixed in the same
108
106
  investigation area (gh#284's comment thread, PR #309).** The narrowing above
109
- rules out `stamp()`'s destructive replace as a live risk but it does NOT mean
107
+ rules out `stamp()`'s destructive replace as a live risk, but it does NOT mean
110
108
  attribute/property-driven components were actually safe under a late/SSR
111
109
  upgrade. They weren't, for an unrelated reason: the custom-elements spec's
112
110
  "upgrade an element" algorithm (§4.13.5 step 6) requires replaying
113
111
  `attributeChangedCallback` for every attribute already present on an element
114
- BEFORE `connectedCallback` fires on upgrade and happy-dom (this repo's test
112
+ BEFORE `connectedCallback` fires on upgrade, and happy-dom (this repo's test
115
113
  DOM) skips that replay entirely, confirmed with a bare, framework-free custom
116
114
  element, no AdiaUI code involved. linkedom (the real SSR consumer's shim) is a
117
115
  similarly from-scratch custom-elements registry, so the same gap is expected
118
- there too. Net effect: `<nav-item-ui text="Profile">` exactly the
119
- attribute-driven shape this narrowing said was safe rendered with an EMPTY
116
+ there too. Net effect: `<nav-item-ui text="Profile">`, exactly the
117
+ attribute-driven shape this narrowing said was safe, rendered with an EMPTY
120
118
  label after a late upgrade, because `this.text` never got initialized from the
121
119
  attribute at all. **Fixed**: `UIElement.connectedCallback` now re-syncs every
122
120
  declared property from its live attribute value before `connected()` runs
123
- (`packages/web-components/core/element.js`) see
121
+ (`packages/web-components/core/element.js`), see
124
122
  [`guard-patterns.md`](guard-patterns.md) §2b for the fix shape. Regression
125
123
  tests: `packages/web-components/core/element.test.js`,
126
- `describe('UIElement SSR attribute-upgrade replay (gh#284)')`.
124
+ `describe('UIElement: SSR attribute-upgrade replay (gh#284)')`.
127
125
 
128
126
  This means the FULL current picture for shape 2 is: destructive `stamp()`
129
127
  re-mounting is real-but-latent (static audit catches a future regression);
130
128
  the attribute-upgrade-replay gap was real-and-live (now fixed in #309). A
131
129
  future "content vanished under SSR" report should check the attribute-replay
132
- mechanism FIRST it's the one that was actually firing.
130
+ mechanism FIRST: it's the one that was actually firing.
133
131
 
134
- ### Worked example answering a new "content vanished" report
132
+ ### Worked example, answering a new "content vanished" report
135
133
 
136
134
  **Ask:** "`<text-ui>Adia Admin</text-ui>` renders as an empty tag in our SSR
137
- output is this a known issue?"
135
+ output, is this a known issue?"
138
136
 
139
137
  **Answer:** It matches shape 2's SYMPTOM (`connectedCallback` destructively
140
- re-stamping existing DOM), but shape 2 was narrowed on 2026-07-17 verify
138
+ re-stamping existing DOM), but shape 2 was narrowed on 2026-07-17, verify
141
139
  against the CURRENT code before reusing the old answer, because this is
142
140
  exactly the case it no longer covers. `text-ui`'s `static template`
143
- (`packages/web-components/components/text/text.class.js`) is `() => null`
144
- `connectedCallback`'s `if (result) stamp(result, this)` never enters the
141
+ (`packages/web-components/components/text/text.class.js`) is `() => null`, `connectedCallback`'s `if (result) stamp(result, this)` never enters the
145
142
  branch, so `stamp()` never touches `<text-ui>`'s children at all. This
146
- component isn't exposed to shape 2; something else is dropping the text
147
- check whether `text-ui` is even registered server-side (a different,
143
+ component isn't exposed to shape 2; something else is dropping the text, check whether `text-ui` is even registered server-side (a different,
148
144
  structural gap: is the tag defined before the SSR pass runs?), or whether
149
145
  another mutation (a parent re-render, `innerHTML` elsewhere) is clearing
150
146
  it. **The general lesson, not just this one component:** before answering
151
147
  "yes, known issue, shape 2" for ANY new report, grep the component's own
152
- `static template` if it's the literal `() => null`, shape 2 cannot be the
148
+ `static template`, if it's the literal `() => null`, shape 2 cannot be the
153
149
  cause, no matter how closely the symptom matches the old description. This
154
- survey (150 components at the 2026-07 survey the census has since grown)
150
+ survey (150 components at the 2026-07 survey, the census has since grown)
155
151
  cites exactly why every current children-accepting component is unaffected.
156
152
 
157
153
  ## 3 · A connect-time layout MEASUREMENT is meaningless before real layout exists
158
154
 
159
155
  **Symptom:** a component makes a decision (a boolean state, a mode, a snapped value)
160
156
  by synchronously reading `getBoundingClientRect()` (or similar) inside `connected()`,
161
- and that decision comes out WRONG not crashed, not empty, just incorrect in any
157
+ and that decision comes out WRONG, not crashed, not empty, just incorrect, in any
162
158
  environment where real layout hasn't happened yet. This includes linkedom (no layout
163
- engine at all always returns a 0×0 rect) but ALSO a real browser connecting an
164
- element before its first layout pass (inside a `display:none` ancestor, for instance)
165
- SSR is the environment that surfaces it reliably, but the bug is not SSR-specific.
159
+ engine at all, always returns a 0×0 rect) but ALSO a real browser connecting an
160
+ element before its first layout pass (inside a `display:none` ancestor, for instance), SSR is the environment that surfaces it reliably, but the bug is not SSR-specific.
166
161
 
167
- **Root cause:** treating "the rect read 0" as equivalent to "the rect really is 0"
168
- they are NOT the same fact. A zero rect from a shim/pre-layout read means "unknown,"
162
+ **Root cause:** treating "the rect read 0" as equivalent to "the rect really is 0", they are NOT the same fact. A zero rect from a shim/pre-layout read means "unknown,"
169
163
  and a decision derived from "unknown" as if it were "confirmed small" is a category
170
164
  error, not a rendering gap.
171
165
 
172
166
  **Status: FIXED for `admin-sidebar`'s specific instance** (gh#286, PR #290, merged
173
- 2026-07-17) see [`guard-patterns.md`](guard-patterns.md) §3 for the fix shape
167
+ 2026-07-17), see [`guard-patterns.md`](guard-patterns.md) §3 for the fix shape
174
168
  (treat a zero read as unknown, defer to the component's own `ResizeObserver`'s first
175
- real tick). **The general pattern is NOT swept framework-wide** any OTHER component
169
+ real tick). **The general pattern is NOT swept framework-wide**, any OTHER component
176
170
  that derives a persistent decision from a synchronous connect-time measurement
177
171
  carries the same latent bug, undiscovered until someone hits it. If you're
178
172
  investigating a "wrong initial state under SSR" report that ISN'T a missing-API crash
179
173
  (shape 1) or a content-loss (shape 2), check whether the component reads a rect/size
180
174
  synchronously at connect and treat that as the working hypothesis first.
181
175
 
182
- ## 4 · Feature gap, not a bug property-only components can't seed from SSR HTML
176
+ ## 4 · Feature gap, not a bug, property-only components can't seed from SSR HTML
183
177
 
184
- **Symptom:** not a bug report at all a `table-ui`/`chart-ui`/`select-ui` with
178
+ **Symptom:** not a bug report at all, a `table-ui`/`chart-ui`/`select-ui` with
185
179
  programmatic-only content (`.columns`, `.data`, `.options` set as JS properties)
186
180
  renders correctly in the browser but is STRUCTURALLY ABSENT from the SSR HTML response,
187
181
  because JS property assignments don't serialize into server-rendered markup. The
188
182
  component pops in empty and fills in after a post-hydration wiring script runs.
189
183
 
190
184
  **Root cause:** no declarative (HTML-serializable) form of the data these components
191
- need exists yet property-only content is a deliberate API shape for CONSUMERS with a
185
+ need exists yet, property-only content is a deliberate API shape for CONSUMERS with a
192
186
  live client, not a gap in any individual component's code.
193
187
 
194
188
  **Status: CLOSED 2026-07-18** (gh#288). The premise that this was blocked on shape
195
- 2/2b never held `table-ui`/`chart-ui`/`select-ui` all use `static template = ()
189
+ 2/2b never held, `table-ui`/`chart-ui`/`select-ui` all use `static template = ()
196
190
  => null`, so neither the narrowed stamp() mechanism nor its later fix ever bore on
197
191
  this at all. On investigation the scope was also narrower than filed:
198
192
 
199
193
  - **`select-ui`** already parsed native `<option>`/`<optgroup>` children
200
- declaratively at connect (`#parseOptions()`, `select.class.js`) plain HTML,
194
+ declaratively at connect (`#parseOptions()`, `select.class.js`), plain HTML,
201
195
  serializes into SSR output fine. No gap, no fix needed.
202
196
  - **`chart-ui`** already hydrated `.data` from a JSON-array `data="[…]"` HTML
203
- attribute at connect (`chart.class.js` `connected()`) shipped in earlier
197
+ attribute at connect (`chart.class.js` `connected()`), shipped in earlier
204
198
  work, just never reconciled against this issue.
205
199
  - **`table-ui`** was the actual gap: `.columns` had `<col-def>` children as its
206
200
  declarative form, but `.data` (row records) had none. Fixed in the SAME shape
@@ -210,18 +204,18 @@ this at all. On investigation the scope was also narrower than filed:
210
204
  a real programmatic `.data =` set before connect always wins. Works together
211
205
  with `<col-def>` children for a fully static-HTML table. Tests:
212
206
  `packages/web-components/components/table/table.test.js`,
213
- `describe('table-ui declarative data="[…]" attribute (gh#288)')`.
207
+ `describe('table-ui: declarative data="[…]" attribute (gh#288)')`.
214
208
 
215
- ## 5 · A custom render path unconditionally rebuilds a subtree that already matches SSR adopt-in-place
209
+ ## 5 · A custom render path unconditionally rebuilds a subtree that already matches, SSR adopt-in-place
216
210
 
217
- **Symptom:** distinct from shape 2 this is not `stamp()`'s destructive
211
+ **Symptom:** distinct from shape 2: this is not `stamp()`'s destructive
218
212
  replace (shape 2 is a `static template` mechanism, narrowed to zero live
219
213
  instances). This is a component with `static template = () => null` whose
220
214
  OWN hand-written `render()`/`connected()` still unconditionally
221
215
  `replaceChild()`s or `setAttribute()`s every position on every invocation,
222
216
  including the very first upgrade render against a byte-identical
223
217
  server-rendered subtree. Not a crash (shape 1), not empty content (shape 2),
224
- not a wrong measurement (shape 3) the rendered RESULT is correct, but a
218
+ not a wrong measurement (shape 3): the rendered RESULT is correct, but a
225
219
  server-rendered subtree that already matched it gets torn down and rebuilt
226
220
  anyway, violating a consumer's zero-subtree-mutation adoption contract
227
221
  (adiav2's spec-ssr-kit AC-004a) and showing up as spurious host-attribute
@@ -234,77 +228,76 @@ values never actually change.
234
228
  already there. `setAttribute()` queues a mutation record even when the new
235
229
  value is byte-identical to the old one (confirmed directly: happy-dom and
236
230
  linkedom both fire a record on a same-value `setAttribute` call, but never
237
- fire one for a `removeAttribute()` on an already-absent attribute the
238
- spec's own asymmetry) so "the value happens to match" is never enough on
231
+ fire one for a `removeAttribute()` on an already-absent attribute, the
232
+ spec's own asymmetry), so "the value happens to match" is never enough on
239
233
  its own; the write itself has to be skipped.
240
234
  - **No adopt-existing-DOM path at all** for the container-level rebuild
241
- (rows, cells, header) every position gets a fresh node and a
235
+ (rows, cells, header), every position gets a fresh node and a
242
236
  `replaceChild()`, whether or not the existing one is already correct.
243
237
 
244
238
  **Status: implemented for `table-ui` in open PR #1756 (gh#1678), pending
245
- merge not yet fixed/shipped.** `table.class.js`
239
+ merge, not yet fixed/shipped.** `table.class.js`
246
240
  `render()`/`connected()`: a module-level `adoptOrDiffChildren()` helper
247
241
  compares each freshly-computed cell against its existing DOM position via
248
- `Node.isEqualNode()` standard DOM, present under linkedom, happy-dom, and
249
- real browsers alike, never one of the browser-only APIs §1 above guards
250
- and only calls `replaceChild()` on an actual mismatch; a match adopts the
242
+ `Node.isEqualNode()`, standard DOM, present under linkedom, happy-dom, and
243
+ real browsers alike, never one of the browser-only APIs §1 above guards, and only calls `replaceChild()` on an actual mismatch; a match adopts the
251
244
  existing node in place, zero mutation. Host/row-level attribute writes
252
245
  (`role`, `tabindex`, the grid-template-columns inline style, `data-index`,
253
246
  `aria-selected`) go through a `setAttrIfChanged()` guard for the same
254
247
  reason. This generalizes pagination-ui's own first-connect adoption fix
255
- (gh#1687 see `guard-patterns.md` §4) from a flat, keyed item list to an
248
+ (gh#1687, see `guard-patterns.md` §4) from a flat, keyed item list to an
256
249
  arbitrary positional child (a header cell, a row cell) via a value check
257
250
  instead of a shape/key check, since a table cell has no stable identity key
258
251
  of its own the way a pagination item does. Tests:
259
252
  `packages/web-components/components/table/table.test.js`,
260
- `describe('table-ui SSR adopt-or-diff render path (gh#1678)')` a real
253
+ `describe('table-ui: SSR adopt-or-diff render path (gh#1678)')`, a real
261
254
  `MutationObserver` proves zero mutations on a byte-identical upgrade, and a
262
255
  deliberately-corrupted single cell proves the fallback rebuilds ONLY that
263
256
  position, never a wider or a half-adopted rebuild.
264
257
 
265
- ### 5.1 · Structural equality is not sufficient renderer-owned runtime state
258
+ ### 5.1 · Structural equality is not sufficient, renderer-owned runtime state
266
259
 
267
260
  **A second, distinct hazard inside the same fix, found by CodeRabbit on
268
261
  PR #1756 and closed in the same PR before merge.** `Node.isEqualNode()` is a
269
262
  *necessary* adoption test (structurally different nodes obviously can't be
270
263
  adopted) but not a *sufficient* one: it compares tag/attributes/text/
271
- descendants only it has no way to see an event listener a renderer
264
+ descendants only, it has no way to see an event listener a renderer
272
265
  attached to the node it returned. `table.class.js`'s `#updateRow()` runs
273
266
  `col.render()` (an arbitrary consumer-supplied cell renderer) or a built-in
274
267
  cell-type renderer (`typeDef.render`) BEFORE `adoptOrDiffChildren()` ever
275
268
  compares the result. If that renderer attaches a listener to the node it
276
269
  hands back, the candidate can still be structurally byte-identical to the
277
- existing (listener-less, e.g. SSR-parsed) DOM `isEqualNode()` reports a
270
+ existing (listener-less, e.g. SSR-parsed) DOM, `isEqualNode()` reports a
278
271
  match, the guard adopts the OLD node and silently discards the fresh one,
279
272
  and the listener is gone. The pre-fix unconditional-`replaceChild()`
280
273
  behavior never had this bug, because it always installed whatever the
281
274
  renderer had just built.
282
275
 
283
276
  **Fix shape:** a module-level `RENDERER_OWNED` `WeakSet` tags exactly the
284
- candidate cells built by something free to attach runtime state `col.render()`
277
+ candidate cells built by something free to attach runtime state, `col.render()`
285
278
  always (arbitrary code, impossible to introspect for safety), and a
286
279
  built-in cell-type renderer only when its registration explicitly declares
287
280
  `attachesListeners: true` (currently only `cellTypes.actions`, the one
288
- built-in type that calls `addEventListener()` directly see
281
+ built-in type that calls `addEventListener()` directly, see
289
282
  `cell-types.js`). `adoptOrDiffChildren()` always replaces a
290
283
  `RENDERER_OWNED` candidate, never adopts it via the structural-equality
291
284
  path, even on an `isEqualNode()` match. Deliberately NOT tagged: `col.format()`,
292
285
  the plain-text fallback, and every other built-in cell type
293
286
  (text/number/currency/percent/date/datetime/boolean/badge/avatar/link/
294
- markdown/progress) each of those only sets attributes on already-
287
+ markdown/progress), each of those only sets attributes on already-
295
288
  declarative custom elements or plain nodes with no listeners, so tagging
296
289
  them would trade away the zero-mutation benefit for the overwhelming common
297
290
  case with no correctness gain. A first attempt at this fix tagged EVERY
298
291
  `typeDef.render` cell unconditionally and broke the AC-004a zero-mutation
299
- test above for exactly that reason narrowed to the declared-flag form
292
+ test above for exactly that reason, narrowed to the declared-flag form
300
293
  before merge. Test: `table.test.js`, `'a renderer-owned cell (col.render
301
- attaching a listener) stays interactive after an SSR-adopted upgrade'` a
294
+ attaching a listener) stays interactive after an SSR-adopted upgrade'`, a
302
295
  button's click listener, attached inside `col.render()`, still fires after
303
296
  an SSR-parsed (listener-less, structurally identical) upgrade.
304
297
 
305
298
  **The general lesson for any OTHER adopt-or-diff work** (elsewhere in this
306
299
  framework, or a future component): `isEqualNode()`/any purely-structural
307
- diff can only prove a node's DECLARATIVE shape is safe to keep never that
300
+ diff can only prove a node's DECLARATIVE shape is safe to keep, never that
308
301
  its imperative/runtime state (listeners, closures, anything a renderer
309
302
  callback stashed on it) is. A renderer whose output is reused across
310
303
  positions or invocations needs its own explicit "does this renderer attach
@@ -315,7 +308,7 @@ equivalent) rather than assuming structural equality is enough. See
315
308
  **Not (yet) swept framework-wide.** Any OTHER component with a hand-written
316
309
  `render()`/`connected()` that unconditionally rebuilds or re-stamps a
317
310
  subtree carries the same latent gap until it's individually checked against
318
- this shape there is no static audit for this one the way shape 2 has
311
+ this shape, there is no static audit for this one the way shape 2 has
319
312
  `audit-template-child-conflict.mjs`. Check for: a `replaceChild()`/
320
313
  `setAttribute()` call inside a `render()`/`connected()` with no preceding
321
314
  comparison against the existing DOM. The renderer-owned hazard in §5.1 is a
@@ -324,22 +317,22 @@ exists: does any renderer this component invokes attach a listener or
324
317
  other runtime state, and if so, is it excluded from the structural-adopt
325
318
  path the way `RENDERER_OWNED` excludes it here.
326
319
 
327
- ## 6 · The conditional-inject class querySelector-guard-before-innerHTML decision recorded (gh#1678)
320
+ ## 6 · The conditional-inject class, querySelector-guard-before-innerHTML, decision recorded (gh#1678)
328
321
 
329
322
  **Shape:** `connected()` checks for a pre-existing structural child before
330
- stamping one `if (!this.querySelector('input-ui')) { this.innerHTML =
323
+ stamping one, `if (!this.querySelector('input-ui')) { this.innerHTML =
331
324
  …; }` (`search-ui`) or `this.#nav = this.querySelector(':scope >
332
325
  nav[slot="nav"]'); if (!this.#nav) { … create fresh … }` (`pagination-ui`,
333
326
  gh#1687). Two real, already-shipped instances; this is not a hypothetical
334
327
  pattern.
335
328
 
336
329
  **Decision (gh#1678 requirement 3):** this IS the correct, SSR-safe shape
337
- for a component that owns exactly one structural child slot it is the
330
+ for a component that owns exactly one structural child slot: it is the
338
331
  SAME "adopt when it structurally matches, rebuild fresh when it doesn't"
339
332
  principle §5 above ships for table-ui's cells and gh#1687 ships for
340
333
  pagination-ui's item list, one level coarser (a single child, not a keyed
341
334
  list or a per-cell diff). It is SSR-safe on exactly one condition: **the
342
- guard's own `querySelector` target must be STRUCTURALLY specific** the
335
+ guard's own `querySelector` target must be STRUCTURALLY specific**, the
343
336
  exact expected tag (`input-ui`) or slot (`nav[slot="nav"]`), never a
344
337
  generic "does this element have any children at all" check, or a
345
338
  mismatched pre-existing child (stale markup, a different component's
@@ -347,16 +340,16 @@ leftover DOM) gets silently adopted and mis-rendered. Both shipped
347
340
  instances already satisfy this.
348
341
 
349
342
  **What this decision does NOT yet close.** Adoption alone doesn't reach the
350
- zero-mutation bar §5 establishes a component can correctly ADOPT the
343
+ zero-mutation bar §5 establishes, a component can correctly ADOPT the
351
344
  pre-existing child and then still unconditionally re-`setAttribute()` it in
352
345
  every subsequent `render()` pass, the exact §5 sub-cause. `search-ui`'s
353
346
  `render()` (`this.#inputEl.setAttribute('placeholder', this.placeholder)`,
354
- the `disabled` set/remove pair) does this today a byte-identical SSR
347
+ the `disabled` set/remove pair) does this today, a byte-identical SSR
355
348
  `<search-ui>` fragment upgrades with the right element adopted, but still
356
349
  takes 1–2 redundant attribute-mutation records on that first render.
357
350
  `pagination-ui`'s own `reconcile()`-driven `#updateItem()` writes are the
358
351
  same shape one level down. **Scoped OUT of gh#1678** (table-ui's own render
359
- path is that ticket's actual evidence and fix) tracked as a follow-up:
352
+ path is that ticket's actual evidence and fix), tracked as a follow-up:
360
353
  extend `setAttrIfChanged()`-style idempotent guards to `search-ui`'s
361
354
  `render()` and `pagination-ui`'s `#updateItem()`/`#createItem()` writes,
362
355
  gh#1755 (filed alongside gh#1678's PR).