@adia-ai/web-components 0.5.11 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,2270 @@
1
+ # Changelog — @adia-ai/web-components
2
+
3
+ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ Scope: custom elements, core (`AdiaElement` + reactivity + templates),
7
+ trait system, style tokens, patterns (composite elements). The A2UI
8
+ runtime ships in the sibling `@adia-ai/a2ui-runtime` package
9
+ (renamed from `@adia-ai/a2ui-utils` in v0.3.0) as of `0.0.4`.
10
+ ## [Unreleased]
11
+
12
+ _No pending changes._
13
+
14
+ ## [0.5.13] - 2026-05-15
15
+
16
+ ### v0.5.13 §310 — `apcaContrast` soft-clamp constant fix (FEEDBACK-35; P2 correctness)
17
+
18
+ `@adia-ai/web-components/color`'s `apcaContrast()` was using `APCA_LO_CLAMP = 0.06` for the soft-clamp threshold where the canonical APCA spec (`apca-w3@0.1.9`'s `SAPC_BLACK_THRESHOLD`) calls for `0.022`. The cubic-root soft-clamp `Math.pow(threshold - Y, 1.414)` operating on the wrong threshold lifted near-black luminance too aggressively. Pre-fix `apcaContrast('#000','#fff')` returned 99.49; post-fix returns 106.04 — matches canonical reference within ±0.01 Lc across all 8 reference pairs. Consumers using `<swatch-ui auto-contrast>` (which transitively calls `pickContrastingFg → apcaContrast`) silently got wrong contrast verdicts for any swatch with `Y < 0.06`.
19
+
20
+ #### Fixed — `color/`
21
+
22
+ - `color/index.js` `apcaContrast()` soft-clamp now uses `APCA_LO_FG`/`APCA_LO_BG = 0.022` (declared correctly since v0.5.12 but never read). Dead constants (`APCA_NORM_L`, `APCA_REVERSE_FACTOR`, `APCA_NORM_FACTOR`, `APCA_LO_CLAMP`, `APCA_RESCALE`) removed; new `APCA_BLACK_CLAMP = 1.414` factored out for readability.
23
+
24
+ ### v0.5.13 §311 — `@adia-ai/web-components/color` P3 helpers (FEEDBACK-31)
25
+
26
+ `@adia-ai/web-components/color` (v0.5.12 §301) shipped sRGB-only gamut helpers; the v0.6.0 §301 plan-doc spec lines 121-126 committed both sRGB AND Display-P3. v0.5.13 §311 closes the gap.
27
+
28
+ #### Added — `color/`
29
+
30
+ - `inP3Gamut(L, C, H): boolean` — returns true if the OKLCH triple is inside Display-P3 gamut (tolerance `0.001`).
31
+ - `gamutMapChromaP3(L, C, H): number` — 8-iteration chroma bisection in P3 (matches `gamutMapChroma`'s shape).
32
+ - Internal `linearSrgbToLinearP3()` matrix sourced from CSS Color 4 § 12.3 (D65 → D65; no Bradford chromatic adaptation needed).
33
+ - `color/index.d.ts` ships matching declarations.
34
+
35
+ ### v0.5.13 §312 — `<stat-ui>` explicit exports map entries (FEEDBACK-32)
36
+
37
+ v0.5.12 §253a hand-authored `stat-ui.d.ts` on disk but the package.json wildcard `./components/*` expanded `./components/stat` → `./components/stat/stat.js` (non-existent; actual file is `stat-ui.js`). Consumer TS imports failed at the package-export layer with `TS2882` even though the file exists + Vite resolved at runtime.
38
+
39
+ #### Changed — `package.json`
40
+
41
+ - `exports` map adds 2 explicit entries: `./components/stat` + `./components/stat/stat-ui.js` both pointing at `stat-ui.{d.ts,js}`.
42
+ - **Drops at v0.6.0 §303** when the `git mv stat-ui.{js,css,d.ts} → stat.{js,css,d.ts}` rename normalizes the filename + the wildcard naturally covers `stat.{js,d.ts}`.
43
+
44
+ ### v0.5.13 §313 — `<color-input-ui>` constraint pass-through + parsed-detail forwarding (FEEDBACK-33)
45
+
46
+ `<color-input-ui>` (v0.5.12 §302) composed `<color-picker-ui>` but dropped 5 generation-constraint props (`maxChroma` / `maxL` / `minL` / `hueDriftMax` / `baseHue`) and 3 parsed-OKLCH-channel-scalars (`{l, c, h}`) from the re-emitted event detail. Consumers using L-range constraints or OKLCH-native logic (Tokens Studio's `defaults.Lmin`/`Lmax`) couldn't migrate to `<color-input-ui>` without a UX regression.
47
+
48
+ #### Added — `components/color-input/`
49
+
50
+ - `class.js` adds 5 generation-constraint props to `static properties` + sets them on inner picker at `#mount()` + diff-detect re-set on `render()` (Object.is handles NaN-equality for `baseHue`/`hueDriftMax`).
51
+ - `#commit()` re-emits with all 6 detail fields including parsed scalars `l: detail.l, c: detail.c, h: detail.h`.
52
+ - `color-input.yaml` declares the 5 props + 3 new detail fields per `change`/`input` events.
53
+ - `color-input.d.ts` `ColorInputChangeEventDetail` interface gains `l: number; c: number; h: number;` fields; class gains 5 typed prop declarations.
54
+
55
+ ### v0.5.13 §314 — `<swatch-ui>` `[slot="chrome"]` named slot (FEEDBACK-34)
56
+
57
+ `<swatch-ui>`'s `#stamp()` lifecycle wiped `innerHTML` then funneled surviving children into `#labelEl`. Consumer chrome positioned absolutely against the swatch tile (gamut badges + override/tracked dots per C1.3 dogfood) landed inside the label's text bbox where `position: absolute; top/right/bottom/left` anchored to the small label geometry instead of the tile. v0.5.13 §314 adds a `[slot="chrome"]` named slot whose children survive stamping as direct host siblings of `#tileEl`.
58
+
59
+ #### Added — `components/swatch/`
60
+
61
+ - `class.js` `#stamp()` extracts `[slot="chrome"]` children BEFORE the wipe; re-attaches them as direct host siblings IMMEDIATELY after `#tileEl`. The host's `position: relative` anchor (when `shape="block"`) lets consumer absolute-positioning anchor to the tile's geometry.
62
+ - `swatch.yaml` `slots:` block declares the `chrome` named slot + clarifies `default` slot's funnel-into-label behavior.
63
+
64
+ #### Migration
65
+
66
+ ```diff
67
+ <swatch-ui shape="block" label-position="overlay" selectable>
68
+ - <badge-ui class="dts-gamut-badge" ...>P3</badge-ui>
69
+ - <span class="dts-override-dot"></span>
70
+ + <badge-ui slot="chrome" class="dts-gamut-badge" ...>P3</badge-ui>
71
+ + <span slot="chrome" class="dts-override-dot"></span>
72
+ </swatch-ui>
73
+ ```
74
+
75
+ Single `slot="chrome"` attribute on each chrome element. Existing absolute-positioning CSS unchanged.
76
+
77
+ ## [0.5.12] - 2026-05-15
78
+
79
+ ### v0.5.12 §252 — `package.json` `files` array completion (FB-28 close-out)
80
+
81
+ Closes FB-28 (CHANGELOG.md tarball asymmetry). `packages/web-components/package.json` `files` array gains `README.md` + `CHANGELOG.md` entries (both files already existed at source; previously absent from publish whitelist). 4-cycle lag (v0.5.8 → v0.5.11) since the original FB-22 §1 P3 adjacent ask went silently un-responded in RESPONSE-22.
82
+
83
+ Sibling-package sweep verified — all 9 published packages (`@adia-ai/web-components` + `@adia-ai/web-modules` + 7 `a2ui-*` + `@adia-ai/llm`) now have symmetric `files` arrays containing `README.md` + `CHANGELOG.md`. No drift.
84
+
85
+ ### v0.5.12 §253 — `stat-ui.d.ts` hand-author + slot 26 widening (FB-30 close-out)
86
+
87
+ Closes FB-30 (display-primitive .d.ts drift). Drift surface #1 (`.d.ts` axis) post-§253: 6 sub-axes mechanically protected, slot 26 now scoped for BOTH form-bearing AND display sub-axes. v0.5.10's "100% structural protection" milestone holds; v0.5.12 §253b extends slot 26 scope without changing the slot count.
88
+
89
+ #### Added — `components/stat/stat-ui.d.ts`
90
+
91
+ `packages/web-components/components/stat/stat-ui.d.ts` NEW hand-authored matching `stat.yaml` props (5: `label` / `value` / `change` / `trend` / `icon`). Filename mirrors `stat-ui.js` (not `stat.js`) for TS side-effect import resolution. Post-v0.6.0 §303 rename, files normalize to `stat.{js,d.ts}` + codegen takes over.
92
+
93
+ Root cause deeper than FB-30 framing: `dts-codegen.mjs:189` `isCssOnly()` looks for `${name}.js` — for `stat/`, that's `stat.js`, but the actual file is `stat-ui.js` (irregular filename). False-positives stat as CSS-only; codegen never writes `.d.ts`. v0.6.0 §303 rename fixes this; v0.5.12 §253a is the bridge.
94
+
95
+ #### Changed — `scripts/release/check-yaml-vs-dts-coverage.mjs` slot 26 widened
96
+
97
+ - `hasShippedJs(dir, comp)` — looks for `<comp>.js` OR `<comp>-ui.js` (handles irregular-filename case)
98
+ - `findDtsPath(dir, comp)` — looks for `<comp>.d.ts` OR `<comp>-ui.d.ts` (mirrors JS filename)
99
+ - NEW `CSS_ONLY_PRIMITIVES` exclusion set (`aside` / `footer` / `header` / `section`) — intentionally yaml-only stubs
100
+ - NEW finding kind: `missing-dts-file` (display-primitive sub-axis catch)
101
+ - Scope: FORM_BEARING-only → all primitives with shipped JS + yaml
102
+
103
+ Validated by smoke test: removing `stat-ui.d.ts` fires `missing-dts-file: stat`; restoring closes clean.
104
+
105
+ ### v0.5.12 §301 — NEW `@adia-ai/web-components/color` subpath (FEEDBACK-29 re-bucket)
106
+
107
+ NEW subpath bundling OKLCH ↔ sRGB conversion, hex ↔ OKLCH, sRGB gamut-mapping, APCA contrast, and OKLab-distance helpers as pure functions. Consumer-side `src/lib/color-engine.ts` ~250 LOC of duplicated helpers can retire.
108
+
109
+ #### Added — `color/`
110
+
111
+ - `color/index.js` — `oklchToOklab`, `oklabToOklch`, `oklabToLinearSrgb`, `linearToSrgb`, `srgbToLinear`, `oklchToRgb`, `rgbToHex`, `oklchToHex`, `hexToOklch`, `isInGamut`, `isOklchInGamut`, `gamutMapChroma`, `apcaContrast`, `pickContrastingFg`, `oklchDistance`, plus `MAX_CHROMA` + `GAMUT_EPSILON` constants. Pure functions; no DOM / runtime side-effects.
112
+ - `color/index.d.ts` — full TS surface, `OKLab` + `OKLCH` interfaces, comprehensive JSDoc with APCA threshold guidance.
113
+
114
+ #### Changed — `package.json`
115
+
116
+ - `exports["./color"]` added pointing at `color/index.{js,d.ts}`.
117
+ - `files: ["color/", …]` added so the subpath ships in the tarball.
118
+
119
+ ### v0.5.12 §302 — NEW `<color-input-ui>` compact form-bearing primitive (FEEDBACK-29 re-bucket)
120
+
121
+ Canonicalizes the USAGE.md §221f recipe (popover + button + color-picker) into a single form-associated tag for inline form contexts (settings drawer "source color" field, swatch-row inline-edit). Light-DOM composition; the inner picker fires `change` + `input` which the host re-emits with a flattened detail payload that always carries both hex + oklch string forms regardless of `[format]`.
122
+
123
+ #### Added — `components/color-input/`
124
+
125
+ - `color-input.yaml` — schema declares 6 props (`name`, `value`, `format`, `disabled`, `placement`, `open`) + 2 events with flattened parallel-views detail.
126
+ - `class.js` — `UIColorInput extends UIFormElement`. Builds its inner DOM (`<popover-ui>` + `<button-ui>` + `<color-picker-ui>`) as light-DOM children in `connected()`; wires picker → host event re-emission + popover `open` mirror via MutationObserver.
127
+ - `color-input.css` — compact swatch chip + tabular-num value label; honors `--color-input-swatch-size` token.
128
+ - `color-input.d.ts` — hand-authored (FORM_BEARING in dts-codegen); declares `ColorInputChangeEventDetail` with parallel `hex` + `oklch` views.
129
+ - `color-input.js` — auto-register shim.
130
+ - `color-input.a2ui.json` — codegen sidecar.
131
+
132
+ #### Changed
133
+
134
+ - `components/index.js` — re-exports `UIColorInput`.
135
+ - `styles/components.css` — `@import` for `color-input/color-input.css`.
136
+ - `scripts/build/dts-codegen.mjs` — `FORM_BEARING` set adds `color-input` so codegen preserves hand-authored event types.
137
+ - `scripts/release/check-enum-vs-css-selector.mjs` — parser now honors yaml `attribute:` overrides when matching CSS selectors (closes the swatch `labelPosition`/`label-position` false-positive class). Baselines added for color-input's behavioral enums (`format`, `placement`) which are JS-routed not CSS-routed; stale baselines for `heatmap:colorScheme:success/warning` + `page:maxWidth:*` (closed by the attribute-override fix) swept from the expected-findings set.
138
+
139
+ ### v0.5.12 R23 §1 — `<swatch-ui label-position="overlay">` (FB-23 §1)
140
+
141
+ NEW enum value on `<swatch-ui>` — `[label-position]` (default `below`) accepts `overlay` to render the label inside the tile via absolute positioning. Pair with `[auto-contrast]` for OKLab-L-driven legibility. Unblocks Tokens Studio's C1.3 dogfood migration (blocked 3 cycles on this design call).
142
+
143
+ #### Added — `components/swatch/`
144
+
145
+ - `swatch.yaml` — `labelPosition` prop (kebab attribute `label-position`) with `below|overlay` enum.
146
+ - `swatch.css` — `:scope[shape="block"][label-position="overlay"]` rules positioning the label absolutely over the tile.
147
+ - `swatch/class.js` — static property declaration; reflected.
148
+
149
+ ## [0.5.11] - 2026-05-15
150
+
151
+ ### v0.5.11 §250 — `?attr=${bool}` silent-failure trap closed (FEEDBACK-27)
152
+
153
+
154
+ Closes the fourth template-parser-contract trap in the R6-R27 series. v0.5.3 §155 closed HTML-comment quote-state edge cases; v0.5.3 §152 closed partial-attribute interpolation with a load-time `console.warn`. §250 graduates the Lit-style `?attr=${bool}` boolean-attribute shape to the same warn + degrade contract.
155
+
156
+ #### Fixed — §250a (v0.5.11) — `core/template.js` `scan()` rejects `?`-prefixed attribute names with a console.warn
157
+
158
+ `packages/web-components/core/template.js` — `scan()` attribute-walk adds a 4th branch alongside `@event=` / `.prop=` / canonical `attr=`. When `name[0] === '?'`:
159
+
160
+ 1. Strip the bogus `?attr` attribute from the DOM (prevents the literal `?disabled="true"` poison-pill that consumers saw pre-§250).
161
+ 2. Fire `console.warn` naming the offending attribute + the corrected migration path (`use .${prop}=${'${value}'}` — property binding).
162
+ 3. Degrade the part slot to a no-op text-node binding (`parts[i] = { t: 'n', n: document.createTextNode(''), ... }`) so the failure mode is "no attribute, with a warn" rather than "inert attribute, no warn".
163
+
164
+ Pre-§250 (silent inert binding):
165
+
166
+ ```js
167
+ // Pre-§250 — silently registered `?disabled="true"` on the DOM; consumer's
168
+ // `<color-picker-ui>` ignored it; picker stayed interactive.
169
+ html`<color-picker-ui ?disabled=${isTracked}>`;
170
+ ```
171
+
172
+ Post-§250 (warn + safe-degrade):
173
+
174
+ ```
175
+ console.warn: [template] Lit-style boolean attribute "?disabled=" is not supported.
176
+ Element: <color-picker-ui>
177
+ Use .disabled=${value} (property binding) instead — the primitive reflects
178
+ the property to the DOM attribute for you.
179
+ See USAGE.md § Template parser — invariants + unsupported syntaxes.
180
+ ```
181
+
182
+ Why NOT actually implement Lit's `?attr=` semantics: AdiaUI primitives declare reflective property/attribute mapping via `static properties = { disabled: { type: Boolean, reflect: true } }`. The runtime handles property-to-attribute reflection automatically. Adding `?attr=` would create two valid syntaxes for the same effect — discoverability tax with no behavioral benefit.
183
+
184
+ 7 new vitest cases in `packages/web-components/core/template.test.js` lock in the §250 contract:
185
+
186
+ - `?attr=` fires warn (truthy + falsy)
187
+ - Does NOT register literal `?attr` attribute on the DOM
188
+ - Does NOT set the matching attribute either (no Lit-attr semantics)
189
+ - No false positives on canonical `attr=` / `.prop=` / `@event=`
190
+
191
+ #### Changed — §250b (v0.5.11) — USAGE.md adds "Template parser — invariants + unsupported syntaxes" section
192
+
193
+ `packages/web-components/USAGE.md` § Template parser — NEW section enumerates all 4 documented parser contracts:
194
+
195
+ | Syntax | Status | Migration |
196
+ |---|---|---|
197
+ | `attr=${val}` (full interpolation) | ✅ Supported | n/a |
198
+ | `.prop=${val}` (property binding) | ✅ Supported | canonical for boolean state |
199
+ | `@event=${handler}` (event listener) | ✅ Supported | n/a |
200
+ | `?attr=${bool}` (Lit-style boolean) | ❌ NOT supported — `console.warn` v0.5.11 §250 | Use `.attr=${bool}` |
201
+
202
+ Also enumerates the 3 historical parser-bug fixes (apostrophes in HTML comments + quoted attributes in HTML comments + backticks-in-comments) so the parser's tacit contract is now a fully-enumerated invariants list.
203
+
204
+ Pattern matures: every documented-but-easy-to-miss template-parser failure now emits a `console.warn` at scan time. Consumer onboarding from Lit / native lit-html no longer hits the silent-failure class.
205
+
206
+ ## [0.5.10] - 2026-05-15
207
+
208
+ ### v0.5.10 §248 — `check-static-methods-vs-dts.mjs` slot 20 base-class spread-walk extension (FB-25 audit-extension)
209
+
210
+
211
+ Closes the FB-25 base-class blindspot mechanically. The audit grows a 5th declaration-axis on the `.d.ts` surface:
212
+
213
+ - For each registered base class (currently `{ UIFormElement }`), walk its `static get properties()` return-object body + cross-reference against base `.d.ts` instance-field declarations.
214
+ - New finding kind: `base-property-undeclared`. Hard-fails strict mode when a base class's runtime property set has no matching field declaration in the base's `.d.ts`.
215
+
216
+ Closes the recurrence path for FB-25: any future addition to `UIFormElement.properties` without a matching `core/form.d.ts` field declaration fails the audit at release time. Subclasses using `...UIFormElement.properties` inherit the property at runtime; without the base-class `.d.ts` declaration, consumers using TS `extends` get TS2339 on `el.<prop> = ...`.
217
+
218
+ Caveat in the audit: the base's `.d.ts` typically declares the metadata-type shape `static properties: { ... };` whose keys overlap the instance-field names. The audit strips the `static properties: { ... };` braced block before scanning for `DTS_FIELD` matches to avoid false-positive coverage.
219
+
220
+ Validated by deliberate-removal test: removing `throttle: number;` from `core/form.d.ts` fires `base-property-undeclared: throttle`; restoring closes clean.
221
+
222
+ ### v0.5.10 §249 (slot 26 NEW) — `check-yaml-vs-dts-coverage.mjs` (FB-26 audit-extension)
223
+
224
+ NEW audit slot 26. Closes the FB-26 hand-authored `.d.ts` drift class mechanically. Walks every form-bearing primitive (`FORM_BEARING` set per `dts-codegen.mjs` — 17 primitives that skip codegen because they hand-author their `.d.ts` for rich event-detail typing) and verifies yaml `props:` reflection:
225
+
226
+ - For each form-bearing primitive: parse `<name>.yaml` `props:` block → set of declared prop names
227
+ - Parse `<name>.d.ts` class body → set of instance-field declarations (including getters)
228
+ - Subtract `UIFormElement`-inherited fields (via `core/form.d.ts` walk; 16 fields including the §247d `throttle` addition)
229
+ - Set-diff yaml-only → flag as `yaml-vs-dts-missing` (hard finding)
230
+
231
+ Slot 26's purpose is parallel to slot 19's: slot 19 catches yaml → class.js drift (schema vs implementation); slot 26 catches yaml → hand-authored .d.ts drift (schema vs typed-API surface). Together they bound the "yaml is the SoT, but the SoT can drift from the type" gap that v0.5.9 §220 + v0.5.6 §192 surfaced from different directions.
232
+
233
+ `EXPECTED_FINDINGS = new Set([])` — post-§247 closures, the audit runs clean across all 17 form-bearing primitives. Any subsequent yaml-prop addition without the matching `.d.ts` hand-author fires strict-mode hard-fail.
234
+
235
+ Validated by deliberate-removal test: removing `inputmode: string;` from `input.d.ts` fires `yaml-vs-dts-missing: input:inputmode`; restoring closes clean.
236
+
237
+ ### v0.5.10 §247 — post-v0.5.9 `.d.ts` drift close-out (FB-23 / FB-24 / FB-25 / FB-26)
238
+
239
+ Six items address consumer-reported drift between v0.5.9 runtime + yaml + `.d.ts` overlay. All PATCH-class additive (no breaking changes; types narrow what was previously `any` or undeclared). Single commit lands all six.
240
+
241
+ #### Added — §247 `HAND_AUTHORED_DTS` skip-list extension (`text` + `feed`)
242
+
243
+ `scripts/build/dts-codegen.mjs` `HAND_AUTHORED_DTS` set grows `{ 'toast' }` → `{ 'toast', 'text', 'feed' }`. Pattern source: v0.5.9 §246. Two new entries:
244
+
245
+ - **`text`** — preserves per-`UITextVariant`-member JSDoc that the yaml `enum:` schema can't carry (one description per enum value, not just the property). Closes FB-23 §3 (§221k Part A — the JSDoc-per-variant deferred mid-cycle in v0.5.9).
246
+ - **`feed`** — preserves the `UIFeed` static-API class + `UIFeedPostOptions` + `FeedHandle` interfaces that the v0.5.9 §228 sibling-yaml walk can't generate (UIFeed has no tag-registered yaml). Closes FB-24 §1.
247
+
248
+ #### Fixed — §247 `text.d.ts` per-variant JSDoc + ARIA-role disclaimer (FB-23 §2 + §3)
249
+
250
+ `packages/web-components/components/text/text.d.ts` hand-authored. Two surfaces:
251
+
252
+ 1. **`UITextVariant` named type** with per-member JSDoc (12 variants × 1-line description). IDE hover on `<text-ui variant="?">` surfaces the picker heuristic inline; complements the §221k chooser guide in USAGE.md (which only the consumer who reads docs sees).
253
+ 2. **Property-level JSDoc** on `variant: UITextVariant` carries the **presentational-only** disclaimer: `<text-ui variant="heading">` does NOT set `role="heading"` + `aria-level`. For semantic headings, wrap with native `<h1>`-`<h6>` OR add `role` + `aria-level` to the host. Closes the FB-23 §2 ARIA-role discoverability gap; the actual API addition (`level=` / `as=`) is deferred to v0.6.0.
254
+
255
+ #### Fixed — §247 `feed.d.ts` UIFeed static-API surface (FB-24 §1)
256
+
257
+ `packages/web-components/components/feed/feed.d.ts` hand-authored. Adds:
258
+
259
+ - `UIFeed` class with `static get(position?)` / `static post(opts)` / `static clear(position?)` / `static purge()` / `static releaseContainerIfEmpty(container)` — the imperative one-shot API consumers use without instantiating `<feed-ui>` declaratively.
260
+ - `UIFeedPostOptions` interface mirroring `UIFeedItem`'s reflected props + `position` + the phase-2 `action?` field.
261
+ - `FeedHandle` interface (id + dismiss + update) — the return type of `UIFeed.post()`. Parallels `UIToastFeedHandle` from `toast.d.ts` (post-v0.5.6 §197 + v0.5.9 §246).
262
+ - `FeedPosition` named type for the 7 lane anchors (6 corners + inline).
263
+
264
+ #### Fixed — §247 `core/form.d.ts` adds `throttle: number` (FB-25 §1)
265
+
266
+ `packages/web-components/core/form.d.ts` adds `throttle: number` field declaration + 3 method signatures (`scheduleThrottledInput` + `flushPendingInput` + `dropPendingInput`) on `UIFormElement`. Closes FB-25's drift: v0.5.9 §220 added `throttle` to the runtime base class's `static get properties()` but never propagated the type declaration. All 17 form-bearing primitives (input / select / slider / range / textarea / switch / check / radio / segmented / search / otp-input / option-card / color-picker / rating / code / calendar-picker / upload) inherit the field via TS `extends`. Single 1-LOC fix covers all 17 instead of 17 per-component edits.
267
+
268
+ **Pre-existing slider.d.ts `throttle: number;` declaration** becomes redundant (now inherited). Cleanup deferred to v0.6.0 hand-author de-dup pass; no functional impact.
269
+
270
+ #### Fixed — §247 5 missing prop declarations on form-bearing `.d.ts` (FB-26 §1)
271
+
272
+ Per FB-26's wider-scope audit beyond FB-25's `throttle`-only scope:
273
+
274
+ - `input.d.ts` adds `inputmode: string` + `autocomplete: string` (HTML5 attribute forwards to contenteditable surface)
275
+ - `select.d.ts` adds `variant: 'default' | 'outline' | 'ghost' | 'soft'` + `size: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (universal `[size]` family + variant chrome)
276
+ - `rating.d.ts` adds `variant: 'star' | 'heart' | 'thumbs'` (icon-set treatment)
277
+
278
+ 5 props × ~3 LOC each. All yaml-declared + runtime-functional pre-v0.5.10; only TS type surface was missing.
279
+
280
+ #### Changed — §247 USAGE.md adds two clarification sections (FB-23 §1 + §2)
281
+
282
+ `packages/web-components/USAGE.md` grows two notes inside the v0.5.9 §221 sweep section:
283
+
284
+ 1. **`<text-ui>` ARIA-role clarification** — three authoring patterns (native `<hN>` wrap / ARIA-only on host / presentational-only) with the picker heuristic. Cross-references the `text.d.ts` property-level disclaimer.
285
+ 2. **`<swatch-ui shape="block">` label-position** — confirms label renders BELOW the tile (flex-column); label-on-tile use case (Tokens Studio C1.3 pattern) is NOT supported in v0.5.x; the auto-contrast classes (`data-on-light` / `data-on-dark`) are designed for a future overlay mode. v0.6.0 candidate: `<swatch-ui label-position="overlay">` (or `shape="block-overlay"`, or sibling `<swatch-tile-ui>` slot composition).
286
+
287
+ ### v0.6.0 candidates surfaced by FB-23/24/25/26
288
+
289
+ - Codegen yaml-schema extension to carry per-`enum:`-member descriptions (retires `text.d.ts` from `HAND_AUTHORED_DTS`)
290
+ - Audit slot 20 spread-walk: when encountering `...Base.properties` in a static-properties literal, recursively walk the base's keys (closes the FB-25 base-class blindspot mechanically)
291
+ - NEW audit slot 26 `check-yaml-vs-dts-coverage.mjs` for form-bearing primitives (closes the FB-26 hand-authored `.d.ts` drift class mechanically)
292
+ - Audit-extension to walk all `export class` (not just `extends UIElement`) — closes the FB-24 ambient static-API class drift
293
+ - `<text-ui level="N">` or `<text-ui as="hN">` semantic-heading attribute (API design call — closes FB-23 §2 with runtime ARIA-role setting, not just docs)
294
+ - `<swatch-ui label-position="overlay">` (or equivalent) — API for label-on-tile use case (FB-23 §1 feature)
295
+
296
+ ## [0.5.9] - 2026-05-15
297
+
298
+ ### Added — §246 (v0.5.9) — `dts-codegen.mjs` `HAND_AUTHORED_DTS` skip-list + restore `toast.d.ts` static-method surface
299
+
300
+
301
+ Post-§228 codegen regenerated all `.d.ts` files including `toast.d.ts`, which lost its `static show(opts): UIToastFeedHandle` declaration (yaml's `props:`/`events:` shape can't express imperative static methods). The runtime API was missing from the TS surface.
302
+
303
+ Fix:
304
+
305
+ - `scripts/build/dts-codegen.mjs` — new `HAND_AUTHORED_DTS = new Set(['toast'])` checked in `generateForComponent()` before regen; status returns `'skip-hand-authored'`. Pattern matches the existing `FORM_BEARING` skip mechanism.
306
+ - `packages/web-components/components/toast/toast.d.ts` — restored full hand-authored shape: `UIToastShowOptions` interface + `UIToastFeedHandle` interface + `static show(opts?): UIToastFeedHandle` declaration. Header doc flags HAND-AUTHORED status + references §246 + cross-links the skip list.
307
+
308
+ Verified: `node scripts/build/components.mjs` runs codegen with `--force` flag; toast.d.ts is preserved across builds.
309
+
310
+ v0.6.0 follow-up candidate: yaml `methods:` schema + codegen support for static-method emission would retire this skip-list entry by making toast's surface fully codegen'd. Filed mentally; not yet on plan-doc.
311
+
312
+ ### Added — §246 (v0.5.9) — regression tests for §220 throttle + §223 empty-state minimal
313
+
314
+ `packages/web-components/components/input/input.test.js` — 5 new tests for the §220 throttle wiring on `<input-ui>`: inheritance from UIFormElement, attribute reflection, synchronous dispatch when throttle=0, trailing-debounce when throttle>0, flushPendingInput() + dropPendingInput() semantics. Passes 13/13.
315
+
316
+ `packages/web-components/components/empty-state/empty-state.test.js` — NEW test file (component had no tests). 6 tests for the §223 minimal mode: default non-minimal state, `[minimal]` attribute reflection, icon-size opt-out (no `size="lg"` when minimal), no-regression for canvas-style stamping, heading + description slots in both modes, consumer-provided `[slot="action"]` preserved. Passes 6/6.
317
+
318
+ Authoring pitfall captured in the input.test.js diff: when testing trailing-debounce, surface-event bubbling pollutes host-event count — `<input-ui>` listens for native `InputEvent` on its contenteditable surface, which bubbles to the host alongside the host's own repackaged `CustomEvent('input', {detail:{value}})`. Filter assertions by `e.detail?.value !== undefined` to count only host-emitted events. Generalizes to any primitive testing repackaged-event semantics.
319
+
320
+ ### Fixed — §230-bundle wave 2 (v0.5.9) — close remaining 9 of 16 baselined orphan-token references
321
+
322
+ The remaining 9 architect-decision entries in `check-component-css-vs-styles-tokens.mjs` (audit slot 25, shipped v0.5.8 §231) resolved. After this commit, the baseline shrinks 9 → 0; `EXPECTED_FINDINGS = new Set([])`. All 16 v0.5.8 orphans closed across the two waves:
323
+
324
+ **Wave 2 architect decisions:**
325
+
326
+ - **NEW token** `--a-radius-xs` (parametric: `--a-radius-xs-k: 0.166`) in `styles/tokens.css`. Resolves heatmap + swatch callsites.
327
+ - **NEW tokens** `--a-chrome-fg` (= `--a-chrome-light` — white text) + `--a-chrome-bg` (= `oklch(0 0 0 / 0.4)` — dark scrim) in `styles/colors/semantics.css`. Canonical badge-on-chrome family. Resolves swatch badge callsites; documents the pattern for video-controls / color-readout / overlay use cases.
328
+ - **NEW token** `--a-ui-line-height` (= `var(--a-body-leading)` = 1.5) in `styles/typography.css`. Resolves alert callsite.
329
+ - `components/nav/nav.css` `var(--a-duration-base, 200ms)` → `var(--a-duration)` (canonical mid-tier, 250ms).
330
+ - `components/code/code.css` `var(--a-surface-floating)` → `var(--a-bg-strong)` (elevated panel surface).
331
+ - `components/feed/feed.css` `var(--a-feed-max-width, 22rem)` → component-local literal `22rem`. Hoisted per RESPONSE-21 guidance.
332
+ - `components/swatch/swatch.css` `var(--a-bg-2)` → `var(--a-bg-muted)` (canonical canvas-2 alias).
333
+
334
+ Closes drift surface #7 (token-reference vs definition) fully. `check-component-css-vs-styles-tokens --strict` exits 0 with zero baseline entries.
335
+
336
+ ### Fixed — §230-bundle wave 1 (v0.5.9) — close 7 of 16 baselined orphan-token references + new `--a-ui-xs: 12px`
337
+
338
+ Wave 1 (kimba prep batch at `73d06956c`):
339
+
340
+ - **NEW token** `--a-ui-xs: 12px` defined in `styles/typography.css` (one notch below `--a-ui-sm: 13px`).
341
+ - `components/tooltip/tooltip.css` (×2) — `var(--a-weight-regular)` → `var(--a-weight-normal)`.
342
+ - `components/range/range.css` — `var(--a-ui-fg)` → `var(--a-fg)`.
343
+ - `components/{select,slider,switch,tree}/*.css` (×4) — `var(--a-fine-size)` fallback → `var(--a-ui-xs)`.
344
+ - `components/swatch/swatch.css` `--a-ui-xs` references (×2) now resolve cleanly via the new definition.
345
+
346
+ ### Added — §232 (v0.5.9) — NEW audit slot 24 `check-enum-vs-css-selector.mjs` (drift surface #6 closed)
347
+
348
+ `scripts/release/check-enum-vs-css-selector.mjs` (~330 LOC). Walks each `<name>.yaml` for `enum:` blocks, cross-references against `:scope[<prop>="<value>"]` selectors in the component's `.css`. Flags enum values declared in yaml but with no matching CSS rule — silent fall-through to default variant (FEEDBACK-17 §1 bug class).
349
+
350
+ Baseline: 119 findings across multiple components (chart type / popover placement / tooltip placement etc. — many JS-routed via Floating UI or chart libraries, false-positive for CSS-cascade routing). Strict mode hard-fails on NEW drift beyond baseline; architect-triage cycle-by-cycle as primitives migrate to true CSS-cascade routing.
351
+
352
+ Closes the 6th drift surface in the 7-surface lockdown taxonomy.
353
+
354
+ ### Added — §222 (v0.5.9) — NEW audit slot 21 `check-connected-reads-attr.mjs` (drift surface #5 closed)
355
+
356
+ `scripts/release/check-connected-reads-attr.mjs` (~300 LOC). Walks each `class.js` for `connected()` / `connectedCallback()` bodies that synchronously read a declared `static properties.X` value, without an `attributeChangedCallback` to re-sync when the attribute applies after connection.
357
+
358
+ Bug class: FEEDBACK-10 §1 `<toggle-scheme-ui scheme="${reactiveValue}">` race — `#initState()` reads `this.scheme` before the framework's attribute-application phase, so declarative-rebinds lose to AUTO fallbacks. v0.5.7 §200 closed for `<toggle-scheme-ui>` specifically; §222 catches the next instance mechanically.
359
+
360
+ Baseline: 67 findings across components. Heuristic intentionally narrow (one-hop method chase, narrow gate), but accepts false positives are expected on first ship. Architect-triage cycle-by-cycle. Strict mode hard-fails on NEW drift beyond baseline.
361
+
362
+ Closes the 5th drift surface in the 7-surface lockdown taxonomy.
363
+
364
+ ### Added — §220 (v0.5.9) — `throttle` parity on form-bearing primitives via shared `UIFormElement` helper
365
+
366
+ `packages/web-components/core/form.js` graduates the v0.5.5 §184 slider trailing-debounce pattern to the shared `UIFormElement` base class:
367
+
368
+ - `throttle` static property (default 0) — declarative ms window for the `input` event
369
+ - `scheduleThrottledInput()` — call from input-event sites; trailing-debounces dispatch when `throttle > 0`
370
+ - `flushPendingInput()` — call from change / blur / commit paths; ensures trailing `input` fires before `change`
371
+ - `dropPendingInput()` — called automatically by `disconnected()` to prevent late dispatches
372
+
373
+ Callsite migrations:
374
+
375
+ - `components/slider/class.js` — drops local `#inputTimer` + `#flushInput()`; delegates via the shared helpers (semantics preserved)
376
+ - `components/input/class.js` `#onInput` — now calls `scheduleThrottledInput()` (was unconditional dispatch)
377
+ - `components/textarea/class.js` `#onInput` — same migration; `#onBlur` + `#onKeydown` call `flushPendingInput()` before commit
378
+
379
+ `input.yaml` + `textarea.yaml` document the new `throttle` prop. Use for expensive `input`-driven computation (server-side autocomplete, large list filter, palette regen) where consumers want trailing-debounce semantics without authoring their own timer.
380
+
381
+ Closes FEEDBACK-14 §3.
382
+
383
+ ### Added — §223 (v0.5.9) — `<empty-state-ui minimal>` single-line muted layout
384
+
385
+ `empty-state.yaml` + `class.js` + `empty-state.css` — new `minimal: boolean` prop drops the centered-column placeholder chrome (no canvas-style padding, no icon-size lg, no max-width on description). Renders as `[icon] heading [description]` inline in muted-fg, with `[slot="action"]` margin-collapsed to trailing edge. Use for inline empty-table-row / empty-list / placeholder cells where the canvas placeholder is too loud.
386
+
387
+ Closes FEEDBACK-13 §4.
388
+
389
+ ### Added — §228 (v0.5.9) — `dts-codegen.mjs` sibling-yaml walk + 5 NEW sibling yamls + 2 parent yaml `name:` fixes
390
+
391
+ `scripts/build/dts-codegen.mjs` (~40 LOC change) — walks ALL `*.a2ui.json` sidecars in each component dir + emits ONE `<dir>.d.ts` with one `export class UI<Name>` declaration per sidecar. Uses `x-adiaui.name` (canonical class name) instead of `UI${title}` (fragile — yaml `name:` and `component:` can drift, e.g. `feed.yaml` `name: UIFeedContainer` + `component: Feed` → old codegen wrote `UIFeed` extending `UIElement` which is wrong since `UIFeed` is the imperative host class, not a custom element).
392
+
393
+ 5 NEW sibling yamls authored:
394
+
395
+ - `feed/feed-container.yaml` (later retired in favor of `feed.yaml` canonical-class) — actually `feed-container` redundant since `feed.yaml` already declares `UIFeedContainer`
396
+ - `menu/menu-item.yaml` (`UIMenuItem`)
397
+ - `menu/menu-divider.yaml` (`UIMenuDivider`)
398
+ - `stepper/stepper-item.yaml` (`UIStepperItem`)
399
+ - `toggle-group/toggle-option.yaml` (`UIToggleOption`)
400
+ - `toolbar/toolbar-group.yaml` (`UIToolbarGroup`)
401
+
402
+ 2 parent yaml `name:` fixes (yaml was wrong; class.js was right):
403
+
404
+ - `col/col.yaml`: `name: UIColumn` → `UICol` (class.js exports `UICol`)
405
+ - `richtext/richtext.yaml`: `name: UIRichtext` → `UIRichText` (class.js exports `UIRichText`)
406
+
407
+ `index.d.ts` tag-map: `UIColumn` → `UICol` (`col-ui`) + `UIRichtext` → `UIRichText` (`richtext-ui`).
408
+
409
+ 13 component `.d.ts` files regenerated by codegen with both parent + sibling classes declared cleanly. After this commit + §229 baseline shrink, `check-static-methods-vs-dts --strict` runs clean across all 4 sub-axes.
410
+
411
+ Closes FEEDBACK-18 §2 (child-class drift) and the 8 audit-driven additions on top of the 7-instance enumeration.
412
+
413
+ ### Fixed — §229 (v0.5.9) — class-export walk baseline shrinks 15 → 0
414
+
415
+ `scripts/release/check-static-methods-vs-dts.mjs` `EXPECTED_CLASS_EXPORT_FINDINGS` shrunk to empty Set after §228 codegen ships. All 15 sibling-class drift entries close mechanically.
416
+
417
+ Drift surface #1 (`.d.ts` axis) now fully strict-mode-protected across all 4 sub-axes (method + event + property + class-export).
418
+
419
+ ### Added — §227 (v0.5.9) — `check-styles-barrel-completeness.mjs` `--package=web-modules` flag
420
+
421
+ `scripts/release/check-styles-barrel-completeness.mjs` — `runWebModulesAudit()` walks all 6 web-modules clusters (shell / chat / editor / simple / runtime / theme) and verifies each `*.css` file is reachable via one of the three `package.json` export-map patterns (`./<cluster>/*.css`, `./<cluster>/*/*.css`, `./<cluster>/*/css/*.css`).
422
+
423
+ Companion audit to §226 (v0.5.9 prep batch's web-modules `package.json` exports map fix). Together they close the second half of drift surface #3 (CSS-barrel — web-modules side).
424
+
425
+ ### Added — §225 (v0.5.9) — `<select-ui>` `#parseOptions()` once-per-element console.warn
426
+
427
+ `packages/web-components/components/select/class.js` — class-level `static #warnedNonOption = new WeakSet()` + `#parseOptions()` collects unknown-child tags + emits a single `console.warn` per element when consumer-authored children aren't native `<option>`/`<optgroup>`. Pattern mirrors v0.5.5 §184 §8 + v0.5.7 §201. `select.yaml` gains `usage:` block documenting native `<option>` children + programmatic `.options = [...]` paths. Closes FEEDBACK-10 §3.
428
+
429
+ ### Added — §224 (v0.5.9) — `<feed-ui>` `data-spawned-by="toast"` marker on toast-spawned containers
430
+
431
+ `packages/web-components/components/toast/class.js` — `UIToast.show()` stamps `data-spawned-by="toast"` on the spawned `<feed-ui>` container at creation. Idempotent (once per container). Lets DOM-query consumers distinguish toast-spawned containers from user-authored ones. `toast.yaml` gains `usage:` block documenting the marker + query example. Closes FEEDBACK-10 §2.
432
+
433
+ ### Changed — §221a-k (v0.5.9) — 11-item docs sweep
434
+
435
+ `packages/web-components/USAGE.md` grows a new section "v0.5.9 — Consumer-feedback discoverability sweep" (~218 lines) covering 11 items surfaced across consumer-feedback rounds R9, R10, R13, R15, R16, R20:
436
+
437
+ - **§221a** Universal `[size]` prop family — token wiring + size-cascade authoring patterns
438
+ - **§221b** `<input-ui>` migration guide from native `<input>` — common gotchas + payload-shape table
439
+ - **§221c** `<switch-ui>` vs `<check-ui>` vs `<segmented-ui>` — the "named binary state" trap
440
+ - **§221d** `<select-ui>` native `<option>` children — declarative + programmatic paths
441
+ - **§221e** `<accordion-item-ui>` action buttons in custom headers — `[slot="action"]` exclusion list
442
+ - **§221f** `<color-picker-ui>` `<popover-ui trigger=click>` inline pattern
443
+ - **§221g** `[data-scheme]` ancestor opt-in for color scheme — patterns + edge cases
444
+ - **§221h** Effect-dispatch timing — `queueMicrotask` batching + the double-microtask test-flush idiom (also added to `signals.d.ts` JSDoc on `effect()`)
445
+ - **§221i** Backticks inside HTML comments inside `html\`…\`` templates — JS-spec footgun
446
+ - **§221j** Typography token cheatsheet — L0/L1 token families + common misreads (`--a-font-semibold` doesn't exist; use `--a-weight-semibold`)
447
+ - **§221k** `<text-ui variant>` chooser guide — 12 canonical variants with picker heuristics
448
+
449
+ `packages/web-components/core/signals.d.ts` — JSDoc on `effect()` documents the queueMicrotask batching semantics + the double-microtask test-flush idiom (the canonical fix for "my effect didn't run yet" in tests).
450
+
451
+ ### Added — §225 (v0.5.9) — `<select-ui>` `#parseOptions()` once-per-element console.warn
452
+
453
+ `packages/web-components/components/select/class.js` — class-level `static #warnedNonOption = new WeakSet()` + `#parseOptions()` collects unknown-child tags + emits a single `console.warn` per element when consumer-authored children aren't native `<option>`/`<optgroup>`. Pattern mirrors v0.5.5 §184 §8 + v0.5.7 §201. `select.yaml` gains `usage:` block documenting native `<option>` children + programmatic `.options = [...]` paths. Closes FEEDBACK-10 §3.
454
+
455
+ ### Added — §224 (v0.5.9) — `<feed-ui>` `data-spawned-by="toast"` marker on toast-spawned containers
456
+
457
+ `packages/web-components/components/toast/class.js` — `UIToast.show()` stamps `data-spawned-by="toast"` on the spawned `<feed-ui>` container at creation. Idempotent (once per container). Lets DOM-query consumers distinguish toast-spawned containers from user-authored ones. `toast.yaml` gains `usage:` block documenting the marker + query example. Closes FEEDBACK-10 §2.
458
+
459
+ ### Fixed — §229 (v0.5.9) — `check-static-methods-vs-dts.mjs` (slot 20) class-export walk extension
460
+
461
+ `scripts/release/check-static-methods-vs-dts.mjs` gains the **4th declaration-axis** on the `.d.ts` surface: walks `export class UI<Name> extends UI<Base>` declarations in `class.js` against `export class` in every `.d.ts` in the component dir. Codegen'd `.d.ts` files NOW INCLUDED for the class-export axis (codegen emits primary; sibling-class drift is §228's gap). Static-method / event / property axes continue to skip codegen'd files (yaml-vs-runtime via slot 19).
462
+
463
+ Audit-driven discovery surfaced **15 class-export-undeclared findings across 13 components** — 8 more than FEEDBACK-18 §2's original enumeration. All 15 baselined as `EXPECTED_CLASS_EXPORT_FINDINGS`; strict mode hard-fails on NEW class-export drift beyond the baseline. After §228 codegen sibling-yaml walk ships, the baseline shrinks 15 → 0; `.d.ts` axis fully strict-mode-protected across all 4 sub-axes.
464
+
465
+ ## [0.5.8] - 2026-05-15
466
+
467
+ ### Changed — `patterns/` demo-page refresh (v0.5.8)
468
+
469
+ - `patterns/component-tokens/component-tokens.examples.html` + `patterns/responsive-shell-sidebar/responsive-shell-sidebar.examples.html` — demo refresh consuming v0.5.7 canonical fixes (`<text-ui>` retired-variant cleanup landed in v0.5.7 §210; component-tokens demo updated to reference real tokens; responsive-shell-sidebar demo aligned with v0.5.3 §157 pattern). Companion to §230 token-reference cleanup.
470
+
471
+ ### Fixed — §230 (v0.5.8) — `--a-font-weight-*` orphaned references closed in 3 components
472
+
473
+
474
+ 3 component CSS files referenced `var(--a-font-weight-*)` tokens that had been removed from the canonical token namespace in v0.5.0 (typography.css's "to be removed" comment was acted on but 3 consumers weren't updated). The references resolved to empty → CSS-keyword default (inherited 400 happened to match intent for nav-item / nav-group; feed.css had an inline `, 600` fallback masking the breakage). Consumer-override path was silently broken.
475
+
476
+ - `components/nav-item/nav-item.css:42` — `var(--a-font-weight-normal)` → `var(--a-weight-normal)`
477
+ - `components/nav-group/nav-group.css:12` — `var(--a-font-weight-normal)` → `var(--a-weight-normal)`
478
+ - `components/feed/feed.css:136` — `var(--a-font-weight-strong, 600)` → `var(--a-weight-semibold)` (preserves rendering weight; drops inline fallback)
479
+ - `styles/typography.css` comment block updated to reflect actual state (REMOVED in v0.5.0; was "to be removed" before).
480
+
481
+ Closes FEEDBACK-20 §1.
482
+
483
+ ### Added — §231 (v0.5.8) — audit slot 25 `check-component-css-vs-styles-tokens.mjs`
484
+
485
+ NEW: `scripts/release/check-component-css-vs-styles-tokens.mjs` — closes the **7th drift surface** in the v0.5.x drift-class lockdown taxonomy: **token-reference vs token-definition axis**. Walks every `components/<dir>/*.css` for `var(--a-<name>)` references, walks `styles/**/*.css` (recursive — includes `styles/colors/semantics.css` + the 18-file color tree) for `--a-<name>:` definitions, reports orphaned references.
486
+
487
+ Audit-driven discovery cadence (per memory `feedback_audit_driven_discovery_cadence`): running the audit against `main` post-authoring surfaced **19 orphaned references across 12 components** — 16 more than FEEDBACK-20 §1's original 3-reference filing. The 16 additional findings are baselined as `EXPECTED_FINDINGS` (matching the v0.5.6 §192 schema-vs-impl-coverage baseline pattern) and filed for v0.5.8+ peer-architect triage. Strict mode hard-fails on NEW orphans beyond the baseline; stale-baseline detection reports entries that no longer fire.
488
+
489
+ Drift-class lockdown taxonomy now covers all 7 surfaces (v0.5.7 closed 4; v0.5.8 closes the remaining 3 via §222 + §232 + §231).
490
+
491
+ **Audit-script family graduation**: v0.5.4 → v0.5.8: 13 → 22+ trip-wires.
492
+
493
+ ## [0.5.7] - 2026-05-15
494
+
495
+ ### Changed — `traits/catalog.examples.js` consumer sweep (§210 follow-on, v0.5.7)
496
+
497
+ `traits/catalog.examples.js` swept for the §210 retired `<text-ui variant="subheading">` — replaced with the canonical `subsection` token at 2 call sites (catalog header rendering). Companion mechanical cleanup for the §210 phantom-variant removal.
498
+
499
+ ### Fixed — §210 (v0.5.7) — `<text-ui>` variant enum drift (token-vs-rule + phantom entries)
500
+
501
+
502
+ Close FEEDBACK-17 §1. `text.yaml` + `text.d.ts` + `text.a2ui.json` documented 18 `variant` enum values; `text.css` shipped `:scope[variant=…]` rules for only 9. The 9 stranded values fell through to `body` defaults silently — no console warning, no visual cue. Sixth instance of the recurring "static-declaration lies about runtime" drift class (R5/R7/R8/R9/R14/R17). Two distinct sub-fixes in one arc:
503
+
504
+ **Token-backed variants restored to rendering (3 values):**
505
+ - `subsection`, `deck`, `metric` — each has a full `--a-<role>-{family,weight,size,leading,tracking,case,color}` token family already shipping in `styles/typography.css`, but no matching `:scope` rule consumed the tokens. Added 3 `:scope[variant=…]` rules to `text.css` matching the existing 9-rule pattern. Verified via the new `text.test.js` suite that each rule references `var(--a-<role>-family|weight|size)` (not `var(--a-body-*)` defaults).
506
+
507
+ **Phantom enum entries retired (6 values):**
508
+ - `subheading`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6` — these had no `--a-<role>-*` tokens shipped anywhere (verified across `styles/typography.css` + `styles/prose.css` + `styles/tokens.css`). The yaml + d.ts + a2ui.json advertised them; the project shipped no implementation surface to render them. Removed from all three documentation surfaces. SemVer-minor compatibility note: any consumer who authored `<text-ui variant="subheading">` or `variant="h<N>">` was already getting body-default rendering — the behavior change is from "silent body-default" to "type-error-at-compile-time + clearer authoring contract."
509
+
510
+ 42 new tests in `text.test.js` cover (1) mount smoke for all 12 surviving enum values, (2) every documented variant has a matching CSS rule, (3) the 3 restored variants reference role tokens (not body defaults), (4) the 6 phantom entries do NOT appear in `.d.ts` union OR `.a2ui.json` enum, (5) `.a2ui.json` enum + `.d.ts` union + CSS rules are mutually consistent 1:1. Plus a section-level demo in `text.examples.html` showcasing the 3 restored variants (subsection/deck/metric) inline with kicker/display/heading/body so the demo page visually surfaces the new contract.
511
+
512
+ Audit-slot extension queued for v0.5.8: extend slot 19 (`check-yaml-impl-coverage.mjs`) one step further — for each `enum` value on a prop, assert a matching `:scope[<name>="<value>"]` rule exists in the component's `.css`. Catches both halves of this drift class (token-vs-rule + phantom enum) mechanically. Reference: FEEDBACK-17 §1 audit-slot suggestion.
513
+
514
+ ### Fixed — §200 (v0.5.7) — `<toggle-scheme-ui>` post-connect reactive-scheme race
515
+
516
+ `packages/web-components/components/toggle-scheme/class.js` — overrides `attributeChangedCallback` to re-run `#initState()` when the `scheme` attribute changes AFTER `connectedCallback` AND before first user interaction.
517
+
518
+ The bug class: `template.js scan()` strips placeholder attributes pre-insertion; `replaceChildren()` upgrades the element + fires `connectedCallback`; the old `#initState()` synchronously reads `this.scheme` and locks to AUTO fallback because the attribute is still stripped; then `template.js update()` re-applies `setAttribute('scheme', '<reactive>')` but the AUTO state is already cached. Consumers using `<toggle-scheme-ui scheme="${reactiveValue}">` saw incorrect scheme on every page load.
519
+
520
+ Fix introduces a `#userTouched` flag set by three paths: (1) the internal button-press handler, (2) `setScheme()` programmatic, (3) `toggle()` programmatic. Until `#userTouched` flips true, post-connect `scheme` attribute changes re-run `#initState()` so the reactive value wins. Once user picks explicitly, the flag locks the choice across subsequent reactive re-renders.
521
+
522
+ 6 new tests in `toggle-scheme.test.js` cover the race repro, initial-attr passthrough, all 3 user-touch paths locking the choice, and pre-touch attribute removal back to auto. Closes FEEDBACK-10 §1.
523
+
524
+ ### Fixed — §201 (v0.5.7) — `<color-picker-ui>` `#parseValue()` accepts `NaN` / `none` / `%`
525
+
526
+ `packages/web-components/components/color-picker/class.js` — `#parseValue()` regex extended to accept three CSS Color L4 / culori-convention channel shapes that the v0.5.6 baseline silently discarded:
527
+
528
+ - **`NaN`** — culori chromaless-hue convention; coerced to `0`.
529
+ - **`none`** — CSS Color L4 powerless-component syntax; coerced to `0` (matches the L4 powerless-cascade spec).
530
+ - **`%`** on L — `oklch(53% 0.05 240)` → divides by 100.
531
+
532
+ Plus: when input doesn't match any documented shape, a `console.warn` fires **once per element** (deduped via class-level `static #warnedBadParse = new WeakSet()`). Matches the v0.5.5 §184 §8 `<button-ui>` icon-only safety-net pattern + v0.5.7 §215 (queued) `<select-ui>` `#parseOptions` pattern.
533
+
534
+ 7 new tests in `color-picker.test.js` cover all 4 input shapes (numeric, NaN, none, %), hex passthrough, warn-once dedup, and per-instance warn budget. Closes FEEDBACK-13 §1 + FEEDBACK-14 §2 (co-credited per the 2026-05-15 slot-dance reconciliation).
535
+
536
+ ### Added — §207 (v0.5.7) — audit slot 20 `check-static-methods-vs-dts.mjs`
537
+
538
+ NEW: `scripts/release/check-static-methods-vs-dts.mjs` cross-checks three runtime surfaces in `class.js` against the sibling hand-authored `.d.ts`:
539
+
540
+ 1. **Static methods** — `static <name>(...)` declarations should appear as static signatures in `.d.ts`. Caught FEEDBACK-09 §9 (`<toast-ui>.show()`).
541
+ 2. **Dispatched events** — `dispatchEvent(new CustomEvent('<name>', ...))` literals should appear as `addEventListener(type: '<name>', ...)` overloads in `.d.ts`. Caught FEEDBACK-07's event-type drift class + R9 §10.
542
+ 3. **Static-properties keys** — `static properties = { <key>: ... }` keys should appear as public field declarations in `.d.ts`. NEW per FEEDBACK-14 §1 (`<slider-ui>` `throttle` + `hint` missing) — 5th instance of the runtime-vs-`.d.ts` drift class family.
543
+
544
+ Codegen'd `.d.ts` files (73 of 90; identified via the "Type declarations generated by scripts/build/dts-codegen.mjs" marker) are skipped — yaml is their SoT, and slot 19 (`check-yaml-impl-coverage.mjs`, v0.5.6 §192) covers the yaml-vs-runtime drift surface.
545
+
546
+ Heuristics: balanced-brace walker to extract `static properties = { ... }` body; depth-tracked key extraction for top-level prop names only; regex extraction for event literals + static method signatures + `.d.ts` field/method declarations.
547
+
548
+ ### Changed — §207-bundled — close 15 `.d.ts` drift findings surfaced by audit slot 20
549
+
550
+ Running the new slot 20 against `main` surfaced 15 hard findings across 6 hand-authored form-bearing primitives — all closed in this same cycle to complete the drift-class lockdown narrative. Like §205 (`requiredIcons` 2 → 5 primitives), the audit-driven discovery materially expanded scope:
551
+
552
+ - `calendar-picker.d.ts` — adds `label`, `placeholder`, `format`, `min: string | null`, `max: string | null` fields.
553
+ - `code.d.ts` — adds `name`, `required`, `disabled`, `readonly` fields.
554
+ - `input.d.ts` — adds `addEventListener(type: 'submit', ...)` overload (the runtime dispatches `Event('submit')` on Enter-key + form-submit at `class.js:717+725`).
555
+ - `select.d.ts` — adds `hint` field.
556
+ - `switch.d.ts` — adds `hint` field.
557
+ - `upload.d.ts` — adds `label`, `accept`, `multiple` fields.
558
+
559
+ All declarations carry `§207 (v0.5.7)` JSDoc cross-refs. Post-fix audit reports clean across all 95 primitives.
560
+
561
+ ### Added — §208 (v0.5.7) — audit slot 22 `check-styles-barrel-completeness.mjs`
562
+
563
+ NEW: `scripts/release/check-styles-barrel-completeness.mjs` cross-checks `packages/web-components/styles/components.css` `@import` lines against the per-component CSS files on disk. Catches the "primitive ships CSS but barrel forgot to @import it" drift class surfaced by FEEDBACK-11 §1.
564
+
565
+ Three drift classes caught:
566
+ 1. `file-exists-not-imported` (hard) — CSS file exists at `components/<X>/<X>.css` (or irregular `<X>-ui.css`) but barrel doesn't @import it.
567
+ 2. `imported-not-existing` (hard) — barrel @imports a file that doesn't exist on disk.
568
+ 3. `duplicate-import` (warn) — same file @imported twice.
569
+
570
+ Filename handling: supports both `<name>.css` (canonical) AND `<name>-ui.css` (irregular — stat-ui-only as of v0.5.7; planned rename in v0.6.0 §303 per R11 §2). CSS-only components (`header`, `footer`, `section`) are skipped via `CSS_ONLY_COMPONENTS` allowlist.
571
+
572
+ Post-§203 audit reports clean across all 95 primitives + 92 @import lines.
573
+
574
+ ### Added — §202 (v0.5.7) — `<slider-ui>` `.d.ts` declares `throttle` + `hint`
575
+
576
+ `packages/web-components/components/slider/slider.d.ts` — adds `throttle: number` + `hint: string` to the `UISlider` class declaration. Closes FEEDBACK-14 §1 — runtime + yaml + a2ui.json all shipped these props since v0.5.5 §184; the `.d.ts` lagged behind. 5th recurrence of the runtime-vs-`.d.ts` drift class (R5 / R7 / R8 / R9 / R14). Consumers using `<slider-ui throttle="50" hint="…">` no longer need `as any` casts or augment shims.
577
+
578
+ ### Changed — §206 (v0.5.7) — `<accordion-item-ui>` action-slot opt-out
579
+
580
+ `packages/web-components/components/accordion/class.js` — `#onClick` now skips toggle when the click originates inside `[slot="action"]`, `[slot="actions"]`, or `[data-no-toggle]`. Lets consumers author rich custom headers with action buttons (Copy / Download / settings) inside `[slot="header"]` without bubbling-toggle UX. Slot vocabulary matches the canonical `drawer-ui` + `pane-ui` naming. Additive — existing consumers see no behavior change. Closes FEEDBACK-16 §2.
581
+
582
+ `packages/web-components/components/accordion/accordion-item.yaml` — gains `slots:` block documenting `header` + `action` slots; `events:` block formalizing the `toggle` event detail; sidecar regenerated.
583
+
584
+ `packages/web-components/components/accordion/accordion.yaml` — `slots.default.description` corrected from `pane-ui children` to `accordion-item-ui children`. Closes FEEDBACK-13 §2 (folded into §206 since adjacent yaml).
585
+
586
+ ### Changed — §205 (v0.5.7) — `requiredIcons` completeness on 5 primitives (2 from FEEDBACK-16, 3 from §209 audit-extension discovery)
587
+
588
+ Five primitives' `static requiredIcons` declarations updated to mirror their runtime dynamic-icon stamps:
589
+
590
+ **Filed by FEEDBACK-16 §1:**
591
+ - `packages/web-components/components/color-picker/class.js:130` — `['copy']` → `['copy', 'check', 'warning']`. Copy-button dynamic-swaps to `check` (success) / `warning` (failure) at line 496.
592
+ - `packages/web-components/components/table/class.js:98` — `['caret-right', 'caret-up-down', 'table']` → adds `'arrow-up'`, `'arrow-down'`, `'funnel-simple'`, `'funnel-simple-fill'`. Dynamic sort-direction + filter-state stamps at lines 679 + 707.
593
+
594
+ **Surfaced by §209 audit-regex extension (see below):**
595
+ - `packages/web-components/components/agent-artifact/class.js` — NEW `static requiredIcons = ['caret-right', 'caret-down']`. Chevron stamps on collapse/expand state at lines 119 + 188.
596
+ - `packages/web-components/components/table-toolbar/class.js` — NEW `static requiredIcons = ['arrow-up', 'arrow-down', 'caret-up-down']`. Nested-ternary sort-indicator stamp at line 576.
597
+ - `packages/web-components/components/timeline/class.js` — NEW `static requiredIcons = ['caret-down', 'caret-right']`. Expanded-state chevron at line 167.
598
+
599
+ (In all 3 newly-surfaced primitives, `this.icon` reads are consumer-supplied — not declared in `requiredIcons` per the established convention.)
600
+
601
+ Yaml `requiredIcons:` blocks mirror the class.js change for all 5; sidecars regenerated. Closes FEEDBACK-16 §1 + 3 additional drift instances that the pre-§209 audit didn't catch.
602
+
603
+ ### Fixed — §209 (v0.5.7) — `check-required-icons.mjs` (audit slot 11) extended to catch ternary-style icon stamps
604
+
605
+ `scripts/release/check-required-icons.mjs` — regex extended to extract icon-name literals from `.setAttribute('name', <expr>)` where `<expr>` is a ternary (or nested-ternary) rather than a singleton string literal. Uses a balanced-paren walker to capture the 2nd-argument expression (handles inner calls like `this.#filters.has(col.key) ? 'A' : 'B'`), then matches literals that appear in ternary-branch position (preceded by `?` or `:`). Avoids false-positives on comparator literals (e.g. `dir === 'asc'`).
606
+
607
+ Closes the audit-blind-spot bug class that allowed FEEDBACK-16 §1's drift to slip past CI: pre-§209 the slot reported "all primitives clean"; post-§209 + §205 the audit reports clean while §205 closes the actual drift. The audit now reliably catches the runtime-vs-`static requiredIcons` drift class on both singleton-literal AND ternary-expression stamp sites.
608
+
609
+ Discovery surface: running the post-§209 audit against `main` surfaced 3 additional primitives with the same drift class (`agent-artifact`, `table-toolbar`, `timeline`) — all 3 fixed under §205 above.
610
+
611
+ ### Added — §204 (v0.5.7) — `package.json` `exports` accepts nested-CSS specifier shapes
612
+
613
+ `packages/web-components/package.json` `exports` map gains two entries:
614
+
615
+ ```json
616
+ "./components/*/*.css": "./components/*/*.css",
617
+ "./components/*/css/*.css": "./components/*/css/*.css"
618
+ ```
619
+
620
+ Consumers can now import component CSS with the path-mirroring shape that matches every JS subpath import:
621
+
622
+ ```js
623
+ // Both shapes now resolve:
624
+ import '@adia-ai/web-components/components/toggle-scheme.css';
625
+ import '@adia-ai/web-components/components/toggle-scheme/toggle-scheme.css';
626
+ ```
627
+
628
+ The second new entry handles `editor-shell`'s nested sub-stylesheets (`components/editor-shell/css/{layout,tokens,bespoke}.css`). Closes FEEDBACK-15 §1.
629
+
630
+ ### Fixed — §203 (v0.5.7) — `styles/components.css` barrel completeness
631
+
632
+ `packages/web-components/styles/components.css` — adds 2 `@import` lines for `<link-ui>` + `<toggle-scheme-ui>`. Both primitives ship per-component CSS files (`components/link/link.css`, `components/toggle-scheme/toggle-scheme.css`) that previously weren't `@import`-ed into the canonical barrel. Consumers using `<link href="@adia-ai/web-components/styles/components.css">` previously rendered both elements unstyled. Closes FEEDBACK-11 §1 (the 2 genuinely-missing instances; `stat-ui` was already imported at its irregular filename per R11 §2, deferred to v0.6.0 §303).
633
+
634
+ ## [0.5.6] - 2026-05-14
635
+
636
+ ### Added — §191 (v0.5.6) — close §178 baseline-missing 7 `.d.ts` siblings
637
+
638
+ `core/anchor.d.ts`, `core/controller.d.ts`, `core/markdown.d.ts`, `core/polyfills.d.ts`, `core/provider.d.ts`, `core/streams-bridge.d.ts`, `core/transport.d.ts` — all 7 public-API JS modules now have hand-authored TypeScript declarations mirroring the runtime exports. Pattern follows §154 `icons.d.ts` (v0.5.4 `cf7c14aaa` hotfix): types live alongside the runtime SoT, documented inline, regenerated only if the runtime changes shape.
639
+
640
+ **Type surfaces now exposed to TS consumers**:
641
+
642
+ - `anchor.d.ts` — `Placement` type (12-value union), `AnchorOptions` interface, `supportsAnchorCSS: boolean`, `anchorPopover(anchor, popover, options?): () => void`.
643
+ - `controller.d.ts` — `BaseController` class (public surface — `host`, `connect`, `disconnect`, `subscribe`, `notify`, lifecycle hooks, `getState`), `RouteController extends BaseController`, plus supporting `ControllerSchema`/`Route`/`RouteState`/`RouteCommands`/`RouteControllerOptions` interfaces.
644
+ - `markdown.d.ts` — `renderMarkdown(src: string): string`.
645
+ - `polyfills.d.ts` — side-effect-only module declaration (`export {}`). Documents the runtime feature checks + the two opt-in polyfill packages.
646
+ - `provider.d.ts` — `RouteController` re-declaration (matches `controller.d.ts` for historical compatibility), `UIRouter extends UIElement`, `TemplateResolver` type, `HTMLElementTagNameMap['router-ui']` declaration so `document.querySelector('router-ui')` returns `UIRouter`.
647
+ - `streams-bridge.d.ts` — `BridgeRenderer` interface (duck-typed renderer contract), `BridgeOptions` interface, `bridgeStream` / `bridgeStreamAsync` signatures.
648
+ - `transport.d.ts` — `TransportErrorKind` discriminator (4-value union), `TransportOptions` interface, `request` / `json` signatures (`json` is generic over response body type), `TransportError` class with `kind`/`status`/`body` fields.
649
+
650
+ **Audit-script closure**: `scripts/release/check-dts-sibling-presence.mjs` (audit slot 17) `EXPECTED_MISSING` allowlist emptied — baseline 7 → 0. Strict mode now hard-fails on any future regression (audit reports 14/14 public-API entries with sibling `.d.ts`, 0 missing). Defensive stale-baseline detection retained — every future entry treated as policy gap requiring justification.
651
+
652
+ **Verification**: `node scripts/release/check-dts-sibling-presence.mjs --strict` → exit 0. `node scripts/build/components.mjs --verify` → clean (135 files). `npm run check` → mcp:smoke OK.
653
+
654
+ ### Changed — §197 (v0.5.6) — FEEDBACK-09 §9 + §10 typing fixes
655
+
656
+ **§9 — `<toast-ui>` `.d.ts` adds the static `show()` declaration**
657
+
658
+ `UIToast.show()` was authored in `class.js` since the imperative path
659
+ shipped but absent from `toast.d.ts`. TS consumers calling
660
+ `UIToast.show({ text: 'Saved' })` got `Property 'show' does not exist on typeof UIToast`. §197 adds two interfaces (`UIToastShowOptions`,
661
+ `UIToastFeedHandle`) and the static method declaration. Same drift class as the v0.5.3 §154 `core/icons.d.ts` hotfix.
662
+
663
+ **§10 — Generic `<V extends string = string>` on Select / Segmented / Switch ChangeEventDetail**
664
+
665
+ `SelectChangeEventDetail`, `SegmentedChangeEventDetail`, and `SwitchChangeEventDetail` previously hard-coded `value: string`, forcing Tokens Studio's 11 consumer call-sites to widen via `as` casts. Adding a generic parameter `<V extends string = string>` defaulting to `string` is backwards-compatible (existing consumers see no change) and lets type-aware call-sites pin the literal value type:
666
+
667
+ ```ts
668
+ type Size = 'sm' | 'md' | 'lg';
669
+ select.addEventListener('change', (e: SelectChangeEvent<Size>) => {
670
+ e.detail.value; // typed as Size, not string
671
+ });
672
+ ```
673
+
674
+ Applies to `SelectChangeEvent`, `SegmentedChangeEvent`, `SwitchChangeEvent` aliases as well. `SwitchChangeEventDetail.checked` stays `boolean` (only `value` is generified).
675
+
676
+ ### Changed — §197 (v0.5.6) — audit slot 11 regex tolerates generic params
677
+
678
+ `scripts/release/check-form-bearing-dts.mjs` (slot 11) was rejecting the new generic-parameterized `EventDetail` interfaces because the old regex required `<X>EventDetail\s*\{`. §197 widens both the interface-parse regex AND the `Event = CustomEvent<...>` alias regex to accept an optional `<...>` generic-params block. Audit stays at 0 errors / 0 warnings against the updated .d.ts files.
679
+ ## [0.5.5] - 2026-05-14
680
+
681
+ ### Added — §176 (v0.5.5) — six new sibling sub-component yamls
682
+
683
+ Six sibling yamls authored per the v0.5.4 §172 scanner pattern (sibling-yaml discovery in the build pipeline). Each describes a co-located sub-component class that previously existed as a registered custom element without its own catalog entry — surfaced as the §175 baseline-orphan class.
684
+
685
+ - `list/list-item.yaml` — `UIListItem` (3 props: icon, text, description)
686
+ - `avatar/avatar-group.yaml` — `UIAvatarGroup` (2 props: max, size)
687
+ - `accordion/accordion-item.yaml` — `UIAccordionItem` (2 props: text, open)
688
+ - `timeline/timeline-item.yaml` — `UITimelineItem` (8 props: text, description, time, duration, icon, variant, status, spinner)
689
+ - `action-list/action-item.yaml` — `UIActionItem` (5 props: icon, text, value, variant, disabled)
690
+ - `tabs/tab.yaml` — `UITab` (4 props: text, value, icon, disabled)
691
+
692
+ After §184's `tree-item.yaml` addition, sibling-yaml pattern is now at **7 components**. Catalog: 125 → 132 components (+7 across §176 + §184).
693
+
694
+ Commit `443365913`. See root CHANGELOG and journal §176 for full context.
695
+
696
+ ### Added — §184 (v0.5.5, FEEDBACK-08 response cycle) — 6 new APIs + 1 build-time audit
697
+
698
+ FEEDBACK-08 (2026-05-14) reported 9 items post-v0.5.3 dogfooding. Items 3 (`<segment-ui>` `aria-label` auto-wire) + 9 (`<segment-ui disabled>` schema docs) were already resolved in v0.5.5 in-flight work; 7 remaining items land here as a single arc:
699
+
700
+ - **`<tree-item-ui>` `badge` attribute (FEEDBACK-08 §1, P1).** Mirrors the `<nav-item-ui>` `badge` API — trailing muted small text alongside the row, right-aligned. Use for counts/labels (e.g. `<tree-item-ui text="Colors" badge="7">`). Empty span auto-hides. Plus a sibling `tree-item.yaml` (per the v0.5.5 §176 sibling-yaml authoring pattern) so the previously-orphan `UITreeItem` component now has its own catalog entry, `.d.ts`, and `.a2ui.json` sidecar. New CSS tokens: `--tree-badge-{bg,fg,px,radius,size}`.
701
+ - **`<tree-ui>` programmatic expand/collapse API (FEEDBACK-08 §2, P1).** Five new methods on `UITree`: `.expand(itemOrValue)`, `.collapse(itemOrValue)`, `.expandAll()`, `.collapseAll()`, `.expandTo(itemOrValue)` (expands ancestors + scrolls into view). All accept either an `HTMLElement` (must match `tree-item-ui`) OR a string (matched against `[value]` first, then `[text]` for ergonomics). Removes the `_expanded[]` + `tree-select`-listener boilerplate consumers were maintaining locally.
702
+ - **`<slider-ui>` `throttle` attribute (FEEDBACK-08 §4, P1).** Declarative debounce for the `input` event when driving expensive computation (palette regen, shader compile, large list reflow). When `throttle > 0`, value updates + visual feedback remain immediate but `input` event emission is debounced — only the FINAL value in the throttle window dispatches. `change` fires unthrottled on pointerup / track click / keyboard; any pending `input` flushes BEFORE `change` so consumers always see `input → input → … → input → change` ordering. `throttle="0"` (default) preserves the pre-§184 every-pointer-move-fires-input behavior. Common values: 50–100ms.
703
+ - **`<slider-ui>` / `<select-ui>` / `<switch-ui>` `hint` attribute (FEEDBACK-08 §7, P2).** Caption rendered beneath the control + wired to `aria-describedby` on the host. Distinct from the in-component `label` (which becomes `aria-label`). `<switch-ui>` had `hint` declared in yaml since §170 but the template never rendered it — this arc closes the schema-vs-implementation gap on switch in addition to adding the new declarations on slider + select. New CSS tokens per primitive: `--{slider,select,switch}-hint-{mt,size,fg,lh}`.
704
+ - **`<button-ui>` icon-only a11y safety net (FEEDBACK-08 §8, P2).** When `<button-ui icon="X">` is used WITHOUT `text`, `aria-label`, `aria-labelledby`, OR `title`, `console.warn()` once per element (deduped via `WeakSet`) so the bug class is diagnosable in dev. When `title="Undo"` IS set, mirror it to `aria-label` automatically — matches the common icon-only-button-with-tooltip pattern (zero-author-cost a11y). Consumers can still set `aria-label` explicitly; the auto-mirror only fires when no other accessible name exists. Per FEEDBACK-08's two options A + B chosen together (warn + auto-derive).
705
+ - **`core/template.js` partial-attribute error message (FEEDBACK-08 §5, P2).** The §152 (v0.5.3) runtime warn previously suggested `.${attr.name}=${expression}` as a generic property-assignment recipe. For `class` specifically, `.class=${expr}` sets a JS expando (`element.class = "foo"`) instead of the `className` property — CSS classes never apply. Updated the warning to special-case `class` (suggests `.className=${expression}` first, then `class="${expression}"` for the whole-attribute path) and `style` (suggests `.style.cssText=${expression}`); other attributes keep the generic recipe.
706
+
707
+ ### Added — §184 audit slot 18 (build-time partial-attribute interpolation scanner)
708
+
709
+ NEW `scripts/release/check-template-interp.mjs` (~220 LOC) closes FEEDBACK-08 §6. Walks every `*.js` / `*.mjs` / `*.ts` / `*.tsx` file under `packages/web-components/`, `packages/web-modules/`, `apps/`, `playgrounds/`, `catalog/` (excludes `node_modules/`, `dist/`, `.test.*`, `.spec.*`), strips comments, finds every `` html` `` tagged template literal via brace-balanced walker (matching the runtime template parser's contract), inspects attribute values for partial-attribute interpolation (`attr="prefix ${var}"` patterns where the value mixes literal + placeholder content). Whole-attribute forms (`attr="${expression}"`) and prop/event forms (`.prop=`, `@event=`) are NOT flagged.
710
+
711
+ The runtime guard at `core/template.js` (§152) warns at mount time; this static scanner catches the bug class before it ships, even in conditional branches that never run during dev testing (the case FEEDBACK-08 §6 highlights). Wired as `npm run check:template-interp` (advisory exit 0) + `:strict` (exit 1 on findings) + added to `npm run check` aggregate alongside the existing 17 audit-script-family trip-wires. Initial run across all 5 scan roots: zero findings. Audit-script family now spans **18 trip-wires** at v0.5.5.
712
+ ## [0.5.4] - 2026-05-14
713
+
714
+ ### Added — `core/icons.d.ts` authored (+148 LOC, NEW; pre-cycle FEEDBACK-08-ish gap closure)
715
+
716
+ NEW `packages/web-components/core/icons.d.ts` (commit `cf7c14aaa`).
717
+ Mirrors all 10 runtime exports the `core/icons.js` SoT declares,
718
+ including the v0.5.3 §154 helpers (`installIconLoadersForRegistered`,
719
+ `discoverRequiredIcons`).
720
+
721
+ **Why this slipped past v0.5.3's gates**: the audit-script family
722
+ had 11 trip-wires at v0.5.3 including §102 `check-core-barrel-dts`
723
+ which catches **drift when a `.d.ts` exists** (every exported runtime
724
+ symbol has a matching type), but does NOT catch the **totally-missing
725
+ `.d.ts` case** for a `core/*.js` file. Result: a new `core/<name>.js`
726
+ file shipping without `<name>.d.ts` was silently consumer-broken on
727
+ TS-strict surfaces (TypeScript consumers importing from
728
+ `@adia-ai/web-components/core/icons` resolved to a missing `.d.ts`
729
+ and got `Cannot find module`) but JS-green elsewhere.
730
+
731
+ In-the-wild detection: `/Users/kimba/Projects/adia/color-app/src/main.ts:48`
732
+ (TypeScript strict mode) carried a stale comment noting the workaround
733
+ needed because `installIconLoadersForRegistered.d.ts` was missing. The
734
+ user wanted to upgrade to the recommended auto-discovering helper but
735
+ was blocked.
736
+
737
+ Filed slot 14 candidate for v0.5.5+ (`check-dts-sibling-presence`):
738
+ detect totally-missing `.d.ts` files for `core/*.js`, not just symbol-level
739
+ drift within existing `.d.ts` files.
740
+
741
+ ### Fixed — §170 `static labelDeprecated = false` opt-out for 7 form-bearing primitives (v0.5.4)
742
+
743
+ Closes the `<select-ui label="...">` false-positive deprecation
744
+ class observed 2026-05-14 console:
745
+
746
+ ```
747
+ form.js:205 [AdiaUI] <select-ui label="…"> is deprecated — wrap in
748
+ <field-ui label="…"> for proper label association.
749
+ ```
750
+
751
+ `UIFormElement.#warnDeprecatedLabel()` (`core/form.js:197`) fires for
752
+ any form-bearing element where `properties.label` exists AND `static
753
+ labelDeprecated !== false` AND `hasAttribute('label')`. Pre-§170,
754
+ only `Input`, `Slider`, `Range` opted out. The 7 other form-bearing
755
+ primitives that declare `label:` in yaml as a first-class prop
756
+ spammed false-positive warnings on every gen-ui render.
757
+
758
+ Added `static labelDeprecated = false` to:
759
+
760
+ - `calendar-picker/class.js`
761
+ - `check/class.js`
762
+ - `radio/class.js`
763
+ - `select/class.js`
764
+ - `switch/class.js`
765
+ - `textarea/class.js`
766
+ - `upload/class.js`
767
+
768
+ Audit method: cross-reference yaml `props.label:` declarations
769
+ against `class.js` `static labelDeprecated` opt-out. Found 11
770
+ form-bearing primitives with `label:` prop; 3 already opted out
771
+ (Input/Slider/Range); 7 missing (this commit); 1 false positive
772
+ in audit (field-ui — extends UIElement not UIFormElement; only
773
+ mentions UIFormElement in a comment).
774
+
775
+ v0.5.5 §171 will add audit-script trip-wire to catch this drift
776
+ class mechanically going forward.
777
+
778
+ ### Added — §172 NEW `chat-input.yaml` catalog entry; `<chat-input-ui>` no longer orphan (v0.5.4)
779
+
780
+ NEW `packages/web-components/components/chat-thread/chat-input.yaml`
781
+ (+251 LOC). Closes the v0.5.4 ticket
782
+ `20260514045025-chatinput-training-data-unclea`: `<chat-input-ui>`
783
+ was an orphan component (lived as a sibling file inside
784
+ `chat-thread/` rather than its own folder); the build scanner only
785
+ looked for canonical `<name>/<name>.yaml`, so the catalog had NO
786
+ `ChatInput` entry. Runtime registry mapped `ChatInput → chat-input-ui`
787
+ but the LLM had no schema describing what the element stamps. Result:
788
+ generator added redundant Button(primary) siblings next to ChatInput
789
+ for "send" because the built-in send button was invisible to it.
790
+
791
+ The yaml encodes:
792
+ - 5 props (disabled, loading, placeholder, model, models)
793
+ - `submit` event with `{text, model}` detail
794
+ - 3 slots (toolbar, send, model — each marked "most authors should
795
+ NOT override")
796
+ - 3 a2ui rules explicitly stating "DO NOT add a separate Button
797
+ sibling for send"
798
+ - 3 anti-patterns
799
+ - 4 examples
800
+
801
+ Sibling yaml in `feed/` (`feed-item.yaml`, pre-existing orphan)
802
+ also unblocked by the build-scanner fix in `@adia-ai/a2ui-compose`
803
+ §172.
804
+
805
+ ### Maintenance — audit-script companion §174 ships in v0.5.4 (no source change to web-components)
806
+
807
+ Repo-level audit script targeting web-components drift:
808
+
809
+ - **§174 `check-form-element-label-opt-out.mjs`** (slot 15) — codifies the
810
+ §170 audit recipe. For every form-bearing primitive (extends
811
+ UIFormElement): if yaml declares `label:` as first-class, class.js MUST
812
+ opt out via `static labelDeprecated = false;`. Catches future drift if a
813
+ new form-bearing primitive is added without the opt-out.
814
+
815
+ No web-components source change in §174 — detects future drift in the
816
+ yaml-vs-class.js invariant established by §170.
817
+
818
+ ### Changed — `version`: `0.5.3` → `0.5.4`.
819
+ ## [0.5.3] - 2026-05-14
820
+
821
+ ### Added — `requiredIcons` static export on 12 primitives + `installIconLoadersForRegistered()` helper + audit slot 11 (§154, FEEDBACK-06 §4 + FEEDBACK-07 §4 P1, v0.5.3)
822
+
823
+ FEEDBACK-06 §4 + FEEDBACK-07 §4 (duplicate ask per author's cross-reference) reported consumers trial-and-error-discovering which Phosphor icons AdiaUI primitives auto-stamp (e.g. `<select-ui>` stamps `caret-up-down`; `<tag-ui>` dismissable stamps `x`; `<tree-ui>` stamps `caret-right`). Without a discoverable contract, consumers built the icon list by refresh-and-look.
824
+
825
+ **Three surfaces**:
826
+
827
+ **(a) `static requiredIcons` field** added to 12 auto-stamping primitives — `accordion`, `agent-questions`, `agent-reasoning`, `agent-trace`, `calendar-picker`, `color-picker`, `command`, `pane`, `select`, `table`, `tag`, `tree`. Each declares the Phosphor names it stamps without consumer markup:
828
+
829
+ ```js
830
+ export class UISelect extends UIFormElement {
831
+ static requiredIcons = ['caret-up-down'];
832
+ // ...
833
+ }
834
+ ```
835
+
836
+ **(b) `requiredIcons:` yaml field** on the same 12 yamls; mirrors the class.js declaration. `scripts/schemas/component.yaml.schema.json` updated to accept the field (string array, kebab-case pattern). `npm run components` validates the yaml against the schema + propagates the field into `.a2ui.json` sidecars.
837
+
838
+ **(c) `installIconLoadersForRegistered(modules)` helper** in `core/icons.js`. Walks `customElements.get()` for the 12 known auto-stamping tag names + collects the union of `static requiredIcons` + installs loaders. Also exposes `.discover()` for consumers who want the icon list without installing:
839
+
840
+ ```js
841
+ import { installIconLoadersForRegistered } from '@adia-ai/web-components/core/icons';
842
+
843
+ const modules = import.meta.glob('/node_modules/@phosphor-icons/core/assets/regular/*.svg', { query: '?raw' });
844
+ const discovered = installIconLoadersForRegistered(modules);
845
+ // discovered: ['caret-up-down', 'x', 'caret-right', 'check-circle', ...]
846
+ ```
847
+
848
+ **(d) Audit-script slot 11** at `scripts/release/check-required-icons.mjs` (~190 LOC). Cross-checks `static requiredIcons` (class.js SoT) vs `requiredIcons:` yaml + runtime `<icon-ui name="X">` literal + `setAttribute('name', 'X')` calls. Catches:
849
+
850
+ 1. **declared-not-stamped** — yaml/class.js says `X` but no runtime stamp site
851
+ 2. **stamped-not-declared** — runtime stamps `X` but declaration omits it (consumer-failure risk)
852
+ 3. **missing-declaration** — runtime stamps icons but no class.js `static requiredIcons` field
853
+ 4. **yaml-class-mismatch** — yaml and class.js disagree on the set
854
+ 5. **yaml-missing-block** / **class-missing-declaration** — one side has the declaration, the other doesn't
855
+
856
+ Wired as `npm run check:required-icons` + added to the `npm run check` aggregate after `check:substitutable-set-drift` (slot 10). Audit-script family now spans **11 trip-wires**:
857
+
858
+ | Slot | Script |
859
+ |---|---|
860
+ | 1 | check-no-live-readers |
861
+ | 2 | check-changelog-coverage |
862
+ | 3 | check-browser-safe |
863
+ | 4 | smoke:genui-composition-retrieval |
864
+ | 5 | check-form-bearing-dts |
865
+ | 6 | check-core-barrel-dts |
866
+ | 7 | check-yaml-events-vs-runtime |
867
+ | 8 | check-free-form-eval-regression |
868
+ | 9 | check-standalone-html-phosphor |
869
+ | 10 | check-substitutable-set-drift |
870
+ | **11** | **check-required-icons — NEW v0.5.3** |
871
+
872
+ Verification: 95 components scanned; 0 drift. The 12 auto-stamping primitives all match yaml ↔ class.js ↔ runtime.
873
+
874
+ Heuristic limitations (documented in script header): computed icon names (`setAttribute('name', this.icon)`), trait-emitted icons, and JSDoc/comment example references are filtered or reported as "unresolvable" — manual audit applies.
875
+
876
+ ### Added — `responsive-shell-sidebar` pattern (§157, FEEDBACK-06 §3 P1, v0.5.3)
877
+
878
+ New pattern at `packages/web-components/patterns/responsive-shell-sidebar/` documents the dual-mount editor-shell sidebar recipe: inline panels at `>900px` viewports, drawer twins below. FEEDBACK-06 §3 reported color-app hand-rolling ~40 LOC of CSS + state plumbing for exactly this pattern in Tokens Studio.
879
+
880
+ **The pattern** ships:
881
+
882
+ - `.wide-only` / `.narrow-only` CSS convention (display: contents / display: none with breakpoint media query)
883
+ - Dual-mount inline sidebars (wrapped in `.wide-only`) + drawer twins outside shell slots
884
+ - Toolbar trigger buttons (inside `.narrow-only`) opening the drawer twins
885
+ - **MediaQueryList listener** that auto-closes drawers on viewport widening (the gap FEEDBACK-06 §3 flagged for the peer agent)
886
+ - Signal-based state sharing recipe (single signal source, parent reads `.value` once, passes to both mount points)
887
+ - Dual-mount caveat documented (signal-bound = safe; local-DOM-state = lost on resize)
888
+ - v0.6.0 graduation path noted: Option A `<editor-sidebar responsive-breakpoint="900">` built-in attribute deferred pending editor-shell + pane-ui semantics audit + paired `<show-when breakpoint>` trait
889
+
890
+ Per FEEDBACK-06 §3's "Option B is cheaper landing and matches the existing pattern catalog" framing. Option A graduation lives in v0.6.0 RFC.
891
+
892
+ Files added: `responsive-shell-sidebar.html` (shell + iframed examples), `responsive-shell-sidebar.examples.html` (working dual-mount + auto-close MQL + `data-chunk` annotations for corpus harvest), `responsive-shell-sidebar.css` (breakpoint grid + visibility rules), `README.md`.
893
+
894
+ ### Fixed — yaml-events drift cleanup on 5 components + audit-script false-positive fix (§144 + §162, v0.5.3)
895
+
896
+ `npm run check:yaml-events` (audit slot 7, v0.5.1 §100a) reported 6 components with yaml/runtime drift after v0.5.2 §130's toast.close fix. §144 closed 4 of the 6 with hard-finding fixes; §162 (folded into the same v0.5.3 cycle) closes the remaining 2 via audit-script fixes that surfaced a real `<table-ui>` drift previously masked by false positives.
897
+
898
+ **§144 — Phantom events dropped** (yaml declared but runtime never dispatched):
899
+
900
+ - **`accordion`** — phantom `change` → dropped. Runtime dispatches `toggle { open: boolean }` (single-pane) which was undeclared in yaml. Replaced `change` block with proper `toggle` declaration + detail typing. Codegen regenerates `AccordionToggleEvent` with `{ open: boolean }` in `.d.ts`.
901
+ - **`canvas`** — 3 phantoms (`change`, `click`, `input`) → all dropped. Runtime dispatches only `canvas-interaction` (at canvas.js:50 + :167); the other three events were never bound.
902
+ - **`card`** — phantom `drag-end` → dropped. No runtime drag-end dispatch in card class. (`<row-ui>` had the same yaml drag-end but also no dispatch — fixed in same arc.)
903
+ - **`row`** — phantom `drag-end` → dropped. Same as card; trait-emitted events may dispatch from droppable trait but not card/row directly.
904
+
905
+ **§162 — Audit-script false-positive fix + table `row-collapse` declared**:
906
+
907
+ The audit's runtime-event extractor used a single regex with `[\s\S]*?detail\s*:\s*(\{[^{}]*\}|...)` between event name and detail. Two bugs: (a) `[^{}]*` couldn't parse `detail: { filters: {} }` (nested braces); the lazy `[\s\S]*?` then greedy-expanded across multiple `dispatchEvent` calls, falsely cross-attributing the NEXT sibling's `detail:` to the current event. (b) `detail: { ...conn }` was parsed as field-named `conn` rather than treating the spread as opaque. Rewritten as a brace-balanced walker with explicit spread detection.
908
+
909
+ - **`noodles`** — false positive cleared. `noodle-connect`/`noodle-disconnect` correctly emit `detail: { ...conn }` (spread); audit now treats spread as opaque and skips comparison (matches the script's "variable detail" handling).
910
+ - **`table`** — false positive cleared AND real drift surfaced + fixed. Pre-§162 the audit reported `filter-change → runtime emits {index, row}` (over-matched from row-expand). Post-§162 the audit honestly found a NEW real drift: `<table-ui>.dispatchEvent(new CustomEvent('row-collapse', { detail: { index, row } }))` at table/class.js:235 was undeclared in yaml (only `row-expand` was declared). Added `row-collapse` declaration mirroring `row-expand`'s shape. Codegen regenerates `TableRowCollapseEvent` + `{ index: number, row: object }` in `.d.ts`.
911
+
912
+ `check:yaml-events` now reports **0 findings of any kind** — first fully-clean run since the audit's v0.4.9 §99g + v0.5.0 §100a graduation. **All 6 components from the §130 drift report are now resolved**; v0.5.4 follow-up dropped (no remaining cleanup arc).
913
+
914
+ ### Added — `<slider-ui>` function-typed `.value` dev-mode guard (§153, FEEDBACK-07 §3 P1, v0.5.3)
915
+
916
+ FEEDBACK-07 §3 reported that `slider-ui .value=${() => Math.round(gen.lightness * 100)}` silently stored the arrow function as the property value — thumb stayed at 0% with no error or console output. Root cause: `slider-ui` reads `this.value` synchronously in `render()`; the framework's `isFn` branch normally wraps functions in `effect()` and calls them, but the wrap is bypassed when consumers assign imperatively (`sliderEl.value = fn`) or when a non-AdiaUI binding system doesn't recognize the function-as-value convention.
917
+
918
+ **Fix**: `slider-ui` `render()` checks `typeof this.value === 'function'` and emits a descriptive `console.warn` naming the two correct patterns:
919
+
920
+ ```
921
+ [slider-ui] .value received a function. Did you mean:
922
+ .value=${fn()} ← call the function to get the current value
923
+ .value=${signal.value} ← read the signal's current value
924
+ Functions are not auto-invoked at render time; the slider reads
925
+ .value synchronously. See USAGE.md "Reactive binding" for the
926
+ parent-template-re-read pattern that works across frameworks.
927
+ ```
928
+
929
+ Plus: `return` early from render so the slider doesn't NaN-math the function ref into a 0% position. Subsequent property assignments (with a numeric value) trigger re-render normally.
930
+
931
+ **Docs**: USAGE.md "Derived reactive bindings" subsection updated with a note about the dev-mode guard + the two fix patterns. The pre-existing `() => signal.value * 100` reactive pattern docs are unchanged — they correctly described Pattern A; the guard catches Pattern B (imperative-set with function ref).
932
+
933
+ §153c (deferred to v0.5.4+): generic `UIFormElement`-level guard at the framework's `installProps()` site that warns for ANY Number/Boolean-typed property assigned a function. Larger change requires audit of edge cases (e.g. computed signals that look function-ish); deferred.
934
+
935
+ ### Added — `<drawer-ui>` `close` event detail carries `reason` (§156, FEEDBACK-06 §2 P2, v0.5.3)
936
+
937
+ `<drawer-ui>` previously dispatched a plain `Event('close', { bubbles: true })` with no detail — and the `.d.ts` lied (declared `CustomEvent<unknown>` while the runtime was actually `Event`). FEEDBACK-06 §2 asked for typed `detail.reason` so consumers can distinguish dismiss-intent (escape / backdrop) from explicit (close-button) from programmatic.
938
+
939
+ **New type** in `drawer.d.ts`:
940
+
941
+ ```ts
942
+ export type DrawerCloseReason = 'escape' | 'backdrop' | 'close-button' | 'programmatic';
943
+ export interface DrawerCloseEventDetail { reason: DrawerCloseReason; }
944
+ export type DrawerCloseEvent = CustomEvent<DrawerCloseEventDetail>;
945
+ ```
946
+
947
+ Runtime captures the reason at each entry point (`#closeReason` private field set in `#onPress` / `#onDialogCancel` / `#onDialogClick`); `#onDialogClose` reads + resets the field, then dispatches `new CustomEvent('close', { detail: { reason }, bubbles: true })`. After dispatch the field resets to `'programmatic'` so a subsequent `.open = false` from consumer code doesn't inherit a stale event-driven reason.
948
+
949
+ `drawer.yaml` `events.close.detail.reason` declares the 4-value enum so codegen produces the typed literal union in `.d.ts`. `check:yaml-events` audit (slot 7) confirms yaml ↔ runtime alignment.
950
+
951
+ **Migration**: existing consumers reading `e.detail` from the `close` event got `undefined` (and TypeScript said `unknown`). Now they get `{ reason: DrawerCloseReason }` (and TS narrows to the literal union). Existing code that ignored `detail` continues to work unchanged. Same shape becomes a template for any future `modal-ui` / `popover-ui` close events.
952
+
953
+ ### Fixed — Template `inTag()` HTML-comment handling (§155, FEEDBACK-06 §1 P0, v0.5.3)
954
+
955
+ `packages/web-components/core/template.js` `inTag()` previously tracked `<`, `>`, `'`, `"` to decide whether each `${…}` placeholder was in tag-position (substitute `{{p:N}}`) or content-position (substitute `<!--p:N-->`). The walker had no HTML-comment awareness — an apostrophe inside `<!-- foo's bar -->` put `q=1` (single-quoted-string state), masked the `-->` closer, and silently mis-classified every subsequent placeholder.
956
+
957
+ FEEDBACK-06 §1 reproduced the bug end-to-end: an explanatory comment containing `<editor-shell>'s named slots` produced post-classification garbage `<segmented-ui .value=<!--p:10--> …>`; the DOM parser ate the trailing `>` of the substituted comment; `scan()` found no part registered at index 10; `update()` fell through the `isFn` branch and invoked the consumer's arrow handler with no arguments → `Cannot read properties of undefined (reading 'detail')` crash at mount, with a stack trace pointing at user code (not framework code).
958
+
959
+ **Fix**: skip HTML comments wholesale before the quote/tag walker runs. Comments cannot open a tag and cannot contain real string delimiters as far as the live HTML parser is concerned. Unterminated comments fall through to `last = 0` (content-position; safe default). ~15 LOC inside `inTag()`. Adopted FEEDBACK-06 §1's proposed patch verbatim.
960
+
961
+ Symptom class closed: any template literal with an HTML comment containing an apostrophe (or any unmatched quote char) no longer mis-classifies subsequent placeholders. The "handler called with `e === undefined`" crash class is gone.
962
+
963
+ **Companion (deferred)**: the same heuristic also doesn't model `<![CDATA[ … ]]>` blocks or `<script>`/`<style>` raw-text content (FEEDBACK-06 §1 closing note). Deferred to v0.5.4 unless a confirmed bug class surfaces. Comment-skip handles the observed FEEDBACK-06 case.
964
+
965
+ ### Added — Template partial-attribute interpolation runtime guard (§152, FEEDBACK-07 §2 P0, v0.5.3)
966
+
967
+ `packages/web-components/core/template.js` `scan()` previously skipped attributes whose value didn't match the full-attr regex `^\{\{p:(\d+)\}\}$`. Partial interpolation like `style="background:${a}; color:${b}"` (post-concat: `"background:{{p:0}}; color:{{p:1}}"`) silently failed — no part registered, the literal `{{p:N}}` text reached the DOM as the attribute's static value. FEEDBACK-07 §2 reported color swatches rendering as empty black squares from exactly this pattern.
968
+
969
+ **Fix**: scan() now detects `{{p:` substrings inside non-matching attribute values + emits a descriptive `console.warn` naming the element + attribute + first 80 chars of the value + the three supported binding modes. Warn (not throw) because hot template paths fire many times — a thrown error would crash render; the warn surfaces the bug class loudly without breaking the page.
970
+
971
+ **Docs**: USAGE.md "Property binding patterns" section grows a "Template attribute bindings" subsection documenting the three modes (`attr="${value}"` full-replacement, `.prop=${value}` property assignment, partial-interpolation not supported) with the `style="background:${a}; color:${b}"` example reproduced from FEEDBACK-07.
972
+
973
+ No behavior change for templates that were already correct (full-attr placeholders). Behavior change only for previously-silent-failing partial-attr templates: now they warn loudly + still render the garbage `{{p:N}}` text (so the symptom is recoverable). Consumers should fix their templates to use one of the three documented modes.
974
+
975
+ ### Fixed — CodeMirror transitive dep declarations (§151, FEEDBACK-07 §1 P0, v0.5.3)
976
+
977
+ `packages/web-components/package.json` previously declared only `@adia-ai/a2ui-runtime` in `dependencies`. The `components/code/code-editor.js` source static-imports 6 packages (`@codemirror/state`, `@codemirror/view`, `@codemirror/commands`, `@codemirror/language`, `@codemirror/lint`, `@lezer/highlight`) and dynamic-imports 6 more (`@codemirror/lang-{javascript,json,css,html,markdown,yaml}`). None were declared. Consumer builds (Vite/Rolldown/esbuild) failed with cascading `Failed to resolve import` errors on `@adia-ai/web-components` install, requiring 12 manual `npm install` commands per FEEDBACK-07 §1.
978
+
979
+ **Fix**: hybrid declaration — 6 always-loaded packages as `dependencies`; 6 `@codemirror/lang-*` packages as `optionalDependencies` (lazy-imported only when the `language` prop is set). `optionalDependencies` is the right shape because `npm install` doesn't error on missing optionals, but the consumer who passes `language="json"` (etc.) gets a clear missing-optional-dep message at runtime instead of a silent dynamic-import failure.
980
+
981
+ Consumers affected: anyone importing `@adia-ai/web-components` whose bundler resolves the `code-editor.js` chunk (side-effect-imported via the barrel). Pre-fix workaround: 12 manual `npm install @codemirror/* @lezer/highlight` commands. Post-fix: `npm install @adia-ai/web-components` resolves all 6 core deps automatically + the lang-* on first use of the `language` prop.
982
+
983
+ No runtime behavior change.
984
+
985
+ ### Fixed — `installIconLoaders` tolerates non-canonical loader-map key paths (§140, v0.5.3-staged)
986
+
987
+ FEEDBACK-06 from color-app reported `installIconLoaders` → `warnMissingIcon` firing for `caret-right` / `x` / `caret-up-down` even though the consumer had installed a loader map. Root cause: `resolveLoader()` only looked up loaders by the canonical `/node_modules/@phosphor-icons/core/assets/<weight>/<filename>` path. When the consumer's Vite root, package manager (pnpm hoist), or manually-built loader map produced keys with a different prefix — e.g. `/node_modules/.pnpm/@phosphor-icons+core@.../node_modules/.../assets/regular/caret-right.svg` — the lookup missed silently + the warn fired at install time.
988
+
989
+ **Fix**: `resolveLoader()` adds a filename-suffix fallback. The fast path still tries the canonical phosphor path first (O(1), preserves chat-ui dev convention); on miss, scans the weight's module map for any key ending with `/<filename>` (O(N), but only on miss). Same two-step shape for the `-fill` back-compat name suffix. Public API unchanged; only internal lookup logic.
990
+
991
+ Consumers affected: any project that calls `installIconLoaders()` with a loader map whose keys aren't prefixed with `/node_modules/@phosphor-icons/core/assets/`. Examples: pnpm projects, projects with custom Vite roots, projects with manually-built loader maps using brace-list globs over absolute paths.
992
+
993
+ New tests: `packages/web-components/core/icons.test.js` (12 cases) — covers canonical fast path, pnpm-style prefix, custom Vite root, manually-built map, `-fill` back-compat (both paths), non-regular weights, alias resolution via suffix, DOM re-stamp end-to-end, and miss-on-both-paths. Full vitest suite 787/787 (+12 new). No public-API change; soft-internal change only.
994
+ ## [0.5.2] - 2026-05-13
995
+
996
+ ### Changed — `toast.yaml` drops phantom `events.close` declaration (§130, v0.5.2)
997
+
998
+
999
+ `packages/web-components/components/toast/toast.yaml` previously declared `events: { close: { description: "Fired when the toast is dismissed via click or auto-timeout", ... } }` — but the 92-line runtime is a thin facade over `<feed-ui>` (since v0.4.7 §82) and never dispatched the `close` event. Codegen pipeline (yaml → `.a2ui.json` x-adiaui.events → `.d.ts` `ToastCloseEvent` + `ToastCloseEventDetail` types) was emitting type surfaces for an event that never fired. Same drift class as v0.4.8 §90 (color-picker phantom `format-change`).
1000
+
1001
+ **Migration**: zero consumer impact — `<toast-ui>.addEventListener('close', handler)` already silently never fired; consumers wanting close-event semantics wire it on the host `<feed-ui>` where the lifecycle actually lives.
1002
+
1003
+ Companion regen via `npm run components --force`:
1004
+ - `toast.a2ui.json` — `x-adiaui.events` block scrubbed
1005
+ - `toast.d.ts` — `ToastCloseEvent` + `ToastCloseEventDetail` types stripped
1006
+ - Catalog `catalog-a2ui_0_9.json` — Toast component events block synced (regen ride-along)
1007
+
1008
+ Type API change technically MAJOR-shaped under strict SemVer (consumer types narrow), but runtime behavior is identical → PATCH appropriate.
1009
+ ## [0.5.1] - 2026-05-13
1010
+
1011
+ _Lockstep ride-along (no source change)._
1012
+ ## [0.5.0] - 2026-05-13
1013
+
1014
+ **v0.5.0-staged** — staged for the next minor cut.
1015
+
1016
+ ### Fixed — `core/element.d.ts` missing runtime re-exports (§102, FEEDBACK-05 P1)
1017
+
1018
+ `core/element.js` re-exports the reactive + template helpers from sibling modules:
1019
+
1020
+ ```js
1021
+ export { signal, computed, effect, batch, untracked, isSignal } from './signals.js';
1022
+ export { html, css, repeat } from './template.js';
1023
+ ```
1024
+
1025
+ `core/element.d.ts` only declared the `UIElement` class + a few interface types. The canonical USAGE.md import pattern:
1026
+
1027
+ ```ts
1028
+ import { UIElement, html, signal, computed, effect } from '@adia-ai/web-components/core/element';
1029
+ ```
1030
+
1031
+ works at runtime but fails `tsc` with `TS2305: Module '"@adia-ai/web-components/core/element"' has no exported member 'html'` (etc.). Tokens Studio surfaced this in FEEDBACK-05 post-v0.4.9 upgrade — the only shim they still carried.
1032
+
1033
+ **Fix:** `core/element.d.ts` now mirrors the runtime re-exports:
1034
+
1035
+ ```ts
1036
+ export { signal, computed, effect, batch, untracked, isSignal } from './signals.js';
1037
+ export type { ReadonlySignal, Signal, ComputedSignal, EffectOptions, EffectDisposer } from './signals.js';
1038
+ export { html, css, repeat } from './template.js';
1039
+ export type { TemplateResult } from './template.js';
1040
+ ```
1041
+
1042
+ Consumers can delete their `declare module` shim after upgrading.
1043
+
1044
+ Same drift class as v0.4.8 §90 (color-picker.d.ts) and v0.4.9 §91b / §98 (form-bearing `.d.ts` audit) — but in a `core/*` barrel, not a `components/*/<name>.d.ts`. The §98 audit covered the 17 form-bearing primitives' types; this surface was orthogonal and slipped past both sweeps.
1045
+
1046
+ ### Added — `core/data-stream.d.ts` (NEW, §102 sibling-fix)
1047
+
1048
+ `core/index.js` explicitly re-exports `streams` + `whenStream` from `./data-stream.js` (the public surface of the attribute-driven data ingestion module). `core/data-stream.d.ts` didn't exist, so neither symbol was reachable via TypeScript through the core barrel. Authored `data-stream.d.ts` declaring `StreamEntry`, `streams` (`{ get, has, keys, size }`), and `whenStream(id): Promise<StreamEntry>`.
1049
+
1050
+ `core/index.d.ts` adds the matching `export { streams, whenStream } from './data-stream.js'` + `export type { StreamEntry }` so the core barrel's TypeScript surface matches the runtime exactly.
1051
+
1052
+ ### Added — `scripts/release/check-core-barrel-dts.mjs` audit (§102)
1053
+
1054
+ New trip-wire to catch the FEEDBACK-05 drift class going forward. Walks every `packages/web-components/core/*.js` barrel, extracts explicit `export { … } from '…'` re-exports, compares against the sibling `.d.ts`. Flags runtime re-exports missing from the type barrel.
1055
+
1056
+ Heuristic limits documented in the script header:
1057
+ - Only matches `export { X, Y } from '…'` form. Misses `export * from '…'` re-bundles (covered by `tsc --strict` round-trips).
1058
+ - Type-only re-exports (`export type { X } from '…'`) match either form in the `.d.ts` — the audit only cares that the name is reachable.
1059
+ - Files without a sibling `.d.ts` are skipped (codegen'd elsewhere or genuinely typeless).
1060
+
1061
+ Wired as `npm run check:core-barrel-dts`. The audit-script family now spans **7 trip-wires** (§72 check-no-live-readers + §81 check-changelog-coverage + §83 check-browser-safe + §84 behavioral composition-retrieval + §98 check-form-bearing-dts + §100a check-yaml-events-vs-runtime + §102 check-core-barrel-dts).
1062
+ ## [0.4.9] - 2026-05-13
1063
+
1064
+ ### Added — `<swatch-ui>` multi-badge + auto-contrast label (§99a + §99b)
1065
+
1066
+ Two follow-on enhancements to the v0.4.9 §91a swatch extension. **§99a multi-badge** — `[badge]` now parses comma- or space-separated variant lists; multiple pips render side-by-side in the upper-right (each with its own semantic color + `aria-label`). Each pip stamps as `<span data-badge-variant="X">` so per-variant CSS targets a stable selector. Diff-on-change re-render — no tear-down on no-op renders. **§99b auto-contrast** — new `[auto-contrast]` boolean reflected prop. When set on `shape="block"`, JS computes the swatch color's OKLab lightness via a 1px canvas probe + stamps `[data-on-light]` or `[data-on-dark]` on the label element. CSS swaps `--swatch-label-fg` accordingly (threshold 0.62, WCAG-aligned for sRGB). Detail line follows the label's choice via sibling combinator. Only applies to `shape="block"` where the label sits ON the tile. Handles every CSS color form the browser parses (oklch / hex / hsl / named / var() refs). Backwards compat preserved — both default off.
1067
+
1068
+ ### Added — `<color-picker-ui>` generation constraints (§100b, FEEDBACK-02 #7)
1069
+
1070
+ Closes consumer-friction item #7 from `FEEDBACK-02--adia-packages--2026-05-12.md`. Six new props let consumers constrain the picker to a generation context:
1071
+
1072
+ - `[max-chroma]` (number, default `Infinity`) — clamp OKLCH chroma channel to at most this value.
1073
+ - `[max-l]` (number, default `1`) — clamp OKLCH lightness to at most this value.
1074
+ - `[min-l]` (number, default `0`) — clamp OKLCH lightness to at least this value (symmetric companion to `[max-l]`).
1075
+ - `[hue-drift-max]` (number, default `NaN`) — max signed-shortest-path hue deviation in degrees from `[base-hue]`. Wrap-aware — a drift of 350° resolves as -10°.
1076
+ - `[base-hue]` (number, default `NaN`) — reference hue for `[hue-drift-max]`; falls back to the picker's hue at first commit so consumers can pre-seed `[value]` and constrain drift from that initial value.
1077
+
1078
+ Clamps run BEFORE the gamut-mapping pass in `#commit()` so consumer-declared bounds win even when the user's drag would otherwise be in-gamut.
1079
+
1080
+ **One new event** — `constraint-clamp` — fires immediately before `change` / `input` when one or more axes were clamped. `detail.clamps` is a non-empty array of `{ axis, requested, clamped, reason }` records. `axis` is `"l" | "c" | "h"`; `reason` names the triggering prop (`max-l` / `min-l` / `max-chroma` / `hue-drift-max`). Consumers can surface UX feedback (toast, validity warning, scope-drift indicator) without polling.
1081
+
1082
+ Types: `ColorPickerClampRecord`, `ColorPickerConstraintClampEventDetail`, `ColorPickerConstraintClampEvent` added to `color-picker.d.ts` + re-exported from the package barrel.
1083
+
1084
+ ### Added — yaml event-detail blocks across 26 non-form-bearing primitives (§100c)
1085
+
1086
+ Closes the v0.5.x captured item "x-adiaui.events detail-type sidecar → typed `CustomEvent<DetailShape>`". The §80 codegen reads `events.<name>.detail` from each yaml; pre-§100c, ~77% of non-form events had only a `description:` and codegen emitted `CustomEvent<unknown>`. The sweep authored structured `detail:` blocks for 30 events across 26 yamls:
1087
+
1088
+ action-list, agent-artifact, agent-feedback-bar, agent-questions, agent-reasoning (×3), agent-suggestions, agent-trace, chart-legend, command, demo-toggle, list, menu, nav, nav-group, nav-item, pagination, swiper (×2), tabs, tag, toggle-group, toggle-scheme, tree, chat-thread, link, stream, noodles (×2). Plus manual fixes for: timeline (yaml said `{ id, open }` but runtime emits `{ expanded }` — drift fix); table (added `filter-change` + `row-expand`); chart (added `legend-update`); heatmap (added chart-hover/leave/select signal-only re-emissions).
1089
+
1090
+ After regen, codegen now produces `CustomEvent<<NamedDetail>>` types for 30+ additional events. TypeScript consumers reading event details get typed access instead of `unknown`.
1091
+
1092
+ ### Added — `cross_package_composes:` yaml block + check-composes-coverage extension (§99d)
1093
+
1094
+ Graduates the hardcoded `CROSS_PACKAGE_ALLOWLIST` from `check-composes-coverage.mjs` to a yaml-driven block. Each component yaml gains an optional `cross_package_composes:` block naming primitives in other `@adia-ai/*` packages it composes:
1095
+
1096
+ ```yaml
1097
+ # canvas.yaml
1098
+ composes: [] # same-package siblings (none)
1099
+ cross_package_composes:
1100
+ - tag: a2ui-root
1101
+ from: '@adia-ai/web-modules/runtime'
1102
+ note: Hosts A2UI rendering. Required when canvas mounts content.
1103
+ - tag: theme-ui
1104
+ from: '@adia-ai/web-modules/theme'
1105
+ note: Optional — only read when [theme-wrapper] is set.
1106
+ ```
1107
+
1108
+ The audit script reads the block at scan time + drops matching tags from the "undeclared composition" finding set. Future cross-package compositions need only their tag declared in yaml — no script change required.
1109
+
1110
+ ### Changed — `<editor-sidebar>` `[name]` → `[persist]` template sweep (§99c)
1111
+
1112
+ Two occurrences in `apps/genui/app/a2ui-editor/a2ui-editor.contents.html` migrated to model the preferred shape. `[name]` continues to work via the v0.4.9 §91c back-compat alias path. Surveyed via background Explore agent; no other `[name]` occurrences across `apps/`, `playgrounds/`, `catalog/`, or `site/`.
1113
+
1114
+ ### Audit summary — non-form primitives' yaml events vs runtime (§100a)
1115
+
1116
+ New `scripts/release/check-yaml-events-vs-runtime.mjs` companion to peer's §98 `check-form-bearing-dts.mjs`. Walks every non-form-bearing component, greps `class.js` for dispatch literals, parses yaml `events:` blocks + detail sub-blocks, reports phantom yaml events / undeclared runtime events / detail drift. Wired as `npm run check:yaml-events`. Heuristic limits accepted + documented in the script header (trait-emitted events, computed event names, variable-detail helpers).
1117
+
1118
+ ### Fixed — `color-picker.d.ts` event detail shape mismatch (§90, FEEDBACK-04 P0)
1119
+
1120
+ Closes FEEDBACK-04 claim #2 — a consumer's 70-line `renderSwatch()` helper composing background color, key label, gamut/contrast badge, copy-to-clipboard, and keyboard-select collapses to a single declarative tag:
1121
+
1122
+ ```html
1123
+ <swatch-ui
1124
+ shape="block" size="lg"
1125
+ color="oklch(0.53 0.18 240)"
1126
+ label="500"
1127
+ detail="oklch(0.53 0.18 240)"
1128
+ badge="apca-pass"
1129
+ copyable
1130
+ selectable
1131
+ ></swatch-ui>
1132
+ ```
1133
+
1134
+ **Six new props** (all additive — existing consumers unchanged):
1135
+
1136
+ - `badge` — enum `"" | "out-of-gamut" | "p3-only" | "apca-pass" | "apca-fail"`. Renders △ / ✦ / ✓ / ✗ in the upper-right of the tile with semantic color + `aria-label` for screen readers (e.g. "Outside sRGB gamut", "Contrast passes APCA").
1137
+ - `detail` — secondary line of text below the label. Typically the raw token value (e.g. `oklch(0.53 0.18 240)`) when the swatch represents a design-token step.
1138
+ - `copyable` — boolean. Renders an inline copy-to-clipboard button (⧉ → ✓ on success, ⚠ on failure; auto-resets after 1.2s). Uses `navigator.clipboard.writeText`.
1139
+ - `copy-value` — string. Override what gets copied; defaults to `[color]`.
1140
+ - `selectable` — boolean. Makes the host keyboard-focusable + clickable. Sets `role="button"` + `tabindex="0"`; wires Enter / Space activation.
1141
+ - `selected` — boolean reflected. Visual selected state with focus ring; sets `aria-pressed`.
1142
+
1143
+ **One new event:**
1144
+
1145
+ - `select` — fires when a selectable swatch is activated (click / Enter / Space). `detail: { value, color, label }` where `value` is `[color]` (or `[label]` as fallback).
1146
+
1147
+ **CSS variants added** under the existing `@scope (swatch-ui)` block: badge tokens (`--swatch-badge-*`), copy-button tokens (`--swatch-copy-*`), select-ring tokens (`--swatch-select-ring*`). Per-badge color uses semantic tones — out-of-gamut maps to `--a-warning`, apca-pass to `--a-success`, etc.
1148
+
1149
+ **Backwards compat:** every new prop defaults off / empty. Existing usage (`<swatch-ui shape="dot" color="..." label="...">`) is byte-equivalent in stamping and behavior.
1150
+
1151
+ **Migration for the Tokens Studio FEEDBACK-04 #2 helper:**
1152
+
1153
+ ```diff
1154
+ - const swatch = renderSwatch({ color, label, badge, copyValue, selected })
1155
+ - // ...70 LOC of manual stamping + badge SVG + copy state + a11y
1156
+ + html`<swatch-ui
1157
+ + shape="block" size="lg"
1158
+ + color=${color} label=${label} detail=${detail}
1159
+ + badge=${gamutBadge} copyable selectable
1160
+ + ?selected=${selected.value}
1161
+ + @select=${(e) => onSelect(e.detail.color)}
1162
+ + ></swatch-ui>`
1163
+ ```
1164
+
1165
+ ### Added — `complete` event declaration on `<otp-input-ui>` (§91b, audit sweep)
1166
+
1167
+ §90 audit recipe applied to all 17 form-bearing primitives. `otp-input.d.ts` was missing the `complete` event declaration even though the runtime has emitted it at `class.js:156` since the primitive shipped — fires once when the user fills the last digit slot with `detail: { value: combined }`. Added `OtpInputCompleteEvent` + `OtpInputCompleteEventDetail` types + the `addEventListener('complete', ...)` overload. Also fixed `otp-input.yaml` `events.complete.description` — said "Detail contains { code }" but actual emission is `{ value }`; updated description + added `detail:` block matching the runtime.
1168
+
1169
+ ### Added — `save` + `language-load-error` event declarations on `<code-ui>` (§91b, audit sweep)
1170
+
1171
+ Same audit. `code.d.ts` was missing two events the runtime has emitted since the primitive shipped:
1172
+
1173
+ - `save` (at `class.js:435`) — fires on Mod+S keybind; `cancelable: true`; `detail: { value }`.
1174
+ - `language-load-error` (at `class.js:226` and `:237`) — fires when CodeMirror's dynamic language-mode or core bundle fails to load; `detail: { phase: 'core' | 'language', language?: string, error: unknown }`.
1175
+
1176
+ Added `CodeSaveEvent` + `CodeSaveEventDetail` + `CodeLanguageLoadErrorEvent` + `CodeLanguageLoadErrorEventDetail` types + both `addEventListener` overloads. Re-exported from `packages/web-components/index.d.ts` barrel.
1177
+
1178
+ Also fixed `code.yaml`: removed a phantom `copy` event declaration (no runtime emission); added `detail:` blocks for the four real events (`input`, `change`, `save`, `language-load-error`).
1179
+
1180
+ ### Audit summary — hand-authored `.d.ts` cleanliness as of v0.4.9
1181
+
1182
+ After §90 + §91b sweeps, **all 17 form-bearing primitives' hand-authored `.d.ts` files are runtime-matched.** Audit recipe captured in `docs/plans/0.4.9-release-plan.md` §91b for future cycles — automatable as `scripts/release/check-form-bearing-dts.mjs` in a later cycle if drift returns.
1183
+
1184
+ ### Documentation — USAGE.md "Derived reactive bindings" subsection (§91d, FEEDBACK-04 #3)
1185
+
1186
+ Closes the last open FEEDBACK-04 item (claim #3, P2 — derived-reactive binding discoverability). The consumer narrative was explicit: *"This pattern is correct but discoverable only by reading the template engine source."* Pre-§91d, USAGE.md mentioned the eager-evaluation trap briefly but didn't ship the worked example with ✅/❌ markers that the consumer asked for.
1187
+
1188
+ New subsection at `USAGE.md § Property binding patterns › Derived reactive bindings` shows four patterns (two correct, two broken) with explicit annotations:
1189
+
1190
+ ```js
1191
+ // ✅ Derived reactive — re-evaluates whenever minL changes
1192
+ html`<slider-ui .value=${() => minL.value * 100} min="0" max="100" />`
1193
+ // ✅ Direct signal — re-evaluates whenever minL changes
1194
+ html`<slider-ui .value=${minL} min="0" max="1" step="0.01" />`
1195
+ // ❌ Static snapshot — reads minL.value ONCE at render time; thumb never moves
1196
+ html`<slider-ui .value=${minL.value * 100} />`
1197
+ // ❌ Function as the value — slider receives the function object, not its result
1198
+ html`<slider-ui .value=${(v) => v * 100} />`
1199
+ ```
1200
+
1201
+ Plus a follow-on showing `computed()` as the documented alternative for complex derived values (memoized, stable handle, works in both `.prop=` bindings and `${…}` text bindings). Cross-links back to the existing "eager-evaluation trap" subsection.
1202
+
1203
+ **FEEDBACK-04 closure status:** 7/7 items addressed (P0 #1 §90 + P1 #2 §91a + P2 #3 §91d + P2 #4 §91b + P2 #5 §90 + P2 #6 §91c + P3 #7 deferred to v0.5.x as wishlist).
1204
+
1205
+ ### Fixed — `color-picker.d.ts` event detail shape mismatch (§90, FEEDBACK-04 P0)
1206
+
1207
+ `FEEDBACK-04--adia-ui--2026-05-13.md` reported the consumer's listener typed as `{ hex, l, c, h }` but mistakenly believed the actual detail was `{ value, format }`. **Both were wrong** — the runtime (`class.js:414`) has always emitted `{ value, l, c, h, hex, oklch }`. The hand-authored `color-picker.d.ts` was the source of the consumer's `{ value, format }` belief — it had been stale since the runtime detail shape was extended.
1208
+
1209
+ **Fix:** updated `ColorPickerChangeEventDetail` to match the runtime:
1210
+
1211
+ ```ts
1212
+ export interface ColorPickerChangeEventDetail {
1213
+ value: string; // format-respecting (hex when [format="hex"], oklch when [format="oklch"])
1214
+ l: number; // OKLCH lightness (0–1)
1215
+ c: number; // OKLCH chroma (0–~0.4)
1216
+ h: number; // OKLCH hue (0–360°)
1217
+ hex: string; // hex string (always present)
1218
+ oklch: string; // oklch string (always present)
1219
+ }
1220
+ ```
1221
+
1222
+ Also removed `ColorPickerFormatChangeEvent` + `ColorPickerFormatChangeEventDetail` from the exports — there is no `format-change` event in the runtime (`#commit()` only dispatches `'input'` and `'change'`). The dead type names are dropped from `index.d.ts` barrel; soft-breaking only for consumers that imported those type names (no runtime listener was firing anyway).
1223
+
1224
+ `ColorFormat` simplified from `'hex' | 'rgb' | 'hsl' | 'oklch'` to `'hex' | 'oklch'` to match the yaml + runtime (the two formats actually supported).
1225
+
1226
+ ### Changed — color-picker.d.ts JSDoc surfaces the dual-form invariant
1227
+
1228
+ JSDoc on `ColorPickerChangeEventDetail` now explicitly states that `hex` and `oklch` are always populated regardless of the `[format]` setting — consumers reading the `.d.ts` see the invariant without needing to consult RESPONSE-02.
1229
+ ## [0.4.8] - 2026-05-12
1230
+
1231
+ ### Changed — `core/icons.js` split into pluggable registry + opt-in Phosphor side-effect (§89, v0.4.8)
1232
+
1233
+ Closes the `FEEDBACK-adia-packages-03.md` ask: every `<icon-ui name="X">` in a consumer app was pulling the entire Phosphor set (~1,500 icons × 6 weights = ~9,000 SVGs) through Vite's build pipeline, emitting **9,074 chunk files** and a **38 MB `dist/assets/`** even for apps rendering 2 icons. Root cause: `core/icons.js` ran six lazy `import.meta.glob('.../<weight>/*.svg')` calls at module load — Vite emits one code-split chunk per matched file regardless of usage.
1234
+
1235
+ **Split shape:**
1236
+
1237
+ - `core/icons.js` — registry surface only. Exports `registerIcon`, `registerIcons`, `getIcon`, `hasIcon`, `isIconName`, `listIcons`, `whenIconRegistryReady`, `installIconLoaders` (NEW), `ICON_WEIGHTS`. No top-level globs, no top-level side effects. Cheap to import.
1238
+ - `core/icons-phosphor.js` (NEW) — side-effect module that runs the six globs (Vite path) or the `icons-manifest.js` fetch (static-serving path) and installs the resulting loader map via `installIconLoaders()`.
1239
+
1240
+ **Three barrels side-effect-import `icons-phosphor.js` to preserve zero-config:**
1241
+
1242
+ - `index.js` — main barrel
1243
+ - `core/index.js` — core barrel
1244
+ - `components/index.js` — components barrel (used by site/docs)
1245
+
1246
+ **Consumer-facing impact:**
1247
+
1248
+ | Import path | Phosphor auto-loaded? | Bundle |
1249
+ |---|---|---|
1250
+ | `import '@adia-ai/web-components'` | ✅ Yes (unchanged) | full set |
1251
+ | `import '@adia-ai/web-components/core'` | ✅ Yes (unchanged) | full set |
1252
+ | `import '@adia-ai/web-components/components'` | ✅ Yes (unchanged) | full set |
1253
+ | `import '@adia-ai/web-components/components/button'` | ❌ **CHANGED** — was full set, now registry-only | scoped |
1254
+
1255
+ **Migration for piecemeal consumers** with bundle-size SLOs:
1256
+
1257
+ ```js
1258
+ // Either: side-effect-import the full set explicitly
1259
+ import '@adia-ai/web-components/components/button';
1260
+ import '@adia-ai/web-components/core/icons-phosphor';
1261
+
1262
+ // Or: register only the icons you use (brace-list glob = static-analyzed → N chunks)
1263
+ import '@adia-ai/web-components/components/button';
1264
+ import { installIconLoaders } from '@adia-ai/web-components/core/icons';
1265
+ installIconLoaders({
1266
+ regular: import.meta.glob(
1267
+ '/node_modules/@phosphor-icons/core/assets/regular/{caret-right,caret-up-down,moon,sun}.svg',
1268
+ { query: '?raw', import: 'default', eager: true }
1269
+ ),
1270
+ });
1271
+ ```
1272
+
1273
+ `installIconLoaders` accepts the `import.meta.glob` output shape (`{ '/path/to/star.svg': loader }`) and supports both eager (loader = SVG string) and lazy (loader = `() => Promise<string>`) globs. Calling it re-stamps any `<icon-ui>` elements that asked for an icon before loaders arrived.
1274
+
1275
+ **Measured impact** (Tokens Studio consumer, 4-icon app, post-fix):
1276
+
1277
+ | Metric | Pre-fix | Post-fix (scoped) |
1278
+ |---|---|---|
1279
+ | Files emitted under `dist/assets/` | ~9,074 | ~10 (target — verified consumer-side) |
1280
+ | Total `dist/assets/` on disk | ~38 MB | ~30 KB (target) |
1281
+ | Main `index-*.js` post-tree-shake | 1.48 MB | ~50-100 KB (target) |
1282
+
1283
+ `sideEffects` array in `package.json` extends to include `./core/icons-phosphor.js` so the registration side effect survives tree-shaking under the documented zero-config paths.
1284
+
1285
+ ### Added — `test/refactor-class-subpath.test.js` (§85b, v0.4.8)
1286
+
1287
+ Unit test pinning the v0.4.7 §77 class-subpath refactor's parser invariants. 158 LOC across 6 cases; synthetic feed-shaped input with one `extends` class + one no-`extends` utility class + module-level listener trailer; asserts the shim re-exports the listener trailer (post-§85a fix) AND that `class.js` does NOT (side-effect-free invariant). The bug class this pins: the §85a fix loosened `classRe` regex + made `generateShimFile` emit `parsed.trailer`; this test prevents future regressions on either path. Refactor script lives at `scripts/refactor/class-subpath.mjs`; the test lives here under `packages/web-components/test/` per project convention (the script's inputs are web-components-shaped).
1288
+
1289
+ ### Added — event-types codegen for 38 non-form primitives (§80, v0.4.8)
1290
+
1291
+ Closes the v0.4.7 deferral from root CHANGELOG "Out of scope": typed event surfaces for non-form primitives. v0.4.6 §68 hand-authored event types for the 17 form-bearing primitives. v0.4.7 §74 added codegen'd property types for 73 non-form primitives but left events as base `CustomEvent` (untyped detail). v0.4.8 §80 fills the gap.
1292
+
1293
+ **What consumers see after upgrading:**
1294
+
1295
+ ```ts
1296
+ import type { UIModal, UITimeline, TimelineToggleEvent } from '@adia-ai/web-components';
1297
+
1298
+ const modal = document.querySelector('modal-ui')!;
1299
+ modal.addEventListener('close', (e) => { /* e: CustomEvent<unknown> — name typed */ });
1300
+
1301
+ const timeline = document.querySelector('timeline-ui')!;
1302
+ timeline.addEventListener('timeline-toggle', (e: TimelineToggleEvent) => {
1303
+ const id: string = e.detail.id; // typed
1304
+ const open: boolean = e.detail.open; // typed
1305
+ });
1306
+ ```
1307
+
1308
+ **Codegen extension:** `scripts/build/dts-codegen.mjs` (+89 LOC) reads `x-adiaui.events` from each component's `.a2ui.json` sidecar and emits:
1309
+ - `<Title><EventPascal>EventDetail` interface (when yaml `detail:` block is populated)
1310
+ - `<Title><EventPascal>Event = CustomEvent<<Detail>>` type alias (`unknown` detail if no `detail:` block)
1311
+ - Per-class `addEventListener` overloads (one generic `HTMLElementEventMap` overload + one specific per typed event)
1312
+
1313
+ Naming convention dedups doubled prefixes — `timeline-toggle` on `UITimeline` → `TimelineToggleEvent`, not `TimelineTimelineToggleEvent`.
1314
+
1315
+ **Yaml authoring scope was small.** 35 of the 38 non-form event-emitters already had yaml `events:` blocks. Only `feed`, `pane`, `timeline` had empty `events: {}` blocks needing population. Sidecar regen via `npm run build:components`.
1316
+
1317
+ **Tally:** 91 typed event aliases + 21 detail interfaces emitted across the 73 codegen'd `.d.ts` files. ~23% of events carry detail typing.
1318
+
1319
+ **Barrel surface — `export type {}` → `export *` per component.** `packages/web-components/index.d.ts` non-form section now re-exports via `export *`, so event type aliases + detail interfaces (e.g. `ModalCloseEvent`, `TimelineToggleEventDetail`) are reachable from the package root.
1320
+
1321
+ ### Coverage closure — 90 of 95 primitives typed
1322
+
1323
+ | Primitive class | Property types | Event types |
1324
+ |---|---|---|
1325
+ | 17 form-bearing | ✅ v0.4.6 §68 (hand-authored) | ✅ v0.4.6 §68 (rich event detail types) |
1326
+ | 38 non-form event-emitters | ✅ v0.4.7 §74 (codegen) | ✅ **v0.4.8 §80 (this release — codegen)** |
1327
+ | 35 non-form non-emitters | ✅ v0.4.7 §74 (codegen) | n/a (no events to type) |
1328
+ | 5 CSS-only elements | n/a (no class) | n/a (no class) |
1329
+
1330
+ The codegen pipeline is now feature-complete for the typed-API surface. Future event additions: author yaml `events:` block → run `npm run build:components` + `npm run codegen:dts` → ship.
1331
+ ## [0.4.7] - 2026-05-12
1332
+
1333
+ ### Refactored — class.js helper extractions in agent + toolbar primitives (post-§77 follow-up)
1334
+
1335
+ Riding along on the §77 `<name>.js` → `class.js` split. Each affected primitive's class.js now hoists previously-inlined imperative DOM helpers to module scope for readability + reuse:
1336
+
1337
+ - **`agent-feedback-bar/class.js`** + **`agent-questions/class.js`** + **`agent-reasoning/class.js`** — `makeButton({ icon, text })` helper (small `<button-ui>` stamper with `variant="ghost" size="sm"` defaults). Replaces ~6 lines of inline `document.createElement` + `setAttribute` per stamp site.
1338
+ - **`toolbar/class.js`** — six new module-scope helpers + one constant: `SPILLOVER_LABEL_FLAG` attr, `isDivider(el)`, `humanizeIcon(name)` (kebab→Title Case fallback), `applySpilloverLabels(root)` + `stripSpilloverLabels(root)` (auto-label buttons when they spill into the overflow menu so menu items aren't icon-only), `outerWidth(el)` (rect + margins). Spillover now stamps `text` from `aria-label` || humanized `icon` so menu items carry readable labels.
1339
+ - **`table-toolbar/class.js`** — four new helpers + two thresholds: `emptyHint(text)` + `popoverHead(text)` (popover header/empty-state stampers), `getCellValue(row, col)` (accessor-or-key path resolver), `looksLikeIdKey(key)` (heuristic skip for `id`/`uuid`/`*Id` columns in filter generators), `SELECT_THRESHOLD = 50`, `SAMPLE_LIMIT = 500`.
1340
+
1341
+ No behavior change for default consumers (the helpers are pure factorings of existing inline logic). Pairs cleanly with §77 because the new class.js files were the natural home for these extractions.
1342
+
1343
+ ### Added — `.d.ts` codegen for 73 non-form primitives (v0.4.7, §74)
1344
+
1345
+ Closes the v0.4.7 deferral from v0.4.6 §68 (`.d.ts` shipment was 17 of 95 primitives). After this release, **90 of 95** primitives that ship a JS class are now typed (the remaining 5 are CSS-only "elements" — `header`, `footer`, `section`, etc. — that have no class). TypeScript consumers now get:
1346
+
1347
+ - `document.querySelector('button-ui')` returns `UIButton` (not `Element`)
1348
+ - `createElement('card-ui')` returns `UICard`
1349
+ - Typed properties on every non-form primitive: `<button-ui>.size` is `'xs' | 'sm' | 'md' | 'lg' | 'xl'`, not `string`; `<card-ui>.variant` is the actual union; etc.
1350
+ - JSDoc descriptions surface in IDE tooltips
1351
+
1352
+ **Codegen script:** `scripts/build/dts-codegen.mjs` (~225 LOC; npm `codegen:dts` / `codegen:dts:dry`) reads each component's `.a2ui.json` sidecar (the v0.9 JSON Schema regenerated from `<name>.yaml` by `npm run components`) and emits a typed `<name>.d.ts` alongside `<name>.js` / `class.js`. Property types derive from JSON Schema: `enum` → string-literal union, `type: "string|number|boolean|object|array"` → primitive / `Record<string, unknown>` / array. The 17 form-bearing primitives (input, select, slider, range, textarea, switch, check, radio, segmented, search, otp-input, option-card, color-picker, rating, code, calendar-picker, upload) stay **hand-authored** because they carry rich event types (`CustomEvent<InputChangeEventDetail>` etc.) not representable in the sidecar; codegen explicitly skips them so re-runs don't overwrite the rich types.
1353
+
1354
+ **Barrel + tag-map:** `packages/web-components/index.d.ts` gets 73 new `export type { UI<X> }` re-exports + 73 new `HTMLElementTagNameMap` entries. The hand-authored "Note: only the typed primitives are listed here…" caveat is removed — every primitive that ships a class is now typed.
1355
+
1356
+ **Coordination with class subpath (§77):** `package.json`'s `./components/*/class` types entry maps to `./components/*/*.d.ts` (the per-component `.d.ts`) — so the class subpath gets typed automatically; no separate `class.d.ts` files needed.
1357
+
1358
+ ### Added — non-side-effect `class` subpath per component (v0.4.7)
1359
+
1360
+ Closes the last roadmap item from the §61/§68 consumer-feedback arc (FEEDBACK-adia-packages.md, claim #7). Every component with an `<x-ui>` tag now ships a sibling `class.js` exporting the class **without** calling `customElements.define`. New subpath:
1361
+
1362
+ ```js
1363
+ // Auto-register (default; tag registered immediately):
1364
+ import '@adia-ai/web-components/components/slider';
1365
+
1366
+ // Class import (no auto-register; consumer decides when to define):
1367
+ import { UISlider } from '@adia-ai/web-components/components/slider/class';
1368
+ ```
1369
+
1370
+ **Use cases enabled:**
1371
+ - **Test isolation** — subclass + register under a unique per-test tag without clobbering the global `slider-ui` registration
1372
+ - **Tag-name override** — register `<my-slider>` for a subclass while `<slider-ui>` remains the canonical
1373
+ - **Selective composition** — import the class for inheritance without forcing tag registration on the host page
1374
+
1375
+ ### Refactored — `packages/web-components/components/` (87 single-class + multi-class)
1376
+
1377
+ Mechanical refactor across all components with `customElements.define`:
1378
+
1379
+ **Before** — `<name>.js` had imports + class definition(s) + `customElements.define` calls + export.
1380
+
1381
+ **After** — split into two files:
1382
+ - **`class.js`** (NEW) — imports + class definition(s) with `export` keyword. No `customElements.define` call. Importing this file gives you the class without auto-registering. ~95% of the original LOC moves here verbatim.
1383
+ - **`<name>.js`** (REWRITTEN, ~17 LOC) — imports the class(es) from `./class.js` + imports `defineIfFree` from `../../core/register.js` + calls `defineIfFree('<tag>', UI<Name>)` per registered tag + re-exports the class(es). Preserves the auto-register subpath unchanged for existing consumers.
1384
+
1385
+ **Why `defineIfFree` not `customElements.define`?** Two reasons: (1) the file may be imported transitively from multiple paths after the refactor — `defineIfFree` (shipped in v0.4.6) is the conflict-safe variant that no-ops on duplicate registration. (2) Documented consumer-side pattern: when two packages register the same tag, `customElements.define` throws `NotSupportedError`; `defineIfFree` returns `false` gracefully.
1386
+
1387
+ **Skipped (5 files)** — `button.js` (refactored manually as the proof), `index.js` (barrel re-export), `code/code-editor.js` + `table/cell-types.js` (helpers, not components), plus 6 CSS-only directories (`section`, `stat`, etc.) that have no `.js` file. Components with multiple co-located `customElements.define` calls (menu/tree/timeline/avatar/etc., 11 total) have their multiple classes all moved to a single `class.js` with multiple `defineIfFree` calls in the shim.
1388
+
1389
+ **Script:** `scripts/refactor/class-subpath.mjs` — re-runnable parser that finds class boundaries via brace-counting, identifies `customElements.define` calls, and rewrites both files. Encoded for future similar refactors (per the §69 multi-`*` substitution memory).
1390
+
1391
+ ### Changed — `package.json` exports
1392
+
1393
+ Added `./components/*/class` subpath with TypeScript types mapped to the existing per-primitive `.d.ts` files (no separate `.d.ts` per subpath needed — same class, same types):
1394
+
1395
+ ```json
1396
+ "./components/*/class": {
1397
+ "types": "./components/*/*.d.ts",
1398
+ "default": "./components/*/class.js"
1399
+ }
1400
+ ```
1401
+
1402
+ The 17 form-bearing primitives that received `.d.ts` files in v0.4.6 automatically get typed class imports under the new subpath. The ~80 non-form primitives (button, card, table, chart, popover, etc.) get codegen'd `.d.ts` via §74 in this same v0.4.7 release — typed class imports work for both subpaths from day one.
1403
+
1404
+ ### Changed — `USAGE.md § Registration` updated
1405
+
1406
+ Removed the "FUTURE — not available yet (v0.4.7+)" caveat; documented the class subpath as shipped + added a test-isolation example showing how to register under a unique per-test tag via `defineIfFree`.
1407
+
1408
+ ### Verification
1409
+
1410
+ | Gate | Result |
1411
+ |---|---|
1412
+ | `vitest packages/web-components` | 483 passed / 0 failed (byte-equivalent to pre-refactor — every existing test consumes the auto-register path, which still works) |
1413
+ | `node -e \"require.resolve(...)\"` for new subpaths | 4/4 representative paths resolve correctly (button/class, slider/class, menu/class multi-class, input/class) |
1414
+ | `customElements.define('tag', class)` not called when `./class` subpath imported alone | Verified — `class.js` files have zero side effects |
1415
+ | Component count post-refactor | 87 processed + 11 skipped (CSS-only / helpers / button-as-proof) = 98 component dirs; 87 paired with `class.js` |
1416
+
1417
+ ### Changed — 17 component yamls populate `synonyms.tags` (§72)
1418
+
1419
+ The alias data that previously lived in `@adia-ai/a2ui-corpus/patterns/_components.json` (retired in §72 as the §65 carry-over from v0.4.6) has been folded into per-component yamls. 17 components touched: `calendar-picker`, `canvas`, `card`, `command`, `input`, `inspector`, `kbd`, `modal`, `otp-input`, `pane`, `pipeline-status`, `progress`, `richtext`, `segmented`, `swiper`, `switch` (plus `chat-thread` in `@adia-ai/web-modules`). Sidecars regenerated by `npm run components` carry the aliases under `x-adiaui.synonyms.tags`.
1420
+
1421
+ Four legacy-only alias entries from the retired `_components.json` were merged into their modern canonicals: `Chat` → `ChatThread` (alongside `chat-log` / `conversation` / `message-thread`), `Panel` → `Pane`, `SegmentedControl` → `Segmented`, `Toggle` → `Switch`.
1422
+
1423
+ ### Coverage closure on FEEDBACK-adia-packages.md (final)
1424
+
1425
+ | # | Friction | Status |
1426
+ |---|---|---|
1427
+ | 1 | Typed events | ✅ v0.4.5 (CustomEvent.detail) + v0.4.6 (per-primitive `.d.ts`) |
1428
+ | 2 | `disconnected()` discoverability | ✅ v0.4.6 (USAGE.md § Lifecycle) |
1429
+ | 3 | Property reactivity discoverability | ✅ v0.4.6 (USAGE.md § Property reactivity) |
1430
+ | 4 | `.d.ts` shipment | ✅ v0.4.6 (19 files for 17 form-bearing primitives) + **v0.4.7 (73 codegen'd for non-form primitives — 90/95 total)** |
1431
+ | 5 | USAGE.md reference | ✅ v0.4.6 (430-line guide) |
1432
+ | 6 | CSS via package specifier | ✅ v0.4.5 |
1433
+ | 7 | Registration helpers + non-side-effect class import | ✅ **v0.4.7 (this release — class subpath)** + v0.4.6 (`defineIfFree`) |
1434
+ | 8 | Form-associated discoverability | ✅ v0.4.6 (USAGE.md § Form participation) |
1435
+
1436
+ **All 8 friction points from the original FEEDBACK doc are now closed.** The §61 meta-finding (5 of 8 traced to undiscoverable docs, not missing features) is fully resolved.
1437
+ ## [0.4.6] - 2026-05-12
1438
+
1439
+ ### Fixed — `<pane-ui>` resize tracking 1:1 + pointer-capture under touch
1440
+
1441
+ `<pane-ui>` resize handle previously delegated `pointermove`/`pointerup` to `document` (assuming the cursor stayed close to the handle). Two consequences: (a) on touch / fast pointer races the handle lost grip mid-drag; (b) cursor flicker around the 4-px handle as the browser swapped `col-resize` ↔ default. Refactored to handle-level listeners with `setPointerCapture()`:
1442
+
1443
+ - **Pointer capture on the resize grabber.** `setPointerCapture(pointerId)` is attempted (try/catch guards browsers without it); subsequent `pointermove` / `pointerup` / `pointercancel` listen on the grabber itself, not `document`. Pointer events flow to the grabber even if the cursor races past it.
1444
+ - **`isPrimary` guard** rejects secondary touches / scroll-pointers.
1445
+ - **Document-level cursor lock** — during a drag, `documentElement.style.cursor = 'col-resize'` is set + restored on `pointerup` / `pointercancel`, so the cursor stays `col-resize` across the entire viewport instead of flickering at the handle boundary. Previous cursor captured into `#prevDocCursor` for restore.
1446
+ - **`touch-action: none`** on the grabber suppresses native scroll-pan during touch drags.
1447
+ - **`[data-resizing]` host attribute** — set during drag, removed on release. CSS adds `:scope[data-resizing] { user-select: none; transition: none; }` to bypass any consumer-declared width transition (e.g. editor-shell's collapse animation) so pointer movements track 1:1 instead of interpolating.
1448
+
1449
+ Same shape pattern as `<admin-sidebar>` (mirrored — keeps the two resize behaviors consistent).
1450
+
1451
+ ### Added — `<toggle-scheme-ui>` primitive
1452
+
1453
+ New focused primitive that toggles `color-scheme` (light ↔ dark) on a target element (default `:root`). Encapsulates the `prefers-color-scheme` initial-resolution, the inline-style write to the target, optional `localStorage` persistence (default prefix `adia-theme-`, matching `<theme-panel>` for cross-element interop), and the moon ↔ sun icon swap. Replaces the hand-wired `<button-ui id="theme-toggle">` + `applyScheme()` pattern previously duplicated across `apps/genui`, `apps/construct-canvas`, and `playgrounds/chat`.
1454
+
1455
+ Authoring API: `[scheme="auto"|"light"|"dark"]`, `[target]`, `[persist]`, `[storage-prefix]`, `[icon-light]` / `[icon-dark]`, `[label-light]` / `[label-dark]`, plus `[size]` / `[variant]` / `[color]` / `[disabled]` forwarded to the internal `<button-ui>`. Reflects `[active-scheme]` (read-only). Public methods: `.toggle()`, `.setScheme("light"|"dark"|"auto")`. Bubbles a `scheme-change` `CustomEvent` with `detail: { scheme, source }` where `source ∈ "press" | "media" | "programmatic"`.
1456
+
1457
+ When `scheme="auto"` and there is no persisted override, the element wires a `prefers-color-scheme` `matchMedia` listener and updates `active-scheme` on OS preference flips. The first explicit user choice (click, keyboard, or `.toggle()` / `.setScheme('light'|'dark')`) defeats auto and the listener detaches. `.setScheme('auto')` re-attaches.
1458
+
1459
+ ### Added — TypeScript declarations + consumer guide + registration helpers (§68 consumer-feedback v0.4.6 deferrals)
1460
+
1461
+ Closes the three items deferred from the §61 consumer-feedback follow-up arc (Design Tokens Studio FEEDBACK doc, 2026-05-12). The §61 audit found that 5 of 8 consumer friction points trace to undiscoverable documentation rather than missing features; this release closes 4 of those 5 via hand-written `.d.ts` types + a single landing reference at `USAGE.md`. The fifth (typed events at compile time) is now solved by the `.d.ts` shipment.
1462
+
1463
+ **`USAGE.md` — comprehensive consumer guide** (~430 lines) at the package root, also shipped in the published tarball. Single landing surface for engineers + agents integrating AdiaUI into an app. Sections: Install, mental model, property reactivity (signal-backed), property binding patterns (the eager-evaluation trap), event contract (`CustomEvent.detail`), form participation (`UIFormElement` + `ElementInternals`), lifecycle (`connected`/`render`/`updated`/`disconnected`), theming/density/size, registration (auto vs explicit + `defineIfFree`), TypeScript, anti-patterns. Cross-linked from `README.md`.
1464
+
1465
+ **Hand-written `.d.ts` declarations** for the public API. Adding the package to dependencies now picks up TypeScript types for:
1466
+
1467
+ - **Core types** at `core/*.d.ts` — `UIElement`, `UIFormElement`, signal/computed/effect/batch/untracked, html/css/repeat/stamp templating, register/defineIfFree/isRegistered helpers
1468
+ - **Per-primitive types** at `components/<name>/<name>.d.ts` for 17 form-bearing primitives: input, select, slider, range, textarea, switch, check, radio, segmented, search, otp-input, option-card, color-picker, rating, code, calendar-picker, upload. Each declares typed properties + typed `CustomEvent` payloads
1469
+ - **`HTMLElementTagNameMap` augmentation** at `index.d.ts` — `document.querySelector('slider-ui')` now returns `UISlider`, not `Element`
1470
+ - **`package.json` `types` field + `exports` types conditions** — module resolution under `bundler` / `node16` / `nodenext` picks up the declarations automatically
1471
+
1472
+ Eliminates the `(e.target as any).value` pattern at compile time:
1473
+
1474
+ ```ts
1475
+ import type { SliderChangeEvent } from '@adia-ai/web-components';
1476
+ slider.addEventListener('change', (e: SliderChangeEvent) => {
1477
+ const v: number = e.detail.value; // typed; would error if value were string
1478
+ });
1479
+ ```
1480
+
1481
+ Verified with negative type tests — string-to-number assignment errors fire; wrong-tag inference fires; wrong detail-shape fires.
1482
+
1483
+ **`core/register.js` + `core/register.d.ts`** — explicit registration helpers exported from the `core` barrel:
1484
+
1485
+ - `register(tagName, ctor)` — alias for `customElements.define` (no-magic; provided for symmetry)
1486
+ - `defineIfFree(tagName, ctor)` — no-op + returns `false` if tag already registered. **Solves the consumer's #7 friction** (`customElements.define` throwing `NotSupportedError` when two packages register the same tag). Backwards-compatible — existing auto-register paths in component `.js` files continue to use `customElements.define` directly
1487
+ - `isRegistered(tagName)` — returns `true` if the tag is taken
1488
+
1489
+ The future non-side-effect `class` subpath per component (allowing class import without auto-registration for test isolation) is documented as roadmap in USAGE.md's Registration section; ships v0.4.7+ when the 95 component-file refactor lands.
1490
+
1491
+ **`package.json` `files:` array** updated to include `index.d.ts` + `USAGE.md` in the published tarball. CSS-export negation patterns already cover `.d.ts` (no exclude rules needed).
1492
+
1493
+ ### Coverage
1494
+
1495
+ - 19 new `.d.ts` files (1 root + 6 core + 17 per-component – overlap = 19 total)
1496
+ - 1 new helper module (`core/register.js`, 56 LOC) + its `.d.ts`
1497
+ - 1 new documentation file (`USAGE.md`, ~430 lines)
1498
+ - Updated `README.md` consumer-guide section (collapsed to a quick-reference card + pointer to `USAGE.md`)
1499
+ - Updated `package.json` `types`, `exports`, `files` to expose the types properly
1500
+
1501
+ ### Refreshed (§69 stale-audit sweep) — `traits/` docs currency
1502
+
1503
+ - `traits/CATEGORIES.md` — primitive count updated 93 → 95 (current verified count).
1504
+ - `traits/resettable/resettable.examples.html` — `AdiaFormElement` → `UIFormElement` in the "When NOT to use" description prose (per ADR-0016 v0.0.32 rename; stale class-name reference caught by post-§68 audit).
1505
+ ## [0.4.5] - 2026-05-12
1506
+
1507
+ ### Added — `CustomEvent.detail` sweep across 17 form-bearing primitives (§61)
1508
+
1509
+ All `change` / `input` event dispatches across form-bearing components now emit a `CustomEvent` carrying `detail: { value: this.value }` (value-semantic primitives) or `detail: { value: this.value, checked: this.checked }` (boolean-semantic — switch / check / radio / option-card). Backwards-compatible — `e.target.value` still works since the host's `value` property is updated before dispatch.
1510
+
1511
+ Driven by [consumer feedback](/Users/kimba/Projects/adia/color-app/docs/outbound/FEEDBACK-adia-packages.md) from Design Tokens Studio (2026-05-12) — eliminates the `(e.target as any).value` pattern in TypeScript consumers.
1512
+
1513
+ **Value-semantic primitives swept** (`detail: { value }`):
1514
+ - `<input-ui>` (input.js — 9 dispatch sites)
1515
+ - `<select-ui>` (select.js — 2 sites)
1516
+ - `<slider-ui>` (slider.js — 4 sites)
1517
+ - `<textarea-ui>` (textarea.js — 2 sites)
1518
+ - `<range-ui>` (range.js — 3 sites)
1519
+ - `<rating-ui>` (rating.js — 1 site)
1520
+ - `<search-ui>` (search.js — 2 sites)
1521
+ - `<otp-input-ui>` (otp-input.js — 3 sites)
1522
+ - `<calendar-picker-ui>` (calendar-picker.js — 1 site)
1523
+ - `<upload-ui>` (upload.js — 1 site; detail also carries `files: this.files`)
1524
+
1525
+ **Boolean-semantic primitives swept** (`detail: { value, checked }`):
1526
+ - `<switch-ui>` (switch.js — 1 site)
1527
+ - `<check-ui>` (check.js — 1 site)
1528
+ - `<radio-ui>` (radio.js — 1 site)
1529
+ - `<option-card-ui>` (option-card.js — 1 site)
1530
+
1531
+ **Already canonical** (no change needed):
1532
+ - `<segmented-ui>` — emits `detail.value` since pre-v0.4.0
1533
+ - `<code-ui>` — emits `detail.value` (CodeMirror state)
1534
+ - `<color-picker-ui>` — emits multiple typed events with structured detail
1535
+ - `<demo-toggle-ui>`, `<toggle-group-ui>`, `<tabs-ui>`, `<swiper-ui>` — already typed
1536
+
1537
+ ### Added — `slider.test.js` locks in property reactivity (§61)
1538
+
1539
+ 7-case vitest spec for `<slider-ui>` validating the contract that the consumer feedback questioned (claim #5: "doesn't react to property changes"):
1540
+
1541
+ 1. Initial render places thumb at correct % for `value=50`
1542
+ 2. **Programmatic `slider.value = 75` moves thumb to 75%** (the contested behavior — UIElement's signal-backed property setter at `core/element.js:31-50` triggers the host's render effect on every property change; verified working)
1543
+ 3. `setAttribute('value', '25')` also propagates through `attributeChangedCallback` → property setter → signal
1544
+ 4. `[slot="value"]` readout text syncs on value change
1545
+ 5. `change` event is a `CustomEvent` with `detail.value` on keyboard step (`ArrowRight`)
1546
+ 6. `input` event is a `CustomEvent` with `detail.value` on keyboard step (`ArrowUp`)
1547
+ 7. `aria-valuenow` reflects current value for screen readers
1548
+
1549
+ The consumer's claim was wrong about the library — `<slider-ui>` IS reactive to property changes. Their specific binding pattern (`.value=${signal.value * 100}`) is non-reactive because the expression evaluates eagerly to a number, never reaching the template-system's signal/function detector at `template.js:179-183`. The fix on the consumer side is `.value=${signal}` (signal direct) or `.value=${() => signal.value * 100}` (function); the binding system effect-wraps both. v0.4.6 ships a USAGE.md reference making this pattern obvious.
1550
+
1551
+ ### Added — `/site/tokens/colors` design-tokens export panel (§52)
1552
+
1553
+ In-page exporter that snapshots the live OKLCH token surface as Figma-
1554
+ importable JSON. Supports W3C DTCG (Figma native direct-import, default
1555
+ — two files: `light.tokens.json` + `dark.tokens.json` with structured
1556
+ `{colorSpace, components, alpha, hex}` color objects at float precision),
1557
+ Variables Pro legacy format (single file, `modes` wrapper, hex/rgba
1558
+ strings matching the canonical `parseColor` regex), and a float-RGB
1559
+ sub-byte-precision experimental path. Polarity-collapsed tonal primitives
1560
+ (`neutral.50` is a single Figma variable with per-mode values; the
1561
+ underlying `-tint` / `-shade` cssVars are internal implementation detail
1562
+ that don't surface in the export). Pure-JS OKLCH → OKLab → linear-sRGB
1563
+ → sRGB → HSL math (Björn Ottosson's matrices), no canvas quantization.
1564
+
1565
+ - **`styles/design-tokens-export.js`** (new ~430 lines) — extraction module:
1566
+ CSSOM scanner with authoritative-selector filter (`:root, theme-ui,
1567
+ [data-theme]` only — skipping attribute-scoped overrides that would
1568
+ pollute the cssVar map under last-wins semantics), symbolic
1569
+ `var()` / `light-dark()` chain walker, OKLCH color-math pipeline, four
1570
+ output formatters (DTCG / hex / float-RGB / HSL-decimal). Public API:
1571
+ `buildFigmaJson({ format })`, `buildDtcgFiles()`, `getExportStats()`,
1572
+ `downloadJson(filename, data)`, `copyJson(data)`.
1573
+ - **`styles/colors.examples.html`** — export panel section: 5-button row +
1574
+ stats banner + workflow documentation for Figma's manual multi-mode
1575
+ import (add mode → right-click → "Import mode").
1576
+ - **`styles/colors.examples.js`** — wires the export panel: DTCG (Light +
1577
+ Dark) primary download, Light-only / Dark-only single-mode, Variables Pro
1578
+ legacy hex, clipboard copy. Theme-aware filenames (`design-tokens-${theme}-
1579
+ hex.json` for legacy; DTCG path keeps `light.tokens.json` /
1580
+ `dark.tokens.json` because Figma derives mode names from filename).
1581
+ - **`styles/colors.html`** (standalone path) — loads `button-ui` +
1582
+ `swatch-ui` CSS+JS for the panel chrome.
1583
+
1584
+ ### Fixed — SPA-router-vs-download-anchor interaction (§52)
1585
+
1586
+ `UIRouter` in `core/provider.js:185` installs a document-level click listener
1587
+ that intercepts `<a href>` clicks for client-side navigation. Programmatic
1588
+ download anchors with `blob:` URLs would trigger `history.pushState(blob:...)`
1589
+ which throws cross-origin. The export's `downloadJson` helper now dispatches
1590
+ a non-bubbling synthetic click (`a.dispatchEvent(new MouseEvent('click',
1591
+ { bubbles: false }))`) — browser's `<a download>` default action still
1592
+ fires because it's bound to the event target, not propagation. Worth
1593
+ mirroring in any future in-page download workflow.
1594
+ ## [0.4.4] - 2026-05-12
1595
+
1596
+ ### Added — corpus drift yaml gaps closed (§44, §50, §51)
1597
+
1598
+ Companion to the corpus simplification arc in `@adia-ai/a2ui-{runtime,compose,corpus}@[Unreleased]`. Yaml + js additions for props that were runtime-supported but yaml-under-declared — surfaced by the new `verify:corpus` validator (§39).
1599
+
1600
+ **Text strong attribute (§43):**
1601
+ - **`text.yaml` + `text.js` — `strong: boolean` prop** (was: styled via `:scope[strong]` selector in text.css line 35 but never declared in yaml/js). Caught by §43's audit as a genuine yaml gap; added with `default: false, reflect: true` so the corpus codemod's `Text.weight="semibold"` → `strong: true` rename has a yaml-declared target.
1602
+
1603
+ **Renderer-auto-routed textContent props (§44):**
1604
+ - **`badge.yaml` + `badge.js` — `textContent: string, dynamic`.** Renderer's `#applyProps` lines 294-297 auto-routes `textContent` → `text` attribute for non-text-bearing tags; yaml under-declared.
1605
+ - **`button.yaml` + `button.js` — `textContent: string`.** Same pattern.
1606
+ - **`tag.yaml` + `tag.js` — `textContent: string`.** Same pattern.
1607
+ - **`code.yaml` + `code.js` — `textContent: string`.** Same pattern.
1608
+
1609
+ **Universal layout grow attribute (§44):**
1610
+ - **`col.yaml` + `col.js` — `grow: boolean, reflect: true`.** `:scope[grow]` selector at col.css line 38 supports it; yaml under-declared.
1611
+ - **`row.yaml` + `row.js` — `grow: boolean, reflect: true`.** Same pattern at row.css line 56. Discovered during synthesis (Row.grow had only 5 corpus records, didn't make the top-20 audit; added anyway for symmetry with col.yaml).
1612
+
1613
+ **Pass-through HTML/ARIA attributes (§44):**
1614
+ - **`button.yaml` — `aria-label: string`.** Pass-through ARIA attribute, native HTML, no js change needed.
1615
+ - **`textarea.yaml` — `name: string`.** Inherited from UIFormElement; yaml under-declared.
1616
+
1617
+ **#JS_PROPS-resident table/select props (§44):**
1618
+ - **`table.yaml` — `columns: array` + `data: array`.** Already in renderer's `#JS_PROPS` set (line 233) but yaml under-declared.
1619
+ - **`select.yaml` — `options: array`.** Runtime supports both prop AND children patterns.
1620
+
1621
+ **Universal [size] system (§50):**
1622
+ - **`select.yaml` — `size: enum [sm, md, lg], reflect: true`.** Sizing via the universal `[size]` attribute system at `packages/web-components/styles/tokens.css:206-260`; select.examples.html already shipped `size="sm"`/`size="lg"` examples. Matches Input's sizing tokens so a Select rendered alongside an Input feels coherent in a form row.
1623
+
1624
+ **HTML standard input attributes (§51):**
1625
+ - **`input.yaml` — `inputmode: enum [text, decimal, numeric, tel, search, email, url, none]`.** Routed via setAttribute to the host element. Mobile keyboard hint per HTML inputmode spec.
1626
+ - **`input.yaml` — `autocomplete: string`.** Routed via setAttribute. Browser autofill behavior per HTML autocomplete spec. Common values documented inline: `off`, `on`, `cc-number`, `cc-exp`, `cc-csc`, `cc-name`, `email`, `username`, `current-password`, `new-password`, `one-time-code`, `given-name`, `family-name`, `street-address`, `postal-code`.
1627
+
1628
+ Companion to the §51 renderer kebab-casing fix in `@adia-ai/a2ui-runtime` — yaml declarations close `verify:corpus` warns AND tell LLM generators these are first-class Input props with specific enum/value lists. The documentation win matters more than the validation win.
1629
+
1630
+ Catalog (`packages/a2ui/corpus/catalog-a2ui_0_9.json`) regenerated to pick up every yaml addition. `verify:traits` clean at 56/56; `components --verify` clean at 125 files.
1631
+
1632
+ See root [CHANGELOG.md `[Unreleased]`](../../CHANGELOG.md) for the cross-cutting arc narrative + [docs/journal/2026/05/2026-05-12.md](../../docs/journal/2026/05/2026-05-12.md) §§ 43 / 44 / 50 / 51 for per-§ details.
1633
+ ## [0.4.3] - 2026-05-11
1634
+
1635
+ ### Added
1636
+
1637
+ - **`<input-ui type="number">` — `locale` attribute** (BCP-47 tag, e.g. `de-DE`, `fr-FR`, `en-IN`). When set, the input accepts both `.` AND the locale's decimal separator (so `<input-ui type="number" locale="de-DE">` parses `"1,5"` and `"1.5"` interchangeably) and uses `Intl.NumberFormat` for display — `valueAsNumber = 9.5` renders as `"9,5"` under `locale="de-DE"`. Internal storage stays canonical so `.value` round-trips through `Number(v)` after `#toCanonical`. Default empty = en-US-equivalent path (no behavior change for existing consumers). Closes v0.4.2 known-limitation #1.
1638
+ - **`<input-ui type="number">` — thousands grouping on blur** when `locale` is set. The textContent renders grouped (e.g. en-US `1,234,567.89`, de-DE `1.234.567,89`, fr-FR `1 234 567,89` with U+202F, en-IN `12,34,567.89`) on initial render and post-blur; on focus the input reverts to the ungrouped form so the user can edit naturally without the group separator jumping around mid-keystroke. `this.value` always stores the ungrouped, locale-decimal form; `valueAsNumber` round-trips correctly. Paste of a grouped value (in either canonical or locale form) sanitizes correctly — e.g. en-US `"1,234,567.89"` and de-DE `"1.234.567,89"` both yield `1234567.89` via `valueAsNumber`.
1639
+ - **`<input-ui type="number">` — hold-to-repeat on stepper buttons.** Pressing and holding a `[+]` or `[−]` button now fires an initial step on `pointerdown` then autorepeats after a 400ms delay at a 60ms cadence (matches native `<input type="number">` cadence in Chromium/Safari). Release anywhere (`pointerup`/`pointercancel` on `document`) stops it — supports the "drag off then lift" abort path without per-button leave/cancel bookkeeping. Boundary hit (`value === min` or `max`) cancels the autorepeat early so no-op events don't keep firing. Keyboard `↑/↓` auto-repeat (already provided by browser key-repeat) is unchanged; this addition covers the pointer-only path that v0.4.2 documented as "not implemented" in known limitations.
1640
+
1641
+ ### Fixed
1642
+
1643
+ - `<input-ui type="number">` `#commitOnBlur` — pre-existing latent bug exposed by the new `locale` support. Was `Number(raw)` directly; locale-formatted values like `"1,5"` (de-DE) parsed as `NaN` on blur, so the function returned early without formatting/snapping. Fixed to `Number(#toCanonical(raw))` — canonicalize first.
1644
+ - `<input-ui type="number">` `#sanitizeNumeric` — was accepting `.` as decimal when locale's separator is `,`. Paste of `"1.234.567,89"` (de-DE grouped) parsed as `1,23456789` (first `.` mistaken for decimal). Now only the locale's separator is accepted as decimal; `.` silently drops as group separator when sep=`,`. Programmatic `.value = "1.5"` on a `locale="de-DE"` element still works (UIFormElement's setter bypasses sanitizer; `valueAsNumber` canonicalizes via `#toCanonical`).
1645
+
1646
+ ### Lockstep
1647
+
1648
+ 9-package coordinated PATCH cut to v0.4.3 (per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy)). Internal `@adia-ai/*` dep ranges stay at `^0.4.0` (patch-cut asymmetry — `^0.4.0` covers `0.4.x` under semver). Ratifies [ADR-0027](../../.brain/adrs/0027-cross-primitive-composition-imports.md) — cross-primitive composition imports are the consumer's responsibility; the primitive's `.js` does NOT side-effect-import composed primitives. Source files touched: `components/input/input.{js,css,yaml,a2ui.json}`. See root [CHANGELOG.md `## [0.4.3]`](../../CHANGELOG.md) for the cut narrative.
1649
+ ## [0.4.2] - 2026-05-11
1650
+
1651
+ ### Changed
1652
+
1653
+ - **`<input-ui type="number">` — drop native `<input type="number">` wrapping; render contenteditable + `[+]/[−]` stepper-button column instead.** Number mode now matches every other input variant (range / select / check / radio / slider) as a "we own the affordance" widget — no native browser stepper, no `-webkit-appearance` suppression, no native min/max coercion. Composes `<button-ui>` + `<icon-ui>` for the stepper column. Password (`type="password"`) remains the only branch that wraps native, kept for `-webkit-text-security` disc masking which has no contenteditable equivalent. Demo shell `input.html` now imports `button.css` + `icon.css` + `button.js` + `icon.js`; any consumer that imports `input-ui` piecemeal (not via the barrel) **AND** uses `type="number"` must also register `button-ui` + `icon-ui`. The component barrel `packages/web-components/components/index.js` handles registration order automatically.
1654
+ - `<input-ui type="number">` host now carries `role="spinbutton"` + `aria-valuenow` / `aria-valuemin` / `aria-valuemax` / `aria-valuetext` (suffix-aware: `<input-ui type="number" value="70" suffix="kg">` announces "70 kg") + `aria-readonly` / `aria-required` / `aria-invalid` mirrored from props. Stepper-button wrapper carries `aria-hidden="true"` — redundant with keyboard ↑/↓.
1655
+ - Existing `type` section's number example in [input.examples.html](./components/input/input.examples.html) upgraded with `min="0" max="100"` so the demo's stepper buttons surface working boundary-disable behavior.
1656
+
1657
+ ### Added
1658
+
1659
+ - `<input-ui type="number">` — four new props:
1660
+ - `min` (Number, default `null`) — minimum value. Drives `aria-valuemin` + the `[−]` button's disabled state.
1661
+ - `max` (Number, default `null`) — maximum value. Drives `aria-valuemax` + the `[+]` button's disabled state.
1662
+ - `step` (Number, default `1`) — increment magnitude for ↑/↓ ArrowUp/Down, PageUp/Down × 10, and the `[+]`/`[−]` buttons. Also drives the implicit decimal count for display formatting unless `precision` is set.
1663
+ - `precision` (Number, default `null`) — decimal places to format + clamp to. Overrides the `step`-derived decimal count — e.g. `step=1 precision=2` formats `"10.00"`.
1664
+ - `<input-ui type="number">` — `valueAsNumber` getter (mirrors the standard HTML5 `<input type="number">.valueAsNumber` read pattern). Returns `NaN` when empty / unparseable / lone-sign-or-decimal. Setter coerces and clamps via `#snap`.
1665
+ - `<input-ui type="number">` — full WAI-ARIA APG spinbutton keyboard model: `ArrowUp` / `ArrowDown` step by `step`, `PageUp` / `PageDown` step by `step × 10`, `Home` → `min` (no-op when null), `End` → `max` (no-op when null), `Enter` commits + fires `change` + `submit`, `Escape` reverts to value at focus-in. `wheel` deliberately **not** bound — wheel-changes-value-on-scroll is an antipattern; users can scroll past an input without fearing silent value changes.
1666
+ - `<input-ui type="number">` — `beforeinput` filter enforces digits / single decimal / leading minus (when `min == null || min < 0`). Permissive during typing (allows lone `"-"`, lone `"."`, trailing `"1."`) + normalizes on blur via `#commitOnBlur` → `#snap` → `#format`. Paste handler sanitizes clipboard text to the same alphabet before insertion.
1667
+ - `<input-ui type="number">` — number-range constraint validation. `#runNumberConstraints` layers `badInput` / `rangeUnderflow` / `rangeOverflow` on top of `UIFormElement`'s base constraints (required / pattern / minlength / maxlength). Custom message data attributes: `data-msg-bad-input`, `data-msg-min`, `data-msg-max` (parallels the existing `data-msg-required` / `data-msg-pattern` convention).
1668
+ - New dedicated demo section in [input.examples.html](./components/input/input.examples.html) (`data-property="number"`) — 8 variants: Quantity (integer min/max) · Price ($ prefix + precision 2) · Weight (kg suffix + step 0.1) · Discount (% suffix + step 5) · Temperature (negative allowed) · sm size · lg size · disabled state.
1669
+ - Five new `examples[]` entries in [input.yaml](./components/input/input.yaml) for retrieval signal: `quantity-stepper`, `price-with-currency`, `weight-with-unit`, `percent-bounded`, `temperature-negative`.
1670
+
1671
+ ### Fixed
1672
+
1673
+ - `<input-ui type="number">` chrome height regression. After the rewrite, number-mode fields initially measured **+6-10 px past** the canonical `--a-size` baseline (38/34/46 vs target 30/24/36 at md/sm/lg) because the stepper-button column's children pushed the field's flex height past `min-height: var(--input-height)`. Resolved by switching the controls column from flex flow to `position: absolute` (`[data-number] { position: relative }` + column `inset-block: 0; inset-inline-end: 0`) so the column never participates in field-height calculation. Value/suffix reserve space via `padding-inline-end` / `margin-inline-end`. Number-mode fields now measure exactly 24/30/36 px at sm/md/lg — identical to text/email/password/etc.
1674
+ - `<input-ui type="number">` stepper-button width collapse. button-ui's `:scope:not([text]) { width: var(--button-height) }` icon-only rule was collapsing the buttons to 0 wide when our scope overrode `--button-height` to 0 (to defeat its square min-width/min-height contract). Resolved by bumping our override's selector specificity from `[slot="controls"] button-ui` (0,1,1) to `[data-number] [slot="controls"] button-ui` (0,2,2).
1675
+ - `<input-ui type="number">` stepper icon overshoot. `<icon-ui>`'s default `--icon-size: calc(1em + 0.125rem)` overshoots the half-column cell, causing 1-2 px vertical clipping. CSS-custom-property declarations on self override inherited values, so the override has to target `icon-ui` directly: `[data-number] [slot="controls"] icon-ui { --icon-size: calc(var(--input-height) * 0.4) }`. Chevrons now measure 12/9.6/14.4 px at md/sm/lg — proportional and well within the half-column.
1676
+
1677
+ ### Known limitations
1678
+
1679
+ - ~~`<input-ui type="number">` filter assumes en-US numeric formatting (`.` as decimal separator). Locale-aware separators (e.g. German `,`) are not parsed. Tracked as a deferred follow-up; right answer is an opt-in `locale="…"` attribute that swaps the filter regex + uses `Intl.NumberFormat` for display.~~ **Resolved in [0.4.3]** above. `locale` attribute + thousands grouping on blur both shipped.
1680
+ - Scientific notation (`"1e3"`) is rejected by the filter. Deliberately out of scope.
1681
+ - ~~Hold-to-repeat on the stepper buttons is not implemented. Browser keyboard auto-repeat on ↑/↓ covers the same affordance.~~ **Resolved post-v0.4.2** — see `[0.4.3]` above. Pointer hold now autorepeats at native cadence.
1682
+
1683
+ ### Lockstep
1684
+
1685
+ 9-package coordinated PATCH cut to v0.4.2 (per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy)). Internal `@adia-ai/*` dep ranges stay at `^0.4.0` (patch-cut asymmetry — `^0.4.0` covers `0.4.x` under semver). ADR-0025 ([`.brain/adrs/0025-no-native-form-controls.md`](../../.brain/adrs/0025-no-native-form-controls.md)) ratified — codifies the "no native or suppressed-native interactive widgets in primitives" principle that the `<input-ui type="number">` rewrite implements; `password` remains the documented exception (physically requires `<input type="password">` for `-webkit-text-security`). See root [CHANGELOG.md `## [0.4.2]`](../../CHANGELOG.md) for the cut narrative.
1686
+ ## [0.4.1] - 2026-05-10
1687
+
1688
+ ### Ride-along (no source changes)
1689
+
1690
+ Lockstep PATCH cut alongside `@adia-ai/web-modules@0.4.1` (simple cluster) + `@adia-ai/a2ui-validator@0.4.1` (Phase 3 foundation) + `@adia-ai/a2ui-corpus@0.4.1` (fragment metrics reconciliation). Source byte-identical to v0.4.0.
1691
+
1692
+ Internal `@adia-ai/*` dep ranges bumped from `^0.4.0` to `^0.4.1`. See root [CHANGELOG.md `## [0.4.1]`](../../CHANGELOG.md) for the cut narrative.
1693
+ ## [0.4.0] - 2026-05-10
1694
+
1695
+ ### Ride-along (no source changes)
1696
+
1697
+ Lockstep MINOR cut alongside `@adia-ai/web-modules@0.4.0` (ADR-0024 legacy shell shapes retired). Source byte-identical to v0.3.6.
1698
+
1699
+ Internal `@adia-ai/*` dep ranges bumped from `^0.3.0` to `^0.4.0`. See root [CHANGELOG.md `## [0.4.0]`](../../CHANGELOG.md) for the cut narrative.
1700
+ ## [0.3.6] - 2026-05-10
1701
+
1702
+ ### Ride-along (no source changes)
1703
+
1704
+ Lockstep version bump only — source byte-identical to v0.3.5. Internal `@adia-ai/*` dep ranges remain at `^0.3.0`. See root [CHANGELOG.md `## [0.3.6]`](../../CHANGELOG.md) for the cut narrative.
1705
+ ## [0.3.5] - 2026-05-07
1706
+
1707
+ ### Ride-along (no source changes)
1708
+
1709
+ Lockstep version bump only — source byte-identical to v0.3.4. Internal `@adia-ai/*` dep ranges remain at `^0.3.0`. See root [CHANGELOG.md `## [0.3.5]`](../../CHANGELOG.md) for the cut narrative.
1710
+ ## [0.3.4] - 2026-05-07
1711
+
1712
+ ### Ride-along (no source changes)
1713
+
1714
+ Lockstep version bump only — source byte-identical to v0.3.3. Internal `@adia-ai/*` dep ranges remain at `^0.3.0`. See root [CHANGELOG.md `## [0.3.4]`](../../CHANGELOG.md) for the cut narrative.
1715
+ ## [0.3.3] - 2026-05-07
1716
+
1717
+ **Lockstep cut.** All 9 published `@adia-ai/*` packages now share version `0.3.3`, governed by [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy).
1718
+
1719
+ ### Changed
1720
+
1721
+ - `version`: `0.3.2` → `0.3.3`.
1722
+ - `dependencies["@adia-ai/...]`: `^0.3.0` (patch-cut asymmetry — caret already covers 0.3.x; ranges unchanged).
1723
+
1724
+ ### No source changes
1725
+
1726
+ `@adia-ai/web-components` source is byte-identical to `0.3.2`. The cut is a version bump + ride-along on the lockstep policy.
1727
+ ## [0.3.2] - 2026-05-06
1728
+
1729
+ **9-package lockstep patch cut to v0.3.2.** All lockstep members share
1730
+ one version per [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy).
1731
+ Internal `@adia-ai/*` dep ranges unchanged at `^0.3.0`.
1732
+
1733
+ ### No source changes
1734
+
1735
+ This package's source is byte-identical to v0.3.1. The cut bumps
1736
+ version only.
1737
+
1738
+ ### Changed
1739
+
1740
+ - `version`: `0.3.1` → `0.3.2`.
1741
+ ## [0.3.1] - 2026-05-06
1742
+
1743
+ **9-package lockstep patch cut + folder-per-trait restructure.** All 9 published `@adia-ai/*` packages bump 0.3.0 → 0.3.1 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges remain at `^0.3.0` (covers `0.3.1` under semver — patch-cut asymmetry).
1744
+
1745
+ This is a **patch cut on top of v0.3.0, no BREAKING changes for npm consumers.** The barrel API (`@adia-ai/web-components/traits`, `@adia-ai/web-components/components`, etc.) is byte-equivalent to v0.3.0 at the public surface. The change is internal: the trait library moved from a flat `traits/<name>.{html,js,examples.html,examples.js,test.js}` layout to one folder per trait (`traits/<name>/<name>.{...}`), mirroring the existing `components/<name>/` convention.
1746
+
1747
+ ### Changed
1748
+
1749
+ - **Trait file layout: folder-per-trait.** 56 traits restructured. The barrel re-exports remain unchanged — `import { activeState } from '@adia-ai/web-components/traits'` is byte-equivalent. **Internal subpath shape changes**: imports that reach into specific trait files (e.g. `import { confettiBurst } from '@adia-ai/web-components/traits/confetti-burst'`) now point at `traits/confetti-burst/confetti-burst.js`. The `package.json` `exports` field doesn't declare per-trait subpaths, so the formal published API is unchanged; consumers using deep imports through the file system would need to update paths.
1750
+ - **Stage companions move with their trait.** `confetti-stage.js` lives under `traits/confetti/`, `announcer-stage.js` under `traits/announcer/`. These were never public; they're co-located with the trait that owns them.
1751
+ - `components/card/card.js` + `components/row/row.js` updated to import `traits/draggable/draggable.js` (was `traits/draggable.js`).
1752
+ - `version`: `0.3.0` → `0.3.1`. Internal `@adia-ai/*` dep ranges: unchanged at `^0.3.0` (covers `0.3.1` under semver — patch-cut asymmetry).
1753
+
1754
+ ### Why
1755
+
1756
+ Symmetry with `components/<name>/` (already folder-per for months) — easier to find related files when working on one trait, allows trait-specific helpers without name pollution at the top level. Survey of the import graph confirmed safety: 247 of 251 `@traits/<X>.js` imports use the barrel (unchanged); 4 reached into specific files (all in-repo, updated in same commit).
1757
+
1758
+ ### Repo-only (not in tarball — but commit graph for transparency)
1759
+
1760
+ - `packages/web-components/traits/` directory restructure: ~280 file moves preserving `git mv` history.
1761
+ - `packages/a2ui/compose/transpiler/transpiler-maps.js` — no change since v0.3.0 (the v0.3.0 entry mentioned this file; correction: that update was part of v0.3.0).
1762
+ ## [0.3.0] - 2026-05-05
1763
+
1764
+ **9-package lockstep cut.** All 9 published `@adia-ai/*` packages bump 0.2.5 → 0.3.0 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges bump `^0.2.0` → `^0.3.0`.
1765
+
1766
+ The lockstep policy expanded from 8 to 9 packages with this cut — `@adia-ai/llm` joins as a foundational primitive (extracted from `@adia-ai/a2ui-compose/llm`), and `@adia-ai/a2ui-utils` was renamed to `@adia-ai/a2ui-runtime`. Both are BREAKING for npm consumers (acceptable under pre-1.0 semver).
1767
+
1768
+ This package itself ships **no source changes** in v0.3.0. The cut bumps version + the internal dep range only. Substantive content lives in `@adia-ai/llm` (first publish), `@adia-ai/a2ui-runtime` (rename), `@adia-ai/web-modules` (chat-shell now imports from `@adia-ai/llm`), `@adia-ai/a2ui-compose` (drops `./llm` subpath), and `@adia-ai/a2ui-mcp` (synthesis tool dep update).
1769
+
1770
+ ### Changed
1771
+
1772
+ - `version`: `0.2.5` → `0.3.0`.
1773
+ - `dependencies["@adia-ai/a2ui-utils"]` removed from any consumer; consumers requiring the runtime should now depend on `@adia-ai/a2ui-runtime`.
1774
+
1775
+ ### Repo-only (not in tarball)
1776
+
1777
+ The following directories saw significant in-repo changes between v0.2.5 and v0.3.0, but the additions are **excluded from the published tarball** via the `files:` array's `!components/**/*.html` negation (per [`feedback_npm_files_array_negation.md`](https://github.com/adiahealth/gen-ui-kit/blob/main/.claude/projects/-Users-kimba-Projects-chat-ui/memory/feedback_npm_files_array_negation.md)) and similar rules for non-shipping demo content. Consumers see byte-identical source for the published surface.
1778
+
1779
+ - `components/` — co-located per-component demo trio (`<name>.html` shell + `<name>.examples.html` + `<name>.examples.js`) added across ~85 components per ADR-0021 (page-trio convention). These are dev-only artifacts; not bundled into the tarball.
1780
+ - `core/` — `streams-bridge.js` import path updated for the runtime rename (`@adia-ai/a2ui-utils` → `@adia-ai/a2ui-runtime`).
1781
+ - `traits/` — `tilt-hover.examples.html` regenerated from the components build (slot-marker convention update). No trait API changes.
1782
+ - `styles/` — no source changes; touched only via the build's catalog-regeneration sweep.
1783
+ - `patterns/` — no source changes since v0.2.5.
1784
+ ## [0.2.5] - 2026-05-04
1785
+
1786
+ **Lockstep cut + new `<fields-ui>` form-grid primitive + `draggable-list-item` last-item drop-zone fix.** All 8 published `@adia-ai/*` packages bump 0.2.4 → 0.2.5 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — **no BREAKING changes**.
1787
+
1788
+ ### Added
1789
+
1790
+ - **New `<fields-ui>` primitive** at `components/fields/`. A form-category container that hosts multiple `<field-ui>` children on a shared 6-column grid (configurable via `[columns="1..12"]`). Each `<field-ui rows="N">` spans `N` columns; default span (no attr) fills the row. The host's `[inline]` attribute propagates onto every direct `<field-ui>` child via a MutationObserver — a whole sub-form switches label-position from one host attribute. Solves the wrap-flex jitter pattern where authors stacked `<field-ui>` children inside `<row-ui wrap>` and labels/controls would refuse to align across rows. Yaml + js + css + 6/6 tests. Catalog: 97 → 98 yamls.
1791
+ - `components/fields/fields.yaml` — declarative API with 2 examples (3-up stacked, inline-search-form) + 2 anti-patterns flagged.
1792
+ - `components/fields/fields.js` — `UIFields extends UIElement`. Properties `[columns]` (Number, default 6, reflective) + `[inline]` (Boolean, reflective). MutationObserver re-syncs `[inline]` propagation on `childList` changes.
1793
+ - `components/fields/fields.css` — single `@scope (fields-ui)` block. `display: grid; grid-template-columns: repeat(var(--fields-columns, 6), minmax(0, 1fr))`. Per-`rows` selectors unrolled 1..12 (CSS doesn't do attribute arithmetic).
1794
+ - `components/fields/fields.test.js` — covers default no-propagation, propagation on connect, on add (post-mount `appendChild`), on remove, non-field-child isolation, and registration. **6/6 PASS**.
1795
+
1796
+ ### Fixed
1797
+
1798
+ - **`traits/draggable-list-item.js` — last-item drop-zone**. In a 3-item list `[A, B, C]`, dragging `B` onto `C` was impossible to land at the bottom slot — the lifted source's DOM still occupied its old position, and the standard mid-line test resolved upper-half-of-`C` to "before `C`" (no-op). Fix: the LAST visible item's whole height is now the "drop after" zone (`indexWithinTarget` returns `visible.length` whenever cursor `y >= rect.top` on the last item). Non-last items keep the standard mid-line; the gap above the last item still resolves through mid-line (so "between second-to-last and last" stays reachable). Universal trait fix — list, kanban, and any future `droppable-collection` consumer benefit. New visual-test assertion in the Tasks UI Playground covers this case.
1799
+
1800
+ ### Changed
1801
+
1802
+ - `version`: `0.2.4` → `0.2.5`.
1803
+ - `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.5` under semver — patch-cut asymmetry).
1804
+ - `components/index.js` adds `UIFields` export.
1805
+ - `styles/components.css` adds `@import "../components/fields/fields.css"`.
1806
+ ## [0.2.4] - 2026-05-04
1807
+
1808
+ **Lockstep cut + Tier 4 capability adds + 2 architectural rewrites.** All 8 published `@adia-ai/*` packages bump 0.2.3 → 0.2.4 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
1809
+
1810
+ ### Changed
1811
+
1812
+ - `version`: `0.2.3` → `0.2.4`.
1813
+ - `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.4`).
1814
+ - New `components/demo-toggle/` directory; `components/index.js` adds `UIDemoToggle` export; `styles/components.css` adds `@import "../components/demo-toggle/demo-toggle.css"`.
1815
+ - `traits/index.js` adds 12 new exports (the 11 Tier-4 traits + droppable from the parallel Tasks UI initiative); `traits/_catalog.json` regenerated to 56 entries; `core/icons.js` extended with ~25 new aliases.
1816
+
1817
+ The substantive content of the next cut, surfaced from the
1818
+ 2026-05-04 trait-library lift initiative (`docs/PLAN.md`,
1819
+ `docs/reports/traits-creative-director-audit-2026-05-04.md`).
1820
+ **No BREAKING changes.** Every public tag, prop, event, and
1821
+ declarative `traits=` attribute from v0.2.3 still works.
1822
+
1823
+ ### Added — 11 new traits (Tier 4 capability adds)
1824
+
1825
+ Catalog: 41 → 56 traits across 9 categories (12 net new + droppable
1826
+ already in flight from the Tasks UI parallel session). 100% test
1827
+ coverage maintained; trait-suite footprint **59 files / 365 tests**
1828
+ (was 44 / 169 at v0.2.3).
1829
+
1830
+ - **`layout-animation`** (motion-positioning) — FLIP primitive:
1831
+ capture old bounds, project transform from old to new, hand to a
1832
+ spring tick loop. Triggered by parent `MutationObserver` (sortable
1833
+ lists) or by toggling `data-layout-animate-trigger`. Closes the
1834
+ most-asked-for Framer Motion feature.
1835
+ - **`drop-target`** (motion-positioning) — declarative drop zone with
1836
+ hit-testing + accept-reject (file types, MIME csv list). Counterpart
1837
+ to `draggable`/`drag-ghost`; emits `drop-enter` / `drop-leave` /
1838
+ `drop-receive` / `drop-rejected`.
1839
+ - **`view-transition`** (motion-positioning) — wraps
1840
+ `document.startViewTransition()` for morph animations between DOM
1841
+ states. Sets `view-transition-name` on the host; installs a
1842
+ `host.startTransition(callback)` method. Same-document baseline
1843
+ Chromium 111+ / Safari 18+ / Firefox 129+; graceful-degrade path
1844
+ fires start+end synchronously when the API is missing.
1845
+ - **`input-mask`** (forms-data) — locale-aware as-you-type formatter
1846
+ with 6 built-in patterns (phone-us, phone-intl, credit-card,
1847
+ date-iso, time-24h, cvv) and caret preservation against entered
1848
+ chars (not mask chars). Closes the structurally-thinnest category
1849
+ per the audit.
1850
+ - **`long-press`** (input-interaction) — press-and-hold trigger with
1851
+ configurable duration / tolerance / progress events; suppresses the
1852
+ trailing `click` and the native `contextmenu` while in flight.
1853
+ - **`scroll-progress`** (layout-measurement) — page or element scroll
1854
+ progress as `data-scroll-progress` (0..1) + CSS variable + event.
1855
+ Three modes: `in-view` (default), `page`, `scrolled`.
1856
+ - **`error-shake`** (animation-feedback) — horizontal lateral
1857
+ oscillation on validation failure. Composes with `validation`
1858
+ trait via the `validated` event.
1859
+ - **`success-checkmark`** (animation-feedback) — SVG stroke-draw
1860
+ checkmark on validation success. **Renders to body-level
1861
+ `position: fixed`** so the trait can't be clipped by the host's
1862
+ `overflow: hidden` ancestor (cards, buttons). Composes with
1863
+ `validation` via `validated` event.
1864
+ - **`focus-restore`** (keyboard-navigation) — captures
1865
+ `document.activeElement` on connect, restores via `el.focus({
1866
+ preventScroll: true })` on disconnect. Falls back to body when the
1867
+ captured target is removed/disabled. Closes the modal-close-leaves-
1868
+ focus-dangling a11y bug class.
1869
+ - **`arrow-grid-nav`** (keyboard-navigation) — 2D arrow-key navigation
1870
+ for grids/calendars/menubars (APG `grid` pattern). Two structural
1871
+ modes: `row-flat` (infers row/col by index ÷ columns) and
1872
+ `row-nested` (each direct child is a row container).
1873
+ - **`announcer`** (audio-haptics-sensory) — singleton aria-live mirror
1874
+ with polite/assertive routing + throttling. Two body-level
1875
+ `aria-live` regions (`#adia-live-polite`, `#adia-live-assertive`)
1876
+ shared across all announcer instances. Lifts every audio/haptic/
1877
+ visual trait above WCAG 4.1.3.
1878
+
1879
+ Plus four traits that landed via the parallel Tasks UI initiative,
1880
+ exported through this package's barrel: `droppable` (input-
1881
+ interaction), `droppable-collection` (input-interaction),
1882
+ `draggable-list-item` (motion-positioning), `keyboard-reorderable`
1883
+ (keyboard-navigation).
1884
+
1885
+ ### Added — `<demo-toggle-ui>` primitive
1886
+
1887
+ New custom element at `components/demo-toggle/`. A side-by-side "with
1888
+ trait / without trait" comparison wrapper that any trait detail page
1889
+ (or example) can drop into a hero. Reflects `data-state="on"|"off"`
1890
+ on the host; emits a `change` event with `detail.state`. Optional
1891
+ `data-mode="overlay"` stacks the slots in place so layout doesn't
1892
+ shift between states. 9/9 tests passing. Integrated into 3 trait
1893
+ pages (`parallax`, `shimmer-loading`, `noise-texture`) and ready for
1894
+ broader retrofit.
1895
+
1896
+ ### Changed — `confetti` + `confetti-burst` rendered to top-layer
1897
+
1898
+ Both traits now render particles into a singleton body-level
1899
+ `<div popover="manual">` stage at z-index 99999, derived from the
1900
+ host's `getBoundingClientRect()` at fire time. Particles escape any
1901
+ `overflow: hidden` ancestor (modals, drawers, cards), no z-index
1902
+ wars, no per-trait container. Both traits share the stage via a new
1903
+ private `confetti-stage.js` helper. Disconnect cleans up the
1904
+ attribute and the listener; in-flight particles outlive the trait
1905
+ and self-remove on `animationend` (matching the spec's "disconnect
1906
+ doesn't yank particles" requirement).
1907
+
1908
+ ### Changed — `anchor-positioning` uses native CSS Anchor Positioning where supported
1909
+
1910
+ On Chromium 125+ and Safari 18.0+, the trait now writes
1911
+ `anchor-name` on the anchor + `position-anchor` / `position-area` on
1912
+ the host, calls `host.showPopover()` to promote into the top layer,
1913
+ and lets the browser handle reflow on its own (no JS scroll/resize
1914
+ loop). The v0 JS-positioning path remains for Firefox + Safari
1915
+ < 18.0 as a fallback. The host carries
1916
+ `data-anchor-mode="native"|"fallback"` so consumers and DevTools
1917
+ sessions can see which path actually ran. Public attribute API
1918
+ (`data-anchor`, `data-anchor-placement`, `data-anchor-gap`) is
1919
+ unchanged.
1920
+
1921
+ ### Changed — `count-up` adds Intl.NumberFormat + prefix/suffix/decimals/locale
1922
+
1923
+ The trait now formats the displayed value via `Intl.NumberFormat` so
1924
+ `2438000` renders as `2,438,000` by default. Four new optional
1925
+ config attrs:
1926
+ - `data-count-prefix` — string prepended to the formatted value
1927
+ (e.g., `$`).
1928
+ - `data-count-suffix` — string appended (e.g., `%`).
1929
+ - `data-count-decimals` — fraction-digit count for currency
1930
+ (`$1,234.56`) or percentage (`12.5%`) cases. Default 0.
1931
+ - `data-count-locale` — BCP 47 language tag override.
1932
+
1933
+ Reduced-motion path also formats via the formatter so the snapped
1934
+ value matches the animated path's final frame.
1935
+
1936
+ ### Changed — `noise-texture` strength attribute + visibility default
1937
+
1938
+ Earlier defaults stacked SVG rect opacity (0.08) × overlay div
1939
+ opacity (0.5) = effective 0.04, visually no grain. New defaults: rect
1940
+ opacity = 0.15, overlay's CSS opacity removed (single multiplier).
1941
+ New `data-noise-strength` config attr (0..1, default 0.15) lets
1942
+ consumers dial intensity without touching the trait source.
1943
+
1944
+ ### Changed — Trait helpers dropped underscore prefix
1945
+
1946
+ `_motion.js` → `motion.js`, `_test-helpers.js` → `test-helpers.js`,
1947
+ `_announcer-stage.js` → `announcer-stage.js`, `_confetti-stage.js`
1948
+ → `confetti-stage.js`. The catalog walker
1949
+ (`scripts/build/traits-catalog.mjs`) switched from filename-prefix
1950
+ detection to an explicit `HELPERS` Set so the convention can keep a
1951
+ uniform naming style. Internal consumers updated; no public API
1952
+ change.
1953
+
1954
+ ### Changed — `--a-section-weight` flipped semibold → normal
1955
+
1956
+ Visible on every `<h2>` and `<h3>` consuming the `section` role
1957
+ token. Lighter weight reads less heavy at the doc-section level —
1958
+ closer to a label than a title — and pairs better with the `heading`
1959
+ role above it (already at semibold). Single-line CSS change at
1960
+ `packages/web-components/styles/typography.css:259`. No API surface
1961
+ change; pure visual.
1962
+
1963
+ ### Fixed — `inertia-drag` / `tossable` / `draggable` translate-read
1964
+
1965
+ `parseTranslate()` now reads `style.translate` first (the trait
1966
+ writes to it), falling back to `style.transform` matrix. Same class
1967
+ as the v0.2.3 `spring-animate` fix (commit `07fb2841`). Pre-fix:
1968
+ every drag after the first read the wrong CSS property and got
1969
+ `m41 = 0`, so subsequent drags jumped the host back near the origin
1970
+ instead of moving from its current position.
1971
+
1972
+ ### Fixed — `drag-ghost` rect-snap pixel-identical clone
1973
+
1974
+ Position the cloned ghost over the host's exact viewport rect at
1975
+ `position: fixed; left/top/width/height; box-sizing: border-box`,
1976
+ copy host's `transform`/`transform-origin`, then call
1977
+ `setDragImage(ghost, clickX, clickY)`. Pre-fix: ghost was offscreen
1978
+ with default content-box width/height; for content-box hosts, the
1979
+ ghost rendered larger than the host and the offset coordinates
1980
+ landed at the wrong bitmap pixel — the ghost drifted off the cursor
1981
+ as the click moved away from the host's top-left corner. Hide via
1982
+ `visibility: hidden` after the synchronous `setDragImage` snapshot.
1983
+
1984
+ ### Fixed — `<list-item-ui>` `[data-active]` CSS state
1985
+
1986
+ `list.css` — added `[data-active]` block inside
1987
+ `@scope (list-item-ui)`: `--a-accent-muted` background,
1988
+ `--a-accent-strong` text + icon, 2px accent-strong inset rail.
1989
+ Reuses the same token pair as the existing
1990
+ `list-ui[selectable] [aria-selected="true"]` rule so the two
1991
+ selection mechanisms paint consistently. Closes the keyboard-nav
1992
+ demo "broken" report at v0.2.3 — events fired correctly, the active
1993
+ state had no visible style.
1994
+
1995
+ ### Fixed — `list-item-ui` empty items collapsed to 0 height
1996
+
1997
+ Empty `<list-item-ui></list-item-ui>` (no `text`, no `icon`, no
1998
+ children) rendered with 0 height and silently disappeared from the
1999
+ layout. Add a 24px floor via `min-height: var(--a-space-6)` so empty
2000
+ items always show a visible row. Aligns with WCAG 2.2 SC 2.5.8
2001
+ (Target Size, Minimum) for `list-ui[selectable]` where the row is
2002
+ the click target.
2003
+
2004
+ ### Fixed — `<button-ui type="reset">` triggers `form.requestReset()`
2005
+
2006
+ Previously a no-op (only `type="submit"` was handled). Pre-fix:
2007
+ `<button-ui type="reset">` clicks fired `press` but never the form's
2008
+ `reset` event, so the `resettable` trait's reset chain dead-ended at
2009
+ the click.
2010
+
2011
+ ### Fixed — `<input-ui>` / `<textarea-ui>` programmatic value sync
2012
+
2013
+ `render()` now syncs `this.value` into the inner contenteditable
2014
+ when they diverge (preserving caret if already in sync). Pre-fix:
2015
+ programmatic `host.value = "x"` updated the reactive prop but not
2016
+ the visible content, so the `resettable` trait's snap-back wrote a
2017
+ value the user couldn't see.
2018
+
2019
+ ### Fixed — `resettable` discriminates checkable controls
2020
+
2021
+ Previously wrote `host.value = initialValue` for everything, which
2022
+ silently no-op'd on `switch-ui`/`check-ui`/`radio-ui` (whose primary
2023
+ state is `checked`, not `value`). The trait now detects boolean
2024
+ controls (native `[type=checkbox|radio]` + custom elements
2025
+ declaring `checked`) and resets via `host.checked` for those.
2026
+
2027
+ ### Fixed — `confetti-burst` fires on press, not just connect
2028
+
2029
+ Refactored `setup()` to extract `fireBurst()` and install it as a
2030
+ `press` listener (composes with the `pressable` trait's published
2031
+ event surface). Old behavior fired exactly once at
2032
+ `connectedCallback` and was inert thereafter. Multiple in-flight
2033
+ bursts tracked in a `Set` so rapid clicks don't clobber each other.
2034
+
2035
+ ### Fixed — `droppable` self-recursion (RangeError stack overflow)
2036
+
2037
+ The trait listened for `dnd-drop` on `document` AND dispatched
2038
+ `dnd-drop` on the host with `bubbles: true`. The bubbled event
2039
+ re-reached `document` → re-fired the same handler → recursion to
2040
+ stack overflow. Renamed the host-emitted event to `dnd-drop-receive`
2041
+ (distinct from the document-level signal). `droppable-collection`
2042
+ also updated to listen for `dnd-drop-receive` on its host. Memory
2043
+ entry filed at `feedback_event_name_collision_recursion.md`.
2044
+
2045
+ ### Fixed — Icon registry: ~25 new aliases across multiple commits
2046
+
2047
+ `core/icons.js` aliases mapping consumer-friendly names to canonical
2048
+ Phosphor names. Suppresses repeated `[icon-ui] Icon "X" not found`
2049
+ warnings on every page that referenced these:
2050
+
2051
+ - `bold` / `italic` / `underline` → `text-b` / `text-italic` /
2052
+ `text-underline`
2053
+ - `reset` / `undo` → `arrow-counter-clockwise`; `redo` →
2054
+ `arrow-clockwise`
2055
+ - `success-checkmark` → `seal-check`; `circuit` → `circuitry`;
2056
+ `contrast` / `theme` → `circle-half`; `dark-mode` → `moon`;
2057
+ `light-mode` → `sun`
2058
+ - `alert-triangle` / `alert-octagon` / `alert-diamond` → Phosphor
2059
+ `warning` / `warning-octagon` / `warning-diamond`
2060
+ - `number` / `numeric` → `hash`; `numbered-list` → `list-numbers`
2061
+ - `circle-warning` → `warning-circle`; `circle-x` → `x-circle`;
2062
+ `circle-check` → `check-circle`; `circle-info` → `info`;
2063
+ `circle-question` → `question`; `circle-plus` → `plus-circle`;
2064
+ `circle-minus` → `minus-circle`
2065
+ - `fade` / `fade-presence` → `gradient` (from v0.2.3 cut)
2066
+
2067
+ ---
2068
+ ## [0.2.3] - 2026-05-04
2069
+
2070
+ **Lockstep cut + accumulated fixes.** All 8 published `@adia-ai/*`
2071
+ packages bump 0.2.2 → 0.2.3 per
2072
+ [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy).
2073
+ Patch cut — no breaking changes. The substantive content is
2074
+ `web-components`-only; the other 7 packages have no source change.
2075
+
2076
+ ### Changed
2077
+
2078
+ - `version`: `0.2.2` → `0.2.3`.
2079
+ - `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.3`).
2080
+
2081
+ ### Fixed — `<list-item-ui>` slot layout CSS (2026-05-02)
2082
+
2083
+ `components/list/list.css` — added the `@scope (list-item-ui)` track plan that was missing since the element shipped: a 2-column grid with the icon column at `auto` (icon spans rows, center-aligned), the text + description stacked in column 2, and `[slot="content"]` spanning all columns. Tokens (`--list-item-gap-{column,row}`, `--list-item-{desc,icon}-color`, `--list-item-desc-font-size`) match the rest of the list family. Before this fix, icon / text / description rendered inline as a single line of text in flow order — visible on every consumer of the element. CSS-only; no JS or API change.
2084
+
2085
+ ### Fixed — `traits/confetti-burst.js` fires on press, not connect (2026-05-02)
2086
+
2087
+ Refactored the trait's `setup()` to extract a `fireBurst()` function and install it as a listener on the host's `press` event (composes with the `pressable` trait's published event surface). Old behavior fired exactly once at `connectedCallback` time and never again — meaning the element with `traits="confetti-burst"` was inert after the initial mount. New behavior preserves the connect-time burst (so animation-page demos still self-demonstrate) but additionally re-fires on every press; multiple in-flight bursts are tracked in a `Set` so back-to-back clicks don't clobber each other.
2088
+
2089
+ ### Fixed — Icon registry: `fade` / `fade-presence` aliases (2026-05-04)
2090
+
2091
+ `core/icons.js` — added two registry aliases mapping the trait-doc names `fade` and `fade-presence` to Phosphor's `gradient` glyph (the closest visual proxy). Without these the trait detail pages logged `[icons] unknown icon: fade-presence` warnings on every load.
2092
+
2093
+ ### Fixed — Trait runtime bugs surfaced by user testing on `/site/traits/*` (2026-05-02)
2094
+
2095
+ - `traits/spring-animate.js`: read initial position from `getComputedStyle(host).translate` first, then fall back to the transform matrix. The trait writes to `style.translate`, but the read previously parsed `cs.transform`, which is independent of `translate` in the modern CSS model — callers nudging via `style.translate = '100px 0'` were observed at position 0, so the spring never animated.
2096
+ - `traits/drag-ghost.js`: capture the pointerdown offset within the host and pass it to `dataTransfer.setDragImage(ghost, x, y)`. The old `(0, 0)` offset snapped the ghost to the cursor's top-left corner regardless of where the user grabbed. The ghost clone is also now sized to the host's bounding rect so it doesn't render undersized.
2097
+
2098
+ ### Changed — Legacy tag-name sweep across nav cluster + 2 docs (2026-05-04)
2099
+
2100
+ Removed 11 references to deleted tag names (`<section-nav-item-ui>`,
2101
+ `<section-nav-group-ui>`) that survived ADR-0015 + nav consolidation
2102
+ in non-historical files. Source files swept:
2103
+ `components/nav-item/{nav-item.yaml,nav-item.css,nav-item.a2ui.json}`,
2104
+ `components/nav-group/{nav-group.yaml,nav-group.css,nav-group.a2ui.json}`.
2105
+ Description text + cascade-context comments rephrased to drop the
2106
+ legacy tag names without losing the design rationale ("section variant
2107
+ doesn't reserve icon space when absent; section-variant groups render
2108
+ as kicker labels with always-visible children"). Catalog regenerated
2109
+ to match.
2110
+
2111
+ ### Changed — `<agent-trace-ui>` STAGE column + detail alignment (2026-05-04)
2112
+
2113
+ `agent-trace.css` row track plan tightened so multi-word stage labels
2114
+ ("Rows returned", "Query duration", "Drift vs. SFDC") stay on one line:
2115
+ `--agent-trace-row-label-col` shifts from a fixed `80px` to
2116
+ `minmax(7rem, max-content)`, letting the longest label set the track.
2117
+ The DETAIL column's text and its column header (`[data-trace-aux]`,
2118
+ `[data-trace-header]:nth-of-type(3)`) are right-aligned so 1-3 word
2119
+ qualifiers ("warehouse", "reconciled") sit flush with the row's right
2120
+ edge instead of leaving the column reading as dead width. Per-row
2121
+ `subgrid` keeps the alignment in lockstep across every row.
2122
+
2123
+ ---
2124
+ ## [0.2.2] - 2026-05-02
2125
+
2126
+ **Lockstep cut + trait coverage 100% + `<traits-host>` wrapper.** All 8 published `@adia-ai/*` packages bump 0.2.1 → 0.2.2 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
2127
+
2128
+ ### Changed
2129
+
2130
+ - `version`: `0.2.1` → `0.2.2`.
2131
+ - `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.2`).
2132
+ - `traits/` is the surface that gained the new behavior below; `traits/index.js` adds a side-effect import of the new wrapper, `scripts/build/traits-catalog.mjs` flipped to hard-fail.
2133
+
2134
+ ### Added — Trait test coverage at 100% + `<traits-host>` wrapper (2026-05-02)
2135
+
2136
+ Follow-up landing on top of the v0.2.1 trait-system work — closes the two follow-up items from [ADR-0018](../../.brain/adrs/0018-trait-source-of-truth-and-declarative-attribute.md).
2137
+
2138
+ - **31 new per-trait behavior test files.** Every trait in `traits/` now has a sibling `.test.js`. Total trait-suite footprint: **44 files / 169 tests** (was 12 / 68). Coverage: **42/42 = 100%** (41 trait files + `traits-host`).
2139
+ - **`verify:traits` CI gate flipped from soft-warning to hard-fail.** `npm run verify:traits` exits non-zero if any trait file lacks a sibling `.test.js`, with the offending names listed in the failure message. Verified via mutation test: deleting `active-state.test.js` immediately fails the gate; restoring it returns to clean.
2140
+ - **`<traits-host>` wrapper element** at [`traits/traits-host.js`](./traits/traits-host.js). Tiny pass-through `UIElement` subclass that lets raw HTML opt into declarative trait composition — wrap any markup in `<traits-host traits="pressable scale-press">…</traits-host>` and the named traits attach to the wrapper. Uses `display: contents` so the wrapper has no layout box; children participate in the parent's flex/grid as if the wrapper weren't there. Auto-registered on import of `traits/index.js`. 6 behavior tests covering attribute swap, child event bubbling, and pure-pass-through with no `traits=`.
2141
+
2142
+ ---
2143
+ ## [0.2.1] - 2026-05-02
2144
+
2145
+ **Lockstep cut + 41-trait library overhaul + `<stat-ui>` ad-blocker rename.** All 8 published `@adia-ai/*` packages bump 0.2.0 → 0.2.1 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
2146
+
2147
+ ### Changed
2148
+
2149
+ - `version`: `0.2.0` → `0.2.1`.
2150
+ - `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.1`).
2151
+ - `styles/colors/semantics.css` — neutral-surface border tokens (`--a-canvas-border-{subtle,strong}` + base) shift to lower-numbered scrim levels (1→0, 3→2, 6→4) for a subtler default polarity. Visible on cards/inputs/buttons that consume `--a-canvas-border*`. No public API change.
2152
+ - `styles/components.css` — `@import` swept to the new `<stat-ui>` filename (see Fixed below).
2153
+
2154
+ ### Added — Trait system source-of-truth + declarative `traits=` attribute (2026-05-02)
2155
+
2156
+ Rolls up under [ADR-0018](../../.brain/adrs/0018-trait-source-of-truth-and-declarative-attribute.md). Consumer-visible additions and one core change to UIElement's lifecycle.
2157
+
2158
+ - **`defineTrait()` schema extended.** Now requires `category` (one of nine canonical values, frozen in `define.js`) and `description` (≥ 11 chars). All 40 trait files updated to declare both inline. Build-time throw on missing fields.
2159
+ - **New `getTrait(name)` export from `traits/define.js`.** Returns the trait factory for a kebab-case name, or null if not yet imported. `getTraitSchema(name)` now reads from the same registry (was a separate map).
2160
+ - **New `_motion.js` helper module.** `prefersReducedMotion()` cached check + `onReducedMotionChange()` subscriber. Used by 7 motion-bearing traits (`shimmer-loading`, `attention-pulse`, `typewriter`, `count-up`, `gradient-shift`, `ripple`, `confetti`, `confetti-burst`) to bail or render static fallbacks under `prefers-reduced-motion: reduce`.
2161
+ - **Generated trait catalog at `traits/_catalog.json`.** Single source of truth for downstream consumers (a2ui-corpus, demo pages, MCP `get_traits`). Regenerated by `node scripts/build/traits-catalog.mjs` (added to `package.json` as `build:traits` / `verify:traits` at the repo root).
2162
+ - **UIElement now reads the `traits` attribute.** `<comp-ui traits="ripple confetti-burst">` works on every UIElement subclass. `traits` is always in `observedAttributes`; `attributeChangedCallback` swaps declarative trait instances live without disturbing static or `addTrait()` instances. Static + declarative coexist; double-apply is prevented.
2163
+
2164
+ ### Fixed — Trait brittleness bugs surfaced by new tests (2026-05-02)
2165
+
2166
+ - **`confetti.js` and `confetti-burst.js` no longer crash without canvas.** Both called `canvas.getContext('2d').clearRect(...)` on null in environments without canvas (SSR, JSDOM, happy-dom). Now null-check `ctx` and degrade gracefully — set the active attribute, return a no-op cleanup. Caught by `_smoke.test.js` connect/disconnect contract test.
2167
+
2168
+ ### Test surface
2169
+
2170
+ - 12 trait test files / 68 tests passing (was 0). Universal contract in `_smoke.test.js`; high-fidelity behavior tests for `pressable`, `focusable`, `validation`, `dirty-state`, `hotkey`, `typewriter`, `count-up`, `shimmer-loading`, `confetti-burst`, `resettable`; declarative-attribute integration in `declarative.test.js`.
2171
+
2172
+ ### Added — `resettable` trait
2173
+
2174
+ - New 41st trait at `traits/resettable.js`. Listens for the host's enclosing `<form>` reset event and restores the host's connect-time initial value; dispatches `reset-applied` event with the initial value in detail. Outside a form: clean no-op. Closes the one phantom-trait claim from the original 2026-05-02 audit that survived as a real behavior gap.
2175
+
2176
+ ### Changed — UI field hover-bg flattened to default
2177
+
2178
+ `--a-ui-bg-hover` was `var(--a-canvas-2-scrim)` (an elevation-2 lift on
2179
+ hover); it's now `var(--a-canvas-0-scrim)` (same as the default). The
2180
+ hover affordance for FIELD UI surfaces (`<input-ui>`, `<textarea-ui>`,
2181
+ `<nav-item-ui>`, `<chat-input-ui>`) is now carried by border-color +
2182
+ foreground transitions, not bg lift. Reduces hover noise on dense
2183
+ form surfaces; matches the visual flatness of the default-elevation
2184
+ canvas.
2185
+
2186
+ Affects 4 components (1-line `semantics.css` change). No API change;
2187
+ no component code change. `--a-ui-bg-active` (canvas-0) and
2188
+ `--a-ui-bg-disabled` (canvas-1) unchanged — active still drops,
2189
+ disabled still raises.
2190
+
2191
+ ### Changed — `<chat-input-ui>` color isolation
2192
+
2193
+ The composite host now owns every color the inner `<textarea-ui>`
2194
+ ships with. Previously, textarea-ui's hover (bg/border/color), focus
2195
+ (color brightening + redundant ring), invalid focus, disabled (color),
2196
+ and placeholder treatments all leaked through `<chat-input-ui>` because
2197
+ each was wired to the generic `--a-ui-*` family with no host-level
2198
+ override. Result: a hover bg flash on the editable area, a brightened
2199
+ fg on focus, two stacked focus rings under invalid state, and a
2200
+ disabled-bg rectangle inside the host's own disabled chrome.
2201
+
2202
+ `chat-input.css` now suppresses every textarea-ui state rule and
2203
+ re-paints them with `--chat-input-*` tokens:
2204
+
2205
+ - `--chat-input-fg` (was implicit via `--a-ui-text`).
2206
+ - `--chat-input-bg-hover`, `-border-hover`, `-fg-hover` — default to
2207
+ `--a-ui-bg-hover` / `--a-ui-border-hover` / `--a-fg`, matching
2208
+ `<input-ui>`'s hover affordance. The host paints the lift on
2209
+ `chat-input-ui:not([disabled]):hover`; the inner textarea-ui's own
2210
+ hover bg/border is forced transparent so the alpha doesn't compound.
2211
+ - `--chat-input-bg-disabled`, `-fg-disabled` — inner-text disabled
2212
+ color is owned here; host-level disabled chrome unchanged.
2213
+ - `--chat-input-placeholder-fg` — was leaking `--a-ui-text-placeholder`.
2214
+
2215
+ Hover lives outside the `@scope` block (Safari 17.x bug — `:scope …
2216
+ [descendant]:hover` doesn't match in Safari 17.x; same workaround
2217
+ input.css and textarea.css already use). Focus / invalid / disabled /
2218
+ placeholder rules stay inside the `@scope` and use the
2219
+ `:scope textarea-ui:STATE [slot="text"]:STATE` (0,4,1) pattern to beat
2220
+ textarea.css's matching rules (0,3,0).
2221
+
2222
+ `--chat-input-duration` shifted from `--a-duration` to `--a-duration-fast`
2223
+ (120 ms) to match `<input-ui>`'s transition speed; the host now
2224
+ transitions `background`, `border-color`, `color`, and `box-shadow`
2225
+ together — same 4-property surface `<input-ui>` and `<select-ui>` use —
2226
+ so hover / focus / invalid / disabled all interpolate as one.
2227
+
2228
+ Inner `[slot="text"]` color overrides changed from explicit
2229
+ `var(--chat-input-fg)` to `color: inherit` so the host's transitioned
2230
+ `color` cascades to the contenteditable. The host's `:scope[disabled]`
2231
+ now sets `color: var(--chat-input-fg-disabled)` directly; the inner
2232
+ inherits, and the disabled-fg shift fades on the host's `color`
2233
+ transition rather than snapping. No JS changes; no API changes.
2234
+ ## [0.2.0] - 2026-05-02
2235
+
2236
+ **Lockstep cut.** All 8 published `@adia-ai/*` packages now share one
2237
+ version, governed by [`docs/specs/package-architecture.md` § 15
2238
+ (Versioning Policy)](../../docs/specs/package-architecture.md#15-versioning-policy)
2239
+ and enforced by `npm run check:lockstep` + the
2240
+ `lockstep-versioning` job in
2241
+ [`.github/workflows/docs-lint.yml`](../../.github/workflows/docs-lint.yml).
2242
+
2243
+ Big version jump (0.0.34 → 0.2.0) signals the lockstep era starts here;
2244
+ this is **not** a breaking change to the component surface — every tag
2245
+ + prop + event from 0.0.34 still works identically. The jump aligns
2246
+ this package with the highest sibling version (a2ui-mcp@0.1.3 → 0.2.0)
2247
+ and avoids the `^0.0.x` caret-lock trap that bit `a2ui-mcp@0.1.3 →
2248
+ a2ui-corpus@0.0.6` (the new `^0.X.0` ranges work properly under npm
2249
+ pre-1.0 semver — see § 15 for the math).
2250
+
2251
+ ### Changed
2252
+
2253
+ - `version`: `0.0.34` → `0.2.0`.
2254
+ - `dependencies["@adia-ai/a2ui-utils"]`: `^0.0.2` → `^0.2.0`.
2255
+
2256
+ ### No source changes
2257
+
2258
+ This package's source tree is byte-identical to 0.0.34. The cut bumps
2259
+ version and the internal dep range only. Consumers who want the
2260
+ substantive 0.0.34 work (the `<step-progress-ui>` primitive) get it
2261
+ via either 0.0.34 or 0.2.0 — both ship the same component code.
2262
+
2263
+
2264
+ ---
2265
+
2266
+ ## Pre-0.2.0 history
2267
+
2268
+ The 0.0.x release series (0.0.34 → 0.0.1, 2026-04-21 to 2026-05-02) is
2269
+ archived in [`CHANGELOG-pre-0.2.0.md`](./CHANGELOG-pre-0.2.0.md). That
2270
+ file covers every entry shipped before the lockstep cut.