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

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 (142) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +3 -3
  3. package/CHANGELOG.md +300 -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 +20 -21
  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 +34 -35
  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 +27 -27
  68. package/skills/gen-ui-review/agents/openai.yaml +1 -1
  69. package/skills/gen-ui-review/evals/routing-corpus.json +1 -1
  70. package/skills/gen-ui-review/references/corpus-html-patterns.md +28 -29
  71. package/skills/gen-ui-review/references/exit-gate-mechanics.md +5 -5
  72. package/skills/gen-ui-review/references/lookup-maintenance.md +2 -2
  73. package/skills/gen-ui-review/references/loop-protocol.md +25 -25
  74. package/skills/gen-ui-review/references/rubric-cosmetic.md +10 -10
  75. package/skills/gen-ui-review/references/rubric-decompose.md +15 -16
  76. package/skills/gen-ui-review/references/rubric-score.md +23 -24
  77. package/skills/gen-ui-review/references/scorecard-worked-examples.md +12 -12
  78. package/skills/gen-ui-review/references/scores.schema.json +2 -2
  79. package/skills/llm-client-maintenance/SKILL.md +12 -12
  80. package/skills/llm-client-maintenance/evals/routing-corpus.json +1 -1
  81. package/skills/llm-client-maintenance/references/adapter-contract.md +27 -27
  82. package/skills/llm-client-maintenance/references/add-a-provider.md +25 -25
  83. package/skills/llm-client-maintenance/references/bridge-facade.md +31 -31
  84. package/skills/llm-client-maintenance/references/browser-proxy-boundary.md +17 -17
  85. package/skills/llm-client-maintenance/references/model-registry.md +14 -14
  86. package/skills/llm-client-maintenance/references/streaming-sse.md +21 -21
  87. package/skills/package-release/SKILL.md +21 -23
  88. package/skills/package-release/evals/evals.json +2 -2
  89. package/skills/package-release/evals/routing-corpus.json +2 -2
  90. package/skills/package-release/references/authorization-model.md +6 -8
  91. package/skills/package-release/references/changelog-discipline.md +25 -27
  92. package/skills/package-release/references/cut-procedure.md +129 -136
  93. package/skills/package-release/references/gates-catalog.md +42 -43
  94. package/skills/package-release/references/independent-package-release.md +14 -15
  95. package/skills/package-release/references/invariants-detail.md +9 -9
  96. package/skills/package-release/references/mechanization.md +11 -12
  97. package/skills/package-release/references/migration-guide-authoring.md +20 -20
  98. package/skills/package-release/references/notes-authoring.md +20 -20
  99. package/skills/package-release/references/recovery-paths.md +28 -28
  100. package/skills/primitive-authoring/SKILL.md +19 -19
  101. package/skills/primitive-authoring/agents/openai.yaml +1 -1
  102. package/skills/primitive-authoring/evals/evals.json +2 -2
  103. package/skills/primitive-authoring/evals/routing-corpus.json +2 -2
  104. package/skills/primitive-authoring/references/INDEX.md +22 -22
  105. package/skills/primitive-authoring/references/anti-patterns.md +76 -77
  106. package/skills/primitive-authoring/references/api-contract.md +63 -65
  107. package/skills/primitive-authoring/references/authoring-cycle.md +47 -50
  108. package/skills/primitive-authoring/references/canonical-pattern-index.md +118 -118
  109. package/skills/primitive-authoring/references/code-style.md +50 -52
  110. package/skills/primitive-authoring/references/common-gotchas.md +23 -23
  111. package/skills/primitive-authoring/references/composite-demo-protocol.md +42 -45
  112. package/skills/primitive-authoring/references/css-patterns.md +97 -97
  113. package/skills/primitive-authoring/references/for-attribute-event-contract.md +12 -12
  114. package/skills/primitive-authoring/references/form-control-sizing.md +16 -18
  115. package/skills/primitive-authoring/references/lifecycle-patterns.md +42 -42
  116. package/skills/primitive-authoring/references/llm-bridge.md +21 -21
  117. package/skills/primitive-authoring/references/module-promotion.md +51 -51
  118. package/skills/primitive-authoring/references/primitive-audit.md +16 -16
  119. package/skills/primitive-authoring/references/shell-patterns.md +81 -81
  120. package/skills/primitive-authoring/references/svg-authoring.md +62 -66
  121. package/skills/primitive-authoring/references/token-contract.md +45 -49
  122. package/skills/primitive-authoring/references/trait-pages.md +8 -8
  123. package/skills/primitive-authoring/references/worked-example.md +39 -39
  124. package/skills/primitive-authoring/references/yaml-contract.md +139 -143
  125. package/skills/site-deployment/SKILL.md +25 -26
  126. package/skills/site-deployment/agents/openai.yaml +1 -1
  127. package/skills/site-deployment/evals/routing-corpus.json +1 -1
  128. package/skills/site-deployment/references/deploy-playbooks.md +51 -53
  129. package/skills/site-docs-authoring/SKILL.md +14 -14
  130. package/skills/site-docs-authoring/agents/openai.yaml +1 -1
  131. package/skills/site-docs-authoring/evals/evals.json +1 -1
  132. package/skills/site-docs-authoring/evals/routing-corpus.json +1 -1
  133. package/skills/site-docs-authoring/intent.md +18 -18
  134. package/skills/ssr-compatibility/SKILL.md +24 -25
  135. package/skills/ssr-compatibility/agents/openai.yaml +1 -1
  136. package/skills/ssr-compatibility/evals/evals.json +1 -1
  137. package/skills/ssr-compatibility/evals/routing-corpus.json +1 -1
  138. package/skills/ssr-compatibility/references/consumer-workarounds.md +22 -23
  139. package/skills/ssr-compatibility/references/failure-shapes.md +84 -91
  140. package/skills/ssr-compatibility/references/guard-patterns.md +54 -58
  141. package/skills/ssr-compatibility/references/status-ledger.md +17 -17
  142. package/skills/ssr-compatibility/references/test-without-linkedom.md +26 -27
@@ -1,31 +1,31 @@
1
- # AdiaUI code style best practices
1
+ # AdiaUI code style, best practices
2
2
 
3
3
  ## First principles (SKILL.md's own summary, expanded)
4
4
 
5
5
  1. **Invariants are enforced by the next author, not the linter.** A component
6
6
  that violates the contract silently teaches the next agent the violation is
7
7
  acceptable. Write as if your component is the reference the next one is
8
- patterned after because it will be.
8
+ patterned after, because it will be.
9
9
  2. **Default behavior is the absent attribute.** `<component-ui>` with no
10
10
  attributes does the expected default thing; every Boolean prop defaults to
11
- `false`. If the expected default is "on," the prop name is wrong flip it
11
+ `false`. If the expected default is "on," the prop name is wrong, flip it
12
12
  (`closable` → `permanent`, `animate` → `static`).
13
13
  3. **Variants change tokens; modes change layout.** A variant body contains
14
- only `--component-*: var(...)` lines no `padding`, `display`, `position`,
14
+ only `--component-*: var(...)` lines, no `padding`, `display`, `position`,
15
15
  `width`, `height`, `gap`, `flex`, `grid`, `overflow`, `border-radius`.
16
16
  Layout-changing attributes are modes and require a Sanctioned Mode
17
17
  Attributes entry in the contract doc.
18
18
  4. **Symmetric lifecycle or it's a leak.** Every listener added in
19
19
  `connected()` is removed in `disconnected()`; every timer cleared, observer
20
20
  disconnected, cached ref nulled. Handlers are stable `#field` arrows so
21
- `removeEventListener` can match inline arrows bit three components in one
21
+ `removeEventListener` can match, inline arrows bit three components in one
22
22
  audit cycle.
23
23
  5. **Component tokens consume L3, not L2.** Alias from the role×state matrix
24
- (`--a-primary-bg-hover`), never the family base (`--a-primary`) bypassing
24
+ (`--a-primary-bg-hover`), never the family base (`--a-primary`), bypassing
25
25
  L3 strands the component outside theme / dark-mode / contrast cascades
26
26
  silently.
27
27
 
28
- Modern AdiaUI is small, declarative, and token-driven. Most of the "bugs" agents write are bugs _against the conventions_ bare `<div>`s where `col-ui` belongs, raw `<input>` where `input-ui` belongs, hex colors where tokens belong. The conventions are not stylistic preferences; each one corresponds to a working feature (theme switching, density modes, form association, focus rings) that breaks silently when the convention is violated.
28
+ Modern AdiaUI is small, declarative, and token-driven. Most of the "bugs" agents write are bugs _against the conventions_, bare `<div>`s where `col-ui` belongs, raw `<input>` where `input-ui` belongs, hex colors where tokens belong. The conventions are not stylistic preferences; each one corresponds to a working feature (theme switching, density modes, form association, focus rings) that breaks silently when the convention is violated.
29
29
 
30
30
  When in doubt: look up the catalog (the `lookup_component` MCP tool, or the adia-factory plugin's composition skill), pick the existing primitive, wire through tokens.
31
31
 
@@ -47,7 +47,7 @@ Every interactive surface, form control, layout container, and content block shi
47
47
  <input type="email" />
48
48
  ```
49
49
 
50
- The catalog has 125 component directories under `packages/web-components/components/`. Before composing, confirm via `get_component_map` (the a2ui MCP) that the one you want exists and what its real prop names are.
50
+ The catalog has 125 component directories under `packages/web-components/components/`. Before composing, confirm via `get_component_map` (the a2ui MCP) that the one you want exists, and what its real prop names are.
51
51
 
52
52
  ### Tag naming across tiers (`-ui` or bare)
53
53
 
@@ -58,18 +58,17 @@ suffix and was renamed to `form-popover-ui` one release later):
58
58
  | Tier | Convention | Examples |
59
59
  | --- | --- | --- |
60
60
  | Primitives (`packages/web-components`) | **always `-ui`** | `button-ui`, `select-ui`, `nav-item-ui` |
61
- | Standalone feature composites (`packages/web-modules`) drop-in anywhere, consumer-facing | **always `-ui`** | `confirm-dialog-ui`, `plan-picker-ui`, `date-range-selector-ui`, `form-popover-ui` |
62
- | Shell/bespoke-cluster children + runtime roots | **bare** the cluster prefix is the namespace | `admin-topbar`, `chat-composer`, `editor-canvas`, `simple-shell`, `a2ui-root`, `gen-root` |
61
+ | Standalone feature composites (`packages/web-modules`), drop-in anywhere, consumer-facing | **always `-ui`** | `confirm-dialog-ui`, `plan-picker-ui`, `date-range-selector-ui`, `form-popover-ui` |
62
+ | Shell/bespoke-cluster children + runtime roots | **bare**: the cluster prefix is the namespace | `admin-topbar`, `chat-composer`, `editor-canvas`, `simple-shell`, `a2ui-root`, `gen-root` |
63
63
 
64
- `theme-panel` (bare, standalone) predates this rule and is grandfathered
65
- do not copy it as precedent for a new standalone composite. The DIRECTORY
64
+ `theme-panel` (bare, standalone) predates this rule and is grandfathered, do not copy it as precedent for a new standalone composite. The DIRECTORY
66
65
  name never carries the suffix in either tier (`plan-picker/` →
67
66
  `<plan-picker-ui>`), and the a2ui `component:` name is unaffected
68
67
  (`FormPopover`).
69
68
 
70
69
  ## Layout primitives
71
70
 
72
- Use `<col-ui>`, `<row-ui>`, `<grid-ui>`, `<stack-ui>` for layout never a bare `<div>`. The primitives carry the project's spacing scale (`gap`, padding tokens) and respond to density / theme providers; a plain `<div>` strands content outside that system.
71
+ Use `<col-ui>`, `<row-ui>`, `<grid-ui>`, `<stack-ui>` for layout, never a bare `<div>`. The primitives carry the project's spacing scale (`gap`, padding tokens) and respond to density / theme providers; a plain `<div>` strands content outside that system.
73
72
 
74
73
  Pick the primitive by the axis the content flows along:
75
74
 
@@ -85,14 +84,14 @@ Pick the primitive by the axis the content flows along:
85
84
  <input-ui label="Password" type="password"></input-ui>
86
85
  </col-ui>
87
86
 
88
- <!-- wrong manual spacing fights the density provider -->
87
+ <!-- wrong, manual spacing fights the density provider -->
89
88
  <div style="display: flex; flex-direction: column; gap: 8px;">
90
89
  <input-ui label="Email" type="email"></input-ui>
91
90
  <input-ui label="Password" type="password"></input-ui>
92
91
  </div>
93
92
  ```
94
93
 
95
- The validator (`check_anti_patterns` → `noBareDivs`) catches this on rendered HTML. If a `<section>` has multiple direct children, wrap them in one layout primitive `cardContentModel` flags the alternative.
94
+ The validator (`check_anti_patterns` → `noBareDivs`) catches this on rendered HTML. If a `<section>` has multiple direct children, wrap them in one layout primitive, `cardContentModel` flags the alternative.
96
95
 
97
96
  ### `@bp` responsive prop annotations
98
97
 
@@ -127,11 +126,11 @@ The card content model is mandatory for anything resembling a block. Header carr
127
126
 
128
127
  Headings live in `<header>`, never in `<section>`. Sections contain exactly one layout primitive. Multiple direct children in `<section>` fail `cardContentModel` validation.
129
128
 
130
- `<card-ui>` and `<drawer-ui>` body content must wrap in `<section>` direct `<col-ui>` / `<row-ui>` / `<stack-ui>` / `<div>` / `<text-ui>` / `<h*>` bypass the canonical body slot, lose `--card-inset` margin, and corrupt gen-UI corpus. Use `<section bleed>` to preserve existing padding when needed. Gated by `npm run audit:card-structure` + `audit:drawer-structure`.
129
+ `<card-ui>` and `<drawer-ui>` body content must wrap in `<section>`, direct `<col-ui>` / `<row-ui>` / `<stack-ui>` / `<div>` / `<text-ui>` / `<h*>` bypass the canonical body slot, lose `--card-inset` margin, and corrupt gen-UI corpus. Use `<section bleed>` to preserve existing padding when needed. Gated by `npm run audit:card-structure` + `audit:drawer-structure`.
131
130
 
132
131
  ## `<field-ui>` for form composition
133
132
 
134
- Wrap form controls in `<field-ui>`. Don't pass `[label]` as an attribute on new form controls it's the legacy form, kept for back-compat but not extended.
133
+ Wrap form controls in `<field-ui>`. Don't pass `[label]` as an attribute on new form controls, it's the legacy form, kept for back-compat but not extended.
135
134
 
136
135
  ```html
137
136
  <!-- right -->
@@ -143,9 +142,9 @@ Wrap form controls in `<field-ui>`. Don't pass `[label]` as an attribute on new
143
142
  <input-ui label="Email" type="email" required></input-ui>
144
143
  ```
145
144
 
146
- `<field-ui>` owns the real `<label for="…">` and binds to the slotted control's `id` for proper click-to-focus a pattern the embedded attribute can't provide. It also carries `[slot="trailing"]` / `[slot="action"]` for composition (clear button, helper actions) and an `inline` mode for stacked vs single-row layout.
145
+ `<field-ui>` owns the real `<label for="…">` and binds to the slotted control's `id` for proper click-to-focus, a pattern the embedded attribute can't provide. It also carries `[slot="trailing"]` / `[slot="action"]` for composition (clear button, helper actions) and an `inline` mode for stacked vs single-row layout.
147
146
 
148
- For a single `check-ui` (which is its own labeled affordance), just use `<check-ui name label>` directly wrapping it in `<field-ui inline>` produces ghost rows.
147
+ For a single `check-ui` (which is its own labeled affordance), just use `<check-ui name label>` directly, wrapping it in `<field-ui inline>` produces ghost rows.
149
148
 
150
149
  ## Generated UIs through `<a2ui-root>`
151
150
 
@@ -155,7 +154,7 @@ Static markup written by hand uses the components directly. UIs generated from i
155
154
  <a2ui-root data-stream-src="/api/genui-stream"></a2ui-root>
156
155
  ```
157
156
 
158
- The runtime handles `createSurface` / `updateComponents` / `wireComponents` messages and binds `FormController` / `DataStreamController` controllers automatically. Don't roll your own message handler the wiring lives in `@adia-ai/a2ui-runtime`.
157
+ The runtime handles `createSurface` / `updateComponents` / `wireComponents` messages and binds `FormController` / `DataStreamController` controllers automatically. Don't roll your own message handler, the wiring lives in `@adia-ai/a2ui-runtime`.
159
158
 
160
159
  For multi-turn refinement, pass back `state_id` from prior responses so the engine can chain through `parent_state_id`. See the sibling **a2ui-maintenance** skill for the operator playbook.
161
160
 
@@ -190,41 +189,41 @@ Detailed pattern in [css-patterns.md](css-patterns.md).
190
189
 
191
190
  ## Tokens, never raw colors
192
191
 
193
- Every color comes through a token. Zero `#hex` / `rgb()` / `rgba()` / `oklch()` in component CSS the only places raw colors live are `packages/web-components/styles/colors/semantics.css` and `packages/web-components/styles/tokens.css`.
192
+ Every color comes through a token. Zero `#hex` / `rgb()` / `rgba()` / `oklch()` in component CSS: the only places raw colors live are `packages/web-components/styles/colors/semantics.css` and `packages/web-components/styles/tokens.css`.
194
193
 
195
194
  Component CSS aliases from L3 (the role × state matrix), not L2 (the family base). The L3 matrix is where state wiring lives; bypassing it strands the component outside the theme system and breaks dark mode / high-contrast modes silently.
196
195
 
197
196
  ```css
198
- /* right L3 alias keeps the state cascade working */
197
+ /* right, L3 alias keeps the state cascade working */
199
198
  :scope[variant="primary"]:hover {
200
199
  --button-bg: var(--a-primary-bg-hover);
201
200
  --button-fg: var(--a-primary-fg-hover);
202
201
  }
203
202
 
204
- /* wrong L2 stops the cascade */
203
+ /* wrong, L2 stops the cascade */
205
204
  :scope[variant="primary"]:hover {
206
205
  --button-bg: var(--a-primary); /* loses theme + contrast wiring */
207
206
  }
208
207
  ```
209
208
 
210
- For text on filled primary discs (radio dots, step numerals), use `--a-chrome-light` theme-stable against any fill. (`--a-primary-fg` also works post-Material-adoption it resolves to the palette's fixed-light on-color but `--a-chrome-light` stays correct on arbitrary/brand fills too.)
209
+ For text on filled primary discs (radio dots, step numerals), use `--a-chrome-light`, theme-stable against any fill. (`--a-primary-fg` also works post-Material-adoption, it resolves to the palette's fixed-light on-color, but `--a-chrome-light` stays correct on arbitrary/brand fills too.)
211
210
 
212
211
  Audit procedure + chrome palette in [token-contract.md](token-contract.md).
213
212
 
214
213
  ## Variants change tokens, modes change layout
215
214
 
216
- A **variant** is cosmetic color, border, shadow depth. A **mode** restructures the box direction, grid template, display type.
215
+ A **variant** is cosmetic, color, border, shadow depth. A **mode** restructures the box, direction, grid template, display type.
217
216
 
218
217
  Variant bodies may only contain `--component-*: var(...)` lines. They must NEVER touch `padding`, `display`, `position`, `width`, `height`, `gap`, `flex`, `grid`, `overflow`, `border-radius`. Layout-changing attributes are modes, and modes require an entry in the Sanctioned Mode Attributes table at `.claude/docs/specs/component-token-contract.md` `Modes` section.
219
218
 
220
219
  ```css
221
- /* right variant overrides tokens only */
220
+ /* right, variant overrides tokens only */
222
221
  :scope[variant="outlined"] {
223
222
  --button-bg: transparent;
224
223
  --button-border: var(--a-border);
225
224
  }
226
225
 
227
- /* wrong variant changes layout; this should be a mode */
226
+ /* wrong, variant changes layout; this should be a mode */
228
227
  :scope[variant="outlined"] {
229
228
  padding: var(--a-space-3) var(--a-space-4);
230
229
  display: flex;
@@ -233,7 +232,7 @@ Variant bodies may only contain `--component-*: var(...)` lines. They must NEVER
233
232
 
234
233
  ## Boolean attributes default to `false`
235
234
 
236
- Every Boolean prop on a component must default to `false`. If the expected default behavior is "on," the prop name is wrong flip it.
235
+ Every Boolean prop on a component must default to `false`. If the expected default behavior is "on," the prop name is wrong, flip it.
237
236
 
238
237
  | Right (default off, opt in) | Wrong (default on, opt out) |
239
238
  | --------------------------------- | ------------------------------ |
@@ -242,24 +241,23 @@ Every Boolean prop on a component must default to `false`. If the expected defau
242
241
  | `<cursor-ui no-cursor>` | `<cursor-ui cursor>` |
243
242
  | `<carousel-ui no-pause-on-hover>` | `<carousel-ui pause-on-hover>` |
244
243
 
245
- Default behavior is the absent attribute. Attributes exist to opt OUT or carry a value never to opt INTO the expected default.
244
+ Default behavior is the absent attribute. Attributes exist to opt OUT or carry a value, never to opt INTO the expected default.
246
245
 
247
246
  State-bearing Booleans must `reflect: true` so CSS can match `:scope[disabled]`, `:scope[selected]`, etc. Without reflection, hover / active / selected styles break silently.
248
247
 
249
248
  [verified 2026-08-19] **The literal string `"false"` is not "presence = true"
250
249
  here.** ADR-0075 special-cases `parseAttr` for `Boolean`-typed props so the
251
- literal attribute string `"false"` parses to JS `false`, not `true`
252
- deviating from strict HTML semantics on purpose, to match this repo's own
250
+ literal attribute string `"false"` parses to JS `false`, not `true`, deviating from strict HTML semantics on purpose, to match this repo's own
253
251
  A2UI transpiler (which already special-cased it defensively) and the
254
252
  authoring intuition both human and generated markup default to. Writing
255
253
  `show-label="false"` on a `default: true` Boolean prop does what it looks
256
254
  like it does. (`default: true` is itself the rare, ratified exception to
257
- this section's "every Boolean defaults to `false`" rule ADR-0063's
258
- stamped-attribute mechanism, gh#961 not a license to skip the flip rule
255
+ this section's "every Boolean defaults to `false`" rule, ADR-0063's
256
+ stamped-attribute mechanism, gh#961, not a license to skip the flip rule
259
257
  above for a new prop.) See [api-contract.md](api-contract.md)'s field-rules
260
258
  section for the full rule and shipped blast radius (`core/element.js:93-94`).
261
259
 
262
- Native DOM accessors (`textContent`, `innerHTML`) get clobbered if declared in `static properties` `installProps` overrides the native setter and `el.textContent = ''` becomes a signal write, not a child-wipe. Don't declare those names in `static properties`.
260
+ Native DOM accessors (`textContent`, `innerHTML`) get clobbered if declared in `static properties`, `installProps` overrides the native setter and `el.textContent = ''` becomes a signal write, not a child-wipe. Don't declare those names in `static properties`.
263
261
 
264
262
  ## Symmetric lifecycle
265
263
 
@@ -280,15 +278,15 @@ class UIBadge extends UIElement {
280
278
  }
281
279
  ```
282
280
 
283
- Inline arrows passed to `addEventListener` look fine and leak quietly `removeEventListener` no-ops because the new arrow has different identity. Three components in one audit cycle bit on this exact bug.
281
+ Inline arrows passed to `addEventListener` look fine and leak quietly, `removeEventListener` no-ops because the new arrow has different identity. Three components in one audit cycle bit on this exact bug.
284
282
 
285
- If the class extends `UIFormElement`, `connected()` and `disconnected()` MUST call `super.*` `ElementInternals` registration depends on it. And declare `disconnected()` exactly once per class; the second silently overrides the first.
283
+ If the class extends `UIFormElement`, `connected()` and `disconnected()` MUST call `super.*`, `ElementInternals` registration depends on it. And declare `disconnected()` exactly once per class; the second silently overrides the first.
286
284
 
287
285
  Full lifecycle patterns in [lifecycle-patterns.md](lifecycle-patterns.md).
288
286
 
289
287
  ## Reactivity through signals + effects
290
288
 
291
- New runtime data flows use the project's `signal()` / `effect()` primitives never parallel `CustomEvent`-only paths. The convention keeps reactivity coherent: a single read-write surface, observable by any number of consumers, integrated with the framework's effect graph.
289
+ New runtime data flows use the project's `signal()` / `effect()` primitives, never parallel `CustomEvent`-only paths. The convention keeps reactivity coherent: a single read-write surface, observable by any number of consumers, integrated with the framework's effect graph.
292
290
 
293
291
  ```js
294
292
  import { signal, effect } from '@adia-ai/web-components/core/reactivity';
@@ -302,7 +300,7 @@ effect(() => {
302
300
  count.value = 1; // effect re-runs
303
301
  ```
304
302
 
305
- `CustomEvent` is fine for one-shot lifecycle notifications (a button click, a form submit). It's wrong for ongoing state that other components want to read that wants a signal.
303
+ `CustomEvent` is fine for one-shot lifecycle notifications (a button click, a form submit). It's wrong for ongoing state that other components want to read, that wants a signal.
306
304
 
307
305
  Custom setter `untracked()` discipline: reactive-property reads inside `set options/data/columns/...` setters that read `this.value` (via installProps getter) subscribe the caller's effect. Next `el.value = X` re-triggers it → drain loop. Wrap setter body in `untracked()`.
308
306
 
@@ -316,7 +314,7 @@ Universal data ingestion across the library is the `data-stream-*` attribute tra
316
314
  <a2ui-root data-stream-src="/api/genui"></a2ui-root>
317
315
  ```
318
316
 
319
- The trait dedupes connections two components pointing at the same URL share one transport and propagates updates through the signal graph automatically.
317
+ The trait dedupes connections, two components pointing at the same URL share one transport, and propagates updates through the signal graph automatically.
320
318
 
321
319
  ## Modern browser baseline
322
320
 
@@ -324,7 +322,7 @@ The project targets Chromium 125+, Safari 18.0+, Firefox 129+ (raised from 17.4
324
322
 
325
323
  Don't propose polyfilling native APIs already at the baseline. The runtime expects native `:has()`, `@scope`, `@property`, OKLCH, `light-dark()`, `popover`, `dialog.showModal()`, `ResizeObserver`, `color-mix()`. Authoring against an older implicit floor is the source of most "but it works in Chrome" surprises.
326
324
 
327
- When transparency-mixing colors, use `oklab` not `oklch` for the interpolation space Safari < 18 had an OKLCH-with-transparent red-shift bug, and `oklab` is perceptually equivalent here:
325
+ When transparency-mixing colors, use `oklab` not `oklch` for the interpolation space, Safari < 18 had an OKLCH-with-transparent red-shift bug, and `oklab` is perceptually equivalent here:
328
326
 
329
327
  ```css
330
328
  --code-active-line-bg: color-mix(in oklab, var(--a-primary-muted) 40%, transparent);
@@ -354,7 +352,7 @@ Other deprecated forms swept in an early cut:
354
352
  - `'chat-submit'` event → `'submit'`
355
353
  - `<field-ui error="…">` → message moves to the slotted control
356
354
 
357
- For a wholesale consumer-side migration, use `app-migration` (adia-factory plugin) it reads the MIGRATION GUIDE and runs the mechanical sweeps.
355
+ For a wholesale consumer-side migration, use `app-migration` (adia-factory plugin), it reads the MIGRATION GUIDE and runs the mechanical sweeps.
358
356
 
359
357
  ## Validate, always
360
358
 
@@ -366,17 +364,17 @@ For consumer markup (HTML pages, exemplars, training data), the checks are manua
366
364
  - Run the project's lint if applicable: `npx eslint <path>`.
367
365
  - Smoke the rendered output via `npm run dev` and load the touched page.
368
366
 
369
- For component source under `packages/web-components/components/**`, the gate is heavier see [authoring-cycle.md](authoring-cycle.md) Step 5 + the full release-side gate roster in the sibling **package-release** skill.
367
+ For component source under `packages/web-components/components/**`, the gate is heavier, see [authoring-cycle.md](authoring-cycle.md) Step 5 + the full release-side gate roster in the sibling **package-release** skill.
370
368
 
371
369
  ## Cross-references
372
370
 
373
- - [authoring-cycle.md](authoring-cycle.md) the full 5-step authoring procedure
374
- - [api-contract.md](api-contract.md) prop naming, type choices, reflection policy
375
- - [css-patterns.md](css-patterns.md) full @scope + variant/mode CSS architecture
376
- - [lifecycle-patterns.md](lifecycle-patterns.md) timers, observers, popovers, listeners
377
- - [token-contract.md](token-contract.md) token audit procedure
378
- - [module-promotion.md](module-promotion.md) promote inline → module
379
- - [anti-patterns.md](anti-patterns.md) failure-mode catalogue with file:line refs
380
- - **a2ui-maintenance** (sibling skill) generator / corpus / MCP pipeline
381
- - **demo-audit** (sibling skill) cross-surface visual / static QA
382
- - the **adia-factory** plugin composition playbook for consumers building apps on the framework
371
+ - [authoring-cycle.md](authoring-cycle.md), the full 5-step authoring procedure
372
+ - [api-contract.md](api-contract.md), prop naming, type choices, reflection policy
373
+ - [css-patterns.md](css-patterns.md), full @scope + variant/mode CSS architecture
374
+ - [lifecycle-patterns.md](lifecycle-patterns.md), timers, observers, popovers, listeners
375
+ - [token-contract.md](token-contract.md), token audit procedure
376
+ - [module-promotion.md](module-promotion.md), promote inline → module
377
+ - [anti-patterns.md](anti-patterns.md), failure-mode catalogue with file:line refs
378
+ - **a2ui-maintenance** (sibling skill), generator / corpus / MCP pipeline
379
+ - **demo-audit** (sibling skill), cross-surface visual / static QA
380
+ - the **adia-factory** plugin, composition playbook for consumers building apps on the framework
@@ -1,4 +1,4 @@
1
- # Common gotchas authoring traps
1
+ # Common gotchas, authoring traps
2
2
 
3
3
  Seven concrete failure modes: five from a billing-overview rebuild + multi-demo grandfather-elimination cycle (CSS/composition-layering traps, §§1–5, composite authoring specifically), one from the site-a2ui migration's router-race root cause (async-lifecycle sequencing, §6, any primitive or module with a multi-await lifecycle method), plus one from minting `anchor-bar-ui` (generated-artifact regeneration ordering, §7, any change that mints/renames a component and touches a demo using it in the same pass). Each is the kind of bug that:
4
4
 
@@ -20,7 +20,7 @@ Composite authors: read §§1–5 BEFORE Phase 3 sketch. Anyone adding an async
20
20
 
21
21
  ---
22
22
 
23
- ## 1. Component used without reading its CSS and especially without reading its composition grammar
23
+ ## 1. Component used without reading its CSS, and especially without reading its composition grammar
24
24
 
25
25
  **Pattern**: Stamping `<X-ui>` and relying on attributes/slots without opening `X-ui.css`. The primitive's `@scope` rules ARE part of its API contract, not implementation details. Particularly load-bearing: composition grammars (which children the primitive expects + how it lays them out).
26
26
 
@@ -39,7 +39,7 @@ Composite authors: read §§1–5 BEFORE Phase 3 sketch. Anyone adding an async
39
39
  **Example**: 4 billing composites all had:
40
40
 
41
41
  ```css
42
- /* WRONG clobbers empty-state-ui's flex column */
42
+ /* WRONG, clobbers empty-state-ui's flex column */
43
43
  :scope > [data-empty] { display: none; }
44
44
  :scope[empty] > [data-empty] { display: block; }
45
45
  ```
@@ -48,10 +48,10 @@ Composite authors: read §§1–5 BEFORE Phase 3 sketch. Anyone adding an async
48
48
 
49
49
  **Detector**: None today. Caught by user visual review.
50
50
 
51
- **Fix** invert visibility toggle so no display value is set when shown:
51
+ **Fix**, invert visibility toggle so no display value is set when shown:
52
52
 
53
53
  ```css
54
- /* RIGHT child's :scope display remains intact */
54
+ /* RIGHT, child's :scope display remains intact */
55
55
  :scope:not([empty]) > [data-empty] { display: none; }
56
56
  ```
57
57
 
@@ -59,7 +59,7 @@ Composite authors: read §§1–5 BEFORE Phase 3 sketch. Anyone adding an async
59
59
 
60
60
  ## 3. Mixed sizes across form/control groups
61
61
 
62
- **Pattern**: A composite stamps multiple form/control primitives in the same visual row (toolbar, button cluster, filter strip) without coordinating `size` attributes. Defaults differ buttons might default `sm`, inputs default to a larger size, search-ui doesn't forward `size` to its inner input.
62
+ **Pattern**: A composite stamps multiple form/control primitives in the same visual row (toolbar, button cluster, filter strip) without coordinating `size` attributes. Defaults differ, buttons might default `sm`, inputs default to a larger size, search-ui doesn't forward `size` to its inner input.
63
63
 
64
64
  **Example**: an invoice-history toolbar had buttons at `size='sm'` (24px), filter chips at `size='sm'` (24px), search input at default (~36px). Same row, mismatched baseline.
65
65
 
@@ -74,7 +74,7 @@ Composite authors: read §§1–5 BEFORE Phase 3 sketch. Anyone adding an async
74
74
 
75
75
  ## 4. minmax(min, 1fr) inside repeat() fighting container queries
76
76
 
77
- **Pattern**: A grid uses `repeat(N, minmax(<min>, 1fr))` with a hardcoded minimum, BUT the container also has `@container` queries that collapse columns at breakpoints. The minmax fights the breakpoints when the container narrows, columns hit the floor and overflow before the breakpoint reduces column count.
77
+ **Pattern**: A grid uses `repeat(N, minmax(<min>, 1fr))` with a hardcoded minimum, BUT the container also has `@container` queries that collapse columns at breakpoints. The minmax fights the breakpoints, when the container narrows, columns hit the floor and overflow before the breakpoint reduces column count.
78
78
 
79
79
  **Example**: a dashboard-layout KPI grid was `repeat(4, minmax(16em, 1fr))` plus `@container ≤48em → 2 cols` and `≤32em → 1 col`. Redundant + conflicted. Removed the minmax; container queries own the responsive collapse cleanly.
80
80
 
@@ -86,27 +86,27 @@ Composite authors: read §§1–5 BEFORE Phase 3 sketch. Anyone adding an async
86
86
 
87
87
  ## 5. Nested `<!-- ... -->` inside design-plan canonical-sketch fenced blocks
88
88
 
89
- **Pattern**: The `<!-- design-plan: ... -->` block contains a fenced ` ```canonical-sketch ... ``` ` body. Authors sometimes paste HTML examples with inner `<!-- ... -->` comments into the sketch. HTML comments DON'T NEST the inner `-->` closes the OUTER `<!-- design-plan: -->`. Trailing ` ``` --> ` then leaks as visible text on the page.
89
+ **Pattern**: The `<!-- design-plan: ... -->` block contains a fenced ` ```canonical-sketch ... ``` ` body. Authors sometimes paste HTML examples with inner `<!-- ... -->` comments into the sketch. HTML comments DON'T NEST, the inner `-->` closes the OUTER `<!-- design-plan: -->`. Trailing ` ``` --> ` then leaks as visible text on the page.
90
90
 
91
91
  **Example**: a billing-overview.examples.html had two inner comments inside its canonical-sketch (annotations + a drawer composition example). Stray ` ``` --> ` rendered above the page header.
92
92
 
93
- **Detector**: ✓ caught by `npm run audit:demo-pattern-source` emits `phase_3_sketch contains an inner <!-- ... --> comment` finding.
93
+ **Detector**: ✓ caught by `npm run audit:demo-pattern-source`, emits `phase_3_sketch contains an inner <!-- ... --> comment` finding.
94
94
 
95
95
  **Fix**: Remove inner HTML comments from the canonical-sketch. Use plain text annotations or remove the doc-noise entirely.
96
96
 
97
97
  ---
98
98
 
99
- ## 6. Async load/render function completing out of order a guard AT the checkpoint isn't enough
99
+ ## 6. Async load/render function completing out of order, a guard AT the checkpoint isn't enough
100
100
 
101
- **Pattern**: A lifecycle method does asynchronous work (fetch, dynamic `import()`, any `await`) BEFORE reaching a sequence/resolver checkpoint that's guarded against stale calls. The guard only checks identity/sequence AT that one checkpoint it doesn't protect the awaits that come after it. A call that started earlier but is slow can resume, pass every checkpoint it reaches (each one, in isolation, looks current), and finish writing state AFTER a faster, later call already completed clobbering the newer result with stale content. The bug is invisible per-checkpoint because each individual guard check "passes"; the invariant that breaks is the ORDER completions land in, not any single check's correctness.
101
+ **Pattern**: A lifecycle method does asynchronous work (fetch, dynamic `import()`, any `await`) BEFORE reaching a sequence/resolver checkpoint that's guarded against stale calls. The guard only checks identity/sequence AT that one checkpoint, it doesn't protect the awaits that come after it. A call that started earlier but is slow can resume, pass every checkpoint it reaches (each one, in isolation, looks current), and finish writing state AFTER a faster, later call already completed, clobbering the newer result with stale content. The bug is invisible per-checkpoint because each individual guard check "passes"; the invariant that breaks is the ORDER completions land in, not any single check's correctness.
102
102
 
103
- **Example**: `router-ui`'s `#loadContent` (`packages/web-components/core/provider.js`) fetched content, then ran it through the template resolver, which itself carried the only staleness guard (checked at resolver entry). A navigation to `/site/components/button` that fetched slowly could resume after a faster later navigation to `/site/dashboard` had already rendered the resolver's own guard had nothing to check against by the time the stale call reached it, since the fetch (before the resolver) was itself unguarded. Reproduced deterministically with a 2-second-delayed fetch: the DOM showed the Dashboard's content under `data-route-path="button"`. Three separate flake classes across the site-a2ui migration all traced back to this one mechanism (`site-a2ui/FINDINGS.md`, "Router race fully root-caused: late-resuming #loadContent steals the sequence").
103
+ **Example**: `router-ui`'s `#loadContent` (`packages/web-components/core/provider.js`) fetched content, then ran it through the template resolver, which itself carried the only staleness guard (checked at resolver entry). A navigation to `/site/components/button` that fetched slowly could resume after a faster later navigation to `/site/dashboard` had already rendered, the resolver's own guard had nothing to check against by the time the stale call reached it, since the fetch (before the resolver) was itself unguarded. Reproduced deterministically with a 2-second-delayed fetch: the DOM showed the Dashboard's content under `data-route-path="button"`. Three separate flake classes across the site-a2ui migration all traced back to this one mechanism (`site-a2ui/FINDINGS.md`, "Router race fully root-caused: late-resuming #loadContent steals the sequence").
104
104
 
105
- **Detector**: None generic a per-page timing repro (delay the async step past the next call's completion, then assert final state matches the LAST call issued, not the first-guarded one) is how this was actually caught; no static check flags it.
105
+ **Detector**: None generic, a per-page timing repro (delay the async step past the next call's completion, then assert final state matches the LAST call issued, not the first-guarded one) is how this was actually caught; no static check flags it.
106
106
 
107
- **Fix**: Claim a monotonic sequence token as the FIRST line of the function, before any `await` not at the first checkpoint reached. Re-check the token after EVERY subsequent `await` (not just once), and return early without writing any state or emitting any event the moment it no longer matches. A checkpoint guard placed only where you happen to already have a natural pause point (a resolver, a render call) is not equivalent to this; it only catches staleness FOR CALLS THAT REACH THAT SPECIFIC POINT before the pause, which a slow-but-eventually-arriving call always will.
107
+ **Fix**: Claim a monotonic sequence token as the FIRST line of the function, before any `await`, not at the first checkpoint reached. Re-check the token after EVERY subsequent `await` (not just once), and return early, without writing any state or emitting any event, the moment it no longer matches. A checkpoint guard placed only where you happen to already have a natural pause point (a resolver, a render call) is not equivalent to this; it only catches staleness FOR CALLS THAT REACH THAT SPECIFIC POINT before the pause, which a slow-but-eventually-arriving call always will.
108
108
 
109
- **Illustrative** (simplified from the real fix see `provider.js:230,245,250,286` for the actual code):
109
+ **Illustrative** (simplified from the real fix, see `provider.js:230,245,250,286` for the actual code):
110
110
 
111
111
  ```javascript
112
112
  async #loadContent(route) {
@@ -119,21 +119,21 @@ async #loadContent(route) {
119
119
  }
120
120
  ```
121
121
 
122
- **Generalizes to**: any lifecycle method with more than one `await` where a caller can re-invoke it before the previous call finishes (route changes, search-as-you-type, tab switches, any "latest wins" async UI update) not just routing.
122
+ **Generalizes to**: any lifecycle method with more than one `await` where a caller can re-invoke it before the previous call finishes (route changes, search-as-you-type, tab switches, any "latest wins" async UI update), not just routing.
123
123
 
124
124
  ---
125
125
 
126
- ## 7. Minting a wrapper-shaped component before its registry.js entry lands the transpiler silently deletes the node, not just mis-types it
126
+ ## 7. Minting a wrapper-shaped component before its registry.js entry lands, the transpiler silently deletes the node, not just mis-types it
127
127
 
128
- **Pattern**: a tag is gated first and only, for `*-ui` tags, by `packages/gen-ui/a2ui/registry.js`'s hand-maintained `registry` map inverted into `reverseRegistry` at `transpiler-maps.js`'s module init, consulted first thing in `compose/transpiler/transpiler.js:149-150`. `registry.js` is hand-edited, not generated by `node scripts/build/components.mjs` (that script writes sidecars/prop-catalog data, consumed only for prop-extraction fidelity on tags the transpiler ALREADY resolved `transpiler-maps.js:22-26`); a runtime `registerType()` call doesn't rescue a stale row either `reverseRegistry` is a one-time init snapshot, not live. Transpile a demo using a component minted in the SAME change, before its `registry.js` line lands (e.g. the chunk harvester, `node scripts/build/harvest-chunks.mjs`, or any other engine-transpiler consumer), and the tag falls through to `transpiler.js`'s "Unknown → Column" branch (line 180-183) same mechanism as gh#535's toolbar-group breakage, which at least rendered visibly-wrong. A NEW component is usually wrapper-shaped (one child, author-defined attributes like `anchor="bottom"` the transpiler doesn't map to any real A2UI prop). That shape trips a SECOND, separate rule right after "single-child container chains flatten" (`transpiler.js:282-285`): a retyped Column with exactly one child and zero recognized props is discarded outright, and its child is spliced directly into the PARENT's children in its place. The wrapper's own id and node are never pushed to the tree at all not visible-but-wrong, just gone. The row is then internally self-consistent (content hash matches source) so `check:chunks-fresh` reports clean.
128
+ **Pattern**: a tag is gated first and only, for `*-ui` tags, by `packages/gen-ui/a2ui/registry.js`'s hand-maintained `registry` map, inverted into `reverseRegistry` at `transpiler-maps.js`'s module init, consulted first thing in `compose/transpiler/transpiler.js:149-150`. `registry.js` is hand-edited, not generated by `node scripts/build/components.mjs` (that script writes sidecars/prop-catalog data, consumed only for prop-extraction fidelity on tags the transpiler ALREADY resolved, `transpiler-maps.js:22-26`); a runtime `registerType()` call doesn't rescue a stale row either, `reverseRegistry` is a one-time init snapshot, not live. Transpile a demo using a component minted in the SAME change, before its `registry.js` line lands (e.g. the chunk harvester, `node scripts/build/harvest-chunks.mjs`, or any other engine-transpiler consumer), and the tag falls through to `transpiler.js`'s "Unknown → Column" branch (line 180-183), same mechanism as gh#535's toolbar-group breakage, which at least rendered visibly-wrong. A NEW component is usually wrapper-shaped (one child, author-defined attributes like `anchor="bottom"` the transpiler doesn't map to any real A2UI prop). That shape trips a SECOND, separate rule right after, "single-child container chains flatten" (`transpiler.js:282-285`): a retyped Column with exactly one child and zero recognized props is discarded outright, and its child is spliced directly into the PARENT's children in its place. The wrapper's own id and node are never pushed to the tree at all, not visible-but-wrong, just gone. The row is then internally self-consistent (content hash matches source) so `check:chunks-fresh` reports clean.
129
129
 
130
- **Example (historical the illustrating consumer has since retired):** minting `anchor-bar-ui` (gh#495, PR #569) and regenerating the `bulk-action-toolbar` pattern's site-a2ui row (site-a2ui itself retired 2026-08-31, ADR-0072 Decision 2 / gh#2410 the underlying registry-gating hazard below is unchanged, only that particular consumer is gone) before the worktree's `registry.js` entry for it existed. Git-verified on the pre-fix commit (`ebf71832d`): the converted artifact contained zero occurrences of `pat-bulk-float-bar` (the anchor-bar-ui's own authored id) anywhere not retyped-and-visible, genuinely absent while its single child (the toolbar content) survived, reparented one level up. The site-a2ui freshness gate of the day reported clean regardless, for the exact reason the Detector below still explains.
130
+ **Example (historical, the illustrating consumer has since retired):** minting `anchor-bar-ui` (gh#495, PR #569) and regenerating the `bulk-action-toolbar` pattern's site-a2ui row (site-a2ui itself retired 2026-08-31, ADR-0072 Decision 2 / gh#2410, the underlying registry-gating hazard below is unchanged, only that particular consumer is gone) before the worktree's `registry.js` entry for it existed. Git-verified on the pre-fix commit (`ebf71832d`): the converted artifact contained zero occurrences of `pat-bulk-float-bar` (the anchor-bar-ui's own authored id) anywhere, not retyped-and-visible, genuinely absent, while its single child (the toolbar content) survived, reparented one level up. The site-a2ui freshness gate of the day reported clean regardless, for the exact reason the Detector below still explains.
131
131
 
132
- **Detector**: none generic a same-source freshness check can't catch this (the artifact IS fresh relative to its source, it transpiled correctly against a registry that was itself incomplete). The only catch is rendering the actual consuming surface and confirming the new tag's node count is nonzero, or re-running the transpile after `registry.js` is updated and diffing the output for the new component name. A non-wrapper-shaped new component (multiple children, or attributes that happen to map to real props) is lower-risk here it survives as a visible-but-wrong Column, the gh#535 class, which at least has a visual tell.
132
+ **Detector**: none generic, a same-source freshness check can't catch this (the artifact IS fresh relative to its source, it transpiled correctly against a registry that was itself incomplete). The only catch is rendering the actual consuming surface and confirming the new tag's node count is nonzero, or re-running the transpile after `registry.js` is updated and diffing the output for the new component name. A non-wrapper-shaped new component (multiple children, or attributes that happen to map to real props) is lower-risk here, it survives as a visible-but-wrong Column, the gh#535 class, which at least has a visual tell.
133
133
 
134
- **Fix**: the `registry.js` entry is what gates resolution land it (not just run `components.mjs`, which is necessary for prop fidelity but not sufficient to avoid the retype) before transpiling anything that uses the new tag. When gating a dispatched agent's PR that did this out of order, re-run the transpile on the merged tree and confirm the tag actually appears in the output never trust a freshness gate's green alone for a surface touching a component minted in the same change.
134
+ **Fix**: the `registry.js` entry is what gates resolution, land it (not just run `components.mjs`, which is necessary for prop fidelity but not sufficient to avoid the retype) before transpiling anything that uses the new tag. When gating a dispatched agent's PR that did this out of order, re-run the transpile on the merged tree and confirm the tag actually appears in the output, never trust a freshness gate's green alone for a surface touching a component minted in the same change.
135
135
 
136
- **Generalizes to**: any hand-maintained resolution map (not build-generated) that a later regeneration step reads through regenerating before the map is updated produces an internally-consistent-but-wrong artifact that passes a same-source freshness check; if the misresolved shape also happens to trip a downstream simplification/collapse rule, the failure escalates from "renders wrong" to "renders nothing," with no visual tell at all.
136
+ **Generalizes to**: any hand-maintained resolution map (not build-generated) that a later regeneration step reads through, regenerating before the map is updated produces an internally-consistent-but-wrong artifact that passes a same-source freshness check; if the misresolved shape also happens to trip a downstream simplification/collapse rule, the failure escalates from "renders wrong" to "renders nothing," with no visual tell at all.
137
137
 
138
138
  ---
139
139
 
@@ -141,4 +141,4 @@ async #loadContent(route) {
141
141
 
142
142
  **Composites and primitives have layered contracts. The parent's CSS shouldn't reach into the child's layout territory. The child's CSS shouldn't fight its parent's container queries. The audit should detect the rendering hazard, not just the parsing structure.**
143
143
 
144
- The structural defense for #1 (composition-grammar bypass) is `npm run audit:card-structure[:strict]` / `npm run audit:avatar-structure` / `npm run audit:alert-structure` (HTML + JS `createElement` scan) plus `npm run audit:sketch-grammar` at Phase 3. The component-literacy read is a hint, not a gate the mechanical defenses above are the proximate fix. See [composite-demo-protocol.md](composite-demo-protocol.md) Phase 2. Gotchas #2–4 are caught only by visual review until corresponding audits are added. #6 is a distinct axis (async-lifecycle correctness, not CSS layering) see its own Detector/Fix above. #7 is a third axis (a hand-maintained resolution map read by a downstream regeneration step, not CSS or async ordering) its own Detector/Fix above; no audit catches it, only a browser probe of the specific route touched.
144
+ The structural defense for #1 (composition-grammar bypass) is `npm run audit:card-structure[:strict]` / `npm run audit:avatar-structure` / `npm run audit:alert-structure` (HTML + JS `createElement` scan) plus `npm run audit:sketch-grammar` at Phase 3. The component-literacy read is a hint, not a gate: the mechanical defenses above are the proximate fix. See [composite-demo-protocol.md](composite-demo-protocol.md) Phase 2. Gotchas #2–4 are caught only by visual review until corresponding audits are added. #6 is a distinct axis (async-lifecycle correctness, not CSS layering), see its own Detector/Fix above. #7 is a third axis (a hand-maintained resolution map read by a downstream regeneration step, not CSS or async ordering), its own Detector/Fix above; no audit catches it, only a browser probe of the specific route touched.