@adia-ai/adia-ui-forge 0.8.61 → 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 +309 -274
  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,4 +1,4 @@
1
- # API Contract Props, Attributes, Reflection
1
+ # API Contract, Props, Attributes, Reflection
2
2
 
3
3
  Deep dive on declaring component APIs in AdiaUI. Read this when adding a new prop whose shape doesn't match any obvious pattern in the good-citizen references.
4
4
 
@@ -17,14 +17,14 @@ static properties = {
17
17
 
18
18
  **Field rules:**
19
19
 
20
- - `type` one of `String`, `Number`, `Boolean`, `Object`, `Array`. The runtime uses this to coerce attribute strings into typed values.
21
- - `default` the value the prop takes when no attribute is present AND no JS value has been assigned. For `Boolean` props, default is `false` in the standard shape (see rule 1); `default: true` is a rare, ratified exception carried by connect-time attribute stamping (ADR-0075, gh#961) never the default shape to reach for. For numeric props, default is `0`, a real value, or `null` for indeterminate NEVER a sentinel like `-1`.
22
- - `reflect` when `true`, JS property changes write back to the HTML attribute so CSS can match it. Required for every state-bearing Boolean. Usually safe to omit for large value props (long strings, big objects).
23
- - `attribute` explicit kebab-case mapping when the JS name doesn't auto-convert cleanly. `camelCase` → `camel-case` automatic; override via `attribute: 'max-length'` when you want non-default behavior.
20
+ - `type`, one of `String`, `Number`, `Boolean`, `Object`, `Array`. The runtime uses this to coerce attribute strings into typed values.
21
+ - `default`: the value the prop takes when no attribute is present AND no JS value has been assigned. For `Boolean` props, default is `false` in the standard shape (see rule 1); `default: true` is a rare, ratified exception carried by connect-time attribute stamping (ADR-0075, gh#961), never the default shape to reach for. For numeric props, default is `0`, a real value, or `null` for indeterminate, NEVER a sentinel like `-1`.
22
+ - `reflect`, when `true`, JS property changes write back to the HTML attribute so CSS can match it. Required for every state-bearing Boolean. Usually safe to omit for large value props (long strings, big objects).
23
+ - `attribute`, explicit kebab-case mapping when the JS name doesn't auto-convert cleanly. `camelCase` → `camel-case` automatic; override via `attribute: 'max-length'` when you want non-default behavior.
24
24
 
25
25
  [verified 2026-08-19] **Literal-string `"false"` parity (ADR-0075).** For a
26
26
  `Boolean`-typed prop, `parseAttr` (`core/element.js:93-94`) special-cases the
27
- literal attribute string `"false"` to parse as JS `false` a deliberate
27
+ literal attribute string `"false"` to parse as JS `false`, a deliberate
28
28
  deviation from strict HTML boolean-attribute semantics (where any presence,
29
29
  including `attr="false"`, means `true`). This matches the A2UI transpiler's
30
30
  own prior `// (defensive)` special-case
@@ -34,10 +34,10 @@ the Decision: "for `t === Boolean`, the literal string `\"false\"` parses to
34
34
  JS `false`; any other present value (including empty string) parses to
35
35
  `true`; absence stays `false`." A component author or generation pipeline
36
36
  that writes `interactive="false"` on a `default: true` Boolean prop gets the
37
- intuitive result do not assume strict HTML semantics here. (`default:
37
+ intuitive result, do not assume strict HTML semantics here. (`default:
38
38
  true` itself remains the rare, ratified exception this ADR's blast radius
39
- runs against ADR-0063's stamped-attribute mechanism, gh#961 never the
40
- default shape "Boolean prop naming the flip rule" above documents.) Named
39
+ runs against, ADR-0063's stamped-attribute mechanism, gh#961, never the
40
+ default shape "Boolean prop naming, the flip rule" above documents.) Named
41
41
  blast radius already shipped and relying on this: `password-strength-ui
42
42
  show-label="false"`, `nav-group-ui collapsible="false"`
43
43
  (`.examples.html` demos for both).
@@ -47,20 +47,20 @@ show-label="false"`, `nav-group-ui collapsible="false"`
47
47
  A real bug caught in iteration 4:
48
48
 
49
49
  ```javascript
50
- // BROKEN silent
50
+ // BROKEN, silent
51
51
  allowHalf: { type: Boolean, default: false, reflect: true, attr: 'allow-half' },
52
52
 
53
53
  // FIXED
54
54
  allowHalf: { type: Boolean, default: false, reflect: true, attribute: 'allow-half' },
55
55
  ```
56
56
 
57
- `attr:` is not a recognized key. The mapper ignores it. The default auto-conversion (`allowHalf` → `allow-half`) happens to produce the same result, so the bug hides until someone changes the prop name and notices the attribute never wired.
57
+ `attr:` is not a recognized key. The mapper ignores it. The default auto-conversion (`allowHalf` → `allow-half`) happens to produce the same result, so the bug hides, until someone changes the prop name and notices the attribute never wired.
58
58
 
59
59
  Use `attribute:` verbatim. If you think you're writing `attr:`, stop and correct it before saving.
60
60
 
61
- ## Boolean prop naming the flip rule
61
+ ## Boolean prop naming, the flip rule
62
62
 
63
- AdiaUI conventions require `default: false` on Boolean props in the standard shape, unless a ratified exception applies (ADR-0075's connect-time attribute stamping, gh#961 a rare exception, not a route open to new work). The naming follows:
63
+ AdiaUI conventions require `default: false` on Boolean props in the standard shape, unless a ratified exception applies (ADR-0075's connect-time attribute stamping, gh#961, a rare exception, not a route open to new work). The naming follows:
64
64
 
65
65
  | Intended default behavior | Wrong name (default:true) | Right name (default:false) |
66
66
  | --- | --- | --- |
@@ -73,15 +73,15 @@ AdiaUI conventions require `default: false` on Boolean props in the standard sha
73
73
 
74
74
  **Naming patterns:**
75
75
 
76
- - `permanent` / `static` / `readonly` describes the non-default state positively.
77
- - `no*` / `hide*` / `disable*` prefixes for "opt-out of a default."
76
+ - `permanent` / `static` / `readonly`, describes the non-default state positively.
77
+ - `no*` / `hide*` / `disable*`, prefixes for "opt-out of a default."
78
78
 
79
79
  **Don't write:**
80
80
 
81
- - `enabled` (invert to `disabled`), `visible` (invert to `hidden`) these clash with standard HTML attribute vocabulary.
81
+ - `enabled` (invert to `disabled`), `visible` (invert to `hidden`), these clash with standard HTML attribute vocabulary.
82
82
  - Double-negatives like `unhide` or `dontSkip`.
83
83
 
84
- ## Enum attribute + container-query auto-snap default no interpolation
84
+ ## Enum attribute + container-query auto-snap default, no interpolation
85
85
 
86
86
  [verified 2026-08-19] ADR-0074 establishes the cross-component pattern for a
87
87
  value-enum attribute whose sensible default is "pick automatically from live
@@ -90,55 +90,54 @@ layout, but let a consumer pin it explicitly": `chart-ui` /
90
90
  values (`3:2`, `1:1`, `2:3`), unset by default. Quoting the Decision:
91
91
  "Unset (the default, empty string) means auto-snap: a CSS container query on
92
92
  the element's own box compares its live aspect ratio against two midpoint
93
- boundaries … and renders the nearest named ratio's studied layout never a
93
+ boundaries … and renders the nearest named ratio's studied layout, never a
94
94
  scaled/interpolated blend. Setting `ratio` explicitly pins that ratio's
95
95
  rendering regardless of the container's actual aspect, overriding the
96
96
  container query."
97
97
 
98
98
  Shape to follow for a new attribute of this kind:
99
99
 
100
- - A plain value-enum attribute, not a boolean ADR-0063's `no-*`
100
+ - A plain value-enum attribute, not a boolean, ADR-0063's `no-*`
101
101
  negation-prefix grammar does not apply.
102
102
  - Unset/default means container-query-driven auto-snap against studied
103
- breakpoints discrete snapping between named values, never continuous
103
+ breakpoints, discrete snapping between named values, never continuous
104
104
  interpolation (an in-between state was never individually studied, so it
105
105
  can't carry a "deliberately designed" claim).
106
106
  - An explicit attribute value always pins and overrides the auto-snap,
107
107
  never blends with it.
108
- - The resolved value reflects back for CSS/consumer introspection
109
- `data-ratio-resolved` on `chart-ui` names which of the enum's snapped or
108
+ - The resolved value reflects back for CSS/consumer introspection, `data-ratio-resolved` on `chart-ui` names which of the enum's snapped or
110
109
  pinned values is currently in effect, distinct from the (possibly unset)
111
110
  `ratio` attribute itself.
112
111
  - Don't fold the new axis into an existing enum attribute that covers a
113
112
  different concern (`size` stayed `sm|md|lg`-only; `ratio` didn't grow a
114
- compound value like `lg-2:3`) orthogonal axes get their own attribute.
113
+ compound value like `lg-2:3`), orthogonal axes get their own attribute.
115
114
 
116
115
  `table-toolbar-ui[stage]` (ADR-0076) is the second precedent for this same
117
- shape see the ADR-0063 conventions list in
116
+ shape, see the ADR-0063 conventions list in
118
117
  [token-contract.md](token-contract.md).
119
118
 
120
119
  Source: [ADR-0074](../../../../../../docs/ops/adr/adr-0074-chart-ratio-attribute-grammar.md).
121
120
 
122
- **[verified 2026-08-23] Failure mode DOM-ancestry auto-detection is riskier
121
+ **[verified 2026-08-23] Failure mode, DOM-ancestry auto-detection is riskier
123
122
  than container-query auto-detection.** ADR-0081 Decision 1 (`chart-ui[labels]`,
124
123
  a 3-value enum: `""`/`chip`/`outside`) initially mirrored this same
125
- auto/explicit-override shape, but auto-detected via DOM ancestry resolving
124
+ auto/explicit-override shape, but auto-detected via DOM ancestry, resolving
126
125
  unset `labels=""` to `chip` whenever the element sat inside a `section[bleed]`
127
- or `card-ui[padding="none"]` ancestor instead of a container query on the
126
+ or `card-ui[padding="none"]` ancestor, instead of a container query on the
128
127
  element's own box. The 2026-08-21 amendment walked that back entirely after it
129
128
  silently flipped 2 of 60 fixtures on the Charts visual-eval floor
130
129
  (`comp-chart-in-card-n-*`, pre-existing full-bleed compositions), caught only
131
- by the floor's pixel-diff gate neither the unit-test suite nor code review
130
+ by the floor's pixel-diff gate, neither the unit-test suite nor code review
132
131
  renders real CSS/layout. Amended decision: unset `labels=""` resolves to
133
132
  `outside` unconditionally; `chip` mode activates only via the explicit
134
- `labels="chip"` attribute no ancestry detection at all. ADR-0081's Decision
133
+ `labels="chip"` attribute, no ancestry detection at all. ADR-0081's Decision
135
134
  2 (a `today` marker attribute) independently rejects clock-derived
136
135
  auto-detection for the same reliability reason (cites IDR-0006). Lesson for a
137
136
  new auto-snap attribute of this shape: auto-detection is safe when it reads
138
137
  the element's *own* rendered geometry (a container query against its own box,
139
138
  as `ratio` does); it is risky when it reads *ancestor* DOM state or wall-clock
140
139
  time, because neither is guaranteed stable across every composition that
141
- happens to nest the element verify any such default against a real
140
+ happens to nest the element, verify any such default against a real
142
141
  pixel-diff/visual-eval gate, not unit tests or review alone. Source:
143
142
  [ADR-0081](../../../../../../docs/ops/adr/adr-0081-chart-2-0-foundations-attribute-grammar.md)
144
143
  Amendment (2026-08-21).
@@ -147,12 +146,11 @@ Amendment (2026-08-21).
147
146
 
148
147
  [verified 2026-08-25, gh#1984] `core/responsive.js`'s `BREAKPOINTS` export is
149
148
  the single canonical reference for any device-class threshold, whether the
150
- query mechanism is `@media` (the five sanctioned viewport primitives
151
- `grid-ui`, `col-ui`, `row-ui`, `block-ui`, `text-ui` — plus top-layer/popover
149
+ query mechanism is `@media` (the five sanctioned viewport primitives, `grid-ui`, `col-ui`, `row-ui`, `block-ui`, `text-ui`, plus top-layer/popover
152
150
  positioning with no ancestor box to query) or `@container` (everything else,
153
- per `adr-0088` `spec-breakpoint-convention` REQ-004 forbids importing
151
+ per `adr-0088`, `spec-breakpoint-convention` REQ-004 forbids importing
154
152
  `core/responsive.js` itself outside those cases). This table MUST match
155
- `core/responsive.js`'s `BREAKPOINTS` export byte-for-byte a gap here was
153
+ `core/responsive.js`'s `BREAKPOINTS` export byte-for-byte, a gap here was
156
154
  exactly the drift `spec-breakpoint-convention` REQ-007 named and closed:
157
155
 
158
156
  | Name | Min-width |
@@ -165,25 +163,25 @@ exactly the drift `spec-breakpoint-convention` REQ-007 named and closed:
165
163
 
166
164
  A `@container`/`@media` rule can't read a CSS custom property at parse time
167
165
  (forbidden-pattern #2 in [token-contract.md](token-contract.md)), so these
168
- values are cited as raw literals in component CSS, never tokenized but
166
+ values are cited as raw literals in component CSS, never tokenized, but
169
167
  every such literal MUST carry a preceding comment naming the rung it
170
168
  implements (device-class threshold) or the ergonomic reason for the number
171
- (a component-intrinsic threshold, exempt from this table entirely `nav-ui`'s
169
+ (a component-intrinsic threshold, exempt from this table entirely, `nav-ui`'s
172
170
  96px icon-rail floor, `table-toolbar-ui`'s compaction stages, `chart-ui`'s
173
171
  200px legend-hide are correctly exempt and are NOT migration candidates).
174
172
  `packages/web-components/core/breakpoint-observer.js` is the shared
175
173
  `ResizeObserver`-to-attribute helper for a component that needs
176
174
  JS-observable state (relocating a node, switching a positioning strategy)
177
- rather than a pure CSS layout switch see its own module doc for the
175
+ rather than a pure CSS layout switch, see its own module doc for the
178
176
  boolean-vs-named-value reflection modes. Full requirements:
179
177
  [spec-breakpoint-convention](../../../../../../docs/ops/spec/spec-breakpoint-convention.md).
180
178
 
181
- ## Numeric props `null` over sentinels
179
+ ## Numeric props, `null` over sentinels
182
180
 
183
181
  Indeterminate, unknown, or "not yet set" numeric state uses `null`, not `-1` or `Infinity`:
184
182
 
185
183
  ```javascript
186
- // BROKEN `-1` is a magic sentinel
184
+ // BROKEN, `-1` is a magic sentinel
187
185
  value: { type: Number, default: -1, reflect: true }
188
186
  // Consumer: if (this.value !== -1) { ... }
189
187
 
@@ -192,7 +190,7 @@ value: { type: Number, default: null, reflect: true }
192
190
  // Consumer: if (this.value != null) { ... }
193
191
  ```
194
192
 
195
- The `progress-ui` component carried `-1` for "indeterminate" for a long time. Branching on a specific number is fragile someone assigns `-1` meaningfully later and the indeterminate check breaks silently. `null` is unambiguous.
193
+ The `progress-ui` component carried `-1` for "indeterminate" for a long time. Branching on a specific number is fragile, someone assigns `-1` meaningfully later and the indeterminate check breaks silently. `null` is unambiguous.
196
194
 
197
195
  **Back-compat coercion is allowed:**
198
196
 
@@ -215,9 +213,9 @@ HTML's global `title` attribute is the browser tooltip. Using `title` on a custo
215
213
 
216
214
  Children can have per-item `active` (a Boolean describing that item's state). Parents use `value` (for a selection) or `step` (for an index into a series).
217
215
 
218
- - `<timeline-item-ui active>` OK, per-item Boolean state.
219
- - `<timeline-ui step="3">` correct: parent holds the index.
220
- - `<timeline-ui active="3">` wrong: `active` shouldn't carry a non-boolean payload.
216
+ - `<timeline-item-ui active>`, OK, per-item Boolean state.
217
+ - `<timeline-ui step="3">`, correct: parent holds the index.
218
+ - `<timeline-ui active="3">`, wrong: `active` shouldn't carry a non-boolean payload.
221
219
 
222
220
  ### `error` as a variant
223
221
 
@@ -236,10 +234,10 @@ Reserve `error` for validation state (`[error]` on form inputs matches ARIA patt
236
234
 
237
235
  ### `disabled` on a non-form component
238
236
 
239
- `disabled` has form-participating semantics it removes the element from the tab order, blocks submission, etc. On a non-form component (a diagram, a toolbar, a noodle editor), use `readonly`:
237
+ `disabled` has form-participating semantics, it removes the element from the tab order, blocks submission, etc. On a non-form component (a diagram, a toolbar, a noodle editor), use `readonly`:
240
238
 
241
- - `<input-ui disabled>` correct: input is form-participating.
242
- - `<noodles-ui readonly>` correct: diagram is read-only, not form-disabled.
239
+ - `<input-ui disabled>`, correct: input is form-participating.
240
+ - `<noodles-ui readonly>`, correct: diagram is read-only, not form-disabled.
243
241
 
244
242
  ### `multiple` with exclusion semantics
245
243
 
@@ -255,34 +253,34 @@ Reserve `error` for validation state (`[error]` on form inputs matches ARIA patt
255
253
 
256
254
  **[historical, 2026-08-31, ADR-0056 amendment]** `<toggle-group-ui>` /
257
255
  `<toggle-option-ui>` were cut outright in `0.8.43` (gh#1617), before the
258
- migration this ADR's Decision 2 anticipated ever ran moot, not
256
+ migration this ADR's Decision 2 anticipated ever ran, moot, not
259
257
  falsified. `segmented-ui`/`segment-ui` absorbed the role
260
258
  (`segmented.yaml`'s `multiple` prop description names the absorption
261
259
  directly, gh#1369/#1363 C1). The example above stays as written because it
262
260
  illustrates the Boolean-false naming rule against a real historical prop
263
- name, not because `<toggle-group-ui>` still exists `segmented-ui`
261
+ name, not because `<toggle-group-ui>` still exists, `segmented-ui`
264
262
  deliberately does NOT follow this same polarity (`[multiple]` is
265
263
  positive-polarity by design, matching `select-ui[multiple]`; there is no
266
264
  `segmented-ui[single]` opt-out), so it is not a drop-in replacement
267
265
  example for this rule.
268
266
 
269
- ## Selection-item state a declared, reflected `selected` prop, never a private `data-*` stamp
267
+ ## Selection-item state, a declared, reflected `selected` prop, never a private `data-*` stamp
270
268
 
271
- A **selection-item primitive** (one selectable option inside a selection-group parent `segment-ui` in `segmented-ui`; historically also `toggle-option-ui` in `toggle-group-ui`, cut in `0.8.43`/gh#1617 see the dated note below) exposes its current state as a declared, reflected `selected: Boolean` prop, documented as parent-managed, and styled via `[selected]`:
269
+ A **selection-item primitive** (one selectable option inside a selection-group parent, `segment-ui` in `segmented-ui`; historically also `toggle-option-ui` in `toggle-group-ui`, cut in `0.8.43`/gh#1617, see the dated note below) exposes its current state as a declared, reflected `selected: Boolean` prop, documented as parent-managed, and styled via `[selected]`:
272
270
 
273
271
  ```javascript
274
- // child the declared API surface
272
+ // child, the declared API surface
275
273
  static properties = {
276
274
  selected: { type: Boolean, default: false, reflect: true },
277
275
  };
278
276
  ```
279
277
 
280
278
  ```yaml
281
- # child yaml the SoT the catalog and A2UI grammar read
279
+ # child yaml, the SoT the catalog and A2UI grammar read
282
280
  selected:
283
281
  description: >-
284
282
  Whether this option is currently selected. Managed by the parent
285
- <group> container don't set directly; the group's `value` is the
283
+ <group> container, don't set directly; the group's `value` is the
286
284
  single source of truth.
287
285
  type: boolean
288
286
  default: false
@@ -290,18 +288,18 @@ selected:
290
288
  ```
291
289
 
292
290
  ```css
293
- /* child css the state hook is the reflected attribute */
291
+ /* child css: the state hook is the reflected attribute */
294
292
  segment-ui[selected] { … }
295
293
  ```
296
294
 
297
295
  Rules:
298
296
 
299
297
  - **The parent's `value` is the single source of truth**; the parent writes `selected` on its children (`segmented.class.js:146,149` sets/removes the attribute; `toggle-group.class.js:104-111` assigns `opt.selected`). Authors and generated markup drive the group's `value`, never `[selected]` on a child (`toggle-group.yaml`'s rules block says so in so many words).
300
- - **No private `data-*` stamp for API-conceptual state.** `data-selected` hides the state from the yaml SoT, the catalog, and generative authoring a consumer or the A2UI grammar cannot express "this option is selected" against a stamp that no schema declares. `toggle-group-ui` used to stamp `data-selected` + style `:scope[data-selected]`; it converged onto `segment-ui`'s mechanism (`toggle-option.yaml:38-46`, `toggle-group.css:14`, `toggle-group.test.js:64` asserts the stamp is gone).
301
- - **Parent-stamped ARIA stays as-is ARIA is wiring, not API.** `segment-ui` derives `aria-checked` from `selected` (`segment.class.js:42`); `toggle-option-ui` derives `aria-pressed` (`toggle-group.class.js:65`). The reflected prop is the API; the ARIA attribute follows it.
302
- - **Reference implementation:** `segment-ui` (`segment.yaml:32-36`, `segment.css:7`) is the current, live reference. **[historical, 2026-08-31, ADR-0056 amendment]** `toggle-option-ui`/`toggle-group-ui` were the converged second instance at ratification time, but both were cut outright in `0.8.43` (gh#1617), before any migration ran `segmented-ui`/`segment-ui` absorbed the role. A new selection-item primitive copies `segment-ui`'s shape directly; there is no second live instance to extend by analogy anymore (the divergence gh#1303 describes was between `segment-ui` and the now-deleted `toggle-option-ui`).
298
+ - **No private `data-*` stamp for API-conceptual state.** `data-selected` hides the state from the yaml SoT, the catalog, and generative authoring, a consumer or the A2UI grammar cannot express "this option is selected" against a stamp that no schema declares. `toggle-group-ui` used to stamp `data-selected` + style `:scope[data-selected]`; it converged onto `segment-ui`'s mechanism (`toggle-option.yaml:38-46`, `toggle-group.css:14`, `toggle-group.test.js:64` asserts the stamp is gone).
299
+ - **Parent-stamped ARIA stays as-is, ARIA is wiring, not API.** `segment-ui` derives `aria-checked` from `selected` (`segment.class.js:42`); `toggle-option-ui` derives `aria-pressed` (`toggle-group.class.js:65`). The reflected prop is the API; the ARIA attribute follows it.
300
+ - **Reference implementation:** `segment-ui` (`segment.yaml:32-36`, `segment.css:7`) is the current, live reference. **[historical, 2026-08-31, ADR-0056 amendment]** `toggle-option-ui`/`toggle-group-ui` were the converged second instance at ratification time, but both were cut outright in `0.8.43` (gh#1617), before any migration ran, `segmented-ui`/`segment-ui` absorbed the role. A new selection-item primitive copies `segment-ui`'s shape directly; there is no second live instance to extend by analogy anymore (the divergence gh#1303 describes was between `segment-ui` and the now-deleted `toggle-option-ui`).
303
301
 
304
- Migration note: removing `data-selected` was a breaking change for external CSS that targeted it, so it shipped in two halves the additive `selected` prop in `0.8.39`, the stamp removal on the `0.8.40` breaking wave with a migration-guide entry (`packages/web-components/CHANGELOG.md`; the ADR text names the wave `0.9.0` it shipped as `0.8.40`).
302
+ Migration note: removing `data-selected` was a breaking change for external CSS that targeted it, so it shipped in two halves, the additive `selected` prop in `0.8.39`, the stamp removal on the `0.8.40` breaking wave with a migration-guide entry (`packages/web-components/CHANGELOG.md`; the ADR text names the wave `0.9.0`, it shipped as `0.8.40`).
305
303
 
306
304
  Source: ADR-0056 (ratified 2026-08-15, gh#1303).
307
305
 
@@ -329,7 +327,7 @@ class UIInput extends UIFormElement {
329
327
  };
330
328
 
331
329
  connected() {
332
- super.connected(); // MUST call registers ElementInternals
330
+ super.connected(); // MUST call, registers ElementInternals
333
331
  // ...
334
332
  }
335
333
 
@@ -347,9 +345,9 @@ class UIInput extends UIFormElement {
347
345
 
348
346
  Key details:
349
347
 
350
- - **Always `super.connected()` and `super.disconnected()`** without them, form-association doesn't register.
351
- - **`this.syncValue(str)`** call this whenever the value changes to update the form-submitted string. Accepts a string.
352
- - **Inheriting properties** spread `UIFormElement.properties` into your own `static properties` so you don't re-declare `name`, `disabled`, `required`.
348
+ - **Always `super.connected()` and `super.disconnected()`**, without them, form-association doesn't register.
349
+ - **`this.syncValue(str)`**, call this whenever the value changes to update the form-submitted string. Accepts a string.
350
+ - **Inheriting properties**, spread `UIFormElement.properties` into your own `static properties` so you don't re-declare `name`, `disabled`, `required`.
353
351
 
354
352
  ## Event conventions
355
353
 
@@ -373,9 +371,9 @@ render() {
373
371
 
374
372
  Rule of thumb: if CSS can do the work via an attribute selector (`:scope[disabled] { ... }`), prefer CSS. Reserve `render()` for propagating state into child inputs, recalculating positions, or reflecting data changes that attribute selectors can't express.
375
373
 
376
- ## Popover `placement` defaults picked by popover-to-trigger width ratio
374
+ ## Popover `placement` defaults, picked by popover-to-trigger width ratio
377
375
 
378
- Every popover-bearing primitive exposes a consumer-overridable `placement` attribute (declare it in the yaml SoT, with the default documented in the prop description). The DEFAULT is selected by the popover's natural width relative to its trigger never by component identity:
376
+ Every popover-bearing primitive exposes a consumer-overridable `placement` attribute (declare it in the yaml SoT, with the default documented in the prop description). The DEFAULT is selected by the popover's natural width relative to its trigger, never by component identity:
379
377
 
380
378
  | Case | Default |
381
379
  |---|---|
@@ -384,6 +382,6 @@ Every popover-bearing primitive exposes a consumer-overridable `placement` attri
384
382
  | Trigger sits at a container's right edge **by construction** (toolbar spillover, right-pinned "more") | `bottom-end` |
385
383
  | Non-bottom-axis surfaces | component-specific: `tooltip-ui` → `top`, `nav-group-ui` collapsed flyout → `right` |
386
384
 
387
- Rationale: `bottom-start` on a wide popover under a right-anchored trigger fires `anchor.js`'s right-edge overflow recovery, snapping the panel far past the trigger's left edge (the v0.6.35 date-range-picker incident ~800px panel under a ~280px button). Centered `bottom` shifts at most half the overflow distance. Classification is done once at authoring time; borderline cases resolve by what the popover *wants*: if it `matchWidth`s the trigger it is trigger-width regardless of absolute size. "The consumer placed their button on the right of the layout" is a call-site `placement="bottom-end"` override, never a default. The overflow-recovery logic in `anchor.js` is the safety net and stays unchanged the default's job is to make recovery rarely fire.
385
+ Rationale: `bottom-start` on a wide popover under a right-anchored trigger fires `anchor.js`'s right-edge overflow recovery, snapping the panel far past the trigger's left edge (the v0.6.35 date-range-picker incident, ~800px panel under a ~280px button). Centered `bottom` shifts at most half the overflow distance. Classification is done once at authoring time; borderline cases resolve by what the popover *wants*: if it `matchWidth`s the trigger it is trigger-width regardless of absolute size. "The consumer placed their button on the right of the layout" is a call-site `placement="bottom-end"` override, never a default. The overflow-recovery logic in `anchor.js` is the safety net and stays unchanged: the default's job is to make recovery rarely fire.
388
386
 
389
387
  Source: ADR-0034.
@@ -1,47 +1,47 @@
1
- # Authoring cycle author a NEW primitive or MODIFY an existing one
1
+ # Authoring cycle, author a NEW primitive or MODIFY an existing one
2
2
 
3
- The 5-step procedure run AFTER [primitive-audit.md](primitive-audit.md) clears (new primitive) or jumping straight to Step 2 when modifying an existing one.
3
+ The 5-step procedure run AFTER [primitive-audit.md](primitive-audit.md) clears (new primitive), or jumping straight to Step 2 when modifying an existing one.
4
4
 
5
5
  ---
6
6
 
7
- ## Step 1 Read the contract and the reference components
7
+ ## Step 1, Read the contract and the reference components
8
8
 
9
9
  Before writing or editing, load these four files. They are the ground truth:
10
10
 
11
- - `.claude/docs/specs/component-token-contract.md` the authoritative invariants. Skim the whole thing if you haven't read it this session; focus on "Variants vs Modes" and "Sanctioned Mode Attributes" if you're adding a new layout-affecting attribute.
12
- - `packages/web-components/core/element.js` `UIElement` base class. The `static properties` schema, `connected()`/`disconnected()`/`render()` lifecycle, and attribute-mapping conventions are all defined here.
13
- - `packages/web-components/core/form.js` `UIFormElement`. Only needed if the new component participates in forms (inputs, selects, checkboxes, etc.).
11
+ - `.claude/docs/specs/component-token-contract.md`, the authoritative invariants. Skim the whole thing if you haven't read it this session; focus on "Variants vs Modes" and "Sanctioned Mode Attributes" if you're adding a new layout-affecting attribute.
12
+ - `packages/web-components/core/element.js`, `UIElement` base class. The `static properties` schema, `connected()`/`disconnected()`/`render()` lifecycle, and attribute-mapping conventions are all defined here.
13
+ - `packages/web-components/core/form.js`, `UIFormElement`. Only needed if the new component participates in forms (inputs, selects, checkboxes, etc.).
14
14
  - At least one good-citizen reference that matches the shape of what you're building. Pick from: `button-ui`, `card-ui`, `input-ui`, `textarea-ui`, `check-ui`. Read both the `.js` and the `.css`.
15
15
 
16
- ## Step 2 Classify the work before you write
16
+ ## Step 2, Classify the work before you write
17
17
 
18
18
  Before typing, answer:
19
19
 
20
- 1. **Is this cosmetic or structural?** If the change affects `display`, `flex-direction`, `grid-template`, `padding`, layout geometry it's a mode, not a variant. Modes require an entry in the Sanctioned Mode Attributes table (`.claude/docs/specs/component-token-contract.md` `Modes` section). Do not introduce undocumented layout-changing variants.
20
+ 1. **Is this cosmetic or structural?** If the change affects `display`, `flex-direction`, `grid-template`, `padding`, layout geometry, it's a mode, not a variant. Modes require an entry in the Sanctioned Mode Attributes table (`.claude/docs/specs/component-token-contract.md` `Modes` section). Do not introduce undocumented layout-changing variants.
21
21
 
22
22
  2. **Does the new prop fit the Boolean-default-false rule?** If the default behavior is "on," negate the prop name before writing (`closable` is wrong if closable is the default; `permanent` is right).
23
23
 
24
24
  3. **Does the component hold state that CSS needs to read?** If yes, every state-bearing Boolean declares `{ type: Boolean, default: false, reflect: true }`.
25
25
 
26
- 4. **Does the component add listeners, timers, or observers?** If yes, plan the `disconnected()` method at the same time as `connected()` do not defer. The symmetric pair is one unit of work, never two.
26
+ 4. **Does the component add listeners, timers, or observers?** If yes, plan the `disconnected()` method at the same time as `connected()`, do not defer. The symmetric pair is one unit of work, never two.
27
27
 
28
- ## Step 3 Apply the non-negotiable rules
28
+ ## Step 3, Apply the non-negotiable rules
29
29
 
30
30
  These rules are the distilled lessons from a 5-iteration audit. Each one corresponds to a real bug that was fixed in the codebase. Full rationale and the bug histories are in [anti-patterns.md](anti-patterns.md).
31
31
 
32
32
  ### API / Attributes
33
33
 
34
- 1. **Boolean defaults are `false`.** If the expected default is "on," rename: `closable` → `permanent`, `animate` → `static`, `cursor` → `noCursor`, `average` → `noAverage`, `pause-on-hover` → `noPauseOnHover`. `no-*` is the canonical negation prefix (ADR-0063 decision 1) a new rename lands on `no-*` directly, never `hide-*`.
34
+ 1. **Boolean defaults are `false`.** If the expected default is "on," rename: `closable` → `permanent`, `animate` → `static`, `cursor` → `noCursor`, `average` → `noAverage`, `pause-on-hover` → `noPauseOnHover`. `no-*` is the canonical negation prefix (ADR-0063 decision 1), a new rename lands on `no-*` directly, never `hide-*`.
35
35
 
36
36
  2. **No magic-value sentinels in numeric props.** Indeterminate = `null`, not `-1`. Consumers branch on `value == null`, which is explicit.
37
37
 
38
- 3. **Use `attribute:` not `attr:`.** `attr:` is a silent typo the mapper ignores it and the kebab-case HTML attribute never wires. This cost a real bug in `rating-ui`.
38
+ 3. **Use `attribute:` not `attr:`.** `attr:` is a silent typo, the mapper ignores it and the kebab-case HTML attribute never wires. This cost a real bug in `rating-ui`.
39
39
 
40
40
  4. **State-bearing Booleans reflect.** `{ type: Boolean, default: false, reflect: true }`. Without `reflect`, CSS can't match `:scope[disabled]`, hover/active/selected states break silently.
41
41
 
42
- 5. **Reserved-name anti-patterns.** Avoid: `title` (collides with HTML tooltip attribute), `active` on parent components (use `value` for a selection or `step` for an index per-item `active` on children is fine), `error` in variant names (use `danger`; reserve `error` for validation state), `disabled` on non-form-participating components (use `readonly`), `multiple` with exclusion semantics (use a negated positive like `single`).
42
+ 5. **Reserved-name anti-patterns.** Avoid: `title` (collides with HTML tooltip attribute), `active` on parent components (use `value` for a selection or `step` for an index, per-item `active` on children is fine), `error` in variant names (use `danger`; reserve `error` for validation state), `disabled` on non-form-participating components (use `readonly`), `multiple` with exclusion semantics (use a negated positive like `single`).
43
43
 
44
- 6. **Element tag ends in `-ui`; JS class is `UI<Component>`.** `<foo-ui>` ↔ `class UIFoo extends UIElement`. Three-way consistency: filename, class name, custom-element tag. The sanctioned `-n` carve-out is `cot-ui` (the chain-of-thought streaming component); `nav-ui` was deprecated in favor of the `-ui` replacements. New `-n` tags require a contract-doc update see `.claude/docs/specs/component-token-contract.md`.
44
+ 6. **Element tag ends in `-ui`; JS class is `UI<Component>`.** `<foo-ui>` ↔ `class UIFoo extends UIElement`. Three-way consistency: filename, class name, custom-element tag. The sanctioned `-n` carve-out is `cot-ui` (the chain-of-thought streaming component); `nav-ui` was deprecated in favor of the `-ui` replacements. New `-n` tags require a contract-doc update, see `.claude/docs/specs/component-token-contract.md`.
45
45
 
46
46
  ### CSS
47
47
 
@@ -57,12 +57,12 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
57
57
  }
58
58
 
59
59
  :scope {
60
- /* ── Base styles consume only component tokens ── */
60
+ /* ── Base styles, consume only component tokens ── */
61
61
  background: var(--component-bg);
62
62
  color: var(--component-fg);
63
63
  }
64
64
 
65
- /* ── Variants / states third override TOKENS only ── */
65
+ /* ── Variants / states third, override TOKENS only ── */
66
66
  :scope[variant="outlined"] {
67
67
  --component-bg: transparent;
68
68
  --component-border: var(--a-border);
@@ -84,7 +84,7 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
84
84
 
85
85
  ### JS Lifecycle
86
86
 
87
- 1. **Every `addEventListener` in `connected()` has a matching `removeEventListener` in `disconnected()`.** Handler must be a stable `#field` arrow (`#onClick = (e) => { ... }`), never an inline arrow passed to `addEventListener`. Inline arrows can't be removed `removeEventListener` needs reference equality.
87
+ 1. **Every `addEventListener` in `connected()` has a matching `removeEventListener` in `disconnected()`.** Handler must be a stable `#field` arrow (`#onClick = (e) => { ... }`), never an inline arrow passed to `addEventListener`. Inline arrows can't be removed, `removeEventListener` needs reference equality.
88
88
 
89
89
  2. **`UIFormElement` subclasses call `super.connected()` and `super.disconnected()`.** `ElementInternals` registration depends on it. Omitting `super` strands the form-association.
90
90
 
@@ -92,7 +92,7 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
92
92
 
93
93
  4. **Null cached DOM refs in `disconnected()`.** `this.#fooEl = null` after removing its listeners. Prevents stale-tree GC pinning when the component is re-attached.
94
94
 
95
- 5. **Never declare `disconnected()` twice in one class.** The second silently overrides the first this exact bug lost `ResizeObserver` cleanup in `chart.js` for several commits. If you find yourself needing a "second disconnected," merge it into the existing one.
95
+ 5. **Never declare `disconnected()` twice in one class.** The second silently overrides the first, this exact bug lost `ResizeObserver` cleanup in `chart.js` for several commits. If you find yourself needing a "second disconnected," merge it into the existing one.
96
96
 
97
97
  6. **Popover/tooltip overlays created in `connected()` are removed in `disconnected()`.** Anything appended to `document.body` or `<body>` via the Popover API needs explicit cleanup; they don't GC with the host.
98
98
 
@@ -100,7 +100,7 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
100
100
 
101
101
  ### Field composition
102
102
 
103
- 1. **Do not add a `label` attribute to a new form-associated control.** `<field-ui label="…">` is the canonical labeled-field wrapper. It owns the real `<label for="…">` and binds to the slotted control's id for proper click-to-focus a pattern the embedded per-control `label` attribute can't provide (no `[for]`, just a shadow slot). Existing controls (input-ui, select-ui, textarea-ui, switch-ui, check-ui, radio-ui, slider-ui, calendar-picker-ui, upload-ui, range-ui) still accept the legacy `label` attr but log a one-shot console.warn; **no new control should declare one**. Wrap instead:
103
+ 1. **Do not add a `label` attribute to a new form-associated control.** `<field-ui label="…">` is the canonical labeled-field wrapper. It owns the real `<label for="…">` and binds to the slotted control's id for proper click-to-focus, a pattern the embedded per-control `label` attribute can't provide (no `[for]`, just a shadow slot). Existing controls (input-ui, select-ui, textarea-ui, switch-ui, check-ui, radio-ui, slider-ui, calendar-picker-ui, upload-ui, range-ui) still accept the legacy `label` attr but log a one-shot console.warn; **no new control should declare one**. Wrap instead:
104
104
 
105
105
  ```html
106
106
  <!-- right -->
@@ -120,8 +120,7 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
120
120
 
121
121
  ```css
122
122
  @scope (my-composite-ui) {
123
- /* 1. Composite paints the ring via :focus-within
124
- wraps both the control and any siblings inside the shell. */
123
+ /* 1. Composite paints the ring via :focus-within, wraps both the control and any siblings inside the shell. */
125
124
  :scope:focus-within {
126
125
  box-shadow: var(--my-composite-focus-ring);
127
126
  }
@@ -131,7 +130,7 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
131
130
  }
132
131
 
133
132
  /* 2. Suppress the inner control's own focus affordance.
134
- The @scope block's containment IS the signal no data
133
+ The @scope block's containment IS the signal, no data
135
134
  attribute or explicit opt-in needed; selectors targeting
136
135
  inner elements only apply when they're inside this host. */
137
136
  textarea-ui [slot="text"]:focus {
@@ -148,12 +147,12 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
148
147
  ```
149
148
 
150
149
  **When to use this (not field-ui):**
151
- - **field-ui** is a _wrapper composite_ it adds chrome (label / hint / error / required) around a control, but the control is still the primary focus target. Control owns the ring.
152
- - **chat-input-ui** (and future equivalents) are _shell composites_ the composite IS the control; the inner textarea is an implementation detail. Host owns the ring.
150
+ - **field-ui** is a _wrapper composite_, it adds chrome (label / hint / error / required) around a control, but the control is still the primary focus target. Control owns the ring.
151
+ - **chat-input-ui** (and future equivalents) are _shell composites_: the composite IS the control; the inner textarea is an implementation detail. Host owns the ring.
153
152
 
154
153
  A user's mental model is the discriminator: do they think of the composite as a single control, or as a labeled/wrapped version of an inner control? The former is a shell; the latter is a wrapper.
155
154
 
156
- ## Step 4 Run the 30-second self-check
155
+ ## Step 4, Run the 30-second self-check
157
156
 
158
157
  Before declaring the work done, run through this checklist. If anything fails, fix before committing.
159
158
 
@@ -184,63 +183,61 @@ Before declaring the work done, run through this checklist. If anything fails, f
184
183
  - [ ] Cached DOM refs (`this.#fooEl`) are nulled in `disconnected()`.
185
184
  - [ ] Class declares `disconnected()` exactly once.
186
185
 
187
- ## Step 4b Author the yaml SoT (+ regenerate the sidecar)
186
+ ## Step 4b, Author the yaml SoT (+ regenerate the sidecar)
188
187
 
189
- A component isn't just its `.js`/`.css` the `<name>.yaml` is the SOURCE OF
188
+ A component isn't just its `.js`/`.css`: the `<name>.yaml` is the SOURCE OF
190
189
  TRUTH the whole pipeline reads (docs site, A2UI registries, consumer
191
190
  harnesses, `.d.ts` codegen). Every new primitive ships one; every prop/slot/
192
191
  event change updates it. Field-by-field contract:
193
- [yaml-contract.md](yaml-contract.md). Then regenerate never hand-edit the
192
+ [yaml-contract.md](yaml-contract.md). Then regenerate, never hand-edit the
194
193
  sidecar (`sidecar-prewrite-guard` blocks it anyway):
195
194
 
196
195
  ```bash
197
196
  npm run build:components # yaml → <name>.a2ui.json + catalog + .d.ts
198
- node scripts/build/components.mjs --verify # "clean N files up-to-date"
197
+ node scripts/build/components.mjs --verify # "clean, N files up-to-date"
199
198
  ```
200
199
 
201
- A primitive also needs BOTH barrel registrations the CSS `@import` in
200
+ A primitive also needs BOTH barrel registrations, the CSS `@import` in
202
201
  `styles/components.css` AND the JS `export` in `components/index.js`
203
202
  (`scripts/audit/check-components-js-barrel.mjs` gates the second). Either
204
- registration drifts the built `dist/` bundles regenerate them in the same
203
+ registration drifts the built `dist/` bundles, regenerate them in the same
205
204
  change (`npm run build:bundle-css && npm run build:bundle-js`) and commit the
206
205
  `dist/` updates, or CI's `check:{css,js}-bundles-fresh` gates fail on the PR
207
206
  (gh#390's PR shipped without this and failed exactly there). Both commands,
208
- not just one `theme-provider.min.js` (a JS bundle) inlines the co-emitted
207
+ not just one, `theme-provider.min.js` (a JS bundle) inlines the co-emitted
209
208
  `dist/web-components.sheet.js` twin, so a CSS-only edit still drifts a JS
210
209
  bundle if only `build:bundle-css` runs (bit the v0.8.13 theme-panel work
211
210
  twice).
212
211
 
213
212
  **Rebuild JS bundles from an `npm ci` scratch checkout, never a pnpm-bootstrapped worktree.**
214
213
  `node scripts/dev/bootstrap-worktree.mjs`'s pnpm install produces `dist/*.min.js` output
215
- ~30% larger than CI's npm-ci install (a lockfile/hoisting difference, not a code difference)
216
- — running `build:bundle-js` in such a worktree commits drift instead of fixing it, and a
214
+ ~30% larger than CI's npm-ci install (a lockfile/hoisting difference, not a code difference), running `build:bundle-js` in such a worktree commits drift instead of fixing it, and a
217
215
  local `check:js-bundles-fresh` run there will falsely pass against its own inflated bundle.
218
216
  When a PR built in a pnpm worktree needs a JS-bundle rebuild: commit the CSS-side changes
219
- first if any (`build:bundle-css` has no install-shape risk LightningCSS's bundling is
220
- deterministic from source so the branch's committed `dist/web-components.sheet.js` is
217
+ first if any (`build:bundle-css` has no install-shape risk, LightningCSS's bundling is
218
+ deterministic from source, so the branch's committed `dist/web-components.sheet.js` is
221
219
  trustworthy going into the next step, satisfying the CSS-before-JS ordering above), then
222
220
  `git worktree add <scratch-path> <branch>`, `npm ci` there (real npm-shaped install), run
223
221
  `build:bundle-js`, copy only the changed `dist/*.min.js` file(s) back into the working
224
222
  worktree, commit there with an explicit pathspec, then remove the scratch worktree. This
225
223
  is the correction to gh#1825's own PR body, which deferred the rebuild to "CI's
226
- derived-artifact pipeline" wrong per ADR-0069 (`dist/` is Class C, committed and
224
+ derived-artifact pipeline", wrong per ADR-0069 (`dist/` is Class C, committed and
227
225
  PR-blocking, never derive-resync'd); the scratch-checkout procedure above is what actually
228
226
  landed the fix, 2026-08-21.
229
227
 
230
228
  **Never `@import` a remote URL from any file `build:bundle-css` bundles.**
231
229
  LightningCSS's `bundleAsync` (the engine behind it) resolves every `@import`
232
- as a local filesystem path, remote `http(s)` URLs included a CSS-level
230
+ as a local filesystem path, remote `http(s)` URLs included, a CSS-level
233
231
  `@import url('https://fonts.googleapis.com/...')` breaks the build with
234
232
  `ResolverError: No such file or directory`, not a lint warning; there's no
235
233
  `bundleAsync` option to skip or externalize one `@import`. Link web fonts
236
234
  (or anything else external) as a real `<link>` in the consumer's `<head>`
237
- instead see `styles/theme-fonts-url.txt` for the pattern this repo's 12
235
+ instead, see `styles/theme-fonts-url.txt` for the pattern this repo's 12
238
236
  named themes use. A generated CSS file's own fix doesn't survive its next
239
- regen unless the generator is fixed too, not just the committed output
240
- the standing "generated artifacts are never hand-edited" invariant
237
+ regen unless the generator is fixed too, not just the committed output: the standing "generated artifacts are never hand-edited" invariant
241
238
  (AGENTS.md) cuts both ways.
242
239
 
243
- ## Step 5 Run the project's verification gates
240
+ ## Step 5, Run the project's verification gates
244
241
 
245
242
  Before committing, run the project's verify scripts. These catch the drift-shaped bugs the checklist can miss:
246
243
 
@@ -256,11 +253,11 @@ If a gate fails, fix before declaring done.
256
253
 
257
254
  ## Cross-references
258
255
 
259
- - [primitive-audit.md](primitive-audit.md) the §0 gate (run BEFORE this)
260
- - [api-contract.md](api-contract.md) deep dive on prop naming, type choices, reflection policy
261
- - [css-patterns.md](css-patterns.md) exhaustive CSS architecture (@scope, variants, modes)
262
- - [lifecycle-patterns.md](lifecycle-patterns.md) timers, observers, popovers, listener patterns
263
- - [anti-patterns.md](anti-patterns.md) full failure-mode catalogue, file:line refs
264
- - [worked-example.md](worked-example.md) badge-ui + counter-ui walkthroughs
265
- - [yaml-contract.md](yaml-contract.md) the `<name>.yaml` SoT schema (Step 4b)
266
- - [token-contract.md](token-contract.md) token audit (post-implementation check)
256
+ - [primitive-audit.md](primitive-audit.md), the §0 gate (run BEFORE this)
257
+ - [api-contract.md](api-contract.md), deep dive on prop naming, type choices, reflection policy
258
+ - [css-patterns.md](css-patterns.md), exhaustive CSS architecture (@scope, variants, modes)
259
+ - [lifecycle-patterns.md](lifecycle-patterns.md), timers, observers, popovers, listener patterns
260
+ - [anti-patterns.md](anti-patterns.md), full failure-mode catalogue, file:line refs
261
+ - [worked-example.md](worked-example.md), badge-ui + counter-ui walkthroughs
262
+ - [yaml-contract.md](yaml-contract.md), the `<name>.yaml` SoT schema (Step 4b)
263
+ - [token-contract.md](token-contract.md), token audit (post-implementation check)