# Changelog — @adia-ai/web-components

## [0.8.8] — 2026-07-19

### Changed
- **Default type scale tightened one step** (`styles/type/roles.css`): `--a-body-*` 14/15/16 → **13/14/15** (base 15px → 14px) and `--a-ui-*` down 1px with `--a-ui-tiny` floored at 10px — the used UI ladder is a clean 10/11/12/13/14 (tiny/xs/sm/md/lg; nav/buttons = md = **13px**). Running text now shares a 14px base with `--a-code-*`, removing the old 1px body↔ui offset. Untouched by design: the `prose` register (16/18 long-form reading), `verse`, `--a-code-*`, `--a-kicker-*`. `--a-ui-2xs` (unused, pre-staged) shares the 10px floor with `tiny`; re-space it if it's ever wired up.
- **`dist/` bundles regenerated** for the type scale: `web-components.min.css`, `host.min.css`, both `.sheet.js`, and `theme-provider.min.js` (it inlines the regenerated CSS sheet — the documented PR-#275 build-order coupling).

### Fixed
- **README CDN pins un-drifted**: `@0.7` → `@0.8` (3 pins), the range prose now describes the actual `0.8.x` tracking behavior, and the exact-pin example is current — the pins had shipped a full minor stale for the entire 0.8 line (caught by the newly-wired repo cut-hygiene gate).

## [0.8.7] — 2026-07-19

### Changed
- **`dist/theme-provider.min.js`, `dist/web-components.min.js` rebuilt** (`npm run build:bundle-js`) — both had drifted stale relative to source; regenerated as part of 0.8.7 release pre-flight. No source change; `patterns/` also joins the package's published `files` (see `package.json`) so a consumer install resolves pattern source from `node_modules` (`adia-patterns` skill, `adia-ui-factory` `CHANGELOG.md`).

## [0.8.6] — 2026-07-18

### Fixed
- **SSR/late-upgrade attribute replay** (`core/element.js`, `core/element.test.js`; gh#284, PR #309): the custom-elements spec's "upgrade an element" algorithm (§4.13.5 step 6) requires replaying `attributeChangedCallback` for attributes already present before `connectedCallback` fires — happy-dom (this repo's test DOM) skips that replay entirely, and linkedom (the real SSR consumers' shim) is a similarly from-scratch registry. Net effect: any `reflect: true` property seeded only from server-rendered/pre-parsed HTML stayed at its class default after upgrade (`<nav-item-ui text="Profile">` rendered an empty label). `connectedCallback` now re-syncs every declared property from its live attribute before `connected()` runs — a no-op in spec-compliant environments (the setter's `Object.is` check short-circuits, no extra render pass). Regression tests: `describe('UIElement — SSR attribute-upgrade replay (gh#284)')`.
- **`table-ui` row-divider doubling** (`components/table/table.css`; TKT-0026, PR #247): body-cell dividers moved from `border-bottom` (every row, so the last row doubled against the container's own inset box-shadow perimeter) to `border-top` scoped `[role="row"]:not(:first-child)` (so the first row doesn't instead double against the header's border). The `[striped]` variant's stale `border-bottom: none` override updated to the new property in the same pass.
- **`field-ui` min-width floor** (`components/field/field.css`; gh#305, PR #315): a field sitting next to a grow field in a row no longer collapses to zero width (the State/City layout).
- **`--a-ui-bg-selected`** (`styles/colors/semantics/features.css`; gh#307, PR #315): resolves to the selected-container role instead of a scrim — nav-item selection reads as a surface again.
- **`--a-bg-muted`** (`styles/colors/semantics/core.css`; PR #314): resolves to `--md-sys-color-neutral-container-low` instead of the plain `-container` tier (which is `--a-canvas-well-strong`'s distinct-object tier) — muted backgrounds are subtler than "own object" surfaces. `verify:contrast` still 42/42 WCAG AA pairs, both schemes.

### Added
- **`table-ui` declarative `data="[…]"` attribute** (`components/table/table.{class.js,yaml}`; gh#288, PR #311): row records can now be supplied as a JSON-array HTML attribute, hydrated once at connect — the same pattern `chart-ui`'s `data` attribute already used — so a static-HTML or SSR-authored table renders its rows without a post-hydration wiring script. A programmatic `.data =` set before connect always wins; malformed JSON is ignored silently. Works together with `<col-def>` children for a fully static table. (`select-ui` and `chart-ui` already had declarative forms — `table-ui`'s row data was the actual gap in gh#288's scope.)

### Maintenance
- `dist/` bundles (CSS + JS + sheet + theme-provider) regenerated for the source changes above (PRs #309, #311, #316).

### Added
- **`<adia-mark-ui>`** (`components/adia-mark/adia-mark.{class.js,css,yaml}`; gh#283): the Adia brand mark as a token-driven primitive — background always reads `--a-canvas-surface-inverse`, the wordmark always reads `--a-fg-inverse`, so one component replaces the four hand-maintained static `agent-ui-{light,dark,light-transparent,dark-transparent}.svg` exports and adapts to scheme changes with zero asset swapping. Delivery shape (inlined custom element, not a bare SVG file or `icon-ui` registry entry) was constrained by the ticket's own finding: an `<img src="...svg">` or CSS `background-image: url(...)` reference cannot resolve the host page's CSS custom properties — only inlined markup can. `size` prop (xs/sm/md/lg/xl) matches `avatar-ui`'s scale. Verified live in both color schemes (Playwright) — background/foreground correctly invert together with no code change. Existing static-asset consumers (`public/icons/*.svg`, the a2ui corpus chunks, `apps/embedded-app`'s CSS-background brand mark) were deliberately left untouched — the static assets aren't deleted, so nothing breaks; migrating those ~35+ call sites is a separate, larger follow-up, out of scope for this ticket's `size:small` label.
- **`scripts/verify/exports-wildcard-resolution.mjs`** (gh#296, shared with `@adia-ai/web-modules`): live-resolution regression gate asserting every documented short-form CSS/JS subpath specifier resolves to a real on-disk file. Removed the dead `./components/*/*.css` exports-map key — confirmed via live `import.meta.resolve()` testing that it never actually resolved (Node's pattern matching always picked the single-wildcard `./components/*.css` sibling instead). Documented the underlying trap (a redundant nested specifier like `components/button/button.css` silently resolves to a garbage, nonexistent quadrupled path instead of throwing) in `USAGE.md` — not fixable in the exports map itself, since Node's `*` matches across `/` with no way to constrain it to one path segment.
- **`scripts/dev/audit-template-child-conflict.mjs`** (gh#284): static audit flagging a component whose `static template` is non-null AND whose yaml declares a `slots.default` entry (the container shape — nested CE children silently wiped by `stamp()`'s first-mount `replaceChildren`) or whose usage example shows body-text content (the projected-text shape, advisory). Verified against the current 168 components: zero findings — every component accepting light-DOM children currently uses `static template = () => null`. Catches a FUTURE regression rather than patching `stamp()`/`connectedCallback` itself; wired into `adia-author`'s structural-gate sequence (`npm run audit:template-child-conflict[:strict]`).

### Changed
- **`button-ui` role-forwarding sweep (gh issue 276 wave 4, operator ruling)**: `button-ui` now defaults `role=button` only when the author set nothing, so composites can stamp `role=tab`/`gridcell` semantics onto the primitive. `components/calendar-grid/calendar-grid.class.js` + `components/calendar-picker/calendar-picker.class.js` stamp `<button-ui variant="ghost">` for all five shapes (prev/next nav, leading/main/trailing day cells), fixing two latent bugs at the root during migration — a `:disabled` pseudo-class selector that only matches native controls (now `[disabled]`), and a today-indicator dot that collided with `button-ui`'s own `::after`-rendered label (relocated to `::before`). `components/agent-questions/agent-questions.class.js` migrates its stamped native `<button role=radio|checkbox>` cards to `option-card-ui`; multi-select mode intercepts click/keydown at the container's capture phase to preserve toggle-off (the primitive is architecturally radio-only). `components/button/button.class.js` + `components/swiper/swiper.class.js`: swiper's pagination dots migrate from a native `<button role=tab>` to `<button-ui variant=ghost role=tab>`. Live-probed in all four components; `scripts/dev/audit-stamped-native-buttons.mjs` SANCTIONED entries removed for swiper and agent-questions.

### Fixed
- **`icons-phosphor.js`'s auto-discovery glob breaks under pnpm workspace hoisting (gh#287)**: the baked `import.meta.glob('/node_modules/@phosphor-icons/…')` resolved as a literal path join against Vite's configured project root — under pnpm's default (non-hoisted, symlinked) linking, `@phosphor-icons/core` (a transitive dependency of this package, not necessarily the consumer's) is linked only inside this package's own resolution scope, so the glob silently matched zero files and every icon rendered blank. Switched to the entry-file-relative form (`../node_modules/…`, resolved against `icons-phosphor.js`'s own directory, one level inside the package) — confirmed empirically against a real hoisting-gap install (root cause + fix verified with a from-scratch pnpm workspace reproduction, not assumed). Also documented a second, independent failure mode named in the same issue — Vite's dev-server `optimizeDeps` pre-bundling this package empties the glob via a different mechanism (esbuild's scan transforms the glob into a literal empty object, no exception) — with the `optimizeDeps.exclude` fix and a scoped explicit-import fallback, in `icons-phosphor.js`'s JSDoc, `icons.js`'s JSDoc, `icon.class.js`'s fail-loud diagnostic, and `USAGE.md` § Icons.
- **Every browser-only API call site guarded for SSR (gh#285)**: `UIElement`'s constructor called `attachInternals()` unconditionally — the highest-leverage crash site, throwing before any subclass code runs — now feature-detected with a no-op `ElementInternals` shim server-side. `adoptStyles()`'s write to `document.adoptedStyleSheets` is now guarded on `'adoptedStyleSheets' in document`. Every `new ResizeObserver/IntersectionObserver/MutationObserver/PerformanceObserver` construction site across `core/element.js`, `core/data-stream.js`, and the shipped `components/{chart,color-input,color-picker,field,fields,nav,noodles,page,select,swatch,toolbar,tree}` + `traits/{error-shake,fade-presence,success-checkmark}` sources is now feature-detected (22 sites needed the guard; 10 more were already correctly guarded). Real-browser behavior unchanged — confirmed via Playwright and the full test suite; 4 new `core/element.test.js` cases simulate the actual API absence (deleting the constructors, not mocking).

### Maintenance
- **`styles/components.css` barrel** gained the `<adia-mark-ui>` `@import` entry (see Added, above) — without it the component would have shipped with zero CSS. **`dist/` bundles rebuilt** (`web-components.min.{css,js}`, `.sheet.js`, `theme-provider.min.js`) to pick up the `<adia-mark-ui>`, exports-map, and SSR-guard source changes above.

## [0.8.4] — 2026-07-16

### Fixed
- **Focus rings pair with transparent borders** (operator ruling 2026-07-16): a control showing its `--a-focus-ring` box-shadow sets `border-color: transparent` in the same rule — border + gap + ring double-drew the edge (visible on the chat composer). Applied to the 21 ring-bearing controls with visible borders (input, textarea, select, combobox, check, radio, switch, range, tag[tone=outline], tags-input's missing invalid-state half, time/calendar/date-range pickers, upload dropzone, option-card, accordion[contained], agent-artifact header, button, toggle-group, chat-input); the pairing law is stated in `styles/colors/semantics/features.css` and the component-token contract.
- **Sidebar carets read the universal `--a-caret-size` (14px)**: tree carets were an 8px outlier (`--a-space-2`), accordion carets rode the 1rem icon default, select's trigger caret rode the ambient icon size (admin-shell's 18px bump), timeline's outcomes toggle likewise, and nav-group's collapsed block carried two `var()`-less declarations whose value was the literal token name (silently invalid — removed).
- **`agent-reasoning-ui` connector spine ~2px right of the dot centers**: the spine read `--timeline-marker-w` from outside timeline-ui's scope (unreachable — always the 1.25rem fallback) while the composed `size="sm"` dots center in a 1rem column; now keyed to the component's own `--agent-reasoning-marker-w`. Probe: 0px delta.
- **`theme-provider.min.js` inlined a stale foundation sheet**: the bundle embeds the GENERATED `dist/web-components.sheet.js`, and building JS before CSS baked the previous iteration in. `npm run build:bundles` now encodes the css→js order.

### Changed
- **No native `<button>` in stamped component DOM (gh#276)**: `select-ui` (clear-all / select-all / +N overflow), `tag-ui` (dismiss), `swatch-ui` (copy), `timeline-item-ui` (outcomes toggle), `chat-input-ui` (attachment remove), and `combobox-ui` (clear x) stamp `<button-ui variant="ghost" size="xs">` with token re-points — shape, semantic intent, theme tokens, and focus-ring discipline come from the primitive. Data-hooks, aria-labels, and event wiring unchanged. Parked with reasons (in `scripts/dev/audit-stamped-native-buttons.mjs`, the new CI gate): table, agent-questions option cards, swiper dots, calendar day grids.
- **`button-ui` respects a consumer-authored `tabindex`** instead of clobbering it with `0` on connect — enables mouse-only affordances (combobox's clear keeps `tabindex="-1"`) and roving-tabindex composition.

### Maintenance
- **`dist/` bundles rebuilt** (focus-ring/caret/button-ui payloads across `web-components.min.{css,js}`, `web-components.sheet.js`, `theme-provider.min.js`).

## [0.8.3] — 2026-07-16

### Maintenance
- **Lockstep version bump only.** No source changes in this package; bumped to maintain the 11-package version coherence enforced by `scripts/release/check-lockstep.mjs`. Substantive v0.8.3 work shipped in the two Claude Code plugins — the adia-tokens knowledge pack + command refresh (adia-ui-factory) and the release-tooling invariant-3 reconciliation (adia-ui-forge); see `packages/plugins/adia-ui-factory/CHANGELOG.md#083--2026-07-16`.
## [0.8.2] — 2026-07-16

### Fixed
- **`button-ui` label ellipsis could never fire** (`components/button/button.css`): `white-space: nowrap` made the `[text]` label part of the button's min-content, so content-sized contexts (flex column cross-axis, row items, grid auto tracks) grew the whole button past its container instead of shrinking the label. `max-width: 100%` on the host caps it (the `img { max-width: 100% }` class of rule); the `::after` ellipsis rules now actually reach. Verified live on the deployed long-text demo before landing. Known residue: child-text buttons clip without an ellipsis (CSS can't reach anonymous text nodes) — the yaml already steers to `text=`.

### Added
- **`MIGRATION.md` ships in the tarball** — a generated, CI-gated copy of the framework's migration guide, so consumer repos can read the breaking-change/sweep/verify recipes for exactly the version they installed (`node_modules/@adia-ai/web-components/MIGRATION.md`). Previously the guide existed only inside the framework monorepo.

### Maintenance
- **`dist/` CSS-carrying bundles rebuilt** for the button fix (`web-components.min.css`, `web-components.sheet.js`, `theme-provider.min.js`).
## [0.8.1] — 2026-07-15

### Added
- **`tags-input-ui` chip slot template (TKT-0023)** (`components/tags-input/`). An author `<template slot="chip">` child is cloned once per committed token, with `{{value}}`/`{{index}}` substituted into every text node and attribute of the clone (single regex pass — a token literally equal to a placeholder can't be re-substituted); the template's first element child is the reconcile unit (positional diff — unchanged tokens keep their DOM node), and chip removal delegates from any `[data-index]` root via a bubbling `remove` event. Falls back to the default `<tag-ui removable text>` when the slot is absent or has no element content. Also fixed the latent `#stampShell()` first-connect `innerHTML` replacement that would have destroyed an author's template before it was ever read. 8 new tests.

### Fixed
- **`--a-link` / `--a-link-hover` / `--a-link-visited` failed WCAG AA** (`styles/colors/semantics/core.css`; as low as 2.23:1 on the light canvas) — they read the bare `--md-sys-color-primary` CTA fill, tuned for large filled surfaces, not small text. Re-pointed to Material's `-on-surface` state family (the same family `--a-primary-text` uses); clears 4.5:1 with margin in both schemes. Visible change: links render darker (light scheme) / lighter (dark scheme) than 0.8.0. Caught by the rewritten `verify:contrast` repo gate, which now resolves the real token cascade instead of the retired parametric formula.

### Maintenance
- **`dist/` bundles rebuilt** (tags-input + link-fix payloads; theme-provider CDN script inlines the swept CSS).
## [0.8.0] — 2026-07-15

### BREAKING — Material Design color-token adoption (MINOR cut; see `.claude/docs/MIGRATION GUIDE.md` § v0.8.0)
- **`--md-sys-color-*` is now the color system's source of truth and public API.** `styles/colors/material-static.css` (new) carries the full static export — 8 palettes × 59 semantic roles, verbatim from the nonoun-color-tokens-adia reference. The `--a-*` semantic layer (`semantics/core.css`, `surfaces.css`, `semantics/{features,aliases}.css`) is now a thin alias layer reading FROM it; the old calc()-computed OKLCH engine no longer feeds the semantic tokens. Contract + full phase history: `.claude/docs/specs/material-color-bridge-contract.md`.
- **Removed: the 19 `--a-accent-*` back-compat aliases** (`accent` → `primary` family rename; sweep: `--a-accent*` → `--a-primary*`, or consume `--md-sys-color-primary*` directly).
- **Removed: `styles/colors/scrims.css`'s 189 raw per-family scrim primitives** (`--a-{family}-{0..6}-{tint-|shade-}scrim`) — zero consumers remained once the semantic layer moved to the static `--md-sys-color-{family}-scrim-{weakest..strongest}` ladder. The semantic `--a-canvas-{0..6}-scrim` / `--a-scrim-*` role names keep working. Alpha ceiling note: the static ladder tops out at 60% (old ramp: 70%).
- **Removed: `styles/colors/primitives-accent.css`** (deep-import path) — renamed `primitives-primary.css`.
- **Behavioral: the 8 named `[theme]` presets no longer re-color components.** The presets overrode parametric hue/chroma knobs the static export doesn't read; only `--a-brand-hue` + the non-color knobs (`--a-radius-k`, `--a-density`, `--a-shadow-intensity`) still respond to `[theme]`. Operator-accepted tradeoff of the adoption.

### Added
- **`secondary` + `tertiary` color families** (full ramps, semantics, scrim roles): `tertiary` at hue 335 — the one slot in the open hue range that clears real CVD separation from all four intent hues (empirical deuteranopia/protanopia sweep via culori, not estimation); `secondary` as a muted `primary` variant (same hue 225, c-max 0.08), zero new CVD risk.

### Changed
- **Repo-wide consumer sweep onto `--md-sys-color-*`** — 2,472 call sites across 584 files (components, traits, demos, patterns, apps, site) moved from `--a-*` reads to the Material roles (`scripts/refactor/adopt-material-tokens.mjs`).
- **12 component yaml SoTs re-synced with their swept CSS** (`calendar-grid`, `date-range-picker`, `inline-edit`, `noodles`, `progress`, `step-progress`, `toc`, `skip-nav` here; 4 more in web-modules) — token `default:` docs referenced the removed `--a-accent-*` names; sidecars + catalog regenerated.
- **`tag-ui`** — warning solid-fill now pairs the amber fill with Material's fixed-light `on-warning` (white in both schemes, per Material's on-color law); the pre-migration dark-text override is gone. Known lower-than-siblings contrast, operator-accepted pending palette adjustment (TKT-0018, wontfix). Tests re-aligned to the post-migration token names (`tag.test.js`, `spinner.test.js`).

### Maintenance
- **`dist/` CDN bundles rebuilt** — and ~6% smaller (`web-components.min.css` 628.6 → ~590 KB): the retired scrims/bridge dead code is actually gone from the payload.
## [0.7.28] — 2026-07-14

### Added
- **`table-ui` (`components/table/cell-types.js`) — `markdown` cell type: inline `` `code` ``/**bold**/*italic*/`[link](href)` rendered as real elements, not a plain string.** TKT-0008 (site-a2ui re-verification, direction 1): docs reference tables (props/API tables) mix prose with multiple inline `<code>` spans per cell — content the `{columns, data}` JSON contract previously couldn't carry, forcing a preserved-HTML fallback that dropped the table from the A2UI graph entirely. Renders via `core/markdown.js`'s `inline()` (now exported) — the exact encode/decode pairing the docs-transpiler's own `convertInline()` already produces elsewhere in the pipeline. `[title]` carries the tag-stripped plain text for the truncation tooltip. New unit suite in `table.test.js` (3 cases).

### Changed
- **`core/markdown.js` — `inline()` is now exported.** Was module-private; reused by `cell-types.js`'s new `markdown` cell type as the client-side half of the docs-transpiler's `convertInline()` contract. No behavior change to existing callers.

### Fixed — TKT-0010, corpus admission-validation content gaps
- **`kbd.yaml` — declares a `textContent` prop.** Every other text-bearing leaf (`Text`, `Code`) already declares one; `Kbd` was missing it, so the transpiler's unconditional `textContent` extraction for leaf types produced a value the catalog schema rejected as an unevaluated property on every harvested `<kbd-ui>` chunk (12 instances). Purely a schema declaration gap — no behavior change (the transpiler already extracted the value correctly).
- **`chart.yaml`/`table.yaml` — `data` prop declares `items: {type: object}`.** Opts these two dynamic array props into `@adia-ai/a2ui-corpus`'s new `DynamicObjectList` schema (array-of-object) instead of the default `DynamicStringList` (array-of-string) — the wrong shape for chart/table row records. Paired with a transpiler fix in `@adia-ai/a2ui-compose` (the attribute value was never actually parsed); see that package's CHANGELOG.

### Fixed — GH #224, SVG zero-viewport silent non-render
- **`styles/resets.css` — `svg` gets its own reset rule, dropping `max-inline-size`/`block-size` instead of sharing `img`/`picture`/`video`/`canvas`'s `max-inline-size: 100%; block-size: auto` rule.** Per the SVG spec, a zero-size viewport — exactly what `max-inline-size: 100%` computes to inside a zero-intrinsic-size ancestor (the common `width:0; height:0; transform:scale(s)` pattern for a CSS-scaled canvas/scene) — makes the element **not render at all**, not "render at 0 visual size": silent, no console warning. `img`/`picture`/`video`/`canvas` don't share this failure mode. Components that embed SVG (`icon-ui`, etc.) already size it explicitly in their own scoped CSS and are unaffected either way (verified: no component CSS relies on the global rule's sizing behavior). Verified via the full web-modules test suite + a manual repro check against the reporting issue's own reproduction case (no dedicated new test — a global CSS reset rule).

### Maintenance
- **`dist/` CDN bundles rebuilt** (`host.min.css`, `host.sheet.js`, `theme-provider.min.js`, `web-components.min.{css,js}`, `web-components.sheet.js`) to inline the `resets.css` fix above.

## [0.7.27] — 2026-07-12

Fixes harvested from the /site → A2UI docs migration's live operator reports (TKT-0002…0006) — each was a real framework defect the migration surfaced, not a docs-only issue — plus the router race that three separate flake classes traced back to.

### Added
- **`richtext-ui` (`components/richtext/`) — `flush` boolean prop: paragraph-grade embedding mode.** Removes the article chrome (the `[data-richtext-body]` `space-8`/`space-6` padding, the 45rem reading measure, the component's own canvas background, and the trailing paragraph margin) so rendered markdown sits flush in a host column like a plain text block. Use it when richtext carries a single paragraph or fragment inside an already-measured layout (doc pages, cards); leave it off for standalone articles and chat transcripts, which keep the article treatment unchanged. Motivation: the docs transpiler rides richtext for every paragraph with inline code marks — 1,672 instances across 275 pages each carried article insets (TKT-0004 B).

### Fixed
- **`router-ui` (`core/provider.js`) — `#loadContent` claims a monotonic navigation token BEFORE any await.** A navigation that started earlier but fetched its content slowly could resume after a faster follow-up navigation had fully rendered, and — because nothing sequenced completion against navigation order — legitimately clobber the newer route's content while `data-route-path` still named the new route. Reproduced deterministically (2s-delayed boot fetch: old code showed `path=button` with the Dashboard DOM). The token is re-checked after every await (content fetch, templateResolver, setup import); a superseded call neither writes `innerHTML` nor emits `route-loaded`. This was the root mechanism behind the site-a2ui migration's "no-artifact despite a valid artifact" residue and its batch-sweep flakes.
- **`nav-ui` (`components/nav/`) — collapsed-mode flyouts no longer re-open after selecting an item (TKT-0005).** The flyout popover is appended inside its `nav-group-ui`, so an option click bubbled to `nav-ui`'s delegated click handler, whose collapsed branch called `group.showPopover()` for any click landing inside the group — instantly re-opening the flyout the option's own handler had just closed. The collapsed branch now ignores clicks originating inside `[popover]`, mirroring the header guard the expanded branch always had.
- **`core/markdown.js` — inline code spans survive emphasis/link regexes.** `inline()` now extracts backtick code spans into placeholders before applying the bold/italic/link transforms and restores them verbatim after — code spans containing `*`, `_`, `[`, or `]` no longer render mangled. New unit suite (`core/markdown.test.js`, 6 cases).

### Maintenance
- `dist/` CDN bundles regenerated (`web-components.min.css`, `web-components.min.js`, `theme-provider.min.js`) carrying the richtext `flush` rules + the router/nav/markdown fixes.

## [0.7.26] — 2026-07-04

### Fixed
- **`empty-state-ui` (`components/empty-state/`) — the AI-composition rule no longer recommends `variant="outline"` for secondary/retry actions.** The `a2ui: rules:` guidance in `components/empty-state/empty-state.yaml` said "Prefer `[variant="primary"]` for create-flows; `[variant="outline"]` for retry / secondary actions" — reinforcing a repo-wide habit of reaching for `outline` just to mark a button as non-primary, when the unstyled default already IS the secondary look (`:where(:scope)` base tokens read `--a-ui-bg`, distinct from `variant="primary"`'s `--a-primary` accent fill). Corrected to: `variant="primary"` for the create-flow CTA, leave `variant` unset for retry/secondary. Regenerated `catalog-a2ui_0_9_rules.txt` from the yaml SoT (never hand-edited); mirrored the same correction into `components/empty-state/empty-state.examples.html`'s doc list. Root-cause investigation (corpus evidence: 446 `button-ui` tags harvested, only 30 bare vs. 91 explicit `outline` + 215 `ghost`) + the companion fixes in `@adia-ai/a2ui-compose` are in that package's changelog.

## [0.7.25] — 2026-07-03

Upstream consumer finding from `adia-pay` (F10, sibling of F1–F9 shipped in 0.7.24) — a token-modeled `variant="flat|contained"` posture for the accordion, plus its two footnote bugs.

### Added
- **`accordion-ui` / `accordion-item-ui` (`components/accordion/`) — `variant="flat|contained"` posture.** New reflected `variant` attribute on both the host and each item (default `flat`, pixel-unchanged from today — verified via the new `accordion-contained-probe.mjs`). `variant="contained"` on the host gives every child item a bounded surface — background, border, and radius spanning its header + open body, with a divider between them once expanded — the composition for embedding an accordion inside `<card-ui>`; a single item can opt out with its own `variant="flat"` (or opt IN against a flat host with `variant="contained"`). Four new item tokens carry the posture and are consumer-overridable in **both** postures: `--accordion-item-bg`, `--accordion-item-border` (full shorthand, mirrors `--card-border`), `--accordion-item-divider`, `--accordion-item-body-pt` — all default to the flat, invisible value. Contained is a token-only re-point plus three documented layout exceptions (`overflow:hidden` clip, `border-radius:0` on the header pill, and focus-ring relocation from the header onto the item surface) — added to `component-token-contract.md`'s sanctioned mode-attribute table alongside `progress-ui`/`pagination-ui`. Nested accordions stay isolated: a flat accordion inside a contained item's body is unaffected by the host's posture (direct-child `>` combinator, not a descendant selector). New examples: standalone contained, contained inside `card-ui`, and a per-item flat override. (upstream finding F10, ticket AC-1…AC-3/AC-5)

### Fixed
- **`accordion-item-ui` — the disclosure caret now survives a custom `[slot="header"]`.** Authoring your own header (icon + title + action buttons) previously suppressed the caret entirely — the component only ever stamped one alongside a header it ALSO stamped. `connected()` now stamps a `caret-down` `icon-ui` into any authored header that doesn't already contain a `[slot="caret"]`, scoped to the header element specifically (a caret placed in `[slot="body"]` no longer falsely suppresses it). Author your own `[slot="caret"]` (even empty) to opt out — no new attribute; documented on the `caret` slot in `accordion-item.yaml`. (upstream finding F10 footnote FN-1)
- **`accordion-item-ui` — `[text]` no longer clobbers an authored `[slot="header-text"]`.** The auto-stamped label is now marked with a `data-autolabel` DOM attribute (survives disconnect→reconnect), and `render()` only drives that marked label — a hand-authored `[slot="header-text"]` inside a custom header is never overwritten. Setting `text` alongside an authored header-text label is now a silent no-op by design (was a silent clobber before). (upstream finding F10 footnote FN-2)
- **`accordion-item-ui` — the default (auto-stamped) header is now keyboard-operable.** It was mouse-only: no `tabindex`, no keydown handling. The auto-stamped header now carries `tabindex="0"`, `role="button"`, and `aria-expanded` (kept in sync on every toggle); `Enter` and `Space` both toggle it, routed through the existing click handler (`header.click()`) so there is exactly one toggle code path. A consumer-authored header (e.g. `<button slot="header">`) already worked and is unaffected — this closes a pre-existing gap for the default header only, folded into this arc on operator review (an additive a11y improvement, not a regression).

### Maintenance
- **`dist/` bundles regenerated** (`web-components.min.{css,js}`, `web-components.sheet.js`, `theme-provider.min.js`) to carry the accordion changes above.
- **Doc-comment path references corrected** (`docs/specs/...` → `.claude/docs/specs/...`) across `patterns/` (2 `.examples.html` + 1 README) and `traits/` (`CATEGORIES.md` + 1 `.examples.html`) — propagated from the repo-wide `docs/` → `.claude/docs/` migration (2026-07-01) on the first full `components.mjs` regen since; no functional change, `styles/type-scale-review.examples.html` likewise.

## [0.7.24] — 2026-07-01

Upstream consumer findings from `adia-pay` against installed `0.7.23` (F1–F6).

### Added
- **`display-field-ui` (`components/display-field/`) — new display-only labeled-value primitive.** Fills a capability gap: a field-shaped read-out for a static or masked value (a card on file, `•••• 4242`, an account id) or a labeled slot that hosts a third-party element (a Stripe Element iframe) under a proper caption. Unlike `field-ui` it wraps **no** interactive control — no focus, no contenteditable, no form participation, no `[for]`/id-minting — so a readonly `input-ui` (which keeps input semantics) is no longer the only option. Props: `label` (required; names the group via `role="group"` + `aria-labelledby`), `value` (static text), `variant` (`default` | `masked` — mono/tabular treatment so `•••• 4242` aligns), `hint` (caption + `aria-describedby`), `icon` (leading glyph). The value comes from the `value` attr **or** an unnamed default-slot child (the foreign-element path) — slot content wins when both are present. Ships yaml + css + js + d.ts + a2ui sidecar + demo (`display-field.examples.html`) + 6 unit tests; registered in the component barrel + `styles/components.css`; `dist/` + `custom-elements.json` (120 → 121 modules) regenerated. Additive (PATCH). (upstream finding F5)
- **`table-ui` (`components/table/`) — `rowExpandable` per-row expand gate.** `expandable` was host-level: every row got the same `caret-right` toggle and one host-level `expandRenderer` drove all detail panes, so heterogeneous rows (a *succeeded* payment needing no detail vs. a *failed* one that expands to a reason) could not vary the affordance. New optional `rowExpandable(row, index) => boolean` callback gates the caret per row — non-expandable rows keep the reserved expand-column cell (grid alignment never shifts) but show no caret and can't open a detail pane; `toggleExpand()` is guarded to match (it still collapses an already-open row if the predicate later flips, so a row can't get stuck open). `null` (default) preserves the legacy "every row expandable" behavior. Demo: the `rowExpandable` section in `table.examples.html` (only Failed payments expand). Regression: 3 tests in `table.test.js`. Additive — no API removed (PATCH). (upstream finding F6)

### Fixed
- **`icon-ui` (`components/icon/`) — fails loud when the icon registry was never wired.** Under Vite 8 / rolldown the zero-config `import.meta.glob('/node_modules/@phosphor-icons/…')` path in `icons-phosphor.js` does not transform from inside a published dependency, leaving the registry empty so every `<icon-ui>` renders blank — previously **silently** (a single missing icon warned, but a wholly-unwired registry did not). New `iconRegistryUnwired()` predicate (`core/icons.js`) distinguishes "never wired" (`registry.size === 0 && !registryReady`) from "one bad name"; `icon.class.js` schedules a one-shot, macrotask-deferred `console.error` naming the exact fix (install `@phosphor-icons/core` + call `installIconLoaders` from the app entry) only when a named icon resolves to nothing AND the registry is unwired. The deferral means a correctly-wired app entry is never false-flagged. Documented as the primary bundler path in `USAGE.md` § Icons. (upstream finding F1)
- **`custom-elements.json` — manifest no longer fabricates nonexistent modules.** `scripts/build/generate-custom-elements-json.mjs` emitted a `javascript-module` + class + `custom-element-definition` for every component yaml by assuming a `components/<dir>/<base>.js` file existed — fabricating e.g. `UIHeader` → `header.js` (the CSS-only `header-ui` has no class file) and pointing co-located children (menu-item-ui, menu-divider-ui, …) at nonexistent per-child `.js` files. The generator now scans the actual `.js` sources for `customElements.define` / `defineIfFree` / `defineIfUndefined` calls, maps each tag to the file that really defines it, groups declarations by real module path, and lists genuinely CSS-only tags (aside/footer/header/section-ui) as skipped. Regenerated manifest: 120 modules / 134 tags, 0 dangling file paths (was 110 modules carrying fabricated paths). (upstream finding F4)

### Changed
- **`@phosphor-icons/core` is now a direct dependency (`^2.1.1`).** It was referenced by the icon-loader glob but absent from every dependency field, so the documented zero-config icon path could not resolve its SVG assets from a clean install. Added to `dependencies` so the assets resolve; the app still wires the loader glob from its own source graph (see `USAGE.md` § Icons / finding F1). (upstream finding F2)

## [0.7.23] — 2026-06-30

### Fixed
- **`button-ui` (`components/button/`) — child text now labels the button, same as `text=`.** A label written as child text (`<button-ui>Save</button-ui>`, no `text=` attr) was treated as icon-only and collapsed to a square icon slot with the text clipped — the collapse rule keyed off `:scope:not([text]), :scope[text=""]`, which can't see text-node children. `render()` now scans child nodes for non-whitespace text and reflects a JS-derived `[data-icon-only]` state **only** when there is neither a `text=` value nor child text; `button.css` keys its collapse + inline-level rules off `[data-icon-only]` instead of the `[text]` selectors. A child-text button now renders at full content width, identical to `text="Save"`, while a genuine `<button-ui icon="…">` still collapses; the icon-only accessible-name warning is re-gated to respect child text. Adds an `icon-only` state to `button.yaml`; `button.a2ui.json` + `dist/web-components.min.{js,css}` + `.sheet.js` (and the `theme-provider` / `everything` bundles) regenerated. (journal 2026-06-30 §1)

### Changed
- **`accordion-ui` (`components/accordion/`) — normal block-flow gap, borderless items.** `--accordion-gap` changed from `0` to `var(--a-gap)`, so stacked items separate with the same canonical block-flow stack gap `col-ui` / `row-ui` / `grid-ui` use instead of butting together. The per-item `border-bottom` separator — with its `--accordion-item-border` token and the `:last-child` border reset — was removed for a clean borderless stack. Visible re-spacing, no public-API change (PATCH). (journal 2026-06-30 §2)

## [0.7.22] — 2026-06-16

### Changed
- **Surface tokens aligned onto the symmetric canvas ramp.** The `--a-canvas-*` surface assignments were consolidated onto one symmetric luminance ramp so elevation reads identically in light + dark (the prior mix of the flipping + symmetric ramps inverted in light mode). Chrome overlays — `components/modal`, `components/popover`, `components/drawer` — sit at `--a-canvas-dimmer`; `components/card` (`card-ui`) roots at `--a-canvas-base` with nesting `base → bright → brighter → brightest` (capped). `styles/colors/surfaces.css` gained the named canvas luminance scales (`--a-canvas-{dimmest…brightest}`); `styles/colors/parameters.css` updated; `drawer-ui` pulled on-ramp (was the only off-ramp overlay). `dist/` bundles (`host`, `theme-provider`, `web-components`) regenerated. Re-skins every modal/card/drawer/popover — a visible behavior change with no public-API change (PATCH). (journal 2026-06-15 §6)

## [0.7.21] — 2026-06-10

### Maintenance In-repo only: 57 `components/`/`traits/` `*.examples.html` files were de-annotated as part of the corpus curation (demo markers kept for the `?chunks` overlay) — `.examples.html` files are excluded from the published tarball (`files:` negation), so the npm artifact is unchanged.
- **Lockstep version bump only.** No source changes in this package; bumped to maintain the 9-package version coherence enforced by `scripts/release/check-lockstep.mjs`. Substantive v0.7.21 work shipped in corpus harvester scope expansion (gated web-components examples root) + 26 curated chunks (368->394). See `packages/a2ui/corpus/CHANGELOG.md#0721--2026-06-10` for details.

## [0.7.20] — 2026-06-10

### Fixed
- **`field-ui` (`components/field/`) — control binding survives junk siblings.** `#findControl()` bound the *first slot-less child* as the control; generated markup that lands a `text-ui` "label" or a `<small>` error inside `field-ui` (the 2026-06-10 gen-UI contact-form incident) stole the `id`/`for` wiring from the real input. Binding now prefers a known form-control tag (timing-independent), then a focusable-containing child, then the old first-slot-less fallback. Regression test with the exact incident markup. `dist/web-components.min.js` regenerated.

## [0.7.19] — 2026-06-10

### Fixed
- **`icon-ui` — glyphs always paint on the standalone import path.** Importing `components/icon` directly (or a demo page loading only `./icon.js`) never loaded `core/icons-phosphor.js`, so the weight-loader map stayed empty and every `getIcon()` returned `''` — **silently** (the not-ready guard suppressed the warning): blank icons, zero console errors. Two fixes: `icon.js` now imports the Phosphor loader as a side-effect (the barrel paths already did), and `icon.class.js` gains a signal-reactive retry (`#rev` bumped by `whenIconRegistryReady`, aborted symmetrically in `disconnected()`) so elements that connect *after* the loaders install — e.g. fetched-and-injected demo markup — re-render once the registry is ready. Production (barrel-served) was never affected; the .claude/docs/demo single-component path was. Regression probe: `scripts/dev/icon-paint-probe.mjs`. `dist/web-components.min.js` (and the web-modules kitchen-sink bundle) regenerated to carry the fix.

## [0.7.18] — 2026-06-09

### Changed
- **Clear caret OPEN state across the dropdown family.** Every dropdown-class primitive's caret now flips 180° while its popover/listbox is open (and returns on close), with a fast reduced-motion-respecting transition: `select-ui` (its trigger glyph also changed `caret-up-down` → `caret-down` to join the family convention), `combobox-ui`, `datetime-picker-ui`, `date-range-picker-ui`, and `menu-ui` (a consumer trigger carrying a `caret-down` icon — e.g. `<button-ui icon-trailing="caret-down">` — flips automatically). All rules are **name-guarded** (`icon-ui[name="caret-down"]`) so custom non-caret trigger/suffix glyphs never rotate. Disclosure-row components already had clear states (nav-group/tree/pane/agent-trace rotate 90°, agent-reasoning/agent-artifact swap glyphs, accordion rotates) and are unchanged; `table-toolbar`'s `caret-up-down` is a sort indicator and `calendar-picker`'s carets are month-nav arrows — deliberately excluded. Regression harness: `scripts/dev/caret-open-state-probe.mjs` (15 browser assertions: renders / flips 180° / returns, per component).

### Fixed
- **`datetime-picker-ui` / `date-range-picker-ui` — the default trigger's caret now actually renders.** Both default triggers declared `trailing-icon="caret-down"`, but `button-ui`'s attribute is `icon-trailing` — the made-up attribute was silently ignored (the silent-failure-on-unknown-attrs class), so the caret never appeared. Fixed to `icon-trailing="caret-down"`; combined with the change above, the picker triggers now show a caret that flips while open.
- **`combobox-ui` — the menu can no longer cover its own input.** Two root causes fixed in `core/anchor.js`: (1) the native anchor path set an inline `max-height: calc(100vh - 3rem)` that CLOBBERED the component's stylesheet cap, so long option lists grew viewport-tall; (2) neither path reliably flipped — Chromium's `position-try-fallbacks: flip-block` demonstrably does NOT fire for a tall scrollable popover (verified via the new probe), and the JS fallback's viewport clamp slid an oversized menu UP OVER the trigger. Both paths now pick the vertical side **explicitly at open** (prefer the requested side; flip when it can't fit and the other side can; when neither fits, take the roomier side) and **clamp `max-height` to that side's available space** — the popover can never cross the anchor. Components can declare their own cap via `--popover-max-height` (the vertical twin of the existing `--popover-max-width` convention). Affects all `anchorPopover` consumers (combobox, select, menu, context-menu, tooltip, pickers) uniformly for the better. Regression harness: `scripts/dev/combobox-overflow-probe.mjs` (11 browser assertions incl. a short-viewport neither-side-fits case).
- **`combobox-ui` — opening a flipped menu no longer yanks the page.** Active-option reveal now scrolls WITHIN the listbox (scroll arithmetic) instead of `scrollIntoView({block:'nearest'})`, which also scrolled document ancestors.
- **`combobox-ui` — match-highlight spacing.** `[role="option"]` is `display:flex`, which dropped the whitespace between the `<mark>` and the rest of the label ("Ada Lovelace" rendered "AdaLovelace"); option labels now render inside a single `[data-option-label]` span. The `mark` rule also consumes the (previously dead) `--combobox-match-bg/fg` tokens with raw-token fallbacks.

### Added
- **`combobox-ui` `[max-visible-items]`** (default **10**) — caps how many option rows are visible before the menu scrolls; the listbox height hugs N rows (measured live, so it tracks `[size]`/density) instead of growing to the 18rem fallback. Distinct from `[max-options]` (the render cap). `0` disables. Demo: the new "long list — visible-item cap + flip" section (60 declarative options) in `combobox.examples.html`.
- **`<theme-provider>` one-`<script>` CDN bundle** — `dist/theme-provider.min.js` (536 KB, self-contained: the element + its foundation-adoption machinery). A single `<script src=".../theme-provider.min.js">` registers `<theme-provider>`, which adopts the foundation from anywhere in the DOM (no `<head>` stylesheet link) — the CDN-first path for the 0.7.12 element. Added as a `bundle-js.mjs` entry, so `check:js-bundles-fresh` (blocking) gates its freshness alongside the other 6 bundles; served via the existing `./dist/*` export + the CDN. (First of the `<theme-provider>` follow-ups; build-time `<head>` hoist + an `adia-ui-kit` compose recipe + the embedded-app dogfood remain on the roadmap.)

## [0.7.17] — 2026-06-08

### Fixed
- **`badge-ui` / `tag-ui` — icon-bearing chips no longer inflate row height.** Capped the inner `icon-ui` block-size to the text line-height (`components/badge/badge.css` `0.75em`, `components/tag/tag.css` `1em`) so a leading icon can't push the chip taller than a text-only one. Plus an `[icon]`-gated spacing tighten — reduced leading padding (`--a-space-1`) + a `~0.2em` icon↔label gap so the icon + label read as one tight unit; text-only chips unchanged. `dist/web-components.min.{css,js}` + `.sheet.js` regenerated to match.

## [0.7.16] — 2026-06-08

### Changed
- **Dist bundles rebuilt — the CDN / bundle path now matches source.** `dist/web-components.min.{css,js}` + the `.sheet.js` twin were regenerated to carry the 0.7.15 `badge-ui` / `tag-ui` padding (`--a-space-2` → `--a-space-1-5`) plus JS that the 0.7.15 cut shipped stale (it never rebundled). Consumers via `@adia-ai/web-components/css/bundled` + `/js/bundled` were getting the pre-0.7.15 output. `check:css-bundles-fresh` + `check:js-bundles-fresh` now gate this in CI.

## [0.7.15] — 2026-06-08

### Added
- **New `<menu-label-ui>` — labeled group headings inside `<menu-ui>`.** A co-located child of `<menu-ui>` (registered via `defineIfFree('menu-label-ui', UIMenuLabel)` in `menu.js`; `UIMenuLabel` declared in the parent `menu.d.ts` by dts-codegen, like `menu-item`/`menu-divider`) that renders a non-interactive section heading to group `<menu-item-ui>` rows under labeled headings. Files: `components/menu/{menu-label.yaml,menu-label.a2ui.json}`. (Powers the embedded-app HCC demo's grouped / by-flow menu.)

### Changed
- **`badge-ui` + `tag-ui` horizontal padding tightened** — `--badge-px` / `--tag-px` `var(--a-space-2)` → `var(--a-space-1-5)` for a snugger chip that reads better in dense rows; vertical padding unchanged.

## [0.7.14] — 2026-06-08

### Fixed
- **`input-ui`/`textarea-ui` `.value` property binding set before upgrade is no longer dropped.** A `.value=${x}` (or any reactive property) assigned during template stamp *before* the element upgrades landed as a pre-upgrade own data-property that `installProps`' `Object.defineProperty` clobbered with the signal default — so the field rendered blank while `value="${x}"` (attribute) worked, and `<select-ui>` was unaffected (inconsistent). `installProps` now captures + reifies the pre-upgrade value (WHATWG upgrade-a-property pattern), fixing it for `value` and every UIElement property. (color-app FEEDBACK-99; covered by `core/element.test.js`.)

## [0.7.13] — 2026-06-06

### Fixed
- **`./css/sheet` subpath export now resolves TS types.** The 0.7.12 `.sheet.js` twin (constructable-`CSSStyleSheet` mirror of `web-components.min.css`) shipped its `./css/sheet` export with `import`/`default` but no `types` — TS consumers got no types for `constructSheet()`. Added `web-components.sheet.d.ts` (`export function constructSheet(): CSSStyleSheet | null`), the `types` condition (types-first), and the `.d.ts` to `files[]` so the tarball packs it. Caught by `verify:exports-conditionals`.
- **`button-ui[color=warning]` hover contrast** — the warning fill's hover surface now uses `--a-warning-bg` (the -20 tint) instead of `--a-warning-strong`, whose mid-tone read muddy under the dark `--a-warning-fg`. Demo (`theme-provider.html`) card header wrapped in `<header>` for canonical structure. (dogfood-audit: warning-strong-vs-bg; check:card-structure.)

### Docs
- README CDN snippets refreshed to the current line: `@0.6` → `@0.7`, primitive count 95 → 122, pin example → `@adia-ai/web-components@0.7.13`.

## [0.7.12] — 2026-06-04

### Added

- **New `<theme-provider>` element — adopt the AdiaUI foundation from anywhere, no `<head>` link.** A Light-DOM, `display: contents` infra wrapper (the 122nd primitive) that adopts the foundation — design tokens + resets + page-frame + every primitive's CSS, the constructable-stylesheet twin of `web-components.min.css` (byte-identical to the CDN bundle) — into `document.adoptedStyleSheets` once, deduped, at module-eval. So SPA roots / embedded apps / dynamic mounts render fully-styled **without** a hand-wired `<link rel="stylesheet">` in `<head>`. Opt-in `theme="ocean|forest|…"` (named preset) + `scale="verse|prose"` (typographic register) adopt their layer twin **on demand** (`theme=` matches the `[theme]` hook directly; `scale=` maps onto the existing `[verse]`/`[prose]` attribute). Coexists with the render-blocking `<link>` path (same bytes) — link for multi-page / top-level surfaces (cacheable, no flash), provider for no-`<head>` contexts. Deliberately **not** in the all-primitives barrel (it carries the whole foundation) — import `@adia-ai/web-components/components/theme-provider` explicitly. Files: `components/theme-provider/{theme-provider.js,.class.js,.css,.yaml,.html,.test.js}`.
- **CDN `.sheet.js` twins.** `bundle-css.mjs` co-emits a constructable-`CSSStyleSheet` twin beside each foundation `.min.css` — `web-components.sheet.js`, `host.sheet.js`, and the `themes` / `verse` / `prose` register twins — from the **same minified buffer** (byte-identical to the `.min.css`, verified by `check:css-bundles-fresh`, exposed as the `./css/sheet` export). A page that links `web-components.min.css` and one that adopts `web-components.sheet.js` render identically. Powers `<theme-provider>` and any runtime style injector that can't add a `<head>` link.

### Changed

- **BREAKING (pre-1.0 PATCH cadence) — the token-theme attribute `data-theme` is renamed `theme`.** `<html data-theme="ocean">` → `<html theme="ocean">`; `[data-theme="…"]` selectors → `[theme="…"]`; the token-root selector `:root, theme-ui, [data-theme]` → `:root, theme-ui, [theme], theme-provider` across the 14 foundation files (so `<theme-provider>` is itself a token-root). Cleaner, and it converges with `<canvas-ui theme="…">`, which already scoped AdiaUI themes the same way. Done as a repo-wide word-boundary sweep — compound attributes (`data-theme-slug`, `data-themes-grid`, `data-themed`, …) are preserved (the sweep also touched the styles/, traits/, and patterns/ trees). **Consumers using `data-theme="…"` must switch to `theme="…"`** — see `.claude/docs/MIGRATION GUIDE.md`.
- **CDN bundles rebuilt** — `dist/web-components.min.{css,js}` + `dist/host.min.css` regenerated so the `[theme]` rename, `<theme-provider>` registration, and the new register twins reach `@adia-ai/web-components@0.7` CDN consumers.

### Fixed

- **`dts-codegen` reads the authoritative tag.** It reconstructed each demo's tag as `${name}-ui`, which mangles a no-`-ui` infra tag (`<theme-provider>` is the first) — it now reads `x-adiaui.tag` from the sidecar. Behavior-neutral for all 121 `-ui` components (their `name-ui` === `tag`); correct for infra tags. File: `scripts/build/dts-codegen.mjs`.

## [0.7.11] — 2026-06-04

### Added

- **`<frame-ui>` slot-override regions.** Beyond the native `<header>` / `<section>` / `<footer>` tags, any element can take a region role via `slot="header|body|footer"` (a CSS `[slot]` match — the same role-slot pattern as `<drawer-ui>`), so e.g. `<section slot="header">` is a pinned rail rather than the scroll body. The native tag stays the 90% sugar; reach for the slot when the element you want differs from the region role. Rail-slotted elements are `:not()`-excluded from the scroll-body selector, so a `[slot="header"]` section never doubles as the body. Files: `components/frame/{frame.css,frame.yaml,frame.html}`.

### Changed

- **`button-ui` hover now intensifies to the family `-strong` step.** Filled buttons strengthen their fill on hover — neutral → `--a-bg-strong`, `variant="primary"` → `--a-accent-strong`, and `color="danger|success|info|warning"` → the matching `--a-{family}-strong`. Outline + ghost keep a transparent fill and instead move the **fg** from its rest color to `-strong` (per-color outline/ghost shift to `--a-{family}-strong` text). This corrects a latent bug where `variant="primary"` and `color="danger"` hovered to the neutral `--a-ui-bg-hover` scrim (dropping their color), while `success` / `info` / `warning` and the neutral/default button had no hover-bg feedback at all. Note `-strong` resolves to the same step as `-active`/pressed, so the `:active` scale transform is what distinguishes press from hover. Override surfaces preserved: ghost hover still reads `--button-fg-ghost-hover` (alert.css retints it) and `--button-bg/border-ghost-hover` (the `component-tokens` demo opts a fill back in); the orphaned `--button-bg-hover-danger` token was removed. Warning's mid-tone `-strong` under dark `--a-warning-fg` may read muddy — flagged to revisit the warning ramp upstream. Files: `components/button/{button.css,button.examples.html}`.
- **`resizable` trait — corner handles + symmetric-from-center resize.** The trait gained 4 corner hit-zones (drag a corner to resize BOTH axes at once) on top of the 4 edges; each handle carries its axis signs (`dw`/`dh`) + cursor, and corners are layered last so they win the hit-test where they overlap an edge. Adds symmetric resize-from-center. Powers `<embed-shell traits="resizable">` and any resizable surface. Files: `traits/resizable/{resizable.js,resizable.examples.html}`.
- **CDN bundles rebuilt** — `dist/web-components.min.css` + `dist/web-components.min.js` regenerated so the `<frame-ui>` slot-override, the `button-ui` hover `-strong` step, the `resizable` corner handles, and the `list-item-ui` / `<preview-ui>` fixes reach `@adia-ai/web-components@0.7` CDN consumers.

### Fixed

- **`list-item-ui` title vertical-centers with icon + action.** `[slot="text"]` lacked `align-self: center`, so when the action (e.g. a full-height `<button-ui>`) made row 1 taller than the title, the title top-aligned *above* the centered icon + action. All three row-1 cells now center together. Surfaced by `onboarding-checklist-ui` "Open" rows. File: `components/list/list.css`.
- **`<preview-ui>` code pane shows JSON attributes cleanly.** A JSON-bearing attribute (e.g. `items='[{"id":"a"}]'`) serializes via `outerHTML` as `items="[{&quot;id&quot;…}]"` — the `&quot;` rendered literally in the code/copy pane, and `<code-ui>`’s Copy emitted entity-encoded source. `dedent()` now re-renders quote-bearing attribute values with single-quote delimiters (`items='[{"id":"a"}]'`) — the clean, valid, copy-paste-able form the author wrote (apostrophe-bearing values keep the escaped form, no safe single-char delimiter). File: `components/preview/preview.class.js`.
- **`[verse]` register radius comment corrected.** `styles/verse.css`'s `--a-radius-max: 0.75rem` carried a stale `/* 16px */` comment — `0.75rem` is 12px. Comment-only; no behavior change (the `0.75rem` max itself shipped in the 0.7.10 `[verse]` type bump). File: `styles/verse.css`.

## [0.7.10] — 2026-06-03

### Added

- **New `<frame-ui>` layout primitive — sticky-header / scroll-body / sticky-footer frame.** The chrome-less app-shell skeleton: a flex column that fills its parent; native `<header>` / `<footer>` children pin (`flex: 0 0 auto`), `<section>` scrolls (`flex: 1; min-block-size: 0; overflow: auto`) — the only overflow region. Regions are positioned by tag + DOM order (Light DOM, no slot projection — ADR-0033). CSS-only `category: layout` component (no props). Common shape `<frame-ui><section>…</section><footer>…</footer></frame-ui>`; add `<header>` only for a panel title/toolbar. Distinct from `<card-ui>` (border + elevation + rich header grid), `<drawer-ui>` / `<modal-ui>` (backdrop + dismiss), and the page shells — those layer chrome over the same region contract (a future cut extracts the shared frame `@layer` they compose). Requires a definite-height parent. Files: `components/frame/{frame.yaml,frame.css,frame.class.js,frame.js,frame.html}`.

### Changed

- **`[verse]` register: bare-verse prose type bumped one tier (`sm` → `md`).** A bare `[verse]` context now resolves its prose type to the `md` tier instead of `sm` — `--a-display-size` / `--a-title-size` / `--a-heading-size` / `--a-section-size` re-point `sm`→`md` (display 18→23, title 16→19, heading 13→14, section 13→14px). `--a-radius-max` tightened `1rem`→`0.75rem` (12px). **Behavior change** — prose-ish text in a compact `[verse]` surface renders one tier larger; controls + the `[verse][size]` sub-tiers are unaffected. File: `styles/verse.css`.
- **CDN bundles rebuilt** — `dist/web-components.min.css` + `dist/web-components.min.js` regenerated so `<frame-ui>` (CSS + registration), the `list-item-ui` grid fix, and the `[verse]` type bump reach `@adia-ai/web-components@0.7` CDN consumers.

### Fixed

- **`list-item-ui` description now spans the full content width.** `[slot="description"]` was pinned to `grid-column: 2`, so when a `[slot="action"]` was present (the 3-column `icon | content | action` grid) a multi-line description was boxed into the content column — it wrapped early and left dead space under the action. The grid is now a **header-row model**: `[slot="icon"]` · `[slot="text"]` · `[slot="action"]` align on row 1, and `[slot="description"]` spans `grid-column: 2 / -1` on row 2 (flowing under the action). The icon + action `grid-row` changed `1 / -1` → `1`, which is identical for single-row items (icon + text only) — so nav menus / simple lists are unchanged; only rows carrying a description are affected (e.g. `onboarding-checklist-ui` rows gain full-width descriptions + title-aligned action buttons). File: `components/list/list.css`.

## [0.7.9] — 2026-06-03

### Changed

- **`[verse]` control heights bumped one tier — `--a-size` 18/20/24 → 20/24/28px** (sm/md/lg @ density=1). Form-element / control heights in the compact `[verse]` register grow: `--a-size-sm` `1.125→1.250rem`, `--a-size-md` `1.25→1.500rem`, `--a-size-lg` `1.5→1.750rem` (still density-scaled rem). **Behavior change** — inputs / buttons / selects in a `[verse]` context render taller; the `[verse][size]` tier rules inherit the new values (they re-point `--a-size` to the sm/lg token). Verified by `scripts/qa/register-scale-probe.mjs` (verse 20/24/28; regular 24/30/36 + prose 28/36/44 unchanged). **Chrome follows:** `--a-toggle-size` 12/16/20 → 16/20/24, `--a-icon-size` 12/14/16 → 14/16/18, `--a-caret-size` 10/12/14 → 12/14/16 (each +1 tier) so check / radio / switch boxes + select carets + icons stay proportional to the larger controls. File: `styles/verse.css`.
- **Semantic color tier lift + status-compat removal (the `warning-yellow-cusp-lift`).** Per-family `-strong` tiers moved one step richer (`-50`/`-70` → `-60`) and the `-bg` aliases now read the base family token (`var(--a-accent)` etc.) instead of `-strong`, across accent / brand / info / success / warning (`colors/semantics/core.css` + `colors/parameters.css` + `colors/primitives-warning.css` — the warning yellow's cusp re-tuned for legibility). The legacy `colors/semantics/aliases.css` **STATUS COMPAT block** that overrode `--a-danger` / `--a-success` to `-strong` was **removed** — status families now resolve to their base tier deliberately (base tokens are defined upstream in `core.css`, so non-breaking); the `--a-scrim-*` role aliases in that file are **retained** (modal / drawer / tour / loading-overlay backdrops depend on them). **Behavior change** — danger / success / accent / brand / info surfaces shift tone. Gate-clean: no raw colors, `verify:contrast` + `check:foundation-layers` pass.
- **CDN bundles rebuilt** — `dist/host.min.css` + `dist/web-components.min.css` regenerated so the color cusp-lift + `[verse]` tier bump reach `@adia-ai/web-components@0.7` CDN consumers.

## [0.7.8] — 2026-06-02

### Fixed

- **Wrapper-trap drain — 8 components no longer mis-handle `.map()`/interpolated slotted children.** A new shared `core/logical-children.js` (`logicalChildren` / `logicalSlotted`) pierces the template engine's `display:contents` / `role="presentation"` wrapper spans, so consumer slot content supplied via interpolation (`${items.map(…)}` / conditionals) is seen by a component's slot reads — not just static literals. Migrated 14 `:scope > [slot]` direct-child reads that previously missed wrapped content: **`combobox-ui`** (prefix / suffix / empty / loading / footer) + **`input-ui`** (leading / trailing) grabbed affordances then ran `innerHTML = ''`, wiping the wrapped ones (the `agent-artifact-ui` data-loss class); **`nav-group-ui`** (header), **`option-card-ui`** (heading / description / icon), **`timeline-item-ui`** (label), **`step-progress-ui`** (caption / track), and **`date-range-picker-ui`** (presets / footer) used `if (!query) stamp()` guards that false-negatived on a wrapped slot and stamped a DUPLICATE over the consumer's content. Companion field-reads were pierced in lockstep so they don't null-ref. `tree-item-ui`'s `[slot="row"]` is left as-is (a single static / stamped container, not an interpolated collection). New `core/logical-children.test.js` + per-component regression tests; consolidates the prior hand-rolled walks (`select-ui` `#logicalOptionChildren`, `tree-ui` `#logicalSlotChildren`). Files: `core/logical-children.js` + 8 component class files.

### Changed
- **CDN bundles rebuilt** — `dist/web-components.min.js` regenerated so the wrapper-trap drain (the new `core/logical-children.js` helper + the 14 pierced component reads) reaches `@adia-ai/web-components@0.7` CDN consumers.

## [0.7.7] — 2026-06-02

### Fixed

- **`[verse]` register now scales `--a-toggle-size` (check / radio / switch) down with its type + controls.** Companion to the 0.7.6 `[verse]` icon/caret fix (`91460aae2`). `--a-toggle-size` (the check / radio / switch control size) is declared at `:root` (`styles/foundation/size.css` = `--a-size-sm − --a-space-1` = 20px @md) and shifted by the global `[size]` rules (`styles/api/sizing.css` = 16/24 at sm/lg), but was **absent from `styles/verse.css`** — so in a verse context check / radio / switch chrome stayed the `:root`-computed 20px against verse's smaller (18/20/24) controls and 11/12/13px type. Added `--a-toggle-size` to all three verse sites (base `[verse]` + `[verse][size="lg"]` + `[verse][size="sm"]`) on clean `--a-space` rungs, one tier down: verse toggle is now **12 / 16 / 20px** at sm/md/lg (was a flat 20px); the regular register is unchanged (16/20/24). The explicit `[size]` tier rules are **required** — verse's `context` layer shadows the lower-layer global `[size]` `--a-toggle-size` rules, the same reason `--a-size` and the icon/caret tokens need them. `--a-space` rungs (not literal px like icon/caret) because the toggle targets land exactly on rungs, keeping the values density-aware and consistent with verse's inset/gap. Verified by `scripts/qa/register-scale-probe.mjs` (extended with a toggle axis): verse 12/16/20, regular 16/20/24, all other registers/axes unchanged. Showcased on the `check` / `radio` / `switch` demos (a "Typography registers" section, mirroring the card / button / text / field / badge / stat pages). File: `styles/verse.css`.
- **`<agent-artifact-ui>` no longer strands `.map()`-rendered action buttons in the body.** `#build()` grabbed the author's `slot="primary"` / `slot="secondary"` buttons with a `:scope > [slot]` direct-child query, then `this.innerHTML = ''`. Buttons supplied via interpolation (`${actions.map(…)}`) arrive nested in the template engine's `display:contents` / `role="presentation"` wrapper spans, so the direct-child grab missed them and the wipe left them rendered in the artifact *body* instead of the header actions cluster. `#build()` now partitions children with a wrapper-piercing logical walk (the FB-92/96/98 pattern, mirroring `select-ui` `#logicalOptionChildren`), so interpolated AND static action buttons both reach the actions cluster. +2 regression tests. Surfaced by `audit-wrapper-trap` arm 2. File: `components/agent-artifact/agent-artifact.class.js`.

### Changed

- **`<drawer-ui>` body inset is now register / size-aware (`--drawer-inset` → `var(--a-inset)`).** `--drawer-inset` hardcoded `var(--a-space-4)` (a literal 16px rung); it now reads `var(--a-inset)`, matching its sibling `card-ui` (`--card-inset: var(--a-inset)`). **Behavior-neutral at the default tier** (`--a-inset-md` == `--a-space-4` == 16px); the change is that the drawer body / header / footer inset (header & footer padding derive from `--drawer-inset`) now scales with `[size]` (sm 14 / lg 18), `[verse]` (12), and `[prose]` (40). drawer's `[padding]` / `[bleed]` are layout *modes* (canvas-bg / no-pad), not a value scale — unchanged. Part of the 0.7.7 inset-alignment audit, which left `page` / `canvas` / `table` filter / `richtext` `pre` as intentional fixed rungs (their literals aren't 16px, so a swap would change behavior). Files: `components/drawer/drawer.css`, `components/drawer/drawer.examples.html`.
- **CDN bundle rebuilt** — `dist/web-components.min.css` regenerated so the `[verse]` `--a-toggle-size` fix + the drawer inset reach `@adia-ai/web-components@0.7` CDN consumers.

## [0.7.6] — 2026-06-02

### Added

- **`[padding]` / `[margin]` named scale (`xs`–`xl`) — complete + parametric.** Previously the named padding/margin vocabulary was only `sm`/`md`/`lg` (literal `--a-space-4`/`6`/`8`). Now the full `xs`–`xl` set, each `calc(--a-space-N × --a-{padding,margin}-k)` — mirroring the `[gap]` scale's `value × --a-*-k` form. New `@property --a-padding-k` / `--a-margin-k` knobs (`<number>`, init 1, inherits — a root/provider-level control, subtree-inert like `--a-gap-k`/`--a-density`) retune all named padding/margin at once, per-dimension. `sm`/`md`/`lg` keep their prior values (16/24/32px @k=1) — **behavior-neutral**; `xs` (8px) + `xl` (40px) are new. Integer rungs (`[padding="4"]`) stay literal space-rungs (k-independent). Composes on `--a-density`. Verified: 8/16/24/32/40 @k=1; root `--a-padding-k:2` → md 48 / xl 80; `--a-margin-k` independent of padding. Files: `styles/foundation/space.css`, `styles/api/sizing.css`.

### Changed

- **`styles/host.css` is now foundation-only (tokens + resets + page-frame); `styles/index.css` becomes the primitives barrel.** Previously `host.css` bundled the full library (tokens + all 120 component styles + resets, via `index.css`) plus the page-frame. Now: `host.css` = `tokens.css` + `resets.css` + `:where(html)` page-frame only; `styles/index.css` = `components.css` wrapped in `layer(components)` (the primitives barrel). Pages link both in order. The package-root `index.css` (`@adia-ai/web-components/css`) imports both and preserves the existing full-stack published entry — no change for consumers using the package entry. All 119 component dev shells and `site/index.html` updated. **Migration for `host.css`-only consumers:** add `<link rel="stylesheet" href="@adia-ai/web-components/styles/index.css" />` after the `host.css` link.
- **`<block-ui>` padding/margin unified with the global `[padding]`/`[margin]` scale — now parametric (behavior change).** `block-ui` carried its own literal padding/margin scale (`none`/`xs`/`sm`/`md`/`lg`/`xl` = `0`/`--a-space-1/2/4/6/10` = 0/4/8/16/24/40px, k-independent), duplicated across `block.css` (static `:scope[padding=…]` rules) and `block.class.js` `_SPACE` (the `@bp` responsive path), and diverging from the global `[padding]`/`[margin]` attribute API — `<block-ui padding="md">` rendered 16px while `[padding="md"]` is 24px (same vocabulary, different sizes). Both paths now reference the parametric `--a-{padding,margin}-{xs..xl}` tokens (`foundation/space.css`), so the two grammars AGREE (the same "one named scale" principle as the `[gap]` unification), `block-ui` tracks the `--a-padding-k`/`--a-margin-k` knobs, and the scale is no longer duplicated. **Behavior change** — `block-ui` spacing grows at `xs`/`sm`/`md`/`lg`: `xs` 4→8, `sm` 8→16, **`md` (the default) 16→24**, `lg` 24→32px; `xl` (40) + `none` (0) unchanged. Verified by computed-style probe (8/16/24/32/40 @k=1, default 24, `--a-padding-k:2` → md 48, `@bp` responsive + margins all correct, 0 console errors) + dev-shell visual QA. Files: `components/block/block.css`, `components/block/block.class.js`. **Migration:** a `<block-ui>` that relied on the prior tighter spacing should step the named value one rung down (`md`→`sm`, `lg`→`md`, …) or use the integer rungs (`padding="4"` = literal 16px, k-independent).
- **CDN bundles rebuilt** — `dist/host.min.css`, `dist/web-components.min.css`, and `dist/web-components.min.js` regenerated so the barrel split + `<block-ui>` + parametric `[padding]`/`[margin]` + `[verse]` re-scaling reach `@adia-ai/web-components@0.7` CDN consumers.

### Fixed

- **`[verse]` register now scales icon + caret sizes down with its type + controls.** The compact `[verse]` register (`styles/verse.css`) shifted inset, gap, radius, control-size (`--a-size`), and every type role down — but left `--a-icon-size` (16px @md) and `--a-caret-size` (14px @md) at their regular-register values. So in a verse context a `<select-ui>` caret (an `<icon-ui>` reading `--a-icon-size`) stayed 16×16 against verse's 12px text in a 43px-tall control, and form-element chrome icons read full size — visually oversized. Added a one-tier-down icon/caret scale to `[verse]`, mirroring how it already re-implements `--a-size`: base `[verse]` = 14/12px (= regular-`sm`), `[verse][size="lg"]` = 16/14px (= regular-`md`), `[verse][size="sm"]` = 12/10px. The explicit `[size]` tier rules are **required** because the global `[size]` icon/caret rules live in the `utilities` layer, which verse's `context` layer shadows (same reason `--a-size` needs them). Covers both `<icon-ui>`-based carets (select / combobox, via `--a-icon-size`) and CSS-drawn carets (calendar-grid / calendar-picker / nav-group / pane, via `--a-caret-size`). Verified by computed-style + render-box probe: verse(md) caret 16→14px, verse-`sm` 14→12px, verse-`lg` 20→16px; regular register unchanged. File: `styles/verse.css`.
- **`<command-ui>` now parses `.map()`/`repeat()`-rendered `<option>`/`<optgroup>` palette items (5th sighting of the `display:contents` wrapper trap).** `#parseOptions()` walked `this.children` directly, so options rendered via `.map()` arrived nested in the template engine's `display:contents`/`role="presentation"` wrapper span and were missed — leaving an empty palette ("No results found.") when the consumer interpolated its option list. New `#logicalOptionChildren()` walks direct children and pierces wrapper spans, mirroring `select-ui#logicalOptionChildren` (FB-98). Eight other `for (const x of this.children)` loops across 8 components (context-menu, field, fields, integration-card, list, menu, swatch, toolbar) were audited and marked `// wrapper-trap-ok` — all read component-stamped or single-literal children that are never consumer-interpolated. `audit:wrapper-trap:strict` added to `check:dogfood-audits:strict`; the audit now reports 0 un-allowlisted loops. File: `components/command/command.class.js`.
- **`<select-ui>` now parses `.map()`/`repeat()`-rendered `<option>` children (FB-98).** `#parseOptions()` walked `this.children` and matched only `tagName === 'OPTION'`/`'OPTGROUP'`, so interpolated options — each nested in the template engine's `display:contents`/`role="presentation"` wrapper span (`core/template.js` Array branch) — were invisible, leaving the popover empty ("No options"); static-literal options worked (direct children, no wrapper). New `#logicalOptionChildren()` collects `<option>`/`<optgroup>` direct OR nested inside the wrapper spans (skipping component-stamped `[slot]` children), and a re-entrancy-guarded `MutationObserver` re-parses options that arrive after `connected()` (the template's `update()` pass) so the empty-state clears. The same `display:contents` wrapper trap fixed for `menu-ui#show()` (FB-92) and `tree-item-ui#stamp()` (FB-96), now in the third component; mirrors tree's `#logicalSlotChildren`. Static-literal selects + the FB-36 `<option selected>` initial-value path are unchanged. +2 regression tests. File: `components/select/select.class.js`. (Tokens Studio / color-app FB-98)
- **`<menu-ui>` — a closed declarative menu no longer takes layout space (FB-97).** The closed-state hide rule (`menu.css`) used a child combinator (`:scope > menu-item-ui`), so `.map()`/`repeat()`-rendered items — nested in the template engine's `display:contents`/`role="presentation"` wrapper span — escaped it on the initial (never-opened) render and laid out in flow, ballooning a compact menu to its widest item's width (a 388px file menu displaced a centered toolbar tab-row until a tab became un-clickable). Added wrapper-piercing descendant selectors (`:scope > [role="presentation"] menu-item-ui`, + `menu-divider-ui`); safe vs. the open state since `#show()` appendChild's the bare items into `[data-menu-popover]` (never the wrappers). Verified: a closed `.map()` menu collapses 388px → trigger width (~41px), items `display:none`, neighbour un-displaced; open still renders all items. The CSS instance of the FB-92/96/98 `display:contents` wrapper trap; makes the FB-92/95 "render menu items declaratively" guidance safe for compact menus. File: `components/menu/menu.css`. (Tokens Studio / color-app FB-97)
- **`button.yaml` `aria-label` prop marked `dynamic: true` — clears dogfood drift.** `aria-label` is a native ARIA attribute managed via `setAttribute` in `connected()` and deliberately kept out of `static properties` (would clobber native `ariaLabel` reflection via `installProps`). The `static-properties-vs-yaml` dogfood audit flagged the yaml declaration as a drift; `dynamic: true` is the established opt-out (same pattern as the `text` prop). `button.a2ui.json` regenerated (`aria-label` → `DynamicString $ref`); dogfood advisory count: 43 → 42.
- **`<button-ui>` trailing slot kbd-pill now tracks the button's label color on every variant.** A `<kbd-ui slot="trailing">` pill rendered `kbd-ui`'s dark default even on a primary (white-label) button. `kbd-ui` styles its own `color`/`background`/`border` inside `@scope (kbd-ui)`, which wins over the button's `[slot="trailing"]` rule by **scope proximity** (closer scope root) — so setting `color` (even `inherit`) on the slot is inert. Fix: the button overrides `kbd-ui`'s own *tokens* — `[slot="trailing"] { --kbd-fg: currentColor; --kbd-bg / --kbd-border: color-mix(in oklab, currentColor …, transparent) }`. `kbd-ui` declares those tokens at `:where(:scope)` (specificity 0,0,0), so the button's `[slot]` decls (0,1,0) win on **specificity — which the cascade resolves before proximity** — and the pill (text + translucent fill + border) tracks the label: white on primary/colored fills, dark on outline/ghost. Verified on a fresh serve (kbd computed `color` == button label across variants). File: `components/button/button.css`.

## [0.7.5] — 2026-06-02

### Fixed

- **`<button-ui>` colored buttons — unreadable solid text + outline rendering as a solid pill.** Two bugs in the `[color]` × `variant` matrix, both verified on the live render (`scripts/qa/button-color-probe.mjs`: WCAG contrast + fill-transparency probe). (1) **Solid** `color="danger|info|success"` set `--button-fg: var(--a-X-fg)`, which resolves to `--a-X-text-strong` — *family-colored* text, ~1:1 contrast on the same-hue fill (near-invisible). Now `--a-chrome-light` (white) like `variant="primary"` → danger 4.75:1, info 4.27:1, success 4.02:1. `warning` keeps dark `--a-warning-fg` (its `--a-warning-bg` fill is light amber). Documented anti-pattern `feedback_text_on_accent_disc` (light text on filled accent surfaces). (2) **Outline/ghost + color** rendered as a solid pill — the `[color]` rule's solid `--button-bg` won source-order over `[variant="outline"]` (the demo's `variant="outline" color="success"` "Approve" button, `button.examples.html:50`). Added combined `[variant][color]` selectors (specificity 0,3,0 > the 0,2,0 `[color]` rule) restoring a transparent fill + colored text (`--a-X-text`) + colored border (`--a-X-border` for outline). **(3) The actual override mechanism.** Fixes (1)+(2) set `--button-fg` correctly, yet the rendered text stayed the fill hue: the global `[color]` presentational utility (`api/text.css`, `utilities` layer) sets `color: var(--a-fg)` and wins over the button's `components`-layer `color: var(--button-fg)` by **layer order** (utilities > components), regardless of specificity. `button-ui` re-purposes `color=` as a semantic *fill* (the `variant="danger"`→`color="danger"` migration, button.css:180) and is the only filled control that does, and is not among the color honorings (`cascade-component-attr-sweep.mjs`) — so it is now excluded from the text-color utility: `[color]:not(:where(button-ui))` (specificity-neutral via `:where()`; a plain `[color="info"]` element still resolves to info text). Verified on a *fresh* serve against the real demo buttons — the persistent dev server caches the `host.css` `@import` chain and masked the fix on edit. Files: `components/button/button.css`, `styles/api/text.css`.

### Changed
- **CDN bundles rebuilt** — `dist/host.min.css` + `dist/web-components.min.css` regenerated so the button-color fix above reaches `@adia-ai/web-components@0.7` CDN consumers (the bundles were stale since the fix landed on `main` pre-cut, ahead of the cut's `build:bundles`).

## [0.7.4] — 2026-06-02

### Added

- **`[verse]` — third typographic register (compact / dense)** — a sibling to `[prose]`: where prose shifts everything UP (long-form), verse shifts DOWN — tight inset/gap (10/12/14), small controls (18/20/24), small type (body 11/12/13), tight radius (≤16px) — for dense product UI (inspectors, tables, toolbars, side rails). Opt-in context layer (`styles/verse.css`), linked alongside `prose.css`. Type SHIFTS with `[size]` (unlike `[prose]`, which pins type at -md — verse re-declares the `-size` pointers under `[verse]:not([size])` so a bare verse resolves verse's own md value, then the global `[size]` rules shift sm/lg); gap is parametric (`value × --a-gap-k`). Docs: a "Typography registers" comparison section on the card / button / text / field / badge / stat pages, and `typography.css` now documents all three registers. Verified by `scripts/qa/register-scale-probe.mjs`.
- **`<grid-ui>` `[align]` / `[justify]` item-alignment props** — grid-ui now supports `align` (block axis → `align-items`) and `justify` (inline axis → `justify-items`), reaching parity with `row-ui` / `col-ui` (which already had `align`). e.g. `<grid-ui columns="2" align="center">` vertically centers a text column against a product visual in a split hero. CSS-only (attribute selectors); yaml + `.d.ts` + a2ui sidecar regenerated.
- **`styles/host.css` — single drop-in host-page foundation** — one `<link>` bundles design tokens + every component's CSS + global resets (via the canonical `index.css` barrel) + a page-frame (`:where(html)` background / foreground / font-family / `min-block-size`). Replaces the hand-rolled `:where(html, body){ … font-family: var(--a-font) }` boilerplate that ~281 demo scaffolds copy-paste. Opt-in layers (themes, prose) + per-module CSS (FB-53) stay explicit on top. `site/index.html` migrated (tokens+components+resets → host.css; verified pixel-identical render). Composes the byte-stable `/css` entry, adds the page-frame above it; CDN bundle at `dist/host.min.css` (`bundle-css.mjs`). (ADR-0035 / ADR-0038)
- **`--a-font` back-compat alias** (`type/scale.css`: `--a-font: var(--a-font-family)`) — the pre-ADR-0035 token name was renamed to `--a-font-family` but left dangling in ~285 references (demo boilerplate + embedded apps + external consumers). An undefined `var(--a-font)` is invalid-at-computed-value-time → `inherit` → UA **serif**; the alias resolves them to the sans stack. DEPRECATED — prefer `--a-font-family`; retire after the host.css migration retires the boilerplate.
- **`font-family` floor on 25 text-bearing primitives** — `segmented` / `segment` / `badge` / `button` / `tag` / `input` / `textarea` / `select` / `combobox` / `tags-input` / `tabs` / `toggle-group` / `range` / `table` / `calendar-grid` / `calendar-picker` / `agent-questions` / `command` / `context-menu` / `menu` / `table-toolbar` / `time-picker` / `toolbar` / `tour` + the `onboarding-checklist` module. Each anchors `font-family` to `var(--a-font-family-ui)` — composites (`select` / `combobox` / `tags-input` / `table` / `calendar-picker`) floor the host `:scope` so internal fields/options/cells inherit it; the rest use the `var(--X-font-family, var(--X-font-family-default))` chain (mirroring `text-ui`). Per-component overridable. `inline-edit` + `spinner` intentionally keep `inherit` (must match surrounding content).
- **New trip-wire `audit:font-family-floor[:strict]`** (`scripts/dev/audit-font-family-floor.mjs`) — flags component CSS using `font: inherit` / `font-family: inherit` without a token floor, plus dead `var(--a-font)` usage. Surfaced 8 of the 25 fixes that manual grep missed; ships green (0 critical, 2 allowlisted).

### Changed

- **Reverted the OD-5 `-default` token-shadowing layer — component CSS now declares a single un-suffixed token per property** — `var(--card-bg, var(--card-bg-default))` → `var(--card-bg)` across 119 component + module CSS files; variants/states re-point the un-suffixed `--<component>-*` token directly (`:where(:scope)` keeps the base at zero specificity). The 2026-05-24 OD-5 split (internal `--<c>-X-default` + public `--<c>-X` read through a fallback chain) added read-past noise without paying for itself: the one capability it bought — ancestor-surface inheritance of *component-named* tokens — had no real usage (a repo sweep found only on-element attribute hooks + parent→child composition, both of which win by specificity and survive). **Consumer overriding is unchanged** for the four real vectors (`--a-*` upstream · on-element `card-ui { --card-bg: … }` / inline · parent→child composition · `@layer overrides`); the *only* behavior change is that a `--<component>-*` token set on a non-targeting **ancestor** no longer inherits in (theme via `--a-*` or `@layer overrides`). No public component/prop API change. Inverse codemod `scripts/refactor/undo-od5-token-shadowing.mjs` (2-level + 3-level chain collapse, precise pair-derived strip, collision-safe residual rewire); forward `od5-token-shadowing.mjs` retired; `.claude/docs/specs/component-token-contract.md` rewritten (§History). Internal: `modal`/`swiper` `getComputedStyle` reads + 5 CSS-string-match tests re-pointed to the un-suffixed names. Verified: suite 1526/1526, `components --verify` clean, bundles fresh, visual QA (composition intact).
- **Typographic registers re-scaled — regular (default) + prose (MINOR behavior change)** — the regular register's inset compressed `8/16/24 → 14/16/18` and its gap re-based to a clean `value × --a-gap-k` multiplier (`8/12/16 → 10/12/14`, replacing the `half+half·k` form); `md` (the default tier) is unchanged for both, so only `[size=sm]`/`[size=lg]` containers shift. Prose moved one rung up (inset → 32/40/48, gap → 16/20/24). **Radius is now a register-scaled axis** — each register sets its own `--a-radius-min/max` (verse ≤16px · regular 20px=base · prose ≤28px) feeding the existing `clamp(min, k·max·tier-k, max)` formula. All values verified by `scripts/qa/register-scale-probe.mjs` (9 cells × 5 axes).
- **Demo scaffolds migrated to the `--a-font-family` token** — the ~114 `patterns/` + `traits/` demo `.html` scaffolds (plus the per-component demos) swapped their hand-rolled `:where(html, body) { font-family: var(--a-font) }` boilerplate to `var(--a-font-family)`, tracking the font-family-floor token rename; `patterns/search-discovery` additionally dropped a stray inline `max-width` so the results card self-frames. Demo-layer migration only — no component or CSS-API change.

### Fixed

- **Text-bearing primitives no longer fall back to UA serif when the host page's font is broken/unset** — 25 primitives (segmented/segment/badge/button/tag, the form controls, menus/toolbars/pickers — see Added) used `font: inherit` / `font-family: inherit` with **no `font-family` floor**, so any consumer page whose inherited `font-family` was wrong (e.g. the embedded-app's `html, body { font-family: var(--a-font) }` → dead token → serif; or a serif host document) rendered their labels in Times while `text-ui` (which anchors to a token) stayed correct — a confusing same-page split. Each now carries a font-family floor (see Added). Root-caused via computed-style probe (segment/badge/button/tag → Times; text-ui → sans); the floor was verified to hold on a deliberately `body{font-family:serif}` page. Robustness layer — the `--a-font` alias additionally fixes the specific embedded-app trigger with no app edit. (embedded-app patient-labs QA)
- **`<grid-ui align="…">` no longer leaks `text-align` into descendants** — grid-ui never neutralized the legacy UA rule that maps the HTML `align=` attribute to `text-align` (`row.css` / `col.css` both do, explicitly). So `<grid-ui align="center">` — authored for grid-item vertical centering — silently set `text-align: center` on **every** descendant, centering split-hero headlines/decks/kickers that should read left (marketing-engagement `hero-cta-split`, v050-marketing-blocks `hero-section-split`). And because grid-ui had **no** `[align]` rule at all, the attribute didn't even perform the intended vertical centering — it was pure leak. grid-ui now sets `text-align: inherit` (neutralizing the UA mapping) and maps `[align]`/`[justify]` to grid-axis item alignment. Demo fix: `hero-cta-split`'s left column → `align="start"` so the "For teams" tag hugs its content instead of stretching the full column. (marketing-engagement QA)

## [0.7.3] — 2026-06-01

### Added

- **Parametric named gap scale `xs`–`xl` (`--a-gap-k`)** — the named gap vocabulary is now a complete, parametric 5-step scale: `--a-gap-{xs,sm,md,lg,xl}` = `half + half·--a-gap-k` (a fixed floor + a k-scaled half). The new `--a-gap-k` knob (`@property`, default 1) is a root/provider-level scale control with the same semantics as `--a-density` (subtree override is inert — set it at `:root`/a top provider). At k=1: **4/8/12/16/20px**. `gap="xs"` and `gap="xl"` now resolve (they were documented in yaml but unimplemented — silently did nothing in both the CSS `[gap]` path and the JS `@bp` responsive path). At k=1 the named scale coincides with the integer scale: xs=1, sm=2, md=3, lg=4, xl=5. (spec GS-P2-SPEC-2 §5.2)

### Changed

- **Behavior change (MINOR): explicit `[gap="sm|md|lg"]` resolves smaller** — `gap="sm|md|lg"` on layout primitives (col/row/grid) now resolves to `--a-gap-{sm,md,lg}` = **8/12/16px** (was the hardcoded `--a-space-4/6/8` = 16/24/32px). This unifies the three gap paths that previously disagreed — explicit `[gap="md"]` was 24px while the responsive (`gap="md@bp"`, JS `_gapToCss`) and ambient (`[size]`) paths were 12px; all three are now `--a-gap-md`. Integer `gap="N"` is unchanged (literal `--a-space-N`, k-independent). `[size]` / `<card-ui size="…">` unaffected. (audit C1/C2)
- **`[gap]` no longer leaks into nested layout primitives (audit C3)** — `[gap="N"]` now sets a NON-inheriting `--a-gap-self` (`@property{syntax:"*";inherits:false}`); the inheriting `--a-gap` is reserved for the *ambient* `[size]`/`[prose]`/`:root` cascade. The six gap-readers (col/row/grid/card/swiper/stat) resolve `var(--a-gap-self, var(--a-gap))` — explicit-on-self wins, else the inherited ambient. Previously a parent's explicit `gap="8"` bled into a nested `<row-ui>` that declared no gap of its own. Verified via `scripts/qa/cascade-gap-self-probe.mjs` (5 truth-table rows + parametric + coincidence, all green).
- **`col`/`row`/`grid` yaml gap docs + layout demo reconciled to impl** — documented the two grammars (named parametric + integer 0–16), fixed grid's range claim (`0–12`→`0–16`), dropped the phantom-value framing (`xs`/`xl` real now). `layout.examples.html`: Properties tables corrected, broken `gap="none"` demo → `gap="0"`, col demo filled out to the complete `xs`→`xl` scale. Sidecars regenerated.

### Fixed

- **`<tree-item-ui>` adopts interpolated `[slot="actions"]`/`[slot="caret"]` children (FB-96)** — the FB-89 actions-adoption used a `this.querySelectorAll(':scope > [slot="actions"]')` direct-child query that ran once at `#stamp()` time, so a `<button-ui slot="actions">` rendered through the template engine (`.map()`/`repeat()`/conditional) was missed twice over: it arrives in a *later* reactive tick (after `#stamp`) **and** nested in a `display:contents` wrapper span. The un-adopted button stayed a bare `role="button"` child of the `group`/`tree`, tripping axe `aria-required-children` even after the FB-94 `role="presentation"` fix (which addresses role-flattening, not structural adoption). `#stamp()` now stamps an empty placeholder and a re-entrancy-guarded `MutationObserver` adopts the real `[slot="actions"]`/`[slot="caret"]` into the row whenever they appear, via a wrapper-piercing "logical children" walk that stops at nested `tree-item-ui` boundaries (so a child item's actions aren't mis-adopted) and drops the placeholder once a real child lands. +2 regression tests; verified with axe-core (`.map()` tree + interpolated actions button → 0 `aria-required-children`). The structural-adoption sibling of the FB-92 `menu #show()` fix — same `:scope >` + `display:contents` trap. (Tokens Studio / color-app FB-96)

### Changed — styles + build

- **Gap scale + grammar span `styles/` → `components/` → `patterns/` → `dist/`** — the parametric `--a-gap-k` scale is defined in `styles/` (foundation tokens); the `[gap]` grammar applies on `components/col` / `components/row` / `components/grid`; `patterns/` demos were updated to the named scale; and the `dist/` CDN bundles (`web-components.min.css` + `web-components.min.js`) were regenerated.

## [0.7.2] — 2026-06-01

### Fixed

- **template engine — ARIA-transparent `display:contents` wrapper spans (FB-94)** — the interpolation wrapper span the engine emits for `${…}` parts (`.map()` / conditional / `repeat()`) now carries `role="presentation"`. `display:contents` removes the span's box from layout but it still generated a generic accessibility node, which broke `aria-required-children` / `aria-required-parent` when the span sat between a role-bearing parent and role-bearing children rendered via interpolation — e.g. a `.map()`-rendered `<tree-ui>` (the mainstream consumer case) tripped axe `aria-required-children` even after the FB-91 static-markup fix landed. `role="presentation"` promotes the children to the real parent in the a11y tree. Fixed at all three wrapper sites (`wrap()` + array branch + keyed `repeat()`); +3 regression tests assert the wrappers carry `role="presentation"`. This is the consumer-preferred + peer-recommended central fix and is structurally sound (the spurious generic node between `tree`→`treeitem` is removed). **Honest verification note:** `aria-required-children` could not be made to fire on any in-repo tree (shallow *or* deeply-nested, axe-core, `:5300`) — it triggers only on color-app's exact published-package `dts-token-tree` — so local axe reads 0 both before and after; the firing→cleared transition is **pending the consumer's confirmation** on their next `@adia-ai/web-components` upgrade. (Tokens Studio / color-app FB-94)
- **`<menu-item-ui>` no longer an orphan `menuitem` when the menu is closed (FB-95)** — `UIMenuItem.connected()` set `role="menuitem"` unconditionally, but a closed `<menu-ui>` moves its items back into its own light DOM (no `role="menu"` ancestor), so every closed declarative menu tripped axe `aria-required-parent`. The role (+ `tabindex="-1"`) is now applied only when the item has a `role="menu"`/`menubar` ancestor; the open popover adopts items via `appendChild` (which re-fires `connected()`), so the role re-applies on open and drops on close. **Verified with axe-core**: closed menu → **0** `aria-required-parent` violations (was 3); open menu → items are `role="menuitem"`, 0 violations. (Tokens Studio / color-app FB-95)

### Changed — styles + build

- **Tokens cascade-layer completed (`styles/`)** — `typography.css` (`type/scale.css`) + `roles.css` `:root` role-token blocks moved into `@layer(tokens)`, finishing the `@layer` migration's tokens cut (the type-scale + role tokens had stayed unlayered). Behavior-neutral — the `[variant]`/state rules keep their existing precedence; only the base `:root` token declarations join the layer.
- **`dist/` CDN bundles regenerated** (`web-components.min.css` + `web-components.min.js`) for the template-engine + tokens-layer changes.

## [0.7.1] — 2026-05-31

### Fixed

- **`<input-ui>` leading/trailing affordance vertical centering** — the affordance slots used `align-self: stretch`, which top-aligned any child carrying a definite `height` (a `<kbd-ui slot="trailing">` ⌘K hint sat ~4px high in a 30px chrome) and also overrode a `<button-ui>` child's own `--button-height`. Changed to `align-self: center` so each affordance keeps its token height and sits on the field's vertical center. Affects every intrinsic-height affordance (kbd / icon / badge); button affordances are unchanged (they already filled the chrome). (bug-60)
- **`<tree-ui>` nested items now satisfy the WAI-ARIA tree pattern** — the `tree-item-ui` host is now `role="group"` and its focusable `[slot="row"]` is the `role="treeitem"` (with `aria-expanded`/`aria-selected` moved onto the row). Previously the host was the `treeitem` and nested items had no `group`/`tree` required parent, tripping axe-core `aria-required-parent` (critical) on every nested node. Done without re-homing children, so it holds for static markup AND template-engine `.map()` trees; no CSS/yaml change. Validated with real axe-core (0 violations). (FEEDBACK-91)
- **`<menu-ui>` renders dynamically-generated items** — `#show()` collected items with a direct-child `:scope >` query, which missed `menu-item-ui` rendered via `.map()`/`repeat()` (the template engine nests them in `display:contents` wrapper spans) → the popover opened empty. Now uses a descendant query (skipping items already relocated into the popover), matching `#hide()` and resolving the show/hide asymmetry. Static children unaffected. (FEEDBACK-92)
- **`<input-ui>` reactive `prefix=`/`suffix=` bindings resolve** — a reactive attribute binding (`suffix="${expr}"`) leaked the literal template placeholder `{{p:N}}` as affix text, because the shell was snapshotted once in `connected()` before the binding resolved and never re-synced. `render()` now re-resolves the prefix/suffix slots each cycle (handling text↔icon affixes), mirroring the slider-ui FB-45 fix. Literal and `.property` affixes are unchanged. (FEEDBACK-93)
- **`dist/` CDN bundles regenerated** (`web-components.min.css` + `web-components.min.js`) to carry the input/tree/menu fixes above.

### Changed — demos

- **`<field-ui>` inline-layout demo** — the "Results: 12" count moved from a field-level `<span slot="trailing">` (beside the input) into the input's `suffix` prop, so it renders **in-chrome, left of the Clear button** (DOM order value → `suffix` → `trailing`). The demo now shows all three affordances inside the input chrome and the note explains the suffix-vs-trailing split. (bug-60)

## [0.7.0] — 2026-05-31

### Added — component features from the docs-QA pass

- **`<stat-ui bleed>`** — horizontal-bleed KPI tile. With a `slot="chart"` child, value/label/change stack on the left while the chart fills the right column at full height and bleeds to the card's top/right/bottom edges (the horizontal counterpart to a chart in a `<section bleed>`). First-class reflected Boolean attr (yaml + `static properties`; not a `data-*` hook).
- **`<page-ui>` region model** — `--page-inset` vertical rhythm between `header`/`section`/`footer` children + `[bleed]` (edge-to-edge, cancels the page `[padding]`) + `section[padding]` (padded sub-surface), mirroring card-ui's section model. The docs promised `@scope` styled the regions but no such rules existed — regions stacked with zero spacing.
- **`<timeline-ui>` `--timeline-marker-box-size`** — unified marker footprint (1rem; 0.875rem at `size=sm`) the dot/icon/spinner center within; icon-mode chips gain a ring (`--timeline-icon-border`) so they read as markers even when the muted fill blends with the surface.
- **`<integration-card-ui>` provider→icon fallback** — a card with only `[provider]` (no `[logo]`) now renders a per-provider Phosphor glyph (slack→chat-circle, github→git-branch, …) or a generic `plug`, instead of a blank logo disc.
- **`<kbd-ui>` `size="lg"`** — added the missing `lg` to the size enum (CSS + docs already shipped three sizes; the yaml capped at sm/md).

### Fixed — docs-QA pass (browser-verified)

- **`<datetime-picker-ui>`** — committed value now uses the picker-family value color (`--a-ui-text`, bright) + `space-between` alignment (value left, caret right), matching input-ui/select-ui/time-picker (was a centered, dim default button); `[data-has-value]` gates the brightening.
- **`<kbd-ui>`** — `md` glyph was `--a-ui-tiny` (10px), identical to `sm`; now `--a-ui-xs` (12px) for a real 10/12/14 sm/md/lg ramp.
- **`<empty-state-ui>`** — heading moved from `--a-ui-lg` (15px control-label ramp; only 1.07× the description) to `--a-heading-md` (17px → 1.21×); the `[minimal]` inline variant keeps the heading bolder + non-muted so it stays distinct from the description.
- **`<heatmap-ui>`** — `type="day-grid"` keeps fixed cell tracks + scrolls horizontally (GitHub-contributions model) instead of crushing fixed cells into an overlapping smear in narrow containers; `matrix`/`density` keep fitting-and-shrinking.
- **`<option-card-ui>` `layout="tile"`** — DOM-order-independent (via `order`) with the radio pinned to the card's top-right (cleared the base `grid-area:indicator` that stranded it in a collapsed 8px gutter); token-driven padding. Fixes plan-picker's tile cards.
- **`<preview-ui>` form-demo width** — the docs render cell capped `<field-ui>` at `max-width:28rem` (448px) together with bare form controls; since field-ui is a full-width form _row_ (its own CSS is `width:100%`), the cap stranded it mid-cell, reading as "not using the available space" on wide single-block demos. Split the rule: field-ui now fills the stage (no cap); bare atomic controls (input/select/combobox/textarea/tags-input/color-input) keep the 28rem measure cap so a lone control doesn't sprawl. (bug-55 follow-up)

### Changed — demos + corpus hygiene (docs-QA pass)

- **`<field-ui>` demos** — migrated in-chrome affordances (copy / clear / ⌘K) onto `input-ui`'s `leading`/`trailing` slots (inside the input box); field's `trailing`/`action` reserved for beside-field captions + submit buttons. Slots table + A2UI guidance now state the boundary. Short in-chrome _status_ text (e.g. "Required") now uses `input-ui`'s `suffix`/`prefix` attribute rather than a `<span slot="trailing">` _outside_ the input — keeps the status inside the field chrome and lets the input fill the full field width (demo `field-default-trailing-caption` → `field-default-inchrome-required`). (bug-55)
- **`<inline-edit-ui>` form-participation demo** — key-labelled rows (title/description) + instructional placeholder so the form-field semantics are legible.
- **`<integration-card-ui>` CRUD-drawer demo** — fixed an invisible drawer: the demo used `side="trailing"`, which is not in the `drawer-ui` `side` enum (`left|right|top|bottom`), so the panel `showModal()`'d but rendered at `opacity:0` collapsed in the top-left. Switched to `side="right"`. (bug-50)
- **Dead `size="sm"` swept** off layout primitives (`row-ui`/`col-ui`/`grid-ui`) + bare `<span slot="action">` across `patterns/` demos (68 sites repo-wide) — the attribute is a no-op on those tags and polluted the gen-UI corpus.
- **`dist/` CDN bundles regenerated** (`web-components.min.css` + `web-components.min.js`) for the docs-QA CSS/JS changes + the breaking removals.

### Removed (BREAKING) — `<avatar-ui name="…">` deprecated alias retired

- **`<avatar-ui>` no longer accepts the `name` attribute** — use `text` instead.
  `name` was a deprecated alias for `text` since 0.4.x (`deprecated_since: "0.4.x"`,
  scheduled for v0.6.0; the codebase reached v0.6.50 still carrying it). Retired in
  full: removed from `avatar.yaml`, the `static properties` map + `displayName`
  fallback + the one-shot deprecation `console.warn` + the `#nameWarned` flag in
  `avatar.class.js`, the `name: string` typing in `avatar.d.ts`, and the
  regenerated `avatar.a2ui.json` sidecar. The `<img>` `alt` now derives from
  `displayName` (= `text`). **Migration:** `<avatar-ui name="Ada">` →
  `<avatar-ui text="Ada">`. No in-repo markup, demo, or corpus chunk used the
  alias (the corpus was swept long ago; the one stray demo was fixed in
  `d3b482c61`). _Pre-1.0 breaking change — bump MINOR on the next cut._

### Removed (BREAKING) — `<chart-ui>` `aspect` + `heading` retired (OD-CHART-02)

- **`<chart-ui>` no longer accepts `aspect` or `heading`** — both were
  deprecated-but-functional with no removal date; retired across yaml + class.js +
  d.ts + css + sidecar.
  - **`aspect`** (`std`/`wide`/`square`/`tall` — derived the chart's implicit
    height from width): charts without an explicit height now always use the
    standard 4:3 ratio (the prior default, so default charts are unchanged; only
    explicit `aspect="wide|square|tall"` changes). **Migration:** size the chart
    via its parent (width/height on the enclosing card/container).
  - **`heading`** (rendered a title element + fed `aria-label`): **Migration:** put
    the visual title in an enclosing `card-ui` `<header><span slot="heading">`; for
    a descriptive accessible name set `aria-label` directly. With neither,
    `aria-label` now falls back to `"<type> chart"` (e.g. "bar chart"). Verified:
    69 demo charts render, 0 console errors. _Pre-1.0 breaking — bump MINOR._

### Removed — `<avatar-ui size>` long-form aliases (were non-functional)

- **`<avatar-ui size>` drops `small`/`medium`/`large`/`xlarge`** — use the
  canonical `xs`/`sm`/`md`/`lg`/`xl`. These were declared in `avatar.yaml` +
  `avatar.d.ts` "for back-compat" but had **no CSS implementation** (`avatar.css`
  only defines `:scope[size="xs|sm|md|lg|xl"]`), so `size="small"` silently
  rendered at the base default size — they never worked. Runtime behavior is
  unchanged; this removes the misleading values from the typed API + the sidecar
  the LLM prompt builder reads (training-data noise). No in-repo markup used them.

### Changed — global styles assigned to `@layer` cascade layers (ADR-0038)

- `packages/web-components/styles/` now assigns every rule to a named cascade layer — `@layer reset, tokens, elements, components, utilities, context, overrides` — so precedence is **declared once** instead of faked per-rule via specificity/`:where()`. The published bundle + barrel carry the layers; **rule content is unchanged** — only layer membership. Cuts: `resets.css` → `reset`; `colors/` + `foundation/` → `tokens`; `type/elements.css` → `elements`; the 128 scoped components → `components`; `api/{sizing,text,layout}` + `[variant]` → `utilities`; `themes.css` + `prose.css` → `context`; `overrides` declared topmost and **reserved (empty)** for consumer rules. A consumer rule in `@layer overrides` now wins by layer order with no `!important` and no specificity war (proven: a `(0,0,1)` `badge-ui{font-weight:321}` override beats both the component's `@scope` 500 and a `[weight="bold"]` 700).
- **One intentional behavior change (IPC-04).** Utility attributes — `[weight]` `[color]` `[transform]` `[text-align]` `[variant]` — now **override** a component's own `@scope` rule (they sit in `utilities`, above `components`). Previously they lost to component `@scope` by scope-proximity, and api/text.css documented that limitation — now retired. Measured blast radius: **39 component×property honorings across 128 components** (25 `color`, 10 `font-weight`, 3 `text-align`, 1 `text-transform`), zero breakage. Every *other* cut is behavior-neutral (real-browser computed-style probe 0 diffs; token identity byte-identical). The `ul/ol[role="list"]` reset and the `h1–h6` role defaults were relocated into `type/elements.css` (element-default refinements that must win by specificity *within* the `elements` layer). See ADR-0038 + `.claude/docs/specs/cascade-layer-architecture.md`.

### Added — `<select-ui>` per-option `icon` / `avatar`, reflected in the trigger

- **`components/select/{select.class.js,select.css,select.yaml}`** — `<option>` now accepts `icon` (Phosphor name) and `avatar` (image URL): `<option value="light" icon="sun">`. Each option renders its glyph in the listbox row **and** the trigger reflects the **selected** option's icon/avatar (theme pickers, assignee/account switchers). Previously a host-level `[icon]` was static — selecting "Light" still showed the fixed `moon`. `#parseOptions` now captures `icon`/`avatar` from declarative `<option>`/`<optgroup>` children (and the `.options` array shape gains `icon?`/`avatar?`); a new `#syncLeading()` reconciles the trigger's `[slot="leading"]` to the selected option on every render (avatar beats icon; host `[icon]`/`[avatar]` is the fallback). Only manages the component-stamped trigger (`[data-select-leading]`) — a consumer-custom trigger owns its own leading. Multi-select is unaffected (chips). +CSS for `[role="option"] img[data-option-avatar]`; sidecar regenerated. Verified in-browser: trigger shows `sun` for selected "Light", updates to `moon` on selecting "Dark"; per-option avatars render in trigger + rows; 21/21 unit tests green.

### Changed — block-level display by default + universal `[inline]` attribute (ADR-0037)

- Composite / control / container primitives now default to a **block-level** host `display` (`flex`/`block`/`grid`); the new universal **`[inline]`** boolean opts back to inline-level (`inline-flex`/`inline-block`/`inline-grid`). Flipped 14 primitives: `button` (text → full-width block, icon-only held inline via `:not([text])`), `color-input`, `date-range-picker`, `datetime-picker`, `time-picker`, `breadcrumb`, `pagination`, `segmented`, `toggle-group`, `toggle-scheme`, `menu`, `popover`, `image`, `qr-code`. **Text-flow atoms stay inline** (`text` `link` `mark` `number-format` `relative-time` `inline-edit` `inline-message` `badge` `tag` `chip` `kbd` `icon` `radio` `check` `switch` `avatar` `swatch` `rating` `spinner`). CSS-only — per-component `:scope[inline]` (specificity 0,2,0 reliably overrides `:scope`); no yaml/JS/sidecar (universal-attribute precedent: `[grow]`/`[hidden]`). **Inside `row-ui`/`col-ui`/`grid-ui` the flip is a no-op** (flex/grid items are blockified), so real-composition rendering is unchanged — only standalone-in-block-flow goes full-width. `field`/`fields` carve out `[inline]` for their single-row *layout* mode. `api/layout.css` documents the per-component rationale. See ADR-0037 + `.claude/docs/specs/attribute-api-system.md` §5.

### Changed — `chevron` → `caret` terminology sweep (FEEDBACK-90, ADR-0036 follow-up)

- Renamed residual `chevron` terminology to `caret` across 14 component files so the codebase carries one vocabulary (matching the `slot="caret"` convention + the Phosphor `caret-*` icon names). **`agent-artifact`** is the only behavioral surface: `[data-artifact-chevron]` → `[data-artifact-caret]` + `#chevronEl` → `#caretEl` (internal-render collapse indicator — a data-attr styling hook, not a slot, same as `agent-trace`/`agent-reasoning`). The rest is comment/description terminology (`timeline`; `table`/`select` yaml + regenerated a2ui descriptions; `input`/`pagination`/`action-list`/`calendar-grid`/`calendar-picker` comments). **Carve-out:** kept breadcrumb's `data-chunk="…-chevron-sep"` corpus identifier — the separator is semantically a chevron, not a disclosure caret, and renaming a harvested chunk name is a separate re-harvest. No consumer-facing behavior change.

### Fixed — form-field demos collapsed to content-width in `<preview-ui>` (demos)

- **`components/preview/preview.css`** — a `<field-ui>` (and bare text-field controls) is a flex item in the render cell, so form-field demos sized to their content — a placeholder-only `combobox-ui` shrank to a ~52px "Sel…" stub (input/select were also content-width, just less obvious). The render cell now stretches `field-ui` + text-field controls (`input-ui`/`select-ui`/`combobox-ui`/`textarea-ui`/`tags-input-ui`/`color-input-ui`) to the cell width, capped at `28rem` so single-block stages don't sprawl. Lifts **every** form-field demo to a realistic width at once (combobox 52→202px rows / 448px single-block); non-form demos (buttons, badges, …) keep content-width. Verified 0 console errors across 12 component + pattern pages.

### Fixed — `<combobox-ui>` `[size]` didn't step the field height

- **`components/combobox/combobox.css`** — the editable `[data-input]` span picked up `8px` block padding from an outer source that out-cascaded the `@scope` rule, pinning the field to ~36px at **every** `[size]` (sm/md/lg measured 36.2 / 37.6 / 39px — barely distinguishable). A higher-specificity guard (`combobox-ui [data-field] [data-input] { padding-block: 0 }`) zeroes it so the field height tracks `--a-size` like `input-ui`/`select-ui` — now **24 / 30 / 36px** for sm/md/lg. (The 8px source isn't in combobox/global/inline CSS — likely a leaked reset or a `contenteditable="plaintext-only"` behavior; flagged for a follow-up audit.)

### Added — `<combobox-ui>` demo depth (demos)

- **`components/combobox/combobox.examples.html`** — +5 attribute sections closing the depth gap surfaced by the demo-coverage audit (was ~7/17 demonstrable attributes): `prefix`/`suffix` slot (search-icon prefix), `highlight-match`, `loading`, `disabled`, `readonly`. All render correctly now that the width fix above is in place (the prefix demo had appeared collapsed only because of the content-width bug).

### Fixed — `<alert-ui>` `slot="action"` rendered wedged between icon and message

- **`components/alert/alert.css`** — a trailing `[slot="action"]` button rendered on the **left** (between the leading icon and the message) instead of trailing right, contradicting the documented "right-aligned trailing affordance." Cause: `alert.js` appends the auto-stamped `[slot="content"]`/`[slot="leading"]` *after* any author-provided action button, and the standard render path (unlike the dunning path) gave the action/close slots no `order`, so they sorted in DOM order. Added explicit `order: 1` (action) + `order: 2` (close) so the layout is always `icon → content → action → close`, with content's `flex: 1` pushing both affordances to the trailing edge. Latent since the feature shipped — first exercised by the new demo (no prior `alert-ui` consumer used `slot="action"`). Verified via DOM geometry probe (`iconX < contentX < actionX ≤ closeX`).

### Fixed — `<alert-ui>` dunning `due-at` showed the previous calendar day

- **`components/alert/alert.class.js`** — `#formatDueDate()` parsed a date-only ISO string (`"2026-05-20"`) as UTC midnight, which a behind-UTC locale then formatted as the *previous* day ("May 19"). Date-only strings are now pinned to local midnight (`"…T00:00:00"`) so the authored calendar day is preserved; full timestamps (with time/offset) keep their exact instant. Invisible in jsdom (UTC) — only manifested in a real browser, so the lenient existing tests (`/May/`, `/2026/`) stayed green (30/30); also corrects the shared `billing-overview` dunning banner.

### Added — `<alert-ui>` demo depth (demos)

- **`components/alert/alert.examples.html`** — +3 sections taking attribute coverage 33% → **100%** (demo-coverage audit): `title`+`description` (two-line headline/body), `action slot` (trailing button), and the full `dunning` payment-failed pattern (SPEC-006 — `pattern`/`amount`/`currency`/`due-at`/`card-last4`/`reason` across all four reasons + a warning grace-period), plus the `dismissible` alias. Props/Events/Slots tables extended to document the dunning API (`actions` slot, `dunning-action` event). Grounded against SPEC-006 + the real `billing-overview` consumer; surfaced the two `alert-ui` fixes above.

### Fixed — `<switch-ui>` `[hint]` rendered beside the label instead of below

- **`components/switch/switch.css`** — the `[hint]` caption is documented (yaml + props table + the rule's own comment "Caption beneath the toggle row") as rendering BELOW the label, but the base `inline-flex` row laid the stamped `[slot="hint"]` out as a third inline item beside the label. A `:has([slot="hint"])`-scoped `flex-wrap` + `flex-basis: 100%` now wraps the hint onto its own full-width line, indented under the label. No display-model change (stays `inline-flex`); hint-less switches are 100% unchanged (the hint span is stamped only when `[hint]` is set). Latent since §184 — surfaced by the demo-depth pass.

### Fixed — `<list-ui>` `[selectable]` selection chrome was never applied (upgrade race)

- **`components/list/list.class.js`** — `selectable` + `selected-key` set no visual selection (`aria-selected` null on every row). Light-DOM upgrade race: `render()`'s `#items()` filtered children by `role="listitem"`, but each `<list-item-ui>` sets that role in its OWN `connected()`, which hasn't run when the parent first `render()`s → `#items()` returned `[]` → `aria-selected` never stamped → the `[aria-selected="true"]` selection CSS never matched. `#items()` now also matches by tag name (`LIST-ITEM-UI`, present pre-upgrade); the attribute persists through the child's later upgrade. Latent since the selection feature shipped (no demo exercised it); +0 test changes, 5/5 green.

### Added — primitive demo-depth wave, Phase 3 (demos)

- Drove six audit-`critical` primitives to full attribute coverage (demo-coverage audit), each authored from source/spec grounding and browser-probed (0 console errors, visual QA): **`swatch`** 27% → 100% (detail/copyable/copy-value, badge gamut pips, selectable/selected, label-position overlay + auto-contrast); **`color-picker`** 44% → 100% (OKLCH generation constraints — max-chroma / min-l·max-l / base-hue·hue-drift-max — plus corrected the `format` demo to the real `[hex, oklch]` enum and dropped a non-existent `required` prop from its table); **`switch`** 43% → 100% (hint, name/value/required); **`list`** 40% → 100% (contained, selectable/selected-key, + real `selection-change` event docs); **`context-menu`** 33% → 100% (open, long-press-ms); **`toggle-scheme`** 33% → 92% (scoped `target` + icon/label-light/dark + color + storage-prefix; the read-only reflected `active-scheme` is intentionally not authored, + corrected two table defaults vs yaml). Demo-only `examples.html` changes except the two `switch`/`list` fixes above.

## [0.6.50] — 2026-05-30

### Changed — `slot="caret"` disclosure-indicator convention + FEEDBACK-89 (ADR-0036)

- **`components/tree/` (`tree-item-ui`) + `components/pane/` (`pane-ui`)** — renamed the expand/collapse indicator slot `slot="chevron"` → **`slot="caret"`** (and tokens `--*-chevron-*` → `--*-caret-*`, e.g. `--tree-caret-size`, `--pane-caret-fg`), standardizing on the slot name already used by `accordion-item-ui` / `nav-group-ui` / `select-ui` and matching the Phosphor `caret-*` icon vocabulary. **No backward-compat alias** (hard rename; the caret is auto-stamped, so blast radius is small). See `ADR-0036`.
- **`tree-item-ui` adopt-or-stamp (FEEDBACK-89)** — `#stamp()` now adopts a consumer's declarative `slot="caret"` and `slot="actions"` children into the auto-stamped row instead of leaving them as siblings below it. A `<button-ui slot="actions">` (the natural way to add a per-row action — add / rename / overflow) now lands inline in the styled, hover-revealed actions area. The host `#onClick` already excludes `[slot="actions"] *` from row selection, so adoption is click-safe. +3 unit tests.
- **`dist/` CDN bundles** (`web-components.min.css` + `web-components.min.js`) regenerated to carry the renamed `--*-caret-*` tokens + caret stamp logic.

## [0.6.49] — 2026-05-30

### Changed — card-ui header slot sweep + `nav-group-ui` fixes (demos)

- **`patterns/*` + catalog demo HTML** — card-ui header anatomy swept to the canonical `<header>` slot grammar across admin-dashboard + 5 catalog patterns; `nav-group-ui` `label=` → `text=` + icon fixes. Demo/corpus-facing; absorbed into the chunk corpus regen.
- **31 module demos** — `<!-- design-plan -->` blocks retrofitted across the composite-demo-protocol campaign (Phase 1+3 artifacts embedded).

### Maintenance

- **`dist/web-components.min.js` + `dist/icons-manifest.js`** — bundle rebuild reflecting the calendar-grid `month` prop + date-range-picker class changes.

### Fixed — `<date-range-picker>` showed the same month in both panes

- **`components/calendar-grid/calendar-grid.{class.js,yaml}`** — new `month`
  attribute (`YYYY-MM`): sets the displayed month when no `value` is selected, so
  a consumer can show a specific month independent of selection. A selected value
  always takes precedence.
- **`components/date-range-picker/date-range-picker.class.js`** — the end pane now
  defaults to **one month after** the start pane (via the new `month` hint), so a
  fresh range picker shows two consecutive months (e.g. May + June) instead of the
  current month twice. A selected `to` date still drives its own pane. Propagates
  to `<date-range-selector>` (embeds the picker).

### Fixed — `drop-target` demo card-structure violation

- **`traits/drop-target/drop-target.examples.html`** — the 4 `<card-ui>` drop
  surfaces held `<text-ui>` / `<col-ui>` directly (bypassing the card body slot),
  failing `check:card-structure` (a `check`-chain gate). Wrapped each in
  `<section bleed style="display:grid;place-items:center">` — clears the gate while
  keeping the centered drop-zone appearance. `audit:card-structure` now clean.

## [0.6.48] — 2026-05-29

### Demos — library-wide consolidation sweep (HTML-first)

- **`components/**/*.examples.html` + `traits/**/*.examples.html`** — Usage-section consolidation across 83 components + 45 traits: every example now shows its own auto-stamped HTML, so the standalone Usage cheat-sheet was redundant. Per-page consolidations (text, card, drawer) + implemented-matrix depth fills. See the root CHANGELOG for the full campaign.

### Changed — foundation styles reorganized by dimension × layer (ADR-0035)

- **`styles/`** — the foundation token surface is now organized by **dimension × layer**. `tokens.css`, `typography.css`, and `colors/semantics.css` remain as **compat barrels** at their published paths (the `@adia-ai/web-components/styles/*` export map and the `<link>` surface are unchanged); their content moved to single-responsibility files — `foundation/{space,size,radius,motion,elevation}.css`, `type/{scale,roles,elements}.css`, `colors/semantics/{core,buckets,features,data-viz,aliases}.css` — and the global attribute API to `api/{sizing,text,layout}.css`. New `styles/index.css` is the real barrel; the package-root `index.css` re-exports it. **Behavior-neutral** — identical custom-property declaration multiset in the minified bundle (see ADR-0035). Fixed in passing: the dead `--ui-focus` reference in the global `:focus-visible` fallback, and element-typography defaults consolidated into `type/elements.css`.
- **`scripts/release/check-foundation-layer-placement.mjs`** — new audit (wired into `npm run check`) enforcing the layout: header tags present, primitives free of attribute selectors, the global attribute API confined to `api/`, no orphan files.
- **`dist/web-components.min.css`** — bundle rebuild reflecting the reorg (no behavioral change).

### Fixed — `<date-range-picker>` popover clipped its second month

- **`core/anchor.js`** — the anchored-popover viewport-safety cap was a hard
  `max-width: min(100vw - 1rem, 32rem)`. It's now `min(100vw - 1rem,
  var(--popover-max-width, 32rem))`, so a wide-content popover can raise the
  32rem cap by setting `--popover-max-width` on the popover element (default
  unchanged → ordinary popovers like `popover-ui` / `menu-ui` / `select-ui` keep
  the 512px cap). Viewport safety still wins.
- **`components/date-range-picker/date-range-picker.css`** — the preset rail plus
  two side-by-side month panes need ~656px, but the 32rem (512px) cap clipped the
  second month inside the popover. The popover now opts into
  `--popover-max-width: 48rem` (new `--date-range-picker-popover-max-width` token),
  and `[data-calendar-area]` gains `flex-wrap: wrap` so the second pane wraps below
  the first when the viewport clamps the popover narrower than two months (instead
  of clipping). Fixes `<date-range-selector>` too (it embeds the picker). 32/32
  picker tests pass; no regression on other popovers.

### Changed — `<preview-ui>` (docs primitive)

- **Side-by-side is the default + self-correcting.** A bare `<preview-ui>` stamps
  `layout="split"` (render | code); a render cell wider than its half-width cell
  downgrades to stacked after layout (rAF + `ResizeObserver`: `split`→`layout="stack"`,
  or a row gains `[data-stack]`), and `overflow-x:auto` lets a page-scale composite
  scroll rather than clip. New `layout="stack"` enum value + CSS.
- **`dedent` indentation fix** — measures the common indent from the body (lines
  after the first) so markup captured with the opening tag at column 0 no longer
  renders its children deeply over-indented.
- **`[data-preview-unwrap]`** — multi-element rows show their children's markup, not
  the throwaway layout wrapper, in the code pane.

## [0.6.47] — 2026-05-29

### Added — `<card-ui>` `[grow]` section attribute

- **`components/card/card.css`** — `<section grow>` inside `card-ui` fills the card's remaining vertical height via `flex: 1`. The card becomes a `flex-direction: column` container when any direct `<section[grow]>` child is present, so the grow section absorbs the leftover space after header/footer. Requires the card to have a definite height (explicit inline height, grid row track, or flex parent) — a content-sized card has nothing to fill. Composable with `[bleed]`: `<section bleed grow>` for edge-to-edge fill surfaces (maps, canvases, full-bleed tables). A `<chart-ui>` directly inside `<section bleed>` auto-grows without `[grow]` (pre-existing zero-config convenience for the common chart case).
- **`components/card/card.examples.html`** — examples demonstrating the grow pattern.
- **`components/card/card.yaml`** — a2ui rules updated to document `[grow]`.

### Maintenance

- **`dist/web-components.min.css`** — bundle rebuild reflecting the `card.css` addition.

## [0.6.46] — 2026-05-29

### Added — declarative `traits="..."` Quick start sections on all 56 trait pages

- **`traits/*/\*.examples.html`** — every trait page now opens with a "Quick start" section before the hero demo showing `traits="traitname"` on a real `*-ui` host element. No JS, no import — the declarative attribute is the primary example. Variants shown where config attrs exist. Hallucinated config attr names corrected in 13 pages (agents invented plausible-sounding names; fixed against JS source: e.g. `data-snap-grid` not `data-grid-size`, `data-noise-strength` not `data-noise-opacity`, `data-intersection-threshold` not `data-threshold`, etc.).

### Added — expanded slot vocabulary for `header-ui`, `footer-ui`, `card-ui`

- **`components/header/header.yaml`** — added `icon`, `heading`, `description` slots (previously only `default` + `action` were declared, but the parent container's `@scope` activates a 5-slot grid vocabulary when these are present). Each description explains the parent-`@scope` activation contract.
- **`components/footer/footer.yaml`** — added `heading`, `description`, `action`, `action-leading` slots (all were styled by card/drawer/modal `@scope` but absent from the yaml; footer `heading`/`description` is the trend-stat / KPI pattern).
- **`components/card/card.yaml`** — added `media` slot (full-bleed hero image/video region); updated `heading`, `description`, `action`, `action-leading` descriptions to accurately state they work in both `<header>` AND `<footer>` context, not just "in the header".

### Fixed — spurious `bleed` removed from 31 section elements

- **`patterns/kanban-board/kanban-board.examples.html`** and **`traits/confetti|magnetic-hover|tilt-hover/...examples.html`** — removed `bleed` from `<section bleed>` where the direct first child was `text-ui`, `stack-ui`, or `list-ui` without `divider`. `bleed` removes the card section's inset padding and is only correct for edge-to-edge content (tables, charts, code, divider-lists); it was being cargo-culted onto text sections that needed normal padding.

### Fixed — stale token references in shipped examples

- **`components/skeleton/skeleton.examples.html`**: `var(--ui-border)` → `var(--a-border)` (dead token; border was rendering colorless).
- **`styles/overview.examples.html`**: token tier documentation table updated — `--ui-primary` → `--a-primary`, `--button-bg: var(--ui-primary)` → `var(--a-primary)` (the actual semantic token and component wiring).

### Maintenance

- **`packages/web-components/README.md`** — CDN example URLs updated from frozen `@0.6.30` to `@0.6` minor-range; `check:cdn-pins` guard added to `npm run check` aggregate.
- **`dist/web-components.min.{js,css}` + `dist/icons-manifest.js`** — bundle rebuild.

## [0.6.45] — 2026-05-29

### Fixed — `<canvas-ui>` lazy `a2ui-root` import uses a bare specifier (FEEDBACK-85, follow-up to FB-81)

- **`components/canvas/canvas.js`** — the FB-81 lazy import used a *relative* cross-package path (`../../../web-modules/runtime/a2ui-root/a2ui-root.js`). That resolves from the real `node_modules` location but breaks once Vite pre-bundles `web-components` into `node_modules/.vite/deps/` — `vite:import-analysis` fails to resolve the relative path (even with `/* @vite-ignore */`) and aborts dep optimization, so the app won't boot (blank page, Vite overlay only). Switched to a **bare specifier** `import('@adia-ai/web-modules/runtime/a2ui-root')`, which resolves identically whether `web-components` is pre-bundled or not (via web-modules' `./runtime/*` export). **`@adia-ai/web-modules` is now declared as an `optionalDependency`** (`^0.6.0`) so the specifier resolves under any bundler; the import stays dynamic + `.catch()`-guarded, so it's loaded only when `<canvas-ui>` is used and degrades gracefully if web-modules is genuinely absent. Lockfile regen (`npm install --package-lock-only`) required at the release cut.

### Fixed — `<select-ui>` derives an accessible name from `placeholder` (FEEDBACK-88)

- **`components/select/select.class.js`** — a placeholder-only `<select-ui placeholder="Status">` (no `label`/`aria-label`/`<field-ui>`) had **no accessible name** (WCAG 4.1.2 fail), while the sibling `<input-ui>` names itself from its placeholder. Added `#syncAccessibleName()` (run in `render()`) mirroring input-ui: derive `aria-label` from `placeholder` when the select is otherwise unnamed. **Guards** (a placeholder-only fix must not clobber a real label): an author `aria-label`, a visible `label`, or `aria-labelledby` always win; the **default `'Select...'` prompt is never used as a name** — otherwise every `<field-ui>`-wrapped select (named via a sibling `<label for>`, which `aria-label` would override) would be renamed to the generic prompt. A `#placeholderNamed` flag tracks the derived name so re-renders refresh/clear it without touching an author's aria-label. +5 unit tests. Known residual (matches input-ui): a `<field-ui>` + a *custom* placeholder on the same select still yields an aria-label from the placeholder — an unusual author combo.

### Maintenance

- **`package.json`** — `@adia-ai/web-modules` added as an `optionalDependency` (`^0.6.0`) for the FB-85 bare-specifier resolution; lockfile regenerated.
- **`dist/web-components.min.js` + `dist/icons-manifest.js`** — bundle rebuild reflecting the `canvas.js` + `select.class.js` fixes above.

## [0.6.44] — 2026-05-28

### Fixed — `<canvas-ui>` no longer hard-requires `@adia-ai/web-modules` at build time (FEEDBACK-81)

- **`components/canvas/canvas.js`** — the A2UI renderer `<a2ui-root>` (which lives in `@adia-ai/web-modules`) was a top-level *static* import. Because `@adia-ai/web-components` declares no dependency on `@adia-ai/web-modules`, the default barrel `import '@adia-ai/web-components'` (which registers `<canvas-ui>`) failed to build for primitives-only consumers who omitted `web-modules` — a hard Vite/rolldown `UNRESOLVED_IMPORT … web-modules/runtime/a2ui-root`. The import is now lazy + guarded: `#ensureRenderer()` runs a `/* @vite-ignore */` dynamic import on `connected()`, and a missing `web-modules` degrades to a one-time console warning instead of breaking the build. Consumers that have `web-modules` keep the prior synchronous render path (fast-path when `<a2ui-root>` is already defined); `process` / `processAll` / `reset` / version-restore buffer until the renderer is ready, preserving call order. The primitives barrel is now self-contained.

### Maintenance

- **`dist/web-components.min.js` + `dist/icons-manifest.js`** — bundle rebuild reflecting the `components/canvas/canvas.js` decouple above.

## [0.6.43] — 2026-05-27

### Added — `<list-ui>` `[contained]` prop declared in yaml

- **`list/list.yaml` + `list.d.ts` + `list.a2ui.json`** — declares the `[contained]` attribute (already implemented in CSS) as a first-class prop so the gen-UI corpus + A2UI sidecar can emit `<list-ui contained>` deterministically. Closes gen-review cycle-18 task #63.

### Fixed — Cycle-18 Ralph Loop substrate fixes (button + card + alert)

- **`button/button.yaml` + `button.class.js` + `button.css` + `button.a2ui.json`** — Ralph-Loop sweep absorbing cycle-18 RL findings (6 visual bugs fixed at SoT).
- **`card/card.css`** — preserves transpiled `text-ui` slot grammar; matches header-slot patterns from RL2.
- **`alert/alert.css`** — header-slot polish from cycle-18 RL2 (10 visual bugs across header slot, leaf children, calendar widget).

### Maintenance

- **`dist/web-components.min.{js,css}` + `dist/icons-manifest.js`** — bundle rebuild reflecting the `components/` substrate fixes above.

## [0.6.42] — 2026-05-26

### Changed — yaml/sidecar polish absorbing gen-review cycle 8-17 findings

- **`description-list.yaml` + `table.yaml` + `upload.yaml`** — yaml + a2ui.json sidecars updated absorbing the gen-review cycle 14-17 corpus-fix arc (slot patterns + table data + header anatomy + accordion-item-ui text= contract).
- **`core/provider.js`** — minor adjustment.

## [0.6.41] — 2026-05-26

### Maintenance

- **Lockstep version bump only — pure ride-along.** No source changes in any of the 9 packages this cycle. Substantive v0.6.41 work is REPO-LEVEL: adia-ui-release skill v1.8.0 → v1.9.0 (catalog-drift recurring graduation — §Step 5.5 pre-commit trip-wire + supersedes-WT contract + §Step 5 allowlist convention; closes FEEDBACK-75). Version bump preserves 9-package lockstep coherence; published tarballs identical to v0.6.40 modulo version metadata.

## [0.6.40] — 2026-05-26

### Fixed — `index.js` barrel side-effect-imports `core/provider.js` (cold-start `<router-ui>` registration)

- **`packages/web-components/index.js`** — the barrel now side-effect-imports `core/provider.js`, which registers `<router-ui>` from the default cold-start path. Closes the FB-69-class regression where consumers importing the cluster-barrel hit "router-ui is not registered" when first navigating in a cold session. Files: `packages/web-components/index.js`.

## [0.6.39] — 2026-05-26

### Added — `<tour-ui>` spotlight-driven product tour primitive (Wave 5e last P1, #259)

- **`<tour-ui>`** — spotlight-driven product tour primitive. Closes the last Wave 5e P1. Component count 177 → 179 (+2 with tour + tour-step sub-primitive).

### Fixed — substrate defensive slot-selector sweep + admin-entity-item icon size

- **Substrate sweep**: slot selectors changed from direct-child to descendant across remaining primitives. Defensive companion to the v0.6.38 `<list-item-ui>` fix — same drift class in adjacent components.
- **`<admin-entity-item>`**: icon size now matches `<nav-item-ui>`'s row icon (24px) instead of hardcoded 16px. Brings the canonical brand-chrome pattern into visual parity with the nav-item it sits next to.
- **`<list-item-ui>`**: now declares `slot="action"` — closes P2 from the v0.6.38 `dogfood:status` gate.

## [0.6.38] — 2026-05-26

### Added — Wave 5e cohort: 3 new primitives + slot-vocab-vs-css backlog closed

- **`<mark-ui>`** — inline highlighted text primitive (#357)
- **`<inline-edit-ui>`** — click-to-edit text in place (#212)
- **Notification deep-link demo + Mark All Read verify** for `<feed-ui>` (#344/#343)

### Fixed — substrate polish across the v0.6.38 window

- **`<list-item-ui>`**: slot selectors changed from direct-child to descendant — description row now lands on row 2 as intended
- **`<segmented-ui>`**: stale indicator stripping — duplicate pills on re-render eliminated
- **`<stat-ui>`**: chart slot bottom-aligns to value baseline; aspect 4:3
- **`<toolbar-ui>`** mixed-controls demo: `<segment-ui>` (not `<button-ui>`) inside `<segmented-ui>`

### Maintenance — slot-vocab-vs-css audit reached 0 critical findings

- `audit-slot-vocab-vs-css` substrate backlog closed → gate now enforced. Multi-yaml + @scope-aware pairing landed in the audit script.
- 5 Wave 5a/b/c/d gap-analysis flips landed (substrate-verified as covered; no new code).

## [0.6.37] — 2026-05-25

### Changed — `--a-warning-bg` redirected from `-strong` to `-20-tint` (bright caution-tape amber, scheme-independent)

- **Token-system fix for the muddy warning surface class.** Previously `--a-warning-bg` was an alias for `--a-warning-strong` (= `-50`, mid-tone amber). Paired with `--a-warning-fg` (= `-text-strong` = `-10-shade`, dark brown — per `semantics.css:309` "warning is light-colored — text on warning fills should be dark"), the pair collapsed to brown-on-brown chips. The local fix shipped in `<tag-ui>` / `<badge-ui>` (literal `--a-warning-20-tint` override) is now hoisted to the L3 token layer so every consumer benefits: progress-row, rating, password-strength, agent-artifact, text[color=warning], timeline. `<button-ui color="warning">` updated to read `--a-warning-bg` (was `-strong`) so it joins the fix. Local tag/badge `-20-tint` overrides removed — they now read the canonical `-bg` token cleanly.
- **`-strong` unchanged** — still the L2 saturated mid-tone, used as a foreground/icon color by alert / chart / code / empty-state / agent-trace (those use it AS a color, not as a surface to put text on, so the `light-dark` swap stays appropriate).
- **Why `-20-tint` is bright in both schemes**: the lightness math `l-max - 0.2*(l-max - l-min)` is scheme-independent (no `light-dark()` swap), so warning surfaces keep the caution-tape look in dark mode too. Other family `-bg` tokens stay scheme-flipping because they pair with `-fg` (= `-05-tint` near-white) which has enough contrast either way.
- Files: `styles/colors/semantics.css`, `components/button/button.css`, `components/tag/tag.{css,test.js}`, `components/badge/badge.css`.

### Added — `<tag-ui>` + `<badge-ui>` gain `[tone="outline"]` third chip style

- **Outline tone**: transparent bg + family-colored 1px border + family-colored text. The lightest visual weight; good in dense data tables or faceted filter rows where multiple chips would otherwise compete. Family variants resolve to `--a-{family}-text` + `--a-{family}-border`; neutral default resolves to `--a-fg-muted` + `--a-border`.
- **Vocabulary mirror across the two primitives**: tag-ui defaults to `solid` tone (filter chip / status pill identity); badge-ui defaults to `muted` (quiet metadata identity). Both accept all three values (`solid` | `muted` | `outline`) so authors can swap shape without learning a new prop. Badge-ui's existing `primary` variant remains as a shortcut for `<badge-ui variant="accent" tone="solid">`.
- New regression guards: 6 source-grep tests on tag-ui for outline-tone rules.
- Files: `components/tag/tag.{css,yaml,test.js,examples.html}`, `components/badge/badge.{css,yaml,examples.html}`, regenerated `.a2ui.json` sidecars.

## [0.6.36] — 2026-05-24

### Changed — `<pagination-ui>` composes `<button-ui>` for every cell + adds universal `[size]` thread-through

- **Native-primitive leak closed.** `pagination.class.js:128` previously stamped raw `<button>` for every page / prev / next cell — the exact failure mode `dogfood-sweep` mode 4 audits for, and the same shape that produced the FB-55 silent-failure trap on `<admin-shell>`. Substrate authors reading pagination as the canonical numbered-button pattern would carry the leak into downstream components. Substrate now stamps `<button-ui>` for every cell so chrome / hover / focus-ring / disabled-state / active-fill come from button-ui's token chain (the canonical surface matrix), not a pagination-tier re-impl.
- **`[size]` prop added** — reflected enum (`sm` | `md` | `lg`, default `md` — aligned with `<button-ui>`'s default since pagination is now a button-ui composite). Threads through to every nested `<button-ui size=…>` so pagination honors the substrate's 24/30/36 px universal size system (with `[density]` modifier). Previously pagination hardcoded `--a-size-sm` (24 px), so consumers on md/lg surfaces had no escape hatch.
- **Active state now `variant="primary"`** instead of a pagination-tier `[data-active]` + custom-bg-token chain. The filled-accent state inherits from button-ui's primary surface matrix — one token chain, one source of truth.
- **Prev / Next now use Phosphor `caret-left` / `caret-right`** via `<button-ui icon=…>` (was: `‹` / `›` Unicode glyphs). Declared in `static requiredIcons` so the audit picks them up.
- **`variant="button"` mode** swaps each non-active cell to `variant="outline"` (the bordered 1×1 mode). `pagination.css` shrunk from 165 → 71 lines — the 94 lines of button styling rules collapsed into button-ui composition.
- **Constant-width compact-mode layout** — `#buildRange` redesigned around an invariant `W = 2*siblings + 5` (= 7 cells at siblings=1; 9 at siblings=2). Three layouts (near-start / middle / near-end) each yield exactly W cells so the row width stays stable when the current page advances. When `total ≤ W`, every page renders (no ellipsis — compressing wouldn't save horizontal slots). Replaces the "bridge if only 1 is skipped" heuristic from an earlier draft, which produced asymmetric cell counts under page advances (page 3 → 6 cells, page 4 → 7 cells — visibly jumpy).
- **17 new DOM + source-grep regression tests** (`pagination.test.js`, NEW). Guards: no raw `<button>` in source; `requiredIcons` declared; size threads to every nested button-ui; active page is `variant="primary"`; rest variant tracks the host's `variant=` (ghost/outline); **width invariance under all page positions for total=10 siblings=1 (always 7 cells) and total=20 siblings=2 (always 9 cells)**.
- Files: `components/pagination/pagination.{class.js,yaml,css,test.js}`, regenerated sidecar + corpus.

### Fixed — `<tag-ui>` dismiss X + slotted icons now inherit the variant text color

- **Visible after the solid-default flip**: the dismiss X (`[slot="dismiss"]`) was hardcoded to `--a-fg-muted` (a neutral grey) regardless of the host's variant, so the X disappeared against saturated solid pills — a near-invisible grey X on the Info/Success/Warning/Error bgs. Same trap on any slotted leading icon: tag had no `currentColor` inheritance rule, so `<icon-ui>` children rendered in their own foreground color rather than the variant's.
- **Fix**: dismiss `color` token now defaults to `currentColor` at `opacity: 0.85` (quieter than the label, but tracks every variant — near-white X on saturated bg, dark X on warning amber, fg-muted X on quiet chrome). Hover restores `opacity: 1` and overlays a `color-mix(in oklch, currentColor 18%, transparent)` bg highlight — same color family as the host, not a stark neutral wash. The dismiss `<icon-ui>` now stamps with `weight="bold"` so the X reads crisply at small size on every variant (was implicit regular — too thin against bright bgs like warning amber). NEW `:scope > icon-ui { color: currentColor; flex-shrink: 0 }` rule mirrors `<badge-ui>`'s convention so leading-icon legends/status chips read as a single color-coded unit.
- Two new component tokens for the dismiss opacity (`--tag-dismiss-opacity`, `--tag-dismiss-opacity-hover`) replace the prior color-swap tokens (`--tag-dismiss-fg-hover` removed — opacity-driven hover instead). Files: `components/tag/tag.css`.

### Fixed — `<tag-ui variant="warning">` solid pill: bright amber bg instead of muddy mid-brown

- **Visible after the solid-default flip**: the canonical `--a-warning-bg` + `--a-warning-fg` pair collapses for warning. `--a-warning-fg` resolves to `--a-warning-10-shade` (a dark brown — per `semantics.css:309` "warning is light-colored — text on warning fills should be dark"), but `--a-warning-bg` (= `--a-warning-strong` = `-50`) is a mid-tone amber. Dark text on mid-tone amber gives the muddy brown-on-brown look the user reported. The other family variants (info/success/danger) all have `-text-strong = -05-tint` (near-white) which contrasts cleanly against their `-50` saturated bg, so they don't hit this trap.
- **Fix at the tag layer**: `:scope[variant="warning"]` now uses the literal `--a-warning-20-tint` step (bright caution-tape amber, scheme-independent) as bg + keeps `--a-warning-fg` as dark text. Restores the bright-amber-with-dark-text shape `semantics.css` intends. Other family variants unchanged. Regression guard added that hard-fails any revert to `--a-warning-bg` for the warning solid bg.
- **Token-system follow-up flagged (not shipped)**: this is a symptom of `--a-warning-bg` pointing at the wrong step. Same muddy contrast likely surfaces wherever `--a-warning-bg` + `--a-warning-fg` is paired (`button-ui variant="warning"`, `chart-ui` warning bars, `rating-ui` filled fg, `progress-row variant="warning"`). A token-side fix that redirects `--a-warning-bg` to `--a-warning-20-tint` would resolve all consumers in one stroke but needs a visual sweep across those components first. Files: `components/tag/tag.{css,test.js}`.

### Changed — `<tag-ui>` family variants default to **solid** fill (BREAKING-visual)

- **`<tag-ui variant="info|success|warning|danger">` now renders as a saturated bg + on-strong (near-white) text** — the chip IS the state, not a tinted metadata label. Aligns the chip vocabulary so consumers can read tag-ui as a status pill at a glance. Previously: muted tinted bg with `--a-{family}-bg` (saturated solid) text — an off-canonical pair that read as "passive metadata with bold color" and diverged from `<badge-ui>`'s canonical muted pair.
- **Opt-out**: NEW `[tone="muted"]` attribute drops a family variant back to the canonical muted pair `--a-{family}-muted` + `--a-{family}-text` (scheme-flipping text — the same pair `<badge-ui>` uses as its default). Use on metadata chips in dense lists where the saturated default would compete for attention.
- **The `default` variant (no family) is unchanged** — it stays as quiet `--a-bg-muted` + `--a-fg` chrome. Opt in to a high-contrast inverse stamp via `<tag-ui tone="solid">` (no variant); resolves to `--a-fg` bg + `--a-bg` fg.
- **Author migration**: existing `<tag-ui variant="info|success|warning|danger">` markup auto-flips to the new saturated default — every existing tag in the codebase becomes louder. If any specific surface needs the prior look, append `tone="muted"`. 75 occurrences across 30 substrate/app/playground files identified; review per surface.
- Source-grep regression guards added: 4 family variants verified to use the solid pair as default; 4 `[tone="muted"][variant=…]` rules verified to use the canonical muted pair; `[variant="default"]` verified unchanged; `[tone="solid"]` neutral inverse verified. Files: `components/tag/tag.{css,yaml,test.js,examples.html,a2ui.json}` + regenerated corpus catalog.

### Fixed — placeholder pseudo no longer pushes the caret to its right after type-then-delete

- **Bug report**: with `<input-ui placeholder="Select country…">`, typing text then deleting it left the caret visually at the END of the placeholder text instead of at position 0. Same trap on `<textarea-ui>`, `<combobox-ui>`, and `<tags-input-ui>`. Root cause: the empty-state placeholder is implemented via `[data-empty]::before { content: attr(data-placeholder); }`, rendered as an **inline** pseudo box. When the contenteditable is empty, the caret IS at offset 0 of the empty text node — but the in-flow `::before` pseudo precedes the text content, occupying inline space, so the caret renders to the right of the pseudo's box. (Only happens after type-then-delete because that path leaves the contenteditable focused, making the caret visible at its true position-0 location.)
- **Fix**: pseudo is now `position: absolute; inset: 0; padding: inherit` — fills the host's content-box without occupying inline-flow space, so the caret renders at the actual content-start where it belongs. Host elements gain `position: relative` as the positioning anchor. For `<tags-input-ui>` + `<combobox-ui>` the pseudo also uses `display: flex; align-items: center` so the placeholder text vertically aligns with the host's flex-centered line-box.
- Regression guards added to `input.test.js` — three source-grep assertions (`:scope` declares `position: relative`, pseudo declares `position: absolute`, pseudo carries `inset: 0` + `padding: inherit`) that hard-fail if the in-flow shape ever re-appears.
- Files: `components/input/input.{css,test.js}`, `components/textarea/textarea.css`, `components/tags-input/tags-input.css`, `components/combobox/combobox.css`.

## [0.6.35] — 2026-05-24

### Fixed — `<popover-ui>` + `<menu-ui>` yaml `slots:` blocks declare the canonical `trigger` / `content` vocabulary (FB-62)

- **CSS positioned slots that the yaml said didn't exist.** `popover.yaml` pre-patch line 43: `slots: {}` (empty). But `popover.css` positions `[slot="trigger"]` and `[slot="content"]` across 7 selectors (open/closed states + first/last-child margin guards), and the yaml's own a2ui rule already states "popover-ui wraps a focusable trigger (slot=\"trigger\") + arbitrary interactive content (slot=\"content\")". Parallel case in `menu.yaml`: `slots:` declared only `default`, but the a2ui rule said "MUST have exactly one child with slot=\"trigger\"". Three sources said the slots existed, one source (the structured `slots:` SoT) said they didn't. Surfaced as 6 INFO findings in the adia-ui-kit v3.4.X recipe-staleness audit; suppressed via `KNOWN_PARENT_SLOT_DRIFT`.
- **Fix**: `popover.yaml` `slots:` now declares `trigger` (focusable element that opens the popover; MUST be focusable for keyboard accessibility — bare `<span>`/`<div>` won't work) and `content` (the body, hoisted to top-layer via `:popover-open`). `menu.yaml` `slots:` adds `trigger` (Required — exactly one child must have `slot="trigger"`; without it the menu cannot open). Pure yaml backfill — no JS, no CSS, no API change. Sidecar `.a2ui.json` files regenerated. Kit's `KNOWN_PARENT_SLOT_DRIFT` allowlist entries can be removed next cycle. Files: `components/popover/popover.{yaml,a2ui.json}`, `components/menu/menu.{yaml,a2ui.json}`.

### Fixed — chat-input + textarea + input substrate prose accurately documents Enter→submit (FB-63)

- **`submit-on-enter` was a documentation phantom across the substrate, and the prose around it carried two adjacent lies.** Three input primitives all dispatch a bubbling `submit` event on Enter (without Shift) unconditionally: `textarea.class.js:97-106`, `input.class.js:713-727`, and `chat-input.js` (which delegates to the inner `<textarea-ui>`'s submit event at line 147). None of them observe a `[submit-on-enter]` attribute — yet the substrate's own demos, yamls, and a2ui rules used `<chat-input-ui submit-on-enter>` as canonical authoring shape. Two adjacent lies discovered in the same cluster: (1) `textarea.yaml` description + a2ui rule + `textarea.examples.html` claimed "Enter inserts a newline — textarea-ui does NOT emit a `submit` event" — false per `textarea.class.js:97-106`. (2) `input.yaml` + `input.examples.html` claimed "only chat-input-ui reflects the [submit-on-enter] attribute" — false, NONE of them reflect it.
- **Fix**: `submit-on-enter` stripped from `patterns/chat-shell/chat-shell.examples.html` (1 occurrence). `chat-input.yaml` event description + `chat-input.js` docstring now state the truth — Enter→submit is unconditional, delegated from inner `<textarea-ui>`, no opt-in/opt-out. `textarea.yaml` description (line 10-18) + a2ui rule (line 119-123) + `textarea.examples.html:253` corrected: "Enter (without Shift) dispatches a bubbling `submit` event; Shift+Enter inserts a newline. Unconditional — no opt-in/opt-out attribute." `input.yaml` + `input.examples.html` adjacent prose: stripped "only chat-input-ui reflects [submit-on-enter]" claim; replaced with accurate "the plain `<input-ui>` primitive ALSO fires `submit` on Enter (unconditional, no opt-in attribute); `<chat-input-ui>` simply builds on that semantic." Companion strip in `@adia-ai/web-modules` chat-cluster (see its changelog). Sweep-grep `submit-on-enter` in authored sources now returns only the two truth-note phrasings (both say "no `[submit-on-enter]` opt-in/opt-out"). Sidecar `.a2ui.json` files regenerated; corpus catalog + rules text rebuilt. Files: `components/chat-thread/chat-input.{yaml,js,a2ui.json}`, `components/textarea/textarea.{yaml,examples.html,a2ui.json}`, `components/input/input.{yaml,examples.html,a2ui.json}`, `patterns/chat-shell/chat-shell.examples.html`.

### Fixed — `<spinner-ui variant="dots">` dot spacing now even (was lopsided)

- **The pre-fix dots layout used `::before` + `::after` pseudo-elements** as the outer two dots and a `box-shadow` on `::before` to paint the middle dot. Three breakages: (1) the box-shadow middle dot doesn't participate in the host's flex-gap math, so the gap between dot1↔dot2 and dot2↔dot3 was unequal (the screenshot showed two dots close together on the left, then one isolated on the right). (2) The box-shadow dot couldn't carry its own `@keyframes` animation, so the middle stayed at default scale while the outer two bounced — a "middle is anchored" look. (3) The author comment explicitly acknowledged the hack ("simpler, more robust shape is to overlay three identical dot pseudos via a flexbox container of pseudo + two child pseudos isn't possible without a stamped child"). Fix: `class.js` `#syncDots()` now stamps three real `<span data-spinner-dot=N>` children when `variant="dots"`. Flex-gap distributes the row evenly; each child gets `animation-delay: 0 / T/3 / 2T/3` so the bounce reads as a left-to-right wave. Reduced-motion + `[paused]` selectors updated to include `> [data-spinner-dot]`. Files: `components/spinner/class.js`, `components/spinner/spinner.css`, `components/spinner/spinner.test.js`.

### Added — `<spinner-ui variant="knight">` knight-rider sliding-bar variant

- **NEW `knight` variant** — a horizontal track with a thumb that slides back-and-forth via `animation-direction: alternate`. Reads as a determinate-looking bar but is indeterminate by intent (no progress value; same loading semantics as the other variants). Tokens added: `--spinner-bar-track-width` (default `4rem`), `--spinner-bar-track-height` (default `var(--spinner-stroke)` — visual continuity with arc/ring), `--spinner-bar-thumb-ratio` (default `0.3` — thumb is 30% of track). Keyframe math: `translateX((1 / ratio - 1) * 100%)` of thumb-relative width = `(track - thumb)` px regardless of track-width override. Reduced-motion path hides the thumb and falls through to the shared ellipsis fallback. yaml `variant` enum + description updated. Files: `components/spinner/spinner.css`, `components/spinner/spinner.yaml`, `components/spinner/spinner.examples.html`.

### Added — `<integration-card-ui>` demo page now ships a live CRUD drawer wiring

- **`integration-card.examples.html` previously showed cards only as static visual showcases** — clicking `Connect` / `Configure` dispatched the `connect` / `configure` events into the void (no consumer wiring on the docs page). Added a "Wired to drawer (CRUD)" section at the top with three cards (`slack` / `github` / `linear`) bound to a shared `<drawer-ui>` form. NEW `integration-card.examples.js` setup module: listens for bubbled events at the grid level, opens the drawer in `connect` or `configure` mode, persists token to an in-memory store keyed by `provider`, and on Save / Disconnect mutates the originating card's `status` attribute. Demonstrates the full Create / Update / Delete loop expected of consumers per SPEC-062 §134-136. Sitemap entry gains `"setup": ".../integration-card.examples.js"` so the docs router auto-imports the module on route load (same pattern as `agent-feedback-bar`, etc.). Files: `components/integration-card/integration-card.examples.html`, `components/integration-card/integration-card.examples.js` (NEW), `site/sitemap.json`.

### Changed — `<date-range-picker-ui>` trigger surface mirrors `<select-ui>` / `<calendar-picker-ui>`

- **`date-range-picker.css` had only `:scope > [slot="trigger"] { min-width: 14em }`** — the entire trigger chrome relied on `<button-ui variant="outline">`'s defaults, which produced an off-pattern surface vs. the rest of the input/picker family. Trigger now styled with the canonical contract: flex row, `min-height: var(--a-size)`, `padding: 0 var(--a-ui-px)`, `border-radius: var(--a-radius)`, `border: 1px solid var(--a-ui-border)`, `background: var(--a-ui-bg)`, `justify-content: space-between` so leading icon + label sit start, trailing caret sits end. Adds canonical `--date-range-picker-trigger-{height,px,gap,radius,bg,bg-hover,fg,border,border-hover,focus-ring}` token shelf — same tokens `<calendar-picker-ui>` already exposes, so consumers can override either family-side. Hover/focus-visible rules attached for parity. Files: `components/date-range-picker/date-range-picker.css`.

### Fixed — `<spinner-ui>` rotational cadence (was 300ms, now 0.8s)

- **`--spinner-duration-default` retargeted from `var(--a-duration-slow)` (300 ms — interaction-duration scale, tuned for hover/focus transitions) to `0.8s`** — canonical loader cadence. A full revolution at 300 ms reads as a frantic blur; 0.8 s gives smooth, perceptible motion that signals "loading" without alarming the eye. The `--a-duration-*` token family (120 / 250 / 300 ms) is now correctly reserved for state transitions; spinner uses its own rotational tempo. Files: `components/spinner/spinner.css`.

### Fixed — `<modal-ui>` header no longer reserves a phantom flex slot for empty `text=`

- **`modal.css`'s `:scope [slot="header"]::before { content: attr(text); flex: 1 }` rule generated an empty-content pseudo-element with `flex: 1` whenever the host's `text` attr was empty or unset.** That phantom flex slot claimed the row's free space and pushed any slotted header content (e.g., `<confirm-dialog-ui>`'s stamped `<text-ui part="title">` + auto-stamped close button) to the trailing edge — the title rendered tight against the X close button instead of left-aligned. Same drift class as the `tag-ui` / `badge-ui` / `segment-ui` / `button-ui` phantom-gap arc (April–May 2026).
- **Fix (defense-in-depth)**: CSS gate the `::before` on `[text]:not([text=""])` so the pseudo isn't generated when text is absent or empty; class.js no longer writes `text=""` on the header when `this.text` is falsy. Either side alone would fix the symptom; both prevent recurrence from independent edit paths. Visible effect: `<confirm-dialog-ui>` now renders "Save changes?" left-aligned with X on the right, matching the canonical header pattern. Files: `components/modal/modal.css`, `components/modal/class.js`.

### Added — `<calendar-grid-ui>` `range-start` / `range-end` props for in-range cell highlighting

- **`<calendar-grid-ui>` gains two new reflected string props — `range-start` and `range-end` (ISO `YYYY-MM-DD`).** When both are set + ordered, day cells strictly between the endpoints get `[data-in-range]` stamped on them. New CSS state styles in-range cells with `--calendar-grid-day-bg-in-range` (defaults to `--a-accent-muted` — the muted accent strip) and `--calendar-grid-day-fg-in-range`. The endpoints themselves continue rendering via the `value` prop's `[data-selected]` state (full `--a-accent` fill); the in-range middle is visually subordinate. Per-day cells also get `data-range-start` / `data-range-end` markers for future shape work (rounded caps, etc.).
- **`<date-range-picker-ui>` now wires this contract**: in `render()`, the effective from/to (pending click-state OR committed value) is pushed onto BOTH calendar-grid panes via `range-start` / `range-end` attributes, so each pane independently lights up its in-range cells. Closes the "selected start + end but no fill between" trap. Files: `components/calendar-grid/calendar-grid.css`, `components/calendar-grid/class.js`, `components/calendar-grid/calendar-grid.yaml`, `components/date-range-picker/class.js`.

## [0.6.34] — 2026-05-23

### Added — Wave 1 + Wave 2: 10 P1 components shipped from SPEC pilot

- **Wave 1 — 8 components** (commit `5ad89318d`): SPEC-001, SPEC-022, SPEC-036, SPEC-037, SPEC-043, SPEC-049, SPEC-056, SPEC-062. First wave of the SPEC-driven component-authoring pilot. Each ships full yaml + a2ui.json + .js class + .css + .examples.html + .test.js per the four-axis contract.
- **Wave 2 — 2 components** (commit `501053880`): SPEC-019, SPEC-038. Completes the 10-component pilot.
- **`<calendar-grid-ui>` substrate primitive extracted** (commit `dcf16cdf8`): a substrate-tier primitive supporting the calendar-family Wave 2 components.
- **Wave 1/2 visual-QA fixes** also landed in `dcf16cdf8`.
- Component count: 92 → 100 primitives (8 net additions). yaml/a2ui.json/d.ts coherence preserved.

### Fixed — OD-5 token-shadowing closed across 98 components (`-default` suffix sweep)

- **`-default` suffix added to all leaf component tokens** (commit `e694be9d0`, single-component fix at `cbdf11f09`): unlocks themability/consumer override across all 98 component CSS files. Pattern: `--button-bg: var(--a-fg);` → `--button-bg-default: var(--a-fg); ... background: var(--button-bg, var(--button-bg-default));`. Consumer overrides at any ancestor surface now apply correctly without being shadowed by the leaf-token default. Closes OD-5 (consumer-side override-discoverability regression).

### Changed — `dist/web-components.min.js` + `dist/web-components.min.css` rebuilt

- Bundle pass absorbed Wave 1+2 + OD-5 + `<calendar-grid-ui>` source changes. Bundle sizes adjusted accordingly.

### Maintenance — `<spinner-ui>` exempted from size-wiring audit

- **`scripts/release/check-size-wiring.mjs`** YAML_SIZE_EXEMPT: added `spinner` — `size:` prop drives diameter via `--spinner-size` matching the icon-ui ladder (sm 14px / md 16px / lg 20px), parallel to existing exemptions for `icon`, `rating`, `swatch`. Content-driven loading glyph, not row-family. Surfaced by the audit after spinner joined the 100-component roster.

## [0.6.33] — 2026-05-23

### Changed — `dist/icons-manifest.js` comment header updated (FB-53 §1 companion)

- **`dist/icons-manifest.js`** is regenerated as a companion to the web-modules co-emit (see `@adia-ai/web-modules` [Unreleased] entry). The manifest file content is byte-identical except for the header comment, which now mentions co-location with both `web-components.min.js` AND `web-modules/everything.min.js`. `dist/web-components.min.js` rebuilt as part of the same bundle pass; no functional change. Files: `dist/icons-manifest.js`, `dist/web-components.min.js`.

### Fixed — `<table-ui raw>` is now a true visual-only passthrough (FB-53 §2)

- **`<table-ui raw>` previously applied the chrome reset but still ran the full data lifecycle**, so wrapping a consumer-authored native `<table>` with no `.data` set produced a phantom `[data-empty]` "No data" overlay above the consumer's content. Root cause: `class.js` `render()` had no `this.raw` short-circuit; `#renderOverlays` injected the empty-state placeholder unconditionally whenever `this.#data.length === 0`. Fix: `render()` now early-returns when `this.raw` is set — no header injection, no body reconciliation, no overlays, no aggregation, no pagination. Raw is now the documented "consumer owns the body shape" mode. yaml description expanded with the full semantic. NEW 4 vitest cases in `table.test.js` (lock the contract: no `[data-empty]`, no `[data-header]`, no `[data-body]`, no rowgroups; non-raw default still injects them); 18/18 passing. Files: `components/table/class.js`, `components/table/table.yaml`, `components/table/table.test.js`.

### Fixed — `<field-ui>` no longer reserves row-gap for an absent message row (FB-54)

- **`field.css` `:scope` declared `row-gap: var(--field-gap)` unconditionally**, so when no `[slot="hint"]` and no `[slot="error"]` were present, the grid's zero-content message row still reserved `--field-gap` (~8 px) between itself and the control row. Result: `<field-ui inline>` rendered ~7-8 px taller than non-field-ui siblings in a `<row-ui align="center">`, visually "lifting" the label relative to sibling control text. Fix: move `row-gap: var(--field-gap)` into a `:has(> [slot="hint"], > [slot="error"])` guard so the gap is reserved only when a message slot is actually present. Existing consumers with hints/errors get the same rendering; consumers without get a tighter, sibling-matching height. `:has()` is baseline (Chromium 105 / Safari 15.4 / Firefox 121 — well within ADR-0007's floor). NEW source-grep regression tests in `field.test.js` lock the contract (19/19 passing). Files: `components/field/field.css`, `components/field/field.test.js`.

### Changed — `<tabs-ui>` row height now anchors to `var(--a-size)` (was hardcoded `2.25rem`)

- **`--tabs-button-height` retargeted from the hardcoded `2.25rem` (36 px, equivalent to `[size="lg"]`) to `var(--a-size)`.** Tabs now respond to the universal `[size]` attribute system like `button-ui`, `select-ui`, `nav-item-ui`, and other row-family primitives — `<tabs-ui size="sm">` → 24 px, default → 30 px, `<tabs-ui size="lg">` → 36 px (all at default `--a-density: 1`). Default-rendered tabs visibly shrink from 36 px → 30 px. **Behavior change** for consumers depending on lg-by-default tabs: set `[size="lg"]` on the host (or override `--tabs-button-height: var(--a-size-lg)`) to restore. Caught by NEW `check:size-wiring` audit (commit `2d931b980`). Files: `components/tabs/tabs.css`, `components/tabs/tabs.examples.html`.

### Maintenance — `<kbd-ui>` exempted from size-wiring audit

- **`scripts/release/check-size-wiring.mjs`** EXEMPT + YAML_SIZE_EXEMPT: added `kbd-ui` to both — `--kbd-height: 1.25rem` is intentional (keyboard-key cap matching small-text rhythm, not row-family) and the `size:` prop drives font-size scale, parallel to existing exemptions for `badge`, `tag`, `icon`. Pre-existing technical debt surfaced by the new audit in v0.6.32; not a v0.6.33-introduced violation. Files: `scripts/release/check-size-wiring.mjs`.

## [0.6.32] — 2026-05-23

### Fixed (P0) — `everything.js` CDN bundle now includes all primitives (FB-51)

- **`packages/web-modules/everything.js` was shells-only in v0.6.29–v0.6.31** — it registered all four shells but did NOT register the 95+ primitives (`search-ui`, `select-ui`, `field-ui`, `stat-ui`, `row-ui`, etc.). Consumers using the CDN-first "just drop in `everything.min.js`" path hit a hard `TypeError: Cannot read properties of undefined (reading 'toLowerCase')` on any interaction with a primitive element. Root cause: `bundle-js.mjs` assembled the bundle from only the web-modules `index.js`, which re-exports shells but not primitives. Fix: bundle now explicitly includes `web-components/index.js` (all primitives) before web-modules. File: `packages/web-modules/everything.js`.

### Added — `dist/icons-manifest.js` CDN icons-manifest; `core/icons-phosphor.js` CDN prefix (FB-51)

- **`packages/web-components/dist/icons-manifest.js`** is now included in the npm tarball's `files:` field and exposed via the `./dist/icons-manifest` export path. Consumers loading via CDN received a 404 on the manifest fetch because the file was excluded from the published package. File: `packages/web-components/dist/icons-manifest.js`.
- **`core/icons-phosphor.js`** — the non-Vite fallback path now destructures `{ cdn: cdnBase, ...weights }` from the manifest. When `cdnBase` is present (as it is in the new generated manifest), SVG fetches use the CDN URL (`https://cdn.jsdelivr.net/npm/@phosphor-icons/core@X.Y.Z/assets/`) instead of `/node_modules/...` (localhost-only). File: `core/icons-phosphor.js`.

### Added — `site/llms.txt` + `scripts/build/llms.mjs` machine-readable docs

- **`site/llms.txt`** is an LLMs.txt-spec document generated by `scripts/build/llms.mjs` and included in every EXE site deploy. It exposes the full component API surface and usage patterns in a format suitable for LLM context injection. Build step wired into `build:site` as a pre-step.

### Added — `status: stable` field on all 141 component YAMLs

- Every component YAML now carries `status: stable`, making the stability signal machine-readable in A2UI catalog queries and corpus retrieval. The field is optional in the schema; previously it defaulted to absent (implicitly stable for all released components).

### Documentation — anatomy sections added to all `components/`, `styles/`, and `traits/` example pages

- **Canonical anatomy sections** (`data-property="slots|data-attrs|keyboard|css-vars|a2ui|related"`) added to all 124 component example pages, 56 trait example pages, and 10 token (`styles/`) example pages via `scripts/docs/anatomy-sweep.mjs`. The sweep generates slot tables, keyboard interaction tables, CSS custom property tables, A2UI composition rules, and related-component links directly from each component's `.a2ui.json` sidecar data. Existing `properties`, `events`, `CSS Tokens`, and `usage` sections were normalized to the canonical `data-property` vocabulary. Files: `components/**/*.examples.html`, `styles/**/*.examples.html`, `traits/**/*.examples.html`.

### Fixed — `<tag-ui>` / `<badge-ui>` / `<segment-ui>` phantom trailing gap when content is slotted (not `[text]`)

- **Three primitives in the same family rendered `::after { content: attr(text) }` unconditionally.** When consumers passed text via the default slot (`<tag-ui>Beta</tag-ui>`) instead of the `[text]` attribute, `attr(text)` resolved to `""` — an empty string IS generated content, so the pseudo-element existed as a flex item. Combined with the host's `display: inline-flex` + `gap`, gap was applied between the slotted text and the empty `::after`, adding a phantom `--{component}-gap`-wide trailing space (~4 px). Fix: gate the rule on `:scope[text]` so the pseudo-element is only generated when the attribute exists — matches the already-correct form in `button.css`. NEW `tag/tag.test.js` (3 cases) pins the contract via source-grep so the ungated form can't drift back. Files: `components/tag/tag.css`, `components/tag/tag.test.js`, `components/badge/badge.css`, `components/segment/segment.css`.

## [0.6.31] — 2026-05-23

### Documentation — 3 legacy `patterns/*` example files rewritten as canonical hubs

Three pre-v0.4.0 `patterns/*.examples.html` files used legacy markup retired by ADR-0024 (`<aside data-sidebar>`, `<main>`, `<dialog data-command>` for admin-shell; raw `<header data-chat-name>` + `<section data-chat-messages>` + `<chat-input-ui data-chat-input>` for chat-shell; raw `<header>` + `<div data-editor-body>` + `<pane-ui data-left|data-right>` + `<div data-canvas>` for editor-shell). All three rewritten as canonical-hierarchy pattern hubs — code-ui block with the bespoke vocabulary + part-by-part table linking to `/site/components/*` reference pages + authoring rules + "where to next" cross-links. Affects EXE-rendered docs only (`patterns/` is NOT in the npm tarball's `files:` field). Files: `patterns/admin-shell/admin-shell.examples.html`, `patterns/chat-shell/chat-shell.examples.html`, `patterns/editor-shell/editor-shell.examples.html`.

### Maintenance

- Lockstep version bump tied to docs surface scrub. No source changes in this package; bumped to maintain the 9-package version coherence enforced by `scripts/release/check-lockstep.mjs`.

## [0.6.30] — 2026-05-23

### Added — `dist/web-components.min.js` rolled-up CDN-ready JS bundle

- **`dist/web-components.min.js`** ships as part of the tarball (1.01 MB raw / ~330 KB gzipped / ~250 KB brotli). Single esbuild-bundled file — flattens every relative import from `index.js` through the 95 primitive registrations + core + tokens into one ESM payload. Browser-target Chromium 125+ / Safari 18+ / Firefox 129+ (matches existing `check:lightningcss-build` baseline).
- **New `./js/bundled` export** in package.json `exports` map points at the bundle. Existing `./` (root) export unchanged — bundler consumers (Vite, webpack, esbuild) continue using it.
- Companion to the v0.6.29 CSS bundle: HTML-first / CodePen / marketing pages can now drop in two `<link>` + one `<script type="module">` and have working AdiaUI primitives. See [`/site/getting-started/cdn-usage`](https://ui-kit.exe.xyz/site/getting-started/cdn-usage) for the full template.
- Generation: `npm run build:bundle-js` (esbuild bundle mode). Freshness enforced at release time via `check:js-bundles-fresh` gate (mirrors `check:css-bundles-fresh` discipline). `npm run build:bundles` runs both CSS + JS together.

### Documentation — README CDN section + AI-agent discoverability

- **README.md** grows two new sections: "CDN (no bundler — CodePen, marketing pages, static HTML)" with the bundle URLs + choice-tree, and "Using with AI agents" pointing at the `adia-ui-kit` skill for Claude Code / Cursor / Windsurf / Claude Desktop harnesses.

## [0.6.29] — 2026-05-23

### Added — `dist/web-components.min.css` rolled-up CDN-ready bundle

- **`dist/web-components.min.css`** ships as part of the tarball (443.1 KB unmin → ~50KB gzipped through any CDN edge). Single LightningCSS-bundled file — flattens every `@import` from `index.css` through the 95 per-component CSS files + tokens + resets into one minified payload. Replaces the @import-waterfall behavior for HTML-first consumers (no bundler) and enables one-line CDN usage: `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@adia-ai/web-components@0.6.29/dist/web-components.min.css">`.
- **New `./css/bundled` export** in package.json `exports` map points at the bundle. Existing `./css` export (@import barrel) is unchanged — bundler consumers (Vite) continue using it. Files: `dist/web-components.min.css`.
- Generation: `npm run build:bundle-css` (LightningCSS bundle mode, Vite-8-default targets matching `check:lightningcss-build`). Freshness enforced at release time via `check:css-bundles-fresh` gate (mirrors `check:embeddings-fresh` discipline).


### Fixed — `text-align: start` reset on 18 components to block inherited centering (§text-align-reset)

- **18 components — `input-ui`, `textarea-ui`, `search-ui`, `select-ui`, `check-ui`, `radio-ui`, `switch-ui`, `toggle-group-ui`, `segmented-ui`, `range-ui`, `slider-ui`, `stat-ui`, `badge-ui`, `tag-ui`, `pagination-ui`, `nav-item-ui`, `progress-row-ui`, `kbd-ui` — lacked a `text-align: start` reset in their `:where(:scope)` rule.** `text-align` is an inherited CSS property; when any of these components is a descendant of a container declaring `text-align: center` (or any non-default alignment), the alignment propagated into the component and visibly misaligned its content. Most critically: `input-ui` placeholder and typed value would center-align inside the input chrome; `select-ui` selected value and placeholder would shift; `textarea-ui` text would center. Also affected: `::after`-rendered label text on check/radio/switch/toggle-group/segmented (label text would visually shift); inline labels on badge/tag/kbd; stat values; pagination page numbers; nav-item labels. Fix: added `text-align: start;` to the `:where(:scope)` block of all 18 affected components. Files: `components/{input,textarea,search,select,check,radio,switch,toggle-group,segmented,range,slider,stat,badge,tag,pagination,nav-item,progress-row,kbd}/*.css`.

## [0.6.28] — 2026-05-23

### Maintenance
- **Lockstep version bump only.** No source changes in this package; bumped to maintain the 9-package version coherence enforced by `scripts/release/check-lockstep.mjs`. Substantive v0.6.28 work shipped in editor-sidebar rAF debounce for wide->narrow display-flip transient (FB-50, 3rd iteration of the latch saga; belt-and-braces with v0.6.26 width guard + v0.6.27 computedStyle guard). See `packages/web-modules/CHANGELOG.md#0628--2026-05-23` for details.

## [0.6.27] — 2026-05-22

### Added — `chart-ui[loading]` skeleton placeholder (§FB-12)

- **`<chart-ui loading>` shows a `<skeleton-ui>` placeholder** instead of the chart body while data is being fetched. Preserves the element's aspect-ratio dimensions so the skeleton occupies the same space the chart will fill once data arrives. Parity with `stat-ui[loading]` and `table-ui[loading]` (both shipped v0.6.18). Closes the remaining open item from FEEDBACK-12. File: `components/chart/class.js` + `components/chart/chart.yaml`.

## [0.6.26] — 2026-05-22

### Added — regression tests for FB-46 (select-ui) and FB-47 (table-ui)

- **`components/select/select.test.js`** (new, 4 tests) — locks the FB-46 `[multiple]` pre-init fix: `<option selected>` captures all values, `.value="a,b"` syncs `aria-selected` via Set, single-select backward compat preserved.
- **`components/table/table.test.js`** (2 new tests) — locks the FB-47 Enter-key fix: Enter on a focused body cell fires `cell-click` with correct row data; Enter on a header cell does NOT fire `cell-click` (sort path only).
- **`components/select/select.examples.html`** — multi-select pre-selected demo variants added (option-selected form and value= form).

### Maintenance

- Lockstep version bump. Substantive v0.6.26 source fix: `editor-sidebar` `#syncCollapsed()` 0-width guard applied (FB-42/FB-48, `@adia-ai/web-modules`). See `packages/web-modules/CHANGELOG.md`.

## [0.6.25] — 2026-05-22

### Fixed — `table-ui` Enter key activates focused body cell (FB-47)

- **Enter on a focused body cell now dispatches a click**, triggering the existing `cell-click` → `row-click` pipeline — the same event chain that fires on mouse click. Previously Enter was a no-op on body cells; only header cells triggered sort. Aligns with WAI-ARIA Grid Pattern §6.2.2. File: `components/table/class.js`.

### Fixed — `select-ui[multiple]` pre-initialization via `<option selected>` and `.value=` setter (FB-46)

- **`[multiple]` now correctly captures ALL `<option selected>` children** as the initial value (joined with commas), not just the first. The single-value `preSelected` accumulator was replaced with `preSelectedArr` that collects every selected option before setting `this.value = arr.join(',')`.
- **`#renderOptions()` and the aria-selected sync loop** now use a `Set` derived from `this.value.split(',')` for `[multiple]` mode, so every pre-selected option gets `aria-selected="true"` rendered. File: `components/select/class.js`.

## [0.6.24] — 2026-05-22

### Fixed — `slider-ui` label and suffix template snapshot trap (FB-45)

- **`[slot="label"]` and `[slot="suffix"]` now re-sync in `render()`**, resolving the snapshot trap where `label=${expr}` via the template engine rendered the literal `{{p:N}}` placeholder instead of the resolved value. Both spans are updated on every reactive cycle; a missing label span is also created if absent at connection time. File: `components/slider/class.js`.

### Fixed — `toast-ui` demo shell missing `feed.css` import

- **`toast.html` now imports `feed/feed.css` + `feed/feed.js`.** `<toast-ui>` renders via `<feed-ui>` — the demo shell was not loading feed's stylesheet, so feed items rendered without background, typography, or spacing. Files: `components/toast/toast.html`.

## [0.6.23] — 2026-05-22

### Added — `row-ui` `wrap-at="bp"` responsive wrap (`components/row/`)

- **`wrap-at="bp"`** enables `flex-wrap: wrap` from the named breakpoint upward (`xs/sm/md/lg/xl`). Complements the boolean `[wrap]` (always-wrap, unchanged) for the common "single row on desktop, wraps on mobile" toolbar pattern: `<row-ui gap="2 3@md" wrap-at="sm">`. Uses inline `flex-wrap` to avoid reactive re-entrancy from toggling the boolean attribute inside `render()`. File: `components/row/row.js` + `components/row/row.yaml`.

### Added — `--a-ui-2xs: 11px;` UI typography scale slot (`styles/typography.css`)

- **`--a-ui-2xs`** (11px) slots cleanly into the existing UI scale between `--a-ui-tiny` (10px) and `--a-ui-xs` (12px). Currently unused — pre-staged for upcoming responsive UI-text variants that need a denser-than-`xs` step. File: `styles/typography.css`.

### Documentation — `components/` examples third sweep (§428/§432)

- **22 `components/*/*.examples.html` files** updated with @bp annotations + missing prop / token tables across the docs sweep arc (badge, block, button, card, check, code, col, fields, grid, image, link, list, modal, nav family, pane, select, stat, stream, table, tabs, tag, text-ui, toggle-group, tooltip, tree). Picks up the v0.6.22 `@bp` substrate in living demos so consumers see responsive composition shapes inline. Source journal sections: §428 (third sweep + token tables) + §432 (collision fix).

## [0.6.22] — 2026-05-22

### Added — `@bp` responsive attribute system (`core/responsive.js`)

- New `core/responsive.js` exports `BREAKPOINTS`, `BP_NAMES`, a shared `breakpoint` signal (matchMedia-driven; SSR-safe), and `parseResponsive(value, activeBp)`. Mobile-first: unannotated value = base; `@bp` applies at that breakpoint and wider. Reading `breakpoint.value` inside any UIElement `render()` auto-subscribes — no manual listener wiring needed.
- **`grid-ui`**: `columns`, `gap` accept `@bp` (e.g. `columns="2 4@md"`). `render()` also sets `gridAutoFlow:row` + `gridAutoColumns:auto` so items wrap correctly — the default `grid-auto-flow:column` would otherwise spread them horizontally.
- **`col-ui`**: `gap`, `align`, `justify` accept `@bp`. `align` sets `text-align` inline to mirror the CSS attribute side-effect.
- **`row-ui`**: `gap`, `align`, `justify` accept `@bp`.
- **`text-ui`**: `size` accepts `@bp` (`size="sm md@lg"`).
- **`block-ui`**: `padding`, `margin` accept `@bp` (`padding="sm md@lg"`).
- Subscription is lazy: each component only subscribes to the breakpoint signal when at least one prop contains `@`.

### Added — `empty-state-ui` `variant="danger|warning"`

- New `variant` prop (default|danger|warning). Colors icon and text via danger/warning semantic tokens — **no background tint or border** (by design: an empty-state occupies the full content surface; a tinted box would frame absent content). Use `variant="danger"` for full-section error states where content cannot be shown; the centered layout is more appropriate than an inline alert banner when data is absent. File: `components/empty-state/empty-state.css`.

### Fixed — `card-ui` sparkline cards fill available height

- `card.css`: `:has(> section[bleed] > chart-ui)` switches matching cards to `flex-direction: column`; bleed section gets `flex: 1; min-height: 0`. Scoped tightly — no effect on other card layouts.
- `chart.css`: sparkline type gains `height: 100%`. Computes to `auto` when parent has no explicit height (backward-compatible); fills flex-grown height when available.

### Fixed — `alert-ui` missing `[slot="action"]` CSS rule

- `slot="action"` was documented in `alert.yaml` but had no CSS. Added `flex-shrink: 0; align-self: start` so action content behaves as a proper trailing flex item.

### Fixed — `segmented-ui` overflow: labels now truncate with ellipsis

- `segmented.css`: added `min-width: 0` to `:scope`. `inline-grid` previously never shrank below its max-content width in flex contexts, leaving the `::after { text-overflow: ellipsis }` rule in `segment.css` (already correct) unreachable.
- `card.css`: changed card header action column from `max-content` → `auto` (`minmax(min-content, max-content)`). `max-content` prevented the action column from shrinking. Added `min-width: 0` to `[slot="action"]`. Chain: card narrows → action column shrinks → action flex shrinks → segmented-ui shrinks → segments share available width → `::after` ellipsis fires.

### Fixed — `stat-ui [slot="label"]` now truncates with ellipsis

- **`[slot="label"]` (the eyebrow line above the value) was missing the overflow-ellipsis triad** that `[slot="value"]` already carried. Long label strings clipped hard at the grid cell boundary with no `…`. Added `overflow: hidden; text-overflow: ellipsis; white-space: nowrap` to bring the two sibling slots into parity. File: `components/stat/stat.css`.

### Fixed — `table-ui` column headers now truncate with ellipsis

- **Column headers had `white-space: nowrap` but no overflow clipping**, so long header text overflowed into adjacent cells. `overflow: hidden` cannot be applied to the `[role="columnheader"]` directly — it would clip the `position: absolute` filter dropdown rendered inside it. Fix: converted `[role="columnheader"]` to `display: flex; align-items: center` and added `flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap` on the label `<span>` child (already created by `class.js`). Sort icons and filter buttons remain fully visible at their natural size; only the label text truncates. File: `components/table/table.css`.

### Fixed — `table-ui` body cells now show ellipsis (not just hard clipping)

- **Body cells declared `text-overflow: ellipsis` but showed hard clipping with no `…` character.** Root cause: `text-overflow: ellipsis` on a `display: flex` container has no effect on anonymous flex items (text nodes set via `cell.textContent`). All 6 built-in text-scalar cell types (`text`, `number`, `currency`, `percent`, `date`, `datetime`) and the two `class.js` fallback paths now wrap text in a `<span>` child element. CSS rules targeting `[role="gridcell"] > span` and `> a` (link type) carry `flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap`. Rules for `> row-ui { min-width: 0; overflow: hidden }` and `> row-ui > span` cover the avatar and progress composite types. The `[wrap]` opt-in is extended to reset child-element overflow properties so per-table or per-column wrap still works. Files: `components/table/cell-types.js`, `components/table/class.js`, `components/table/table.css`.

### Changed — UI context inset/gap scale reduced

- **`--a-inset-sm/md/lg` and `--a-gap-sm/md/lg` shifted down the space scale** to better suit dense UI surfaces. New values at `--a-density: 1`: inset sm 16→8px, md 24→16px, lg 32→24px; gap sm 12→8px, md 16→12px, lg 24→16px. The `[prose]` context is unchanged — it stays coarser by design. File: `styles/tokens.css`.

### Changed — `stat-ui` gap tokens cascade with container size context

- **`--stat-column-gap` and `--stat-row-gap` are now `var(--a-gap)` and `var(--a-gap-sm)`** instead of hardcoded space tokens. When a parent container sets `size="sm"` or `size="lg"`, stat-ui gaps adapt automatically. File: `components/stat/stat.css`.

### Documentation — 21 component examples pages corrected

- **YAML-vs-HTML audit of all 93 components** found 58 with missing or stale API documentation. 21 fixed: missing props added, stale enum values corrected (`block`→`stretch` for button, `error`→`danger` for tag/alert, size ranges extended), full API sections added where absent (link, field, nav, nav-group, nav-item). See journal §411–§418 for the complete list.

### Documentation — `packages/web-components/patterns/` + `packages/web-components/traits/` examples sweep

- **§407 ripple sweep on `patterns/` + `traits/` examples.html files.** As a side-effect of the §407 spacing-scale rebase + table-truncation work, demo pages for `conversion-funnel`, `editor-shell`, `form-drawer`, `record-detail-drawer`, `retention-cohort`, `wizard-drawer` (patterns) and `active-state`, `arrow-grid-nav`, `draggable-list-item`, `drop-target`, `focus-restore`, `focus-trap`, `focusable`, `hotkey`, `hoverable` (traits) were re-stamped to use the new inset/gap tokens consistently.

## [0.6.21] — 2026-05-21

### Changed — `table-ui` body cells truncate by default (opt-in wrap via `[wrap]` / `[data-wrap]`)

- **`<table-ui>` body cells now default to single-line with ellipsis truncation** instead of `overflow-wrap: anywhere`. Long unbreakable strings (URLs, IDs) clip gracefully rather than re-wrapping the row, matching the row convention shared by `<select-ui>` + `<nav-item-ui>`. Three opt-in paths to wrapping: (1) `<table-ui wrap>` — new reflected boolean prop, whole-table opt-in; (2) `col.wrap: true` in `columns[]` — renderer sets `[data-wrap]` on each body cell in that column (per-column opt-in); (3) `<td data-wrap>` — surgical per-cell opt-in for hand-authored cells. Behavior change for consumers who relied on the legacy wrap default — set `[wrap]` on the host to restore. New tests cover the three paths. Files: `components/table/{table.css,table.yaml,class.js,table.test.js}`.

### Fixed — toast / feed-item variant text colour now safe in light + dark mode

- **`<feed-item-ui>` — the element `<toast-ui>` / `UIToast.show()` render through — paired a scheme-locked foreground with a scheme-flipping background.** The `info` / `success` / `warning` / `danger` variants set the text to `--a-{variant}-fg` (= `--a-{variant}-text-strong`, locked for solid `--variant-strong` fills) over a `--a-{variant}-muted` tint that flips light↔dark — going low-contrast in one scheme (e.g. a warning toast in dark mode). They now use the scheme-aware `--a-{variant}-text`, which flips with the background — matching `alert.css` and `toast.css` variant wiring. Files: `feed.css`.

### Fixed — `input-ui` inline `[slot="label"]` no longer crowds the value text

- **An inline `<span slot="label">` sat flush against the value** with only the field gap between them, so a short label and the value text read as one run-on string. `[slot="label"]` now carries a `min-width` floor (new `--input-label-min-width` token, default `8ch`) — it reserves a label column and the value start aligns down a stack of inputs. `flex-shrink: 0` keeps it from collapsing; a label wider than the floor still grows past it. Files: `input.css`.

## [0.6.20] — 2026-05-21

### Added — `table-ui` `row-click` event (FEEDBACK-35)

- `<table-ui>` now dispatches a bubbling `row-click` event (detail:
  `{ row, dataIndex }`) once per row click, alongside the existing per-cell
  `cell-click`. Master-detail / open-flyout wiring no longer has to
  deduplicate `cell-click` by `dataIndex`. `table.yaml` documents it.

### Changed — `select-ui` honours declarative `<option selected>` (FEEDBACK-36)

- `<select-ui>` now promotes an `<option selected>` child to the initial
  value when the host carries no `[value]` attribute — matching native
  `<select>` semantics. Previously the `selected` attribute was silently
  ignored and the placeholder shown. Guarded on an empty `this.value`, so
  consumers who set `[value]` explicitly are unaffected.

### Changed — `chart-ui` wrong-`.data`-shape warning shows the correct shape (FEEDBACK-34)

- The one-shot `console.warn` emitted when `.data` is set to a non-array
  now embeds a minimal correct-shape example
  (`[{ label: "Jan", value: 100 }, … ]`) instead of only naming the wrong
  API and pointing at `chart.yaml`.

### Docs — `table.yaml` / `select.yaml` description clarifications (FEEDBACK-35 §2, FEEDBACK-36)

- `components/table/table.yaml` `sort` event: the `key` / `dir` detail
  descriptions now name the fields explicitly (`key` not `column`, `dir` not
  `direction`) — closing the guess the bare names invited.
- `components/select/select.yaml` `value`: documents that a declarative
  `<option selected>` child sets the initial value when the host `[value]`
  attribute is absent.

## [0.6.19] — 2026-05-21

### Fixed — `drawer-ui` `innerHTML`-on-host orphaned the internal `<dialog>` (FEEDBACK-30)

- Setting `.innerHTML` on a mounted `<drawer-ui>` wiped the stamped
  `<dialog>` part; `#dialogRef` (bound once in `connected()`) was left
  detached, so `#syncDialog()` called `showModal()` on a detached node and
  the drawer silently never reopened. `render()` now re-binds `#dialogRef`
  + its listeners when `ensure('dialog')` re-stamps; `#syncDialog()` guards
  `dialog.isConnected`; a failed `showModal()` surfaces a `console.error`.
  `drawer.yaml` gains an `anti_patterns` entry for the innerHTML trap.

### Added — `drawer-ui` `opened` event (FEEDBACK-27)

- `<drawer-ui>` now dispatches a bubbling `opened` event after the open
  transition completes (mirrors `close`). Lets tests + consumers await full
  interactivity instead of `waitForTimeout` heuristics. `drawer.yaml`
  `events:` documents it.

### Added — `chart-ui` `.data` shape docs + wrong-shape warning (FEEDBACK-24)

- `chart.yaml` now documents the `data` prop: an array of plain objects
  keyed by the `x`/`y` attributes — not the Chart.js `{labels, datasets}`
  envelope. Setting `.data` to a non-array (the common envelope mistake)
  emits a one-shot `console.warn` instead of silently rendering blank.
  (`x` and `y` were already documented props — the ticket's Finding 2
  claim otherwise was inaccurate.)

### Added — `segmented-ui` non-`<segment-ui>`-child warning (FEEDBACK-23)

- `<segmented-ui>` emits a one-shot `console.warn` when a direct child is
  not a `<segment-ui>` (a bare `<segment>` renders text but gets no sliding
  indicator, `role`, or `aria-checked`). `segmented.yaml` `description` +
  `slots` now document the `<segment-ui>` child contract.

### Changed — `UIElement.ensure()` part-wipe invariant (FEEDBACK-31)

- `UIElement.ensure()` (`core/element.js`) marks stamped structural parts
  with `_uiPart = true` and its doc comment states the invariant: call
  `ensure()` from `render()`, never cache a part reference once in
  `connected()` — a host `innerHTML` wipe detaches it.

### Added — `types` condition on CSS subpath exports (FEEDBACK-26)

- `@adia-ai/web-components/css` and the per-component `.css` subpath exports
  now carry a `types` condition (`css-module.d.ts` stub), so TypeScript
  under `moduleResolution: bundler` no longer raises TS2882 on CSS
  side-effect imports.

### Docs — no-`.js`-suffix import rule (FEEDBACK-22)

- `USAGE.md` notes that component subpath imports use the directory name
  (`…/components/button`), never the explicit file (`…/button/button.js`) —
  the `exports` wildcard rejects the suffixed form under Vite 8.

## [0.6.18] — 2026-05-21

### Added — `[data-scheme]` attribute on `toggle-scheme-ui` (FB-14)

- **`toggle-scheme-ui` now writes the `[data-scheme]` attribute** on the target
  element alongside the existing `color-scheme` inline style. `styles/themes.css`
  selects on `[data-scheme="light|dark|system"]`; consumer CSS patterned after
  it never fired because the component wrote only the inline style.
  `#writeTarget()` sets the attribute, `#clearTargetOverride()` removes it —
  paired with the style so the two never diverge. Non-breaking: consumers
  relying on the `color-scheme` style alone are unaffected. (~6 LOC `class.js`.)

### Added — `button-ui` text/icon symbol-duplication warning (FB-15)

- **`button-ui` emits a one-shot `console.warn`** when `text=` begins with a
  symbol that `icon=` already renders — e.g. `text="+ New Item" icon="plus"`
  renders a doubled `+`. Covers the high-frequency `plus` / `minus` / `x` /
  `check` / `arrow-*` collisions. Warning-only (WeakSet-guarded, GC-friendly);
  the button still renders and `text=` is never mutated. `button.yaml` gains the
  matching `anti_patterns` entry so A2UI validators flag it at generation time.
  (~36 LOC `class.js` + `button.yaml` + a2ui regen.)

### Added — Loading state as first-class citizen (FB-12 P2)

- **`stat-ui loading` prop.** New `loading: Boolean` (reflect: true). When set,
  the value + change slots render `<skeleton-ui>` shimmer placeholders and the
  host gets `aria-busy="true"`. Label and icon are preserved as static
  metadata. Toggle off when data arrives — slot textContent setters restore
  cleanly. `composes:` now includes `skeleton-ui`. (~28 LOC `stat.js` + 9 LOC
  `stat.yaml` + a2ui chunk regen.)
- **`table-ui` skeleton rows.** `loading=true` now renders N ghost
  `[data-skeleton-row]` rows (N = `paginate` capped at 8, else 5) instead of
  the legacy `<progress-ui>` overlay. Header + columns stay intact so layout
  is preserved while data fetches. Cell widths cycle 60/80/70/50/90% so rows
  read as natural data. Host gets `aria-busy="true"`. `composes:` now
  includes `skeleton-ui`. Fixes yaml-vs-impl drift: the yaml description
  always promised "skeleton rows" since the initial cut; the impl only
  shipped the overlay. (~42 LOC `class.js` rewrite of `#renderOverlays` +
  CSS rewrite of the `:scope[loading] [data-body]` rule.)

### Added — `text-ui` overlay attributes (FB-10)

- **`size` / `color` / `weight` / `text-align` props on `text-ui`.** Pre-v0.6.18
  the skill documented an overlay API (`<text-ui color="subtle" size="sm"
  weight="semibold" text-align="center">`) but the substrate ignored those
  attributes — they were valid HTML but no-op CSS. v0.6.18 implements them
  as `:scope[attr="value"]` rules that override the `--text-*` CSS vars set
  by `[variant]`. Enums:
  - `size` — `sm | md | lg` → `--a-body-sm | --a-body-md | --a-body-lg`
  - `color` — `default | subtle | strong | accent | danger | success | warning`
  - `weight` — `regular | medium | semibold | bold`
  - `text-align` — `start | center | end | justify` (sets `text-align` directly;
    only takes effect when text-ui is block-like)
  Permissive: unknown values are no-ops (variant default wins). Substrate now
  matches the skill's already-documented intent.

### Tests

- 9 new tests in `stat.test.js` (FB-12 P2 #1 coverage).
- 9 new tests in `table.test.js` (FB-12 P2 #3 coverage; uses RAF-based settle
  helper because table-ui renders via `requestAnimationFrame`, not microtasks).
- 18 new tests in `text.test.js` for the overlay attributes (attribute
  reflection × 18 values + CSS rule presence × 18 + 3 enum-vs-CSS
  consistency checks + permissive-fallback smoke).
- **Total: 99/99 passing** (text 82 + stat 9 + table 9 — the existing text
  variant tests still pass alongside the new overlay tests).

### Deferred (see `outbox/FEEDBACK-12--...--followup.md`)

- **`chart-ui` `loading` prop (FB-12 P2 #2)** — design decision upstream.
  `chart.examples.html` documents intentional "compose loading wrappers"
  pattern; pivoting in a patch cut would silently lock in a direction.
- **`skeleton-ui` `variant` reconcile (FB-12 P3)** — two valid dispositions
  (purge a2ui corpus refs / implement prop) need maintainer signoff.

### No-op (FB-11 P3 substrate)

- Verified that `import { UIFeed } from '@adia-ai/web-components/components/feed/class'`
  resolves correctly via the existing `./components/*/class` exports pattern.
  No package.json change needed. The original ticket flagged
  `/components/feed/feed.js` as failing — confirmed (Node exports wildcards
  can't substitute `*` across multiple path segments) but the `/class`
  shorthand is the canonical path and works today. Skill v2.11.0 documents
  this.

## [0.6.17] — 2026-05-21

### Maintenance
- **Lockstep version bump only.** No source changes in this package; bumped to maintain the 9-package version coherence enforced by `scripts/release/check-lockstep.mjs`. Substantive v0.6.17 work shipped in `@adia-ai/web-modules` — collapsed-sidebar rules grew a forgiving fallback block for vanilla HTML consumers (`<button class="nav-item">`, `[slot="heading"]`) so text labels don't overflow the 48px rail. See `packages/web-modules/CHANGELOG.md#0617--2026-05-21` for details.

## [0.6.16] — 2026-05-21

### Maintenance
- **Lockstep version bump only.** No source changes in this package; bumped to maintain the 9-package version coherence enforced by `scripts/release/check-lockstep.mjs`. Substantive v0.6.16 work shipped in `@adia-ai/web-modules`: (1) `<admin-page>` selector relaxed to descendant combinator so router-wrapped layouts no longer collapse to `display: inline` at narrow viewports (was the root of the page-header/KPI overlap regression); (2) `--page-content-header-bg` rebased from `--a-canvas-2` to `--a-canvas-1` so the page-header reads as shell-chrome continuation. See `packages/web-modules/CHANGELOG.md#0616--2026-05-21` for details.

## [0.6.15] — 2026-05-21

### Fixed
- **`installIconLoaders` / `installIconLoadersForRegistered` no longer silently accept a malformed shape (FB-07).** The previous behavior was `weightModules = { ...EMPTY_WEIGHTS, ...modules }` which spread any top-level keys verbatim. A flat path→svg glob (the documented quick-start shape; see FB-09) had its `/path/foo.svg` keys spread at the top level of `weightModules`, leaving every per-weight slot empty. Every `<icon-ui>` then rendered the icon NAME as visible text in its slot — confusing to debug because the per-icon `warnMissingIcon` console message points at name-resolution issues (Phosphor alias, `registerIcon`, etc.), not at loader-shape issues. The fix shape-detects three cases: (1) per-weight map keyed by `regular`/`thin`/`light`/`bold`/`fill`/`duotone` installs verbatim; (2) flat path→svg map auto-promotes to `{ regular: <flat> }` with a one-time `console.warn` so the caller knows to wrap explicitly (and so bold/fill/etc. aren't silently lost); (3) anything else logs `console.error` with the received key sample and aborts the install (registry stays unchanged). Net: the documented flat shape from `.claude/docs/guides/02-quick-start.md` continues to work but emits one diagnostic warn per install; consumers on prior versions writing the flat shape needed to wrap manually to get icons at all. Files: `packages/web-components/core/icons.js` (+59 LOC: `WEIGHT_KEYS` const, `isFlatGlobShape()`, `warnedShapes` WeakSet, three-branch dispatch inside `installIconLoaders`).
- **`core/icons-phosphor.js` no longer silently swallows `import.meta.glob` failures (FB-08).** Two silent-fail paths fixed. (a) **Empty-glob path** — `import.meta.glob('/node_modules/@phosphor-icons/core/assets/<weight>/*.svg', …)` returns `{}` (not an error) when zero files match. This happens when the consumer's Vite root sits below `node_modules`, or with pnpm/yarn-berry virtual paths, or when this module is loaded from a published package directory (the absolute-style glob is resolved against the Vite root, not this file's directory). Previously `hasViteGlob` was set true and `installIconLoaders({ regular: {}, … })` ran anyway, leaving the registry empty with no diagnostic. The fix sums per-weight entry counts after the `try` block; if all six weights have zero entries, emits a `console.warn` with the canonical manual-install snippet. (b) **Caught-error path** — the bare `catch {}` was scoped to handle "no Vite at all" (`ReferenceError` because `import.meta.glob` doesn't exist in static serving), but it also swallowed every OTHER error type (plugin order issues, Vite-config misconfigs, etc.). The catch now logs non-`ReferenceError` failures via `console.warn` before falling back to the manifest fetch. Files: `packages/web-components/core/icons-phosphor.js` (+38 LOC, -2 LOC).

### Docs
- **Quick-start + migration guide examples corrected (FB-09).** `.claude/docs/guides/02-quick-start.md` lines ~76-81 (boot sequence) and ~126-131 (the dedicated `installIconLoadersForRegistered` section) both showed the flat-glob form passed directly to `installIconLoadersForRegistered`. Both now wrap the glob in `{ regular: … }` to match the canonical contract. New `### Pitfall: flat glob silently auto-promoted (v0.6.15+)` section explains the FB-07 auto-promote behavior and when the per-weight form is strictly required (bold/fill/etc.). `.claude/docs/MIGRATION GUIDE.md` lines ~279-282 (the v0.5.3 §154 recommended upgrade example) had the same flat-glob bug — corrected. The skill `adia-ui-kit` v2.9.0 ships the same scaffold-template correction.

### Verification
- `verify:traits` 56/56 clean.
- `components.mjs --verify` clean (142 files up-to-date).
- `smoke:engines` green; `smoke:register-engine` 11/11.
- `npm run check` omnibus PASSES (LightningCSS minify, link check, primitive composes coverage, demo shell imports, phosphor wiring, MCP server smoke 3/3, register-engine 11/11, traits 56/56).
- `eval:diff --engine zettel` cov=49% / avg=90 (baseline-identical).
- Browser-verified four cases against `installIconLoaders`: flat glob → 1 warn + install; garbage shape → 1 error + abort; null → 1 error + abort; per-weight map → silent. Live `/site/examples/admin-dashboard` shows 406/412 icon-ui elements with rendered SVGs — same baseline as pre-fix; substrate change non-regressing.

## [0.6.14] — 2026-05-21

### Maintenance
- **Lockstep version bump only.** No source changes in this package; bumped to maintain the 9-package version coherence enforced by `scripts/release/check-lockstep.mjs`. Substantive v0.6.14 work shipped in `@adia-ai/web-modules` — `<admin-page-header>` now uses `var(--page-content-header-bg)` (= `--a-canvas-2`) + `var(--a-shadow-sm)` so the page-header band reads as elevated chrome over the body canvas. See `packages/web-modules/CHANGELOG.md#0614--2026-05-21` for details.

## [0.6.13] — 2026-05-20

### Maintenance
- **Lockstep version bump only.** No source changes in this package; bumped to maintain the 9-package version coherence enforced by `scripts/release/check-lockstep.mjs`. Substantive v0.6.13 work shipped in `@adia-ai/web-modules` — `<admin-shell>` default `mode` is now `"rounded borderless"` (bare-tag auto-promote on connect; any explicit `mode` attribute still wins). See `packages/web-modules/CHANGELOG.md#0613--2026-05-20` for details.

## [0.6.12] — 2026-05-20

### Maintenance
- **Lockstep version bump + examples-doc cleanup.** Bumped to maintain the 9-package version coherence enforced by `scripts/release/check-lockstep.mjs`. The only file change in this package: `patterns/admin-shell/admin-shell.examples.html` — removed a stale `data-content-root` row from the data-attribute hooks reference table (ADR-0032 retired that legacy hook). No runtime `.js` / `.css` changes. Substantive v0.6.12 work shipped in `@adia-ai/web-modules` — admin-shell legacy CSS bridges retired per ADR-0032 (continuation of ADR-0023 + ADR-0024). See `packages/web-modules/CHANGELOG.md#0612--2026-05-20` for the breaking-change details + migration path.

## [0.6.11] — 2026-05-20

### Fixed
- **`<canvas-ui>` — `.d.ts` hand-authored with full runtime API (claims-ui-v2 inbound FB-02 finding 2, P2).** Pre-fix: `canvas.d.ts` was 27-line codegen output declaring only `theme: string` + the `canvas-interaction` event listener — all 8 imperative runtime methods (`process`, `processAll`, `reset`, `pushVersion`, `back`, `forward`, `historyLength`, `historyIndex`) plus `getHTML()` were absent, forcing TS consumers to write `(canvas as any).processAll(...)`. The yaml/a2ui.json sidecar can only express declarative props per ADR-0027; codegen correctly skips imperative methods, but no compensating hand-authored layer existed. **Fix**: replaced `canvas.d.ts` with 63-line hand-authored declaration covering the full public API surface (each method documented with JSDoc matching the JS contract). Added `'canvas'` to the `HAND_AUTHORED_DTS` skip-set in `scripts/build/dts-codegen.mjs` with referencing comment so the next codegen run preserves the hand-authored file. Consumers post-upgrade can drop `(canvas as any).processAll(...)` casts entirely. Closes claims-ui-v2 inbound FEEDBACK-02 finding 2.

## [0.6.10] — 2026-05-21

### Added
- **`<table-toolbar-ui>` — one-shot dev-mode `console.warn` when unknown opt-out attributes are set (claims-ui FB-04 enhancement).** Triggered by claims-ui FB-04 (RESPONSE-04, v0.6.9 §379): consumers writing `<table-toolbar-ui searchable>` (or `exportable` / `sortable` / `filterable` / `columns-visible`) thinking those attributes toggle visibility saw silent no-ops — the canonical attrs are `no-search` / `no-filter` / `no-sort` / `no-columns`. Pre-v0.6.10 behavior: the unknown attrs simply did nothing. Post-fix: a one-shot `console.warn` per element fires from `connected()`, names the unknown attrs, and points consumers at `npx adia-ai-doc table-toolbar` and the yaml SoT for the canonical reference. Pattern: warn-once-per-element via static `WeakSet` (`UITableToolbar._warnedUnknownAttrs`), matching the `tooltip-ui._warnedMissing` discipline. Scope: case-insensitive attrs starting with `search` / `export` / `sort` / `filter` / `column`, minus the 4 canonical opt-outs. Standard HTML/ARIA attrs (`id`, `class`, `role`, `aria-*`, `data-*`) are never flagged. Pinned by 12 new vitest cases in `table-toolbar.test.js`.

### Internal
- **`scripts/release/check-browser-safe.mjs` — strict-by-default + `/bin/` allowlist.** Flipped `STRICT = args.has('--strict')` → `STRICT = !args.has('--loose')` to match the convention shared by the other 6 release gates (`check:absolute-imports`, `check:no-self-import-css`, `check:scope-bare-descendants`, `check:lightningcss-build`, `check:rolldown-glob`, `check:with-css-pairing`). Paired with `/\/bin\//` added to `NODE_ONLY_PATTERNS` — silences the only pre-existing warning (`packages/web-components/bin/doc.mjs`, the `adia-ui-doc` CLI shipped v0.6.7). The CLI's `node:fs` / `node:path` / `node:url` imports are by design (#!/usr/bin/env node shebang); the broad `/bin/` pattern covers any future package-bin CLIs. Tracked at journal §378 as deferred-to-v0.6.10. No consumer-facing API change.

## [0.6.9] — 2026-05-21

### Fixed
- **9 component CSS files — `@scope` blocks no longer use bare-combinator descendants (F-002, P1).** Pre-fix: 92 selector clauses across `action-list.css`, `card.css`, `chart.css`, `chat-thread.css`, `code.css`, `command.css`, `grid.css`, `pane.css`, `stack.css` used the `@scope { > X { } }` shape (bare `>` combinator with no left-hand selector inside `@scope`). Chromium/Firefox tolerate this; LightningCSS rejects it as "Invalid empty selector" (matches the CSS Nesting + `@scope` spec strictly). Symptom: consumers on Vite 7 (default `cssMinify: 'lightningcss'`) saw build-time failures at `card.css:152`; Vite 6 + earlier Vite 7 (esbuild minify) silently swallowed the same source. Fix: codemod-driven rewrite of all 92 clauses from `> X` to `& > X` (explicit `&` reference matches the modern CSS Nesting idiom; semantically identical; LightningCSS-clean). All 120 CSS files now pass LightningCSS minify against Vite 8 default targets (Chromium 125+, Safari 18+, Firefox 129+). Codemod source: `scripts/build/codemod-scope-bare-descendants.mjs` (committed for future audits; idempotent + `--verify` mode). Paired CI gate `check:scope-bare-descendants` blocks regression at PR time; companion `check:lightningcss-build` smoke-minifies every CSS file end-to-end against Vite 8 targets. Closes claims-ui FEEDBACK-02.

### Documentation
- **`USAGE.md` — new "Looking up a component's API" subsection.** Three discovery paths now documented in one place: `npx adia-ai-doc <component>` CLI (machine-readable + offline-safe), `<component>.examples.html` per-component live demo + Properties table, and `<component>.yaml` source-of-truth schema. Closes claims-ui FEEDBACK-09 (per-component opt-out attributes are documented; this subsection makes the doc-path discoverable from the top-level USAGE entrypoint).

## [0.6.8] — 2026-05-20

### Fixed
- **`core/template.js` — `.camelCaseProp=${expr}` property bindings now route to the canonical setter (FB-55, P1).** Pre-fix: the HTML parser lowercases attribute names inside `<template>.innerHTML` per HTML5 §13.2.5.32, so `.className=${expr}` arrived at the binding scanner as `.classname`. The binding then wrote `element['classname'] = v` — an enumerable expando — never invoking the `HTMLElement.prototype.className` setter. Classes never applied; no warning, no error. Trap affected every camelCase DOM property surface (`.className`, `.innerText`, `.tabIndex`, `.ariaLabel`, `.contentEditable`, `.readOnly`, `.maxLength`, `.minLength`, `.colSpan`, `.rowSpan`) AND every camelCase property declared via `UIElement.static properties` (80+ across primitives — `<color-picker>` `.maxChroma`, `<grid>` `.columnGap`, `<breadcrumb>` `.collapseKeepLeading`, etc.). Even the canonical `<my-panel .minL=${minL}>` example from `README.md:212` silently failed. **Fix: two-layer name resolution** — (1) `PROP_CASE_FIX` static map at `scan()` time covers built-in DOM camelCase (zero-cost lookup, no prototype walk for the common case); (2) lazy prototype-walk fallback at `applyValue()` time finds canonical camelCase property names on the element's instance + prototype chain via case-insensitive match, caching resolution on the part after first hit. Catches UIElement custom-element props installed per-instance in `installProps()` (those don't exist at `scan()` time because the element hasn't been upgraded yet). Backward-compatible: when neither layer finds a case-insensitive match, the original lowercase name is preserved (consumers writing to lowercase expandos unaffected). 10 NEW vitest cases in `core/template.test.js`'s `FB-55` describe block pin the contract. Closes RESPONSE-55 (reviewer-#A expansion to UIElement primitives explicitly covered).
- **`core/template.js` — nested `${html\`<svg-child/>\`}` interpolations now render in SVG namespace (FB-57, P1).** Pre-fix: each nested `html\`\`` is a separate cached template whose `tpl.innerHTML = m` is parsed at document level without SVG insertion-mode context, so `<circle>`/`<line>`/etc. arrived as `HTMLUnknownElement` (NS `http://www.w3.org/1999/xhtml`). The `<span style="display:contents">` wrapper from `wrap()` didn't fix it because `display: contents` is layout-level, not namespace-level; SVG layout is namespace-strict → present-but-invisible output, zero bounding rect, no error. **Fix: namespace-aware `mount()`** — discriminate via `container.closest('svg, math, foreignObject, annotation-xml')` at mount time; when stamping into an SVG/MathML subtree, recursively re-namespace the cloned fragment via `createElementNS`, transferring attributes via `setAttributeNS` to preserve attr-namespaces (e.g., legacy `xlink:href`). `<foreignObject>` (SVG) and `<annotation-xml encoding="text/html"|"application/xhtml+xml">` (MathML) descendants correctly REVERT to HTML namespace per HTML5 §12.2.5 foreign-content insertion mode. Implementation site: `mount()` rather than `getTemplate()` — keeps the template cache coherent (same `html\`\`` source can stamp into either context without doubling cache entries). 6 NEW vitest cases in `core/template.test.js`'s `FB-57` describe block (inline-SVG regression, nested-SVG fix, direct-stamp into SVG container, foreignObject boundary, MathML, HTML-container no-op). Closes RESPONSE-57 (Option A accepted with implementation-site refinement; Option B rejected as half-measure).
- **`core/template.js` — partial-interp warning text recommends `class="${expr}"` first (FB-55 #2).** The v0.5.5 §184 warn text put `.className=${expression}` first as the recommended fix for partial class interpolation; that path now works post-FB-55 fix above, but `class="${expr}"` (full-attribute interpolation) is the more discoverable form that doesn't require knowing the camelCase property name. Promoting it first. The `.classList=${{foo: true, bar: false}}` aspirational line is removed — classList is a read-only DOMTokenList accessor, no `PROP_CASE_FIX` entry can make it assignable.

### Docs
- **`USAGE.md` — three new template-related sections** documenting the FB-55/56/57 cluster:
  - **§-TBD (v0.6.8) — Host element display defaults (FB-56)** — surfaces the HTML-spec inline default for custom elements + the `:host(:where(*))` zero-specificity recipe used across the in-tree primitive set (30+ flex-container primitives). Documents why `UIElement` doesn't bake a `display` rule into the host (container vs leaf vs passthrough is a consumer-layout decision). Worked symptom + diagnostic recipe ("inspect the host directly — `getComputedStyle(host).display === 'inline'`").
  - **§-TBD (v0.6.8) — SVG composition + namespacing (FB-57)** — documents three modes (inline, nested-interpolations, imperative-`.innerHTML`) with the `<foreignObject>` boundary semantics and the MathML parallel. Migration is zero — pre-fix code that worked keeps working; pre-fix code that was silently broken (nested SVG interpolations) starts producing visible output.
  - **§-TBD (v0.6.8) — `.camelCaseProp=${expr}` property binding (FB-55)** — explains the two-layer name resolution, lists affected camelCase property surfaces, documents backward-compatibility for genuine expando consumers, and notes the discoverability trade-off (the simpler `class="${expr}"` doesn't require knowing the camelCase property name).
- **USAGE.md `§-TBD (v0.6.8) — UIElement lifecycle hooks (FB-59 + FB-43/44/47/58 cluster)`** — new section documenting the canonical hook order: `adoptStyles` → `prepareParts` → **`connected()`** (line 122, inside `untracked()`) → effect first-run with **stamp at line 127** → `render()` → `updated()`. Source-cited from `core/element.js:107-142`. Resolves a cluster-wide misunderstanding about when `connected()` fires relative to the template stamp: it runs BEFORE the stamp wipes consumer-supplied light-DOM children, NOT after. Includes the "Container UIElement pattern" example showing `connected()` → capture children → arm MutationObserver, plus the defensive `if (present.length > 0) keep` cache-fallback pattern (consumer-discovered) that makes capture robust to ordering. Distinguishes `connected()` (recommended subclass hook) vs `connectedCallback()` (rarely overridden W3C hook). Closes FB-59 P3 docs ask + corrects the §B mechanism walkthrough in RESPONSE-58 (sibling followup-RESPONSE retracts the §B order claim).

## [0.6.7] — 2026-05-19

### Added
- **`<name>.examples.md` ships in npm package** (96 components, ~67KB total).
  Generated by `scripts/build/generate-examples-md.mjs` from `<name>.examples.html`.
  External consumers can now read canonical markup fragments directly from
  `node_modules/@adia-ai/web-components/components/<name>/<name>.examples.md`.
- **`custom-elements.json` ships in npm package** (W3C Custom Elements Manifest v2.1.0).
  Generated from per-component yaml files. Enables IDE LSP autocomplete and
  cross-tool interop (Storybook, VS Code WC extensions).
- **`npx @adia-ai/web-components doc <name>` CLI** prints yaml summary + live
  demo URL + first example fragment for any component. Bare `doc` lists all.
- **`USAGE.md § Data binding with data-stream-*`** new top-level section
  covering the declarative `data-stream-*` attribute family (was previously
  undocumented despite being used in 12+ components).

## [0.6.6] — 2026-05-18

### Added
- `<field-ui align="end">` — new `align` prop (reflected string, enum: `start`|`end`).
  `start` is the current default (no visual change). `end` right-aligns the label
  within its grid cell (`justify-self: end; text-align: end`), useful in multi-field
  forms where labels share a visual column and trailing-flush alignment aids scanability
  (right-to-input reading path). CSS-only; no JS logic change. §304.
## [0.6.5] — 2026-05-18

### Changed
- Lockstep version bump for TS Phase 6 sealing (ADR-0029).
Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
[Semantic Versioning](https://semver.org/).

Scope: custom elements, core (`AdiaElement` + reactivity + templates),
trait system, style tokens, patterns (composite elements). The A2UI
runtime ships in the sibling `@adia-ai/a2ui-runtime` package
(renamed from `@adia-ai/a2ui-utils` in v0.3.0) as of `0.0.4`.
## [0.6.4] - 2026-05-18

### v0.6.4 — Lockstep ride-along

TS Phase 5: a2ui.schema.json schema-driven codegen (§359). No source changes in this package.



## [0.6.3] - 2026-05-18

### v0.6.3 — Lockstep ride-along

TS Phase 5: runtime real declarations (§358) + compose branded IDs.
No source changes in this package. Internal dep ranges stay at `^0.6.0`.



## [0.6.2] - 2026-05-18

### v0.6.2 — Lockstep ride-along

TS Phase 4: retrieval (§356) + mcp (§357) `.ts` sources + TS-MIG-001 closed.
No source changes in this package. Internal dep ranges stay at `^0.6.0`.



## [0.6.1] - 2026-05-18

### v0.6.1 §354 — `<link-ui>` `--link-color-*` → `--link-fg-*` token rename

**BREAKING** for consumers overriding `--link-color`, `--link-color-hover`, or
`--link-color-visited`. Renamed to `--link-fg`, `--link-fg-hover`, `--link-fg-visited`.
No in-tree consumer references found. Internal dep ranges stay at `^0.6.0`.



## [0.6.0] - 2026-05-18

_Lockstep ride-along (no source change in this package; companion to web-components v0.6.0 — see root CHANGELOG)._

**MINOR-line internal-dep range update:** internal `@adia-ai/*` dependency ranges move from `^0.5.0` to `^0.6.0` per AdiaUI lockstep policy.

## [0.5.21] - 2026-05-18

_Lockstep ride-along (no source change in this package; companion to web-components v0.5.21 — see root CHANGELOG)._

## [0.5.20] - 2026-05-18

### Added — §353 (v0.5.20) — `<pane-ui>` + `<tabs-ui>` bar-rule alignment

Both primitives' header/tablist bars converted from `border-bottom` to `box-shadow: inset 0 -1px 0`. Header height fixed at `--pane-bar-height` / `--tabs-button-height` (was `min-height` + the border added a pixel). Pane header gains `box-sizing: border-box` + `justify-content: flex-start`. Collapsed pane header drops the box-shadow. Visually identical to consumers; measurable change in computed height (1px shorter than border-bearing version). Brings these two primitives in line with the rest of the catalog using box-shadow for bar rules.

### Docs
- §351 (FB-53 #2) — USAGE.md `§349 Event handling inside reactive lists` section gains a `CSS child-combinator caveat` paragraph. Calls out that `display:contents` wrapper-spans ARE in the DOM tree, so the CSS child combinator (`>`) walks past them: `.parent > .my-item` will NOT match items inside `repeat()` because the wrapper-span sits between. Recommends descendant combinator (single space) instead. Same caveat extended to `:nth-child` / positional pseudo-classes. Closes the post-Lane-AA1 migration docs gap surfaced by Tokens Studio.
- §352 (FB-53 #3) — USAGE.md `§345 .map() vs repeat() for reactive lists` section gains a `Sizing heuristic` paragraph. Two cases where `repeat()` pays for itself: (a) ≥20 items re-rendering on every signal change, OR (b) any-size list whose items host custom elements with internal state (chrome slots, focus, scroll). Discourages reflexive `.map()` → `repeat()` migration when neither threshold applies.

### Fixed
- §-headers (v0.5.20) — USAGE.md headers for `§345` and `§349` renumbered from `§-TBD (v0.5.19)` to `§345 (v0.5.19)` / `§349 (v0.5.19)` — peer-agent's v0.5.19 pre-cut docs sweep renumbered the CHANGELOG but missed the USAGE.md section headers. Sync restored so the section anchors match between CHANGELOG and USAGE.md.

## [0.5.19] - 2026-05-17

### Added
- §348 (FB-51 #3; P3) — `<description-list-ui>` `align="stretch"` enum value (joins `start`, `between`). For `layout="inline"`, sets `justify-self: stretch` + `width: 100%` on `<dd>` (and `width: 100%` on its direct children) so block-level form controls — `<slider-ui>`, `<select-ui>`, `<color-picker-ui>` — reach the column edge rather than shrink-wrapping to content. Closes the Tokens Studio dts-right-panel migration path for property-row pairs where the value is a control, not text. Schema + generated `.d.ts` + `a2ui.json` updated. NEW `description-list.test.js` with 3 cases (CSS source contract, host attribute precondition, yaml enum shape).
- §347 (FB-50 #3; P3) — `<command-ui>` `select` event detail now carries `category` from the parent `<optgroup label="…">`. Items without a group surface `category = ''` so destructuring stays safe. Schema (`command.yaml`) + generated `.d.ts` + `a2ui.json` updated. Closes Tokens Studio's consumer pattern of routing selected commands by section (Navigation / Edit / etc.) without re-walking the DOM to find the parent optgroup label.
- §342 (FB-44; P1) — `<swatch-ui>` `MutationObserver` for late-arriving `[slot="chrome"]` children. `#absorbChromeSlot()` runs from `#syncCore()` which only fires on attribute / property changes; `scan()` walks `element.attributes` BEFORE descending to child nodes, so the parent template's `update()` loop assigns properties (→ `render()` → `#syncCore()` → `#absorbChromeSlot()`) BEFORE interpolating child node parts. The once-only absorb pass therefore sees an empty children set and chrome — arriving moments later — sits at its arrival position rather than the canonical post-tile region. Fix: arm a `MutationObserver` on host `childList` in `connected()` that re-runs `#absorbChromeSlot()` whenever a direct `[slot="chrome"]` child OR a `display:contents` wrapper-span containing chrome is added. Observer is torn down in `disconnected()`. Closes the post-§341 follow-up gap in Tokens Studio's C1.3 main-palette dogfood (gamut badges + override / tracked dots invisible despite §341 placeholder-preservation). 3 NEW vitest cases in `swatch.test.js`'s `FB-44 / §-TBD` describe block.
- §343 (FB-46; P2) — `<tree-ui>` `tree-select` event detail now forwards `ctrlKey` / `metaKey` / `shiftKey` from the originating `MouseEvent` or `KeyboardEvent`. Unblocks consumer-side Ctrl/Cmd+click multi-select implementations (Tokens Studio token-tree). `select(item, originatingEvent = null)` is the new public signature; programmatic calls (no event) surface all three modifiers as `false`. Schema (`tree.yaml`) + generated `.d.ts` + `a2ui.json` updated. 4 NEW vitest cases in new `tree.test.js`. Backward-compatible: existing `detail.{item,text,value}` shape unchanged; new fields are additive.

### Tests
- §344 (FB-47; partial / misdiagnosed) — 3 NEW vitest cases in `core/template.test.js`'s `FB-47` describe block lock the actual contract: (1) `stamp()` with same template strings reuses the mounted instance (no `replaceChildren()`); (2) `repeat()` with same key preserves wrapper children across re-renders; (3) the `Array.isArray(v)` branch in `applyValue()` DOES rebuild on every re-render (the actual `.map()` destruction surface, documented behavior). The FB-47 author's "`stamp()` is unconditional" + "`repeat()` keyed diffing defeated" claims are empirically false; the tests pin current behavior to catch future drift. See RESPONSE-47 for the consumer-side `.map()` → `repeat()` migration recommendation.
- §347 (FB-50 #3) — NEW `command.test.js` (first dedicated test file for `<command-ui>`) with 3 cases: emits `category=""` for ungrouped options, emits `category="<optgroup label>"` for items inside an optgroup, backward-compat `{ value, label }` destructuring still works. 3/3 passing.

### Docs
- USAGE.md — new `§345 (v0.5.19) — .map() vs repeat() for reactive lists (FB-47)` section under the template parser invariants. Documents the keyed-reuse contract, when `repeat()` matters, and the key-selection guidance (use a stable consumer-supplied identifier, NOT array index).
- USAGE.md — new `§349 (v0.5.19) — Event handling inside reactive lists (FB-49)` section. Explains that `closest()` works through `display:contents` wrapper-spans (DOM walk, not box walk) for typical bubble-phase handlers; reserves `composedPath()` for cross-subtree / shadow-DOM / whitespace-retarget cases with explicit examples. Closes the documentation gap that caused three consumer `composedPath()` workarounds in Tokens Studio to ship without rationale.
- §346 — NEW `components/command/USAGE.md` (FB-50 #1) — first dedicated USAGE doc for `<command-ui>`. Documents the native `<option data-shortcut data-icon>` content API (the actual shape; there is no `command-item-ui` custom element), the `<optgroup label>` grouping pattern, the new `category` field in `select` detail (per FB-50 #3 above), the Cmd+K wiring pattern, `setItems()` imperative API, recent-items behavior, and the `empty` + `footer` slots. Closes Tokens Studio's discoverability gap that drove ~350 LOC of hand-rolled `<dts-command-palette>` fallback.

## [0.5.18] - 2026-05-16

### Fixed
- §341 (FB-43; P1) — `<swatch-ui>` chrome destroyed when interpolated via parent `html\`\`` template `${chromeFrag}`. `template.js`'s `scan()` replaces `<!--p:N-->` comments with empty text nodes inside the swatch element when the parent template is cloned; `wrap()` later mutates those text nodes in-place via `replaceWith()`. Pre-§341: the swatch's `#stamp()` ran `innerHTML = ''` between the parent's `mount()` and `update()` (synchronous custom-element connectedCallback timing), wiping the text-node placeholders. Parent's `replaceWith()` then ran on detached nodes — silent no-op — leaving wrapper-spans detached and chrome stamped into a subtree that never reached the live DOM. Symptom: chrome interpolated as `${chromeFrag}` was completely invisible (queryable from neither swatch nor host). Blocked Tokens Studio's C1.3 main-palette dogfood migration (~80 swatches inside `repeat()`). **Fix:** preserve empty text nodes + comment nodes (template-engine placeholders) AND display:contents wrapper-spans through `innerHTML = ''`. Detach them before the wipe, re-attach at the canonical chrome position (between tile + badge) after the canonical structure is stamped. Wrapper-spans preserved in-place keep the template-engine's `part.n` reference stable across re-renders → chrome content updates land in the same DOM location automatically. `#absorbChromeSlot()`'s late-arriving wrapper handler also updated: MOVE the wrapper (not hoist chrome out of it) to canonical position — same identity-preservation rationale. **§331 contract refined:** previously chrome was hoisted to be a direct sibling of the swatch's internal elements (`parentElement === swatch`); §341 keeps chrome inside the wrapper-span which is a direct sibling. Visually identical (display:contents collapses the wrapper's box) but the DOM identity is now wrapper-bearing. 3 NEW vitest cases (`FB-43 / §341`) + 3 §331 (FB-39) tests updated for new contract. 15/15 swatch tests passing.

## [0.5.17] - 2026-05-16

### Fixed
- §339 (FB-41; P2) — `<swatch-ui>` `[auto-contrast]` overrode `[label-position="overlay"]` position. Two `@scope (swatch-ui)` rules at equal specificity (0,3,1) collided on `> [data-label] { position }`; source order favored auto-contrast's `position: relative` over overlay's `position: absolute` → label dropped below tile (host doubled to 2× block-h). Blocks the canonical `label-position="overlay" + auto-contrast` pattern §253 enabled. Fix: drop `position: relative` from `[auto-contrast]` data-label + data-detail rules — z-index works on positioned elements (overlay supplies position: absolute), and non-overlay defaults are flex flow where z-index is irrelevant. 3 NEW vitest cases lock the CSS source structurally.
- §340 (FB-42; P2) — `<list-item-ui>` `render()` text-branch asymmetry. Icon + description branches had create-if-missing paths; text branch only updated existing stamped spans. Template engine `.text=${expr}` (canonical authoring pattern per USAGE.md) arrives AFTER `connectedCallback` `#stamp()` — `this.text` was `''` at stamp time so no span created, `render()` fired but couldn't recover → primary text silently invisible. Fix: mirror description branch's create-if-missing + remove-when-cleared pattern. NEW `list.test.js` with 5 vitest cases (create-on-prop-set / update / remove / parity-with-description / consumer-stamp non-interference).

### Tests
- §338 (FB-40; misdiagnosed) — 4 NEW vitest cases in `core/template.test.js` lock the existing FEEDBACK-06 comment-aware `inTag()` fix (line 55-78). FB-40 reported apostrophes-in-HTML-comments crashes claiming the parser mis-classifies subsequent `${…}` placeholders; the cited mechanism is incorrect — `inTag()` has skipped past `<!--…-->` wholesale since FEEDBACK-06 (pre-v0.5.x). The 4 new tests empirically verify the fix covers apostrophe-comment + double-quote-comment + multi-apostrophe + apostrophe+em-dash cases. RESPONSE-40 requests stack-trace evidence for the actual crash mechanism since the cited apostrophe diagnosis is empirically wrong against current source.

## [0.5.16] - 2026-05-16

### v0.5.16 §331 — `<swatch-ui>` chrome-slot wrapper-span deep-walk (FB-39; P1)

Closes FB-39. P1 lifecycle gap that FB-38's "C1.3 unblocked" close-out missed: §326 (v0.5.15) fixed the late-direct-append case but NOT the template-render wrapper-span case. AdiaUI's `html\`\`` template engine wraps each interpolated child fragment in a `<span style="display: contents">` (per `core/template.js:212` `wrap()`). When consumer writes `html\`<swatch-ui>${badgeFrag}</swatch-ui>\``, the `<span slot="chrome">` ends up nested INSIDE the wrapper, not as a direct child. `#stamp()`'s `Array.from(this.children).filter(n => n.getAttribute?.('slot') === 'chrome')` returned `[]`; `innerHTML = ''` wiped the wrapper spans + chrome content; `#absorbChromeSlot()` ran on destroyed DOM.

Blocked Tokens Studio's C1.3 dogfood migration (chrome elements silently destroyed) + >95% of AdiaUI consumers using `html\`\`` templates.

#### Fixed — `components/swatch/class.js` (dual-site deep-walk)

- `#stamp()`'s chrome extraction extends from direct-children-only to also walk `querySelectorAll('[slot="chrome"]')` on non-chrome wrapper-span children. Catches both static-HTML (direct child) AND template-render (nested in wrapper) paths.
- `#absorbChromeSlot()` gains a parallel deep-walk for wrapper-spanned chrome children that arrive AFTER `#stamp()` (parent template re-render path). Hoists nested chrome out of any non-internal wrapper into the canonical sibling-of-tile slot via `this.insertBefore(child, this.#badgeEl)`.
- Wrapper spans stay in place post-hoist (`display: contents` makes them invisible to CSS layout; removing them risks interfering with the parent template engine's diff tracking via the `PART` symbol).

#### Added — `components/swatch/swatch.test.js` (6 → 9 cases)

3 NEW vitest cases under "FB-39 / §331":
- `hoists chrome child from inside wrapper span at #stamp() time (template-interpolation path)` — single-wrapper-single-chrome case.
- `handles MULTIPLE chrome children nested in separate wrapper spans` — multi-`${...}` template interpolation; each fragment lands in its own wrapper.
- `hoists chrome from late-arriving wrapper span (post-stamp template re-render)` — parent re-renders + appends a new wrapper-span carrying chrome after `#stamp()` already ran.

Total `swatch.test.js`: 9/9 passing (4 §325 + 2 §326 + 3 §331).

#### Migration

Consumers using `<swatch-ui>` with chrome-slot composition via `html\`\`` templates (`<swatch-ui>${badge}${dot}</swatch-ui>`) now get chrome children placed at canonical position post-§331. The consumer-side workaround (skip chrome composition or use bare `<swatch-ui>` without chrome) can be retired. No consumer code changes required — the §331 fix is invisible to consumers writing canonical templates.

### v0.5.x (Unreleased) §330 — `slider-ui` "Next" design + `segmented-ui` initial-render fix

**`slider-ui`** — full visual redesign to iOS/macOS-style pill slider. Pill-shaped track (`var(--a-toggle-size)` = 16/20/24px for sm/md/lg), primary-color progressive fill (`--a-primary-bg`), white chrome thumb (`--a-chrome-light`) with 2:1 width:height ratio and 2px internal padding on all sides. Mathematical geometry documented inline in CSS: `fill_width(p) = t + p·(W−t)`, `thumb_center(p) = t/2 + p·(W−t)`; fill starts at thumb-width (p=0) and grows incrementally to full track width (p=1). Two-layer thumb architecture: transparent full-height container provides grab area; `::before` renders the inward-padded white pill. `class.js` `#valueFromX()` maps full track width to [min,max]; `#valueFromX()` simplified back to direct rect ratio. `data-dragging` CSS transition lock kills the `left` transition during pointer moves for instant visual feedback. `slider.examples.html` gains Sizes section (lg/md/sm/14px/12px) + updated CSS token table.

**`segmented-ui`** — initial rendering race fix. Added `ResizeObserver` on self + `document.fonts.ready` promise in `connected()`; both call `#updateIndicator()` when layout stabilizes so the animated pill positions correctly after font load or resize. Previously the indicator computed once at mount and stayed misaligned — only corrected on next user interaction.

Files: `slider.css`, `slider.class.js`, `slider.examples.html`, `slider.yaml`, `segmented.class.js`.

### Added
- §333 `<color-input>` gains canonical demo-shell loader pair to restore catalog symmetry with the other 92 primitives. NEW `color-input.html` (52-line shell pattern matching `color-picker.html`: tokens base + composed primitive CSS + composed primitive JS per ADR-0027 + `fetch('./color-input.examples.html')`) + NEW `color-input.examples.html` (7 sections: default · initial values · format hex vs oklch · popover placement · §313 generation constraints `max-chroma`/`max-l`/`min-l`/`hue-drift-max`/`base-hue` · disabled · inside a form). Closes gemini-review refactor-backlog P3-1.
- §332 `<swiper-ui>` gains `chrome="toolbar"` layout mode with companion `label` + `counter` props. Default chrome (overlay paddles + centered dots below) is unchanged. Toolbar chrome stamps a header row (label on the left, paddles on the right) above the track and a footer row (counter on the left, dots on the right) below — supports gallery-style compositions where consumers want the chrome out of the slide area. `#stampToolbarChrome()` builds the head + foot rows; `#updateCounter()` writes "N / M" (page-based, snap-aware) into `[data-swiper-counter]` on every scroll. New examples at `/site/components/swiper` § chrome (photo gallery / featured products / autoplay testimonials). Counter is page-based so it tracks the dot count correctly for multi-column + center-snap.
- §329 `<code-ui>` gains a `<template>`-child authoring path — wrap the source in `<template>…</template>` and write literal `<` / `>` without entity-escaping. `#stampBlock` reads `template.innerHTML`, dedents the wrapper column via a new static `#dedent()` helper, and feeds the result to CodeMirror. The HTML parser treats `<template>` content as inert (it lives in a `DocumentFragment`, not rendered), so embedded `<nav-ui>` / `<button-ui>` / etc. don't get upgraded inside the template. Three authoring paths now in priority order: (1) `[text]` attribute, (2) `<template>` child, (3) escaped textContent (back-compat — existing examples unchanged). Demo at `/site/components/code` § "escape-free authoring via &lt;template&gt;"; dogfooded on `patterns/app-nav` structure example. (Carry-forward from worktree §318.)

### Fixed
- §332 `<swiper-ui>` — comprehensive consumer-feedback hardening pass (9 distinct fixes consolidated). (a) **Click-and-drag preempted by native HTML5 image-drag**: `<img>` inside slide cards fired `dragstart` before our 5px pointermove threshold was reached, suspending pointer events. Fix: `dragstart` listener gated on `this.#drag !== null` (set by `#onPointerDown`) so genuine drag-out of intentionally draggable slide content stays available. (b) **Pagination dots ignored `--swiper-columns`**: stamping one dot per slide regardless of `slides-per-view`. New `#getPageCount()` returns `max(1, slides − cols + 1)` for start/end snap (reads `--swiper-columns` via `getComputedStyle(track)`); `ResizeObserver` on the host re-stamps when container queries shift the cv across breakpoints. `next/prev/play` cap at last page; dot labels read "Page N" (was "Slide N", misleading once dot-index ≠ slide-index). (c) **CSS `container-type` on the wrong element** — `container-type: inline-size` was on `[data-swiper-track]` but `@container` rules targeted `:scope` (the host = track's PARENT, not a descendant). Container queries match descendants of the container, so the rules fell through to whatever further-up ancestor had a container set (the docs shell) and incorrectly stamped `cols=2-3` on every swiper. Moved `container-type: inline-size; container-name: swiper;` to host; `@container swiper (...)` rules now target `:scope > [data-swiper-track]` (a real descendant). JS `#getColumns()` reads from the track via `getComputedStyle` so both `[slides-per-view]` (cascades from host) and `@container` (writes on track) converge. (d) **Snap-aware active tracking** — IntersectionObserver "leftmost intersecting" couldn't distinguish the last two snap positions for center/end snap (both kept the last two slides >50% visible). Replaced with `scroll` listener + `#computeActiveIndex()` that walks every slide, computes its snap target (`left` / `left + width/2 − trackWidth/2` / `left + width − trackWidth` per snap mode), clamps to `[0, maxScroll]`, and picks the closest target to `scrollLeft`. `#pageToSlideIndex` / `#slideToPageIndex` map between page and slide indices (identity for start/center; end-snap shifts by `cols − 1`); snap-aware `goTo()` scrolls to the computed target so dot clicks land without a "scroll to left edge, then scroll-snap re-correct" jank step. `#getPageCount()` returns `slides.length` for `snap="center"` (each slide is its own page; 4-slides-@-cols=2 now stamps 4 dots, not 3). (e) **Loop / autoplay demos didn't visibly demonstrate looping** — at responsive widths (≥960px → cols=3) the 3-slide demos had `pageCount=1`, paddles produced no visible effect. Pinned demos to `slides-per-view="1"`; added a new combined "loop + autoplay" example. (f) **`gap` only accepted token indices despite docs claiming "any CSS length"**: `render()` blindly wrapped `gap` in `var(--a-space-${gap})`. For `gap="1rem"` that became `var(--a-space-1rem)` (undefined token) → property invalidated → CSS gap fell back to 0; three demo variants (`gap=0/1rem/2rem`) all rendered identically. Fix: numeric-only strings resolve to space tokens, anything else used as a raw CSS length. (g) **Dot CSS lost in toolbar chrome**: `:scope > [data-swiper-dots] > button` (direct-child combinator) stopped matching when dots moved inside `[data-swiper-foot]`; buttons fell back to browser defaults (wide gray bars). Relaxed all five dot rules to `:scope [data-swiper-dots]` (descendant combinator). (h) **Active dot rendered much larger than inactive** — `background: <color>` shorthand in `:hover` and `[aria-current="true"]` rules silently RESET `background-clip` back to its initial `border-box`, so the colored area filled the full 1rem outer hitbox and `border-radius: 100rem` rounded that into a ~16px circle (vs the 6px content-box-clipped circle for inactive). Fix: longhand `background-color: <color>` so `background-clip: content-box` from the base rule is preserved. Active dot now has a deliberate +4px (`--a-space-1`) size delta via NEW `--swiper-dot-size-active` token; padding back-computed so the 1rem hitbox stays invariant. Focus ring moved from outer `var(--a-focus-ring)` to inset 2px shadow so focus doesn't change footprint either. **Pitfall worth knowing for future CSS work: `background: <color>` shorthand resets `background-clip`/`background-origin`/`background-position`/etc. to initial — use `background-color: <color>` to preserve them.**
- §327 P2 correctness — `<code-ui language="…">` (every `/site/patterns/*` page) rendered as flow-wrapped plain text with chrome header below the content + no syntax highlighting. Root cause: the `textContent` key in `static properties` of `code/class.js`, `button/class.js`, `tag/class.js`, `badge/class.js` made `UIElement.installProps()` shadow the native `HTMLElement.prototype.textContent` setter. `this.textContent = ''` in `code-ui`'s `#stampBlock` became a signal-write no-op — original authored HTML-escaped source stayed in the element above the stamped chrome, and CodeMirror mounted with an empty doc (1 cm-line, 19.5px tall editor below leftover authored text). Same hazard on `<tag-ui>` produced visible empty pills across every `/site/traits/*` page's API table (the `chip()` helper in `traits/_api-table.js` and 3 other call sites all do `t.textContent = text` after `createElement('tag-ui')`). Fix: removed the `textContent` entry from each of the 4 class.js files. Yaml + `.d.ts` entries kept (renderer routes via `setAttribute('text', …)` for non-text-bearing tags; doesn't depend on JS prop declarations). New drift class #7 (native-DOM-accessor collision). **Carry-forward note:** initially staged as §316 on a worktree branch (2026-05-15) before v0.5.14 cut; §316-§318 were claimed by parallel peer work that shipped via v0.5.15. Renumbered to §327 on re-apply to main against v0.5.15 HEAD (2026-05-16).

### Docs
- §328 source-side polish: re-flowed 6 one-line `<code-ui>` blocks across `stat`/`pagination`/`rating`/`count-up`/`theme-panel`/`theming` USAGE sections — multi-attribute single tags now author each attribute on its own line for readability + consistency with how the rest of the corpus is authored. (Carry-forward from worktree §317.)

## [0.5.15] - 2026-05-16

> **Scope note:** §325 + §326 were originally planned as a separate v0.5.16 cycle (see retired `.claude/docs/plans/0.5.16-release-plan.md`); absorbed into v0.5.15 since both landed within the same release window (same-day commits prior to bump). The peer's v0.5.16 plan-doc explicitly anticipated this absorption option ("Hermes's call per `/lockstep-release`"). v0.5.16 plan-doc marked SHIPPED-IN-V0.5.15.

### v0.5.15 §326 — `<swatch-ui>` `[slot="chrome"]` lost in template contexts (FB-37; P1)

Closes FB-37. Pre-§326, `<swatch-ui>` consumed via any `html\`\`` template engine (or any engine that batches child appends separately from element creation) lost its `[slot="chrome"]` children entirely. Root cause: `#stamp()` is gated by a `#stamped` once-only guard + scans `this.children` for chrome slots at `connectedCallback` time. Template engines create the host via `createElement` + connect it FIRST (children empty), then batch-append children AFTER. `#stamp()`'s chrome scan returns `[]`; the `#stamped` guard prevents re-runs; chrome children landed at the END of host's children (after `data-copy`) instead of the canonical sibling-of-tile position. Blocked Tokens Studio's C1.3 dogfood migration (9 failing Playwright tests root-caused to this) — and >95% of AdiaUI consumers using `html\`\`` templates.

#### Fixed — `components/swatch/class.js`

- NEW `#absorbChromeSlot()` helper. Idempotent. Walks `this.children`; for any child with `slot="chrome"` that's not one of the internal stamped elements + not in the canonical position, calls `this.insertBefore(child, this.#badgeEl)` to move it to the slot between `#tileEl` + `#badgeEl`.
- `#syncCore()` calls `#absorbChromeSlot()` at the top of every render pass. Late-arriving chrome children (template-render path) get picked up on the next reactive cycle; pre-positioned chrome children (static-HTML path) are no-op-moves.
- `#stamp()`'s once-only eager scan stays intact for the static-HTML path; `#absorbChromeSlot()` is the safety net for late-append.

#### Added — `components/swatch/swatch.test.js`

- 2 NEW vitest cases under "FB-37 / §326":
  - `absorbs chrome child appended AFTER connectedCallback (template-render path)` — mimics the bug's exact timing (create + connect with empty children → late append → property mutation triggers render → fix picks up the chrome child).
  - `still handles chrome child appended BEFORE connect (static-HTML path)` — regression check that §314's original behavior still works.
- Total suite: 6 cases (4 from §325 + 2 from §326).

#### Migration

Consumers using `<swatch-ui>` via `html\`\`` template engines (lit-html, AdiaUI's own `html\`\``, Preact, Vue, etc.) get the §314 chrome-slot feature working as documented in v0.5.13's `swatch.yaml slots:` block. No consumer-side changes required — the §326 fix is invisible to consumers writing canonical templates.

### v0.5.15 §325 — `<swatch-ui>` default-slot wiped by label-attr (FB-36)

Closes FB-36 + FB-36-followup. Pre-§325, `<swatch-ui label="500"><span>tip</span></swatch-ui>` written as indented multi-line HTML dropped the default-slot child entirely; only `label="500"` rendered. Root cause: `#stamp()`'s `Array.from(this.childNodes).filter(...)` captured pure-whitespace text nodes from the indentation; those landed in `#labelEl.firstChild`; `#syncCore()`'s "stale text-node" branch (designed for runtime `el.label = 'X'` mutations on attr-only swatches) then matched the whitespace + did `textContent = this.label`, wiping both the whitespace AND the consumer's `<span>`.

Pre-§325 the yaml spec ("Use the default slot for richer content" — `swatch.yaml:40`) and the implementation contradicted each other; post-§325 they agree.

#### Fixed — `components/swatch/class.js`

- `#stamp()`'s `slotted` filter chain gains `&& !(n.nodeType === 3 && !n.textContent.trim())` — strips pure-whitespace text nodes from the capture. `#labelEl.firstChild` now becomes the consumer's element (or non-whitespace text), so `#syncCore`'s stale-text-node branch fires only for genuinely attr-driven cases.

#### Added — `components/swatch/swatch.test.js`

- NEW 4-case vitest suite covering FB-36's 3 input axes (default-slot present/absent × label-attr present/absent × runtime mutation). Locks the §325 fix structurally — future regressions of any of the 4 paths fail CI.

#### Migration

`<swatch-ui label="500"><span>tip</span></swatch-ui>` now behaves per yaml spec: default-slot wins, label-attr drops. Consumers using the workaround `.label=` property binding (per FB-36-followup) to bypass the bug can switch back to the canonical `[label]` attribute form post-upgrade.

### v0.5.15 §324 — Yaml `enum_descriptions:` codegen extension (v0.6.0 FB-23 §3 retire carry-forward, PATCH-eligible)

Closes the v0.6.0 carry-forward "Codegen yaml `enum:` per-member descriptions (FB-23 §3 retire)" from the RESPONSE-29 carry-forwards table. v0.5.15 ships the additive codegen surface on PATCH cadence; v0.6.0 no longer needs to carry this item.

**The surface change:** `scripts/schemas/component.yaml.schema.json` `Prop` definition gains an optional `enum_descriptions:` object field — when present alongside `enum:`, dts-codegen emits an exported type alias (e.g. `UITextVariant`) above the class declaration with per-variant JSDoc carrying each description. IDE hover surfaces the disambiguation at authoring time without requiring hand-authored .d.ts.

#### Added

- `scripts/schemas/component.yaml.schema.json` — NEW optional `enum_descriptions: { type: object, additionalProperties: { type: string } }` field per prop. Backward-compatible: existing yamls without the field continue to emit inline enum unions.
- `scripts/build/components.mjs` — forwards `enum_descriptions:` through `compileProp()` into the a2ui.json sidecar.
- `scripts/build/dts-codegen.mjs` — `emitEnumTypeAlias()` helper detects props with both `enum:` and `enum_descriptions:`; emits a typed union alias `${className}${PascalCase(propName)}` with per-variant JSDoc; class property references the alias instead of inlining the union.

#### Changed — `components/text/text.yaml`

- `variant:` prop gains 12-entry `enum_descriptions:` block (one description per variant). Prop `description:` block also gains the FB-23 §2 ARIA-role disclaimer that previously lived in the hand-authored .d.ts.

#### Changed — `scripts/build/dts-codegen.mjs` `HAND_AUTHORED_DTS`

- `text` removed from skip-list. `text.d.ts` now codegen-driven; per-variant JSDoc surfaces via the new `enum_descriptions:` field. Net effect: 1 of 3 remaining `HAND_AUTHORED_DTS` entries closed (`toast` + `feed` remain — both carry runtime API the yaml shape can't express).

#### Regenerated — `components/text/text.d.ts`

- Replaces hand-authored .d.ts with codegen output. `UITextVariant` type alias preserved as exported type; per-variant JSDoc preserved verbatim. Net result: zero consumer-facing diff in the .d.ts surface; codegen takes over maintenance.

### v0.5.15 §322 — NEW audit slot 32: `check-dead-constants.mjs`

Graduates the FB-35 bug class (declared-but-unread module-level consts) to a CI gate. Walks every `.js` file under `packages/<pkg>/`; extracts SCREAMING_SNAKE_CASE / PascalCase `const NAME = ...;` declarations at module level; flags zero-reference cases. Skips `export const NAME` (consumed externally) + test/spec/examples/config files.

First sweep surfaced + removed 2 real dead constants:
- `packages/a2ui/validator/validator.js:38` — `WIRING_WEIGHTS` (vestigial from planned weighted-scoring system; safe deletion).
- `packages/llm/adapters/openai.js:10` — `DEFAULT_MAX_TOKENS = 4096` (replaced by explicit `opts.max_tokens` plumbing).

### v0.5.15 §323 — NEW audit slot 33: `check-hover-guard-discipline.mjs`

Graduates the `analyze-css` bare-`:hover` discipline class to a CI gate. Detects `:hover` rules on host primitives + interactive slots/roles that lack a `:not([disabled])` guard AND set visual properties (background/color/border/box-shadow/etc.). Skips cursor-only / outline-style-only rule bodies.

First sweep surfaced 19 findings across 11 components — all baselined in EXPECTED_DRIFT for cycle-by-cycle close:
- **Real drift** (rely on `pointer-events: none` for click-suppression but hover styles still apply): action-list (4×), menu (1×), nav-item (2×), nav-group (1×).
- **Slotted-trigger drift** (mitigated via parent guard rules; cosmetic-OK but worth tightening): select (5×), calendar-picker (1×), range (2×), tag (1×).
- **Decorative-pop hover** (intentional): avatar (1×). Long-term whitelist candidate.
- **@scope-Safari-17.x-workaround**: action-list (3 of 4) + nav-item (2) + rating (1) — guard can't live inside @scope; lift alongside the hover rule.

#### Removed — dead module-level constants

- `packages/a2ui/validator/validator.js` — `WIRING_WEIGHTS` constant (zero refs).
- `packages/llm/adapters/openai.js` — `DEFAULT_MAX_TOKENS` constant (zero refs).

### v0.5.15 §319 — NEW audit slot 29: `check-exports-map-resolves.mjs`

Graduates the FB-32 bug class to a CI gate. Closes the recurring drift where a package.json `exports` map entry resolves (via wildcard expansion or static path) to a file that doesn't exist on disk — consumer TS imports fail with `TS2882` even though Vite/Node can fall through `node_modules/<pkg>/<path>` directly.

#### Added — `scripts/release/check-exports-map-resolves.mjs`

- ~270 LOC. Walks every `packages/<pkg>/package.json` `exports` map. For each STATIC entry, verifies the referenced file path exists. For each WILDCARD entry matching the FB-32 shape (`./X/*` key + `./X/*/*.<ext>` value), enumerates filesystem candidates that would match the wildcard + verifies each resolved path exists.
- Skips wildcard expansions where an explicit non-wildcard entry overrides the resolution (Node exports-map resolution algorithm precedence).
- Skips CSS-only / no-JS components (header / footer / section / aside / various web-modules shells) — these intentionally ship no `.js`.
- Result: 9 packages / 52 static + 39 wildcard exports entries / 59 resolutions verified / 0 unresolved.

### v0.5.15 §320 — NEW audit slot 30: `check-composes-passthrough.mjs`

Graduates the FB-33 bug class to a CI gate. Closes the recurring drift where a form-bearing primitive that programmatically creates ANOTHER form-bearing primitive in its lifecycle (light-DOM composition) doesn't expose / forward the composed primitive's documented prop + event-detail surface.

#### Added — `scripts/release/check-composes-passthrough.mjs`

- ~270 LOC. Static `COMPOSITION_PAIRS` list declares the required pass-through surface per pair. For each pair: verifies composing primitive's yaml `composes:` block lists the composed primitive; verifies yaml `props:` block exposes every required prop; verifies yaml `events:` block declares each event with required detail keys.
- Initial pair: `color-input → color-picker` (8 props + 12 event-detail keys across `change` + `input`).
- Adding a new pair: any new primitive that programmatically wraps a form-bearing primitive declares its contract in `COMPOSITION_PAIRS`; the audit fires at PR time.
- Companion to slot 23 (`check-form-bearing-dts.mjs`): that audit checks .d.ts type-coverage; this audit checks yaml-declared shape.

### v0.5.15 §321 — NEW audit slot 31: `check-token-name-canonical-shape.mjs`

Graduates the stepper / list state-first naming drift class to a CI gate. Closes the recurring drift where a component-local CSS token uses state-first (`--stepper-active-bg`) or color-infix-state (`--link-color-hover`) naming instead of the canonical property-first state-suffix shape (`--<comp>(-<sub>)*-<prop>(-<state>)?`).

#### Added — `scripts/release/check-token-name-canonical-shape.mjs`

- ~210 LOC. Extracts `--<comp>-*` declarations from each component CSS file. Classifies each by segment shape: canonical-prop / canonical-prop-state / canonical-leaf / canonical-domain / drift-state-first / drift-infix-color-state.
- Vocabularies (PROP_TOKENS + STATE_TOKENS) capture the catalog's recognized property + state terms (bg/fg/border/ring/shadow/color/outline/size + hover/active/selected/disabled/focus/checked/done/...).
- Result: 92 components / 1809 component-local tokens audited / 0 drift findings.
- `link.css` `--link-color-hover` not flagged at v0.5.15 ship: `color` is a recognized PROP_TOKEN + `hover` is a recognized STATE_TOKEN — the shape matches canonical. A future, stricter audit (alternative-property-naming) could flag the `color` vs `fg` divergence; deferred to v0.6.0 BREAKING window since link.css's `--link-color-*` are documented public API.

### v0.5.15 §316 — Naming-convention drift fix (stepper + list state-first → property-first)

`<stepper-ui>` and `<list-ui>` declared internal tokens with state-first naming (`--stepper-active-bg`, `--list-selected-fg`, etc.) — the catalog convention is property-first (`--<comp>-{prop}-{state}`). 18 token names renamed; usage sites updated in-place. `<link-ui>` skipped — its `--link-color-*` tokens are documented public API per `link.yaml tokens:` block; renaming is BREAKING and defers to v0.6.0.

#### Changed — `components/stepper/`, `components/list/`

- `stepper.css` — 12 tokens renamed: `--stepper-{active,done}-{bg,border,fg}` + `--stepper-item-{active,done}-{bg,border,fg}` → `--stepper-{bg,border,fg}-{active,done}` + `--stepper-item-{bg,border,fg}-{active,done}`.
- `list.css` — 3 tokens renamed: `--list-selected-{bg,fg,border}` → `--list-{bg,fg,border}-selected`.

### v0.5.15 §316 — Hand-rolled transition-duration literals → token references (swatch + heatmap + page)

Three components hand-rolled `100ms ease-out` / `120ms ease-out` / `0.1s ease-out` / `150ms ease` instead of routing through the canonical `var(--a-duration-fast)` + `var(--a-easing*)` token chain. Theme designers couldn't swap transition cadence centrally.

#### Changed — `components/swatch/`, `components/heatmap/`, `components/page/`

- `swatch.css:294,311` — `100ms ease-out` + `120ms ease-out` → `var(--a-duration-fast) var(--a-easing-out)`.
- `heatmap.css:80` — `0.1s ease-out` → `var(--a-duration-fast) var(--a-easing-out)`.
- `page.css:61` — `150ms ease` → `var(--a-duration-fast) var(--a-easing)`.

### v0.5.15 §317 — NEW audit slot 27: `check-raw-fg-in-component-css.mjs`

Graduates the §313/§315/§316 bug class to a CI gate. Closes the recurring drift class where component CSS rules bypass their own `--<comp>-<family>-<state>` semantic-layer token and reference the raw global `--a-<family>-<state>` token directly. The audit slot found 5 additional bypass instances across the codebase (nav-group + select + toolbar) — all routed through the component-local semantic layer in this commit.

#### Added — `scripts/release/check-raw-fg-in-component-css.mjs`

- ~190 LOC audit script. Parses each `<comp>.css` for the `:where(:scope) { ... }` declaration block + the set of `--<comp>-*` tokens defined therein. Scans the remainder for `<standard-css-property>: var(--a-<family>-<state>?)` usage where the parallel `--<comp>-<family>-<state>?` is defined — emits a finding per bypass.
- Whitelisted CSS properties: `color`, `background`, `background-color`, `border` (+ all sub-axis variants), `outline`, `fill`, `stroke`, `caret-color`, `accent-color`, `text-decoration-color`, `column-rule-color`.
- Distinguishes declaration sites (`--<comp>-X: var(--a-X)`) from usage sites mechanically — declaration sites are NOT flagged (those are correct semantic-layer aliasing).
- Companion to slot 25 (`check-component-css-vs-styles-tokens.mjs`): that audit catches orphan references (`var(--a-X)` where `--a-X` doesn't exist); this audit catches the inverse (`var(--a-X)` where it exists but `--<comp>-X` should be preferred).
- EXPECTED_FINDINGS baseline: 0 entries at v0.5.15 ship (sweep landed every concrete bypass).

#### Fixed — 5 component CSS bypass sites

- `nav-group.css:243,252` — `background: var(--a-bg-hover)` → `var(--nav-group-bg-hover)`.
- `select.css:199` — listbox container `color: var(--a-fg)` → `var(--select-fg)`.
- `select.css:319` — hint slot fallback `var(--a-fg-muted)` → `var(--select-fg-muted)`.
- `toolbar.css:107` — overflow popover `color: var(--a-fg)` → `var(--toolbar-fg)`.

### v0.5.15 §317 — NEW audit slot 28: `check-form-bearing-events-symmetric.mjs`

Graduates the FB-31/§315 bug class to a CI gate. Closes the recurring drift class where UIFormElement-extending primitives emit only `input` (or only `complete`/`search`/`pick`) without the canonical `change` event with `detail.value`. The audit confirms all 17 form-bearing primitives are now symmetric post-v0.5.14 §315.

#### Added — `scripts/release/check-form-bearing-events-symmetric.mjs`

- ~145 LOC audit script. Walks every `class.js` for classes that `extends UIFormElement`. For each, verifies a `dispatchEvent(new CustomEvent('change', { detail: { value: ... } }))` occurs (statically or via dynamic-name dispatch + nearby `'change'` string literal + `detail = { value: ... }` construction).
- Heuristic limitations documented: false negatives possible if event name is computed via complex variable assignment; manual review at addition time required.
- ALLOW_LIST mechanism for primitives where the commit model legitimately omits `change` (none at v0.5.15 ship).
- Companion to slot 23 (`check-form-bearing-dts.mjs`): that audit verifies typed `<Comp>ChangeEvent` exists in `.d.ts`; this audit verifies the runtime dispatch.

### v0.5.15 §318 — ADR-0028: L3 state-matrix orphan retirement (78 unused tokens deleted)

ADR-0028 ratifies the deletion of 78 orphaned L3 state-matrix tokens from `semantics.css`. The `maintain-tokens` skill audit during v0.5.14 §315 surfaced that 73 of ~80 L3 `--a-{family}-{prop}-{state}` tokens have zero consumers across the entire codebase. The per-primitive `--<comp>-*` token pattern (validated across v0.5.13/14/15 sweeps) is the actual consumer-facing API.

#### Removed — `styles/colors/semantics.css`

- 78 `--a-{accent,primary,brand,info,success,warning,danger,fg,border}-{fg,bg,border}-{active,selected,disabled,invalid}` declarations deleted. ~80 LOC reduction.
- Token count dropped 1301 → 1217 (-6.5%).
- Kept (have consumers): `--a-bg-{active,selected,disabled,invalid}`, `--a-fg-{selected,disabled}`, `--a-accent-bg-active`, `--a-primary-bg-active` (transitive via accent-bg-active).
- Kept (consumer-facing hover state): all `--a-*-hover` variants — hover is the only state where the generic-L3 layer is load-bearing.

#### Added — `.brain/adrs/0028-l3-state-matrix-orphan-retirement.md`

- ADR documents the audit-driven discovery, the retention rationale for the 7 used tokens, the deletion rationale for the 78 orphans, alternatives considered (keep all / delete only family-specific / add audit), and the per-primitive convention as the canonical consumer-facing override surface.

## [0.5.14] - 2026-05-15

### v0.5.14 — Form-control hover-fg token unification (precursor to §315; commit `178418525`)

Form-control primitives diverged on hover-text token routing. `button-ui` correctly used `--a-ui-text-hover` (resolves to `--a-fg-strong` — canonical hover-lift for form-control text); every other form-control primitive used the generic `--a-fg` (no actual lift from default `--a-canvas-text`) — so "hover" had zero text-color effect on `input`/`textarea`/`segment`, and `select-ui`'s listbox/caret hover paths bypassed the `--select-*` token layer entirely with raw `--a-fg-*` references.

#### Fixed — `components/{input,textarea,segment,select}/`

- `input-ui`: `--input-fg-hover: var(--a-fg)` → `var(--a-ui-text-hover)`; `--input-affix-fg-hover: var(--a-fg-subtle)` → `var(--a-ui-text-subtle)`.
- `textarea-ui`: matching routing on hover/affix-hover.
- `segment-ui`: same routing change.
- `select-ui`: listbox + caret + option-hover tokens previously raw `--a-fg-*` references now route through their `--select-*` semantic layer (which itself anchors at `--a-ui-text-*` family).

Net effect: hover-text colors now consistent across all form-control primitives + theme designers can swap them all by changing one semantic token (`--a-ui-text-hover`) at the `--a-ui-*` family root in `semantics.css`.

### v0.5.14 §315 — Drift-class sweep follow-up (`ui-audit-coherence` + `maintain-tokens` + `analyze-css` triple-pass)

Three parallel skill-driven audits surfaced 7 mechanical drift-classes; landed as a single sweep in one commit (`025a981db`). Follow-up to the form-control hover-fg unification at `178418525` — same bug shape (component CSS bypassing its own semantic-layer routing through raw global tokens), broader scope.

#### Fixed — `components/select/`, `components/nav-group/`, `components/chat-thread/chat-input.css`

- `select.css:254,286,293,300` — 4 raw `var(--a-fg-*)` callsites routed through `--select-fg-{muted,fg,muted,muted}` (semantic layer already existed but was bypassed).
- `nav-group.css:225,234,244,253,286,298` — 6 raw `var(--a-fg-{muted,subtle,strong})` callsites routed through `--nav-group-fg-{muted,muted,hover,selected,muted,muted}`.
- `chat-input.css:40,50,58` — placeholder/hover/disabled tokens routed through the canonical `--a-ui-text-{placeholder,hover,disabled}` field-family chain instead of the generic `--a-fg-*` family. Continues the v0.5.13 hover-fg unification arc.

#### Fixed — `components/{check,radio,switch,tabs,action-list,calendar-picker,menu}/`

- 6 form-adjacent primitives' `--{comp}-fg-disabled` token routed through canonical `--a-ui-text-disabled` instead of `--a-fg-muted` (the disabled-text version of the v0.5.13 hover-fg drift class).
- `menu.css:96,99` also fixed `--menu-item-fg-disabled` + `--menu-item-icon-disabled` (the third variant `--a-fg-disabled` shape; same canonical target).

#### Added — `components/otp-input/`, `components/search/`

- `<otp-input-ui>` + `<search-ui>` now dispatch `change` alongside `input` on every value-altering event. Both primitives previously emitted only `input` (the sole exceptions across all UIFormElement-bearing primitives). Closes consumer event-binding gap — `@change=${handler}` now works against every form-bearing tag uniformly.
- `.d.ts` files gain `OtpInputChangeEvent` + `SearchChangeEvent` type exports (reuse existing detail interfaces; matches v0.5.10 `check-form-bearing-dts` audit convention).
- yaml `events:` blocks declare the new `change` event with `detail.value`.

#### Fixed — `styles/typography.css`

- `:where(mark)` background reference fixed: `var(--a-highlight-default)` → `var(--a-highlight)` (`--a-highlight-default` token does not exist; pre-fix `<mark>` silently fell back to UA-default highlight). Single-line typo discovered via `maintain-tokens` skill sweep.

#### Fixed — `components/{textarea,upload,option-card,pagination}/` transition consistency

- `textarea.css:76` transition list expanded from `border-color` only → `background + border-color + color + box-shadow` (matches `input-ui`'s 4-property parity; pre-fix hover/focus slammed in instead of easing).
- `upload.css:64-67` + `option-card.css:102-104` + `pagination.css:71-73` each gain `box-shadow` (and `border-color` for pagination) so focus-ring eases in alongside the other state changes. Cross-primitive hover/focus polish parity restored.

#### Fixed — `components/input/`, `components/select/` `-active` token routing

- `--input-fg-focus: var(--a-fg-strong)` → `var(--a-ui-text-active)` (closes orphan: `--a-ui-text-active` semantic-layer alias existed at `semantics.css:552` with zero consumers).
- `--select-option-fg-active: var(--a-fg)` → `var(--a-ui-text-active)` (same routing; cross-primitive form-control consistency).

#### Fixed — `components/heatmap/`

- `class.js:disconnected()` now cancels `#raf` via `cancelAnimationFrame()`. Pre-fix the `requestAnimationFrame` handle was stored but never cleaned up on detach — minor memory leak in scroll-heavy demos. ~3 LOC.

## [0.5.13] - 2026-05-15

### v0.5.13 §310 — `apcaContrast` soft-clamp constant fix (FEEDBACK-35; P2 correctness)

`@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`.

#### Fixed — `color/`

- `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.

### v0.5.13 §311 — `@adia-ai/web-components/color` P3 helpers (FEEDBACK-31)

`@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.

#### Added — `color/`

- `inP3Gamut(L, C, H): boolean` — returns true if the OKLCH triple is inside Display-P3 gamut (tolerance `0.001`).
- `gamutMapChromaP3(L, C, H): number` — 8-iteration chroma bisection in P3 (matches `gamutMapChroma`'s shape).
- Internal `linearSrgbToLinearP3()` matrix sourced from CSS Color 4 § 12.3 (D65 → D65; no Bradford chromatic adaptation needed).
- `color/index.d.ts` ships matching declarations.

### v0.5.13 §312 — `<stat-ui>` explicit exports map entries (FEEDBACK-32)

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.

#### Changed — `package.json`

- `exports` map adds 2 explicit entries: `./components/stat` + `./components/stat/stat-ui.js` both pointing at `stat-ui.{d.ts,js}`.
- **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}`.

### v0.5.13 §313 — `<color-input-ui>` constraint pass-through + parsed-detail forwarding (FEEDBACK-33)

`<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.

#### Added — `components/color-input/`

- `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`).
- `#commit()` re-emits with all 6 detail fields including parsed scalars `l: detail.l, c: detail.c, h: detail.h`.
- `color-input.yaml` declares the 5 props + 3 new detail fields per `change`/`input` events.
- `color-input.d.ts` `ColorInputChangeEventDetail` interface gains `l: number; c: number; h: number;` fields; class gains 5 typed prop declarations.

### v0.5.13 §314 — `<swatch-ui>` `[slot="chrome"]` named slot (FEEDBACK-34)

`<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`.

#### Added — `components/swatch/`

- `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.
- `swatch.yaml` `slots:` block declares the `chrome` named slot + clarifies `default` slot's funnel-into-label behavior.

#### Migration

```diff
 <swatch-ui shape="block" label-position="overlay" selectable>
-  <badge-ui class="dts-gamut-badge" ...>P3</badge-ui>
-  <span class="dts-override-dot"></span>
+  <badge-ui slot="chrome" class="dts-gamut-badge" ...>P3</badge-ui>
+  <span slot="chrome" class="dts-override-dot"></span>
 </swatch-ui>
```

Single `slot="chrome"` attribute on each chrome element. Existing absolute-positioning CSS unchanged.

## [0.5.12] - 2026-05-15

### v0.5.12 §252 — `package.json` `files` array completion (FB-28 close-out)

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.

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.

### v0.5.12 §253 — `stat-ui.d.ts` hand-author + slot 26 widening (FB-30 close-out)

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.

#### Added — `components/stat/stat-ui.d.ts`

`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.

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.

#### Changed — `scripts/release/check-yaml-vs-dts-coverage.mjs` slot 26 widened

- `hasShippedJs(dir, comp)` — looks for `<comp>.js` OR `<comp>-ui.js` (handles irregular-filename case)
- `findDtsPath(dir, comp)` — looks for `<comp>.d.ts` OR `<comp>-ui.d.ts` (mirrors JS filename)
- NEW `CSS_ONLY_PRIMITIVES` exclusion set (`aside` / `footer` / `header` / `section`) — intentionally yaml-only stubs
- NEW finding kind: `missing-dts-file` (display-primitive sub-axis catch)
- Scope: FORM_BEARING-only → all primitives with shipped JS + yaml

Validated by smoke test: removing `stat-ui.d.ts` fires `missing-dts-file: stat`; restoring closes clean.

### v0.5.12 §301 — NEW `@adia-ai/web-components/color` subpath (FEEDBACK-29 re-bucket)

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.

#### Added — `color/`

- `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.
- `color/index.d.ts` — full TS surface, `OKLab` + `OKLCH` interfaces, comprehensive JSDoc with APCA threshold guidance.

#### Changed — `package.json`

- `exports["./color"]` added pointing at `color/index.{js,d.ts}`.
- `files: ["color/", …]` added so the subpath ships in the tarball.

### v0.5.12 §302 — NEW `<color-input-ui>` compact form-bearing primitive (FEEDBACK-29 re-bucket)

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]`.

#### Added — `components/color-input/`

- `color-input.yaml` — schema declares 6 props (`name`, `value`, `format`, `disabled`, `placement`, `open`) + 2 events with flattened parallel-views detail.
- `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.
- `color-input.css` — compact swatch chip + tabular-num value label; honors `--color-input-swatch-size` token.
- `color-input.d.ts` — hand-authored (FORM_BEARING in dts-codegen); declares `ColorInputChangeEventDetail` with parallel `hex` + `oklch` views.
- `color-input.js` — auto-register shim.
- `color-input.a2ui.json` — codegen sidecar.

#### Changed

- `components/index.js` — re-exports `UIColorInput`.
- `styles/components.css` — `@import` for `color-input/color-input.css`.
- `scripts/build/dts-codegen.mjs` — `FORM_BEARING` set adds `color-input` so codegen preserves hand-authored event types.
- `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.

### v0.5.12 R23 §1 — `<swatch-ui label-position="overlay">` (FB-23 §1)

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).

#### Added — `components/swatch/`

- `swatch.yaml` — `labelPosition` prop (kebab attribute `label-position`) with `below|overlay` enum.
- `swatch.css` — `:scope[shape="block"][label-position="overlay"]` rules positioning the label absolutely over the tile.
- `swatch/class.js` — static property declaration; reflected.

## [0.5.11] - 2026-05-15

### v0.5.11 §250 — `?attr=${bool}` silent-failure trap closed (FEEDBACK-27)


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.

#### Fixed — §250a (v0.5.11) — `core/template.js` `scan()` rejects `?`-prefixed attribute names with a console.warn

`packages/web-components/core/template.js` — `scan()` attribute-walk adds a 4th branch alongside `@event=` / `.prop=` / canonical `attr=`. When `name[0] === '?'`:

1. Strip the bogus `?attr` attribute from the DOM (prevents the literal `?disabled="true"` poison-pill that consumers saw pre-§250).
2. Fire `console.warn` naming the offending attribute + the corrected migration path (`use .${prop}=${'${value}'}` — property binding).
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".

Pre-§250 (silent inert binding):

```js
// Pre-§250 — silently registered `?disabled="true"` on the DOM; consumer's
// `<color-picker-ui>` ignored it; picker stayed interactive.
html`<color-picker-ui ?disabled=${isTracked}>`;
```

Post-§250 (warn + safe-degrade):

```
console.warn: [template] Lit-style boolean attribute "?disabled=" is not supported.
  Element: <color-picker-ui>
  Use .disabled=${value} (property binding) instead — the primitive reflects
  the property to the DOM attribute for you.
  See USAGE.md § Template parser — invariants + unsupported syntaxes.
```

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.

7 new vitest cases in `packages/web-components/core/template.test.js` lock in the §250 contract:

- `?attr=` fires warn (truthy + falsy)
- Does NOT register literal `?attr` attribute on the DOM
- Does NOT set the matching attribute either (no Lit-attr semantics)
- No false positives on canonical `attr=` / `.prop=` / `@event=`

#### Changed — §250b (v0.5.11) — USAGE.md adds "Template parser — invariants + unsupported syntaxes" section

`packages/web-components/USAGE.md` § Template parser — NEW section enumerates all 4 documented parser contracts:

| Syntax | Status | Migration |
|---|---|---|
| `attr=${val}` (full interpolation) | ✅ Supported | n/a |
| `.prop=${val}` (property binding) | ✅ Supported | canonical for boolean state |
| `@event=${handler}` (event listener) | ✅ Supported | n/a |
| `?attr=${bool}` (Lit-style boolean) | ❌ NOT supported — `console.warn` v0.5.11 §250 | Use `.attr=${bool}` |

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.

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.

## [0.5.10] - 2026-05-15

### v0.5.10 §248 — `check-static-methods-vs-dts.mjs` slot 20 base-class spread-walk extension (FB-25 audit-extension)


Closes the FB-25 base-class blindspot mechanically. The audit grows a 5th declaration-axis on the `.d.ts` surface:

- For each registered base class (currently `{ UIFormElement }`), walk its `static get properties()` return-object body + cross-reference against base `.d.ts` instance-field declarations.
- 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`.

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> = ...`.

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.

Validated by deliberate-removal test: removing `throttle: number;` from `core/form.d.ts` fires `base-property-undeclared: throttle`; restoring closes clean.

### v0.5.10 §249 (slot 26 NEW) — `check-yaml-vs-dts-coverage.mjs` (FB-26 audit-extension)

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:

- For each form-bearing primitive: parse `<name>.yaml` `props:` block → set of declared prop names
- Parse `<name>.d.ts` class body → set of instance-field declarations (including getters)
- Subtract `UIFormElement`-inherited fields (via `core/form.d.ts` walk; 16 fields including the §247d `throttle` addition)
- Set-diff yaml-only → flag as `yaml-vs-dts-missing` (hard finding)

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.

`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.

Validated by deliberate-removal test: removing `inputmode: string;` from `input.d.ts` fires `yaml-vs-dts-missing: input:inputmode`; restoring closes clean.

### v0.5.10 §247 — post-v0.5.9 `.d.ts` drift close-out (FB-23 / FB-24 / FB-25 / FB-26)

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.

#### Added — §247 `HAND_AUTHORED_DTS` skip-list extension (`text` + `feed`)

`scripts/build/dts-codegen.mjs` `HAND_AUTHORED_DTS` set grows `{ 'toast' }` → `{ 'toast', 'text', 'feed' }`. Pattern source: v0.5.9 §246. Two new entries:

- **`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).
- **`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.

#### Fixed — §247 `text.d.ts` per-variant JSDoc + ARIA-role disclaimer (FB-23 §2 + §3)

`packages/web-components/components/text/text.d.ts` hand-authored. Two surfaces:

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).
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.

#### Fixed — §247 `feed.d.ts` UIFeed static-API surface (FB-24 §1)

`packages/web-components/components/feed/feed.d.ts` hand-authored. Adds:

- `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.
- `UIFeedPostOptions` interface mirroring `UIFeedItem`'s reflected props + `position` + the phase-2 `action?` field.
- `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).
- `FeedPosition` named type for the 7 lane anchors (6 corners + inline).

#### Fixed — §247 `core/form.d.ts` adds `throttle: number` (FB-25 §1)

`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.

**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.

#### Fixed — §247 5 missing prop declarations on form-bearing `.d.ts` (FB-26 §1)

Per FB-26's wider-scope audit beyond FB-25's `throttle`-only scope:

- `input.d.ts` adds `inputmode: string` + `autocomplete: string` (HTML5 attribute forwards to contenteditable surface)
- `select.d.ts` adds `variant: 'default' | 'outline' | 'ghost' | 'soft'` + `size: 'xs' | 'sm' | 'md' | 'lg' | 'xl'` (universal `[size]` family + variant chrome)
- `rating.d.ts` adds `variant: 'star' | 'heart' | 'thumbs'` (icon-set treatment)

5 props × ~3 LOC each. All yaml-declared + runtime-functional pre-v0.5.10; only TS type surface was missing.

#### Changed — §247 USAGE.md adds two clarification sections (FB-23 §1 + §2)

`packages/web-components/USAGE.md` grows two notes inside the v0.5.9 §221 sweep section:

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.
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).

### v0.6.0 candidates surfaced by FB-23/24/25/26

- Codegen yaml-schema extension to carry per-`enum:`-member descriptions (retires `text.d.ts` from `HAND_AUTHORED_DTS`)
- 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)
- 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)
- Audit-extension to walk all `export class` (not just `extends UIElement`) — closes the FB-24 ambient static-API class drift
- `<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)
- `<swatch-ui label-position="overlay">` (or equivalent) — API for label-on-tile use case (FB-23 §1 feature)

## [0.5.9] - 2026-05-15

### Added — §246 (v0.5.9) — `dts-codegen.mjs` `HAND_AUTHORED_DTS` skip-list + restore `toast.d.ts` static-method surface


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.

Fix:

- `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.
- `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.

Verified: `node scripts/build/components.mjs` runs codegen with `--force` flag; toast.d.ts is preserved across builds.

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.

### Added — §246 (v0.5.9) — regression tests for §220 throttle + §223 empty-state minimal

`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.

`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.

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.

### Fixed — §230-bundle wave 2 (v0.5.9) — close remaining 9 of 16 baselined orphan-token references

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:

**Wave 2 architect decisions:**

- **NEW token** `--a-radius-xs` (parametric: `--a-radius-xs-k: 0.166`) in `styles/tokens.css`. Resolves heatmap + swatch callsites.
- **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.
- **NEW token** `--a-ui-line-height` (= `var(--a-body-leading)` = 1.5) in `styles/typography.css`. Resolves alert callsite.
- `components/nav/nav.css` `var(--a-duration-base, 200ms)` → `var(--a-duration)` (canonical mid-tier, 250ms).
- `components/code/code.css` `var(--a-surface-floating)` → `var(--a-bg-strong)` (elevated panel surface).
- `components/feed/feed.css` `var(--a-feed-max-width, 22rem)` → component-local literal `22rem`. Hoisted per RESPONSE-21 guidance.
- `components/swatch/swatch.css` `var(--a-bg-2)` → `var(--a-bg-muted)` (canonical canvas-2 alias).

Closes drift surface #7 (token-reference vs definition) fully. `check-component-css-vs-styles-tokens --strict` exits 0 with zero baseline entries.

### Fixed — §230-bundle wave 1 (v0.5.9) — close 7 of 16 baselined orphan-token references + new `--a-ui-xs: 12px`

Wave 1 (kimba prep batch at `73d06956c`):

- **NEW token** `--a-ui-xs: 12px` defined in `styles/typography.css` (one notch below `--a-ui-sm: 13px`).
- `components/tooltip/tooltip.css` (×2) — `var(--a-weight-regular)` → `var(--a-weight-normal)`.
- `components/range/range.css` — `var(--a-ui-fg)` → `var(--a-fg)`.
- `components/{select,slider,switch,tree}/*.css` (×4) — `var(--a-fine-size)` fallback → `var(--a-ui-xs)`.
- `components/swatch/swatch.css` `--a-ui-xs` references (×2) now resolve cleanly via the new definition.

### Added — §232 (v0.5.9) — NEW audit slot 24 `check-enum-vs-css-selector.mjs` (drift surface #6 closed)

`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).

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.

Closes the 6th drift surface in the 7-surface lockdown taxonomy.

### Added — §222 (v0.5.9) — NEW audit slot 21 `check-connected-reads-attr.mjs` (drift surface #5 closed)

`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.

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.

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.

Closes the 5th drift surface in the 7-surface lockdown taxonomy.

### Added — §220 (v0.5.9) — `throttle` parity on form-bearing primitives via shared `UIFormElement` helper

`packages/web-components/core/form.js` graduates the v0.5.5 §184 slider trailing-debounce pattern to the shared `UIFormElement` base class:

- `throttle` static property (default 0) — declarative ms window for the `input` event
- `scheduleThrottledInput()` — call from input-event sites; trailing-debounces dispatch when `throttle > 0`
- `flushPendingInput()` — call from change / blur / commit paths; ensures trailing `input` fires before `change`
- `dropPendingInput()` — called automatically by `disconnected()` to prevent late dispatches

Callsite migrations:

- `components/slider/class.js` — drops local `#inputTimer` + `#flushInput()`; delegates via the shared helpers (semantics preserved)
- `components/input/class.js` `#onInput` — now calls `scheduleThrottledInput()` (was unconditional dispatch)
- `components/textarea/class.js` `#onInput` — same migration; `#onBlur` + `#onKeydown` call `flushPendingInput()` before commit

`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.

Closes FEEDBACK-14 §3.

### Added — §223 (v0.5.9) — `<empty-state-ui minimal>` single-line muted layout

`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.

Closes FEEDBACK-13 §4.

### Added — §228 (v0.5.9) — `dts-codegen.mjs` sibling-yaml walk + 5 NEW sibling yamls + 2 parent yaml `name:` fixes

`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).

5 NEW sibling yamls authored:

- `feed/feed-container.yaml` (later retired in favor of `feed.yaml` canonical-class) — actually `feed-container` redundant since `feed.yaml` already declares `UIFeedContainer`
- `menu/menu-item.yaml` (`UIMenuItem`)
- `menu/menu-divider.yaml` (`UIMenuDivider`)
- `stepper/stepper-item.yaml` (`UIStepperItem`)
- `toggle-group/toggle-option.yaml` (`UIToggleOption`)
- `toolbar/toolbar-group.yaml` (`UIToolbarGroup`)

2 parent yaml `name:` fixes (yaml was wrong; class.js was right):

- `col/col.yaml`: `name: UIColumn` → `UICol` (class.js exports `UICol`)
- `richtext/richtext.yaml`: `name: UIRichtext` → `UIRichText` (class.js exports `UIRichText`)

`index.d.ts` tag-map: `UIColumn` → `UICol` (`col-ui`) + `UIRichtext` → `UIRichText` (`richtext-ui`).

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.

Closes FEEDBACK-18 §2 (child-class drift) and the 8 audit-driven additions on top of the 7-instance enumeration.

### Fixed — §229 (v0.5.9) — class-export walk baseline shrinks 15 → 0

`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.

Drift surface #1 (`.d.ts` axis) now fully strict-mode-protected across all 4 sub-axes (method + event + property + class-export).

### Added — §227 (v0.5.9) — `check-styles-barrel-completeness.mjs` `--package=web-modules` flag

`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`).

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).

### Added — §225 (v0.5.9) — `<select-ui>` `#parseOptions()` once-per-element console.warn

`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.

### Added — §224 (v0.5.9) — `<feed-ui>` `data-spawned-by="toast"` marker on toast-spawned containers

`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.

### Changed — §221a-k (v0.5.9) — 11-item docs sweep

`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:

- **§221a** Universal `[size]` prop family — token wiring + size-cascade authoring patterns
- **§221b** `<input-ui>` migration guide from native `<input>` — common gotchas + payload-shape table
- **§221c** `<switch-ui>` vs `<check-ui>` vs `<segmented-ui>` — the "named binary state" trap
- **§221d** `<select-ui>` native `<option>` children — declarative + programmatic paths
- **§221e** `<accordion-item-ui>` action buttons in custom headers — `[slot="action"]` exclusion list
- **§221f** `<color-picker-ui>` `<popover-ui trigger=click>` inline pattern
- **§221g** `[data-scheme]` ancestor opt-in for color scheme — patterns + edge cases
- **§221h** Effect-dispatch timing — `queueMicrotask` batching + the double-microtask test-flush idiom (also added to `signals.d.ts` JSDoc on `effect()`)
- **§221i** Backticks inside HTML comments inside `html\`…\`` templates — JS-spec footgun
- **§221j** Typography token cheatsheet — L0/L1 token families + common misreads (`--a-font-semibold` doesn't exist; use `--a-weight-semibold`)
- **§221k** `<text-ui variant>` chooser guide — 12 canonical variants with picker heuristics

`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).

### Added — §225 (v0.5.9) — `<select-ui>` `#parseOptions()` once-per-element console.warn

`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.

### Added — §224 (v0.5.9) — `<feed-ui>` `data-spawned-by="toast"` marker on toast-spawned containers

`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.

### Fixed — §229 (v0.5.9) — `check-static-methods-vs-dts.mjs` (slot 20) class-export walk extension

`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).

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.

## [0.5.8] - 2026-05-15

### Changed — `patterns/` demo-page refresh (v0.5.8)

- `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.

### Fixed — §230 (v0.5.8) — `--a-font-weight-*` orphaned references closed in 3 components


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.

- `components/nav-item/nav-item.css:42` — `var(--a-font-weight-normal)` → `var(--a-weight-normal)`
- `components/nav-group/nav-group.css:12` — `var(--a-font-weight-normal)` → `var(--a-weight-normal)`
- `components/feed/feed.css:136` — `var(--a-font-weight-strong, 600)` → `var(--a-weight-semibold)` (preserves rendering weight; drops inline fallback)
- `styles/typography.css` comment block updated to reflect actual state (REMOVED in v0.5.0; was "to be removed" before).

Closes FEEDBACK-20 §1.

### Added — §231 (v0.5.8) — audit slot 25 `check-component-css-vs-styles-tokens.mjs`

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.

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.

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).

**Audit-script family graduation**: v0.5.4 → v0.5.8: 13 → 22+ trip-wires.

## [0.5.7] - 2026-05-15

### Changed — `traits/catalog.examples.js` consumer sweep (§210 follow-on, v0.5.7)

`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.

### Fixed — §210 (v0.5.7) — `<text-ui>` variant enum drift (token-vs-rule + phantom entries)


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:

**Token-backed variants restored to rendering (3 values):**
- `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).

**Phantom enum entries retired (6 values):**
- `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."

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.

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.

### Fixed — §200 (v0.5.7) — `<toggle-scheme-ui>` post-connect reactive-scheme race

`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.

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.

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.

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.

### Fixed — §201 (v0.5.7) — `<color-picker-ui>` `#parseValue()` accepts `NaN` / `none` / `%`

`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:

- **`NaN`** — culori chromaless-hue convention; coerced to `0`.
- **`none`** — CSS Color L4 powerless-component syntax; coerced to `0` (matches the L4 powerless-cascade spec).
- **`%`** on L — `oklch(53% 0.05 240)` → divides by 100.

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.

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).

### Added — §207 (v0.5.7) — audit slot 20 `check-static-methods-vs-dts.mjs`

NEW: `scripts/release/check-static-methods-vs-dts.mjs` cross-checks three runtime surfaces in `class.js` against the sibling hand-authored `.d.ts`:

1. **Static methods** — `static <name>(...)` declarations should appear as static signatures in `.d.ts`. Caught FEEDBACK-09 §9 (`<toast-ui>.show()`).
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.
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.

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.

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.

### Changed — §207-bundled — close 15 `.d.ts` drift findings surfaced by audit slot 20

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:

- `calendar-picker.d.ts` — adds `label`, `placeholder`, `format`, `min: string | null`, `max: string | null` fields.
- `code.d.ts` — adds `name`, `required`, `disabled`, `readonly` fields.
- `input.d.ts` — adds `addEventListener(type: 'submit', ...)` overload (the runtime dispatches `Event('submit')` on Enter-key + form-submit at `class.js:717+725`).
- `select.d.ts` — adds `hint` field.
- `switch.d.ts` — adds `hint` field.
- `upload.d.ts` — adds `label`, `accept`, `multiple` fields.

All declarations carry `§207 (v0.5.7)` JSDoc cross-refs. Post-fix audit reports clean across all 95 primitives.

### Added — §208 (v0.5.7) — audit slot 22 `check-styles-barrel-completeness.mjs`

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.

Three drift classes caught:
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.
2. `imported-not-existing` (hard) — barrel @imports a file that doesn't exist on disk.
3. `duplicate-import` (warn) — same file @imported twice.

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.

Post-§203 audit reports clean across all 95 primitives + 92 @import lines.

### Added — §202 (v0.5.7) — `<slider-ui>` `.d.ts` declares `throttle` + `hint`

`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.

### Changed — §206 (v0.5.7) — `<accordion-item-ui>` action-slot opt-out

`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.

`packages/web-components/components/accordion/accordion-item.yaml` — gains `slots:` block documenting `header` + `action` slots; `events:` block formalizing the `toggle` event detail; sidecar regenerated.

`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).

### Changed — §205 (v0.5.7) — `requiredIcons` completeness on 5 primitives (2 from FEEDBACK-16, 3 from §209 audit-extension discovery)

Five primitives' `static requiredIcons` declarations updated to mirror their runtime dynamic-icon stamps:

**Filed by FEEDBACK-16 §1:**
- `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.
- `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.

**Surfaced by §209 audit-regex extension (see below):**
- `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.
- `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.
- `packages/web-components/components/timeline/class.js` — NEW `static requiredIcons = ['caret-down', 'caret-right']`. Expanded-state chevron at line 167.

(In all 3 newly-surfaced primitives, `this.icon` reads are consumer-supplied — not declared in `requiredIcons` per the established convention.)

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.

### Fixed — §209 (v0.5.7) — `check-required-icons.mjs` (audit slot 11) extended to catch ternary-style icon stamps

`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'`).

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.

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.

### Added — §204 (v0.5.7) — `package.json` `exports` accepts nested-CSS specifier shapes

`packages/web-components/package.json` `exports` map gains two entries:

```json
"./components/*/*.css": "./components/*/*.css",
"./components/*/css/*.css": "./components/*/css/*.css"
```

Consumers can now import component CSS with the path-mirroring shape that matches every JS subpath import:

```js
// Both shapes now resolve:
import '@adia-ai/web-components/components/toggle-scheme.css';
import '@adia-ai/web-components/components/toggle-scheme/toggle-scheme.css';
```

The second new entry handles `editor-shell`'s nested sub-stylesheets (`components/editor-shell/css/{layout,tokens,bespoke}.css`). Closes FEEDBACK-15 §1.

### Fixed — §203 (v0.5.7) — `styles/components.css` barrel completeness

`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).

## [0.5.6] - 2026-05-14

### Added — §191 (v0.5.6) — close §178 baseline-missing 7 `.d.ts` siblings

`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.

**Type surfaces now exposed to TS consumers**:

- `anchor.d.ts` — `Placement` type (12-value union), `AnchorOptions` interface, `supportsAnchorCSS: boolean`, `anchorPopover(anchor, popover, options?): () => void`.
- `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.
- `markdown.d.ts` — `renderMarkdown(src: string): string`.
- `polyfills.d.ts` — side-effect-only module declaration (`export {}`). Documents the runtime feature checks + the two opt-in polyfill packages.
- `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`.
- `streams-bridge.d.ts` — `BridgeRenderer` interface (duck-typed renderer contract), `BridgeOptions` interface, `bridgeStream` / `bridgeStreamAsync` signatures.
- `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.

**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.

**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.

### Changed — §197 (v0.5.6) — FEEDBACK-09 §9 + §10 typing fixes

**§9 — `<toast-ui>` `.d.ts` adds the static `show()` declaration**

`UIToast.show()` was authored in `class.js` since the imperative path
shipped but absent from `toast.d.ts`. TS consumers calling
`UIToast.show({ text: 'Saved' })` got `Property 'show' does not exist on typeof UIToast`. §197 adds two interfaces (`UIToastShowOptions`,
`UIToastFeedHandle`) and the static method declaration. Same drift class as the v0.5.3 §154 `core/icons.d.ts` hotfix.

**§10 — Generic `<V extends string = string>` on Select / Segmented / Switch ChangeEventDetail**

`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:

```ts
type Size = 'sm' | 'md' | 'lg';
select.addEventListener('change', (e: SelectChangeEvent<Size>) => {
  e.detail.value; // typed as Size, not string
});
```

Applies to `SelectChangeEvent`, `SegmentedChangeEvent`, `SwitchChangeEvent` aliases as well. `SwitchChangeEventDetail.checked` stays `boolean` (only `value` is generified).

### Changed — §197 (v0.5.6) — audit slot 11 regex tolerates generic params

`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.
## [0.5.5] - 2026-05-14

### Added — §176 (v0.5.5) — six new sibling sub-component yamls

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.

- `list/list-item.yaml` — `UIListItem` (3 props: icon, text, description)
- `avatar/avatar-group.yaml` — `UIAvatarGroup` (2 props: max, size)
- `accordion/accordion-item.yaml` — `UIAccordionItem` (2 props: text, open)
- `timeline/timeline-item.yaml` — `UITimelineItem` (8 props: text, description, time, duration, icon, variant, status, spinner)
- `action-list/action-item.yaml` — `UIActionItem` (5 props: icon, text, value, variant, disabled)
- `tabs/tab.yaml` — `UITab` (4 props: text, value, icon, disabled)

After §184's `tree-item.yaml` addition, sibling-yaml pattern is now at **7 components**. Catalog: 125 → 132 components (+7 across §176 + §184).

Commit `443365913`. See root CHANGELOG and journal §176 for full context.

### Added — §184 (v0.5.5, FEEDBACK-08 response cycle) — 6 new APIs + 1 build-time audit

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:

- **`<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}`.
- **`<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.
- **`<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.
- **`<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}`.
- **`<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).
- **`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.

### Added — §184 audit slot 18 (build-time partial-attribute interpolation scanner)

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.

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.
## [0.5.4] - 2026-05-14

### Added — `core/icons.d.ts` authored (+148 LOC, NEW; pre-cycle FEEDBACK-08-ish gap closure)

NEW `packages/web-components/core/icons.d.ts` (commit `cf7c14aaa`).
Mirrors all 10 runtime exports the `core/icons.js` SoT declares,
including the v0.5.3 §154 helpers (`installIconLoadersForRegistered`,
`discoverRequiredIcons`).

**Why this slipped past v0.5.3's gates**: the audit-script family
had 11 trip-wires at v0.5.3 including §102 `check-core-barrel-dts`
which catches **drift when a `.d.ts` exists** (every exported runtime
symbol has a matching type), but does NOT catch the **totally-missing
`.d.ts` case** for a `core/*.js` file. Result: a new `core/<name>.js`
file shipping without `<name>.d.ts` was silently consumer-broken on
TS-strict surfaces (TypeScript consumers importing from
`@adia-ai/web-components/core/icons` resolved to a missing `.d.ts`
and got `Cannot find module`) but JS-green elsewhere.

In-the-wild detection: `/Users/kimba/Projects/adia/color-app/src/main.ts:48`
(TypeScript strict mode) carried a stale comment noting the workaround
needed because `installIconLoadersForRegistered.d.ts` was missing. The
user wanted to upgrade to the recommended auto-discovering helper but
was blocked.

Filed slot 14 candidate for v0.5.5+ (`check-dts-sibling-presence`):
detect totally-missing `.d.ts` files for `core/*.js`, not just symbol-level
drift within existing `.d.ts` files.

### Fixed — §170 `static labelDeprecated = false` opt-out for 7 form-bearing primitives (v0.5.4)

Closes the `<select-ui label="...">` false-positive deprecation
class observed 2026-05-14 console:

  ```
  form.js:205  [AdiaUI] <select-ui label="…"> is deprecated — wrap in
               <field-ui label="…"> for proper label association.
  ```

`UIFormElement.#warnDeprecatedLabel()` (`core/form.js:197`) fires for
any form-bearing element where `properties.label` exists AND `static
labelDeprecated !== false` AND `hasAttribute('label')`. Pre-§170,
only `Input`, `Slider`, `Range` opted out. The 7 other form-bearing
primitives that declare `label:` in yaml as a first-class prop
spammed false-positive warnings on every gen-ui render.

Added `static labelDeprecated = false` to:

  - `calendar-picker/class.js`
  - `check/class.js`
  - `radio/class.js`
  - `select/class.js`
  - `switch/class.js`
  - `textarea/class.js`
  - `upload/class.js`

Audit method: cross-reference yaml `props.label:` declarations
against `class.js` `static labelDeprecated` opt-out. Found 11
form-bearing primitives with `label:` prop; 3 already opted out
(Input/Slider/Range); 7 missing (this commit); 1 false positive
in audit (field-ui — extends UIElement not UIFormElement; only
mentions UIFormElement in a comment).

v0.5.5 §171 will add audit-script trip-wire to catch this drift
class mechanically going forward.

### Added — §172 NEW `chat-input.yaml` catalog entry; `<chat-input-ui>` no longer orphan (v0.5.4)

NEW `packages/web-components/components/chat-thread/chat-input.yaml`
(+251 LOC). Closes the v0.5.4 ticket
`20260514045025-chatinput-training-data-unclea`: `<chat-input-ui>`
was an orphan component (lived as a sibling file inside
`chat-thread/` rather than its own folder); the build scanner only
looked for canonical `<name>/<name>.yaml`, so the catalog had NO
`ChatInput` entry. Runtime registry mapped `ChatInput → chat-input-ui`
but the LLM had no schema describing what the element stamps. Result:
generator added redundant Button(primary) siblings next to ChatInput
for "send" because the built-in send button was invisible to it.

The yaml encodes:
- 5 props (disabled, loading, placeholder, model, models)
- `submit` event with `{text, model}` detail
- 3 slots (toolbar, send, model — each marked "most authors should
  NOT override")
- 3 a2ui rules explicitly stating "DO NOT add a separate Button
  sibling for send"
- 3 anti-patterns
- 4 examples

Sibling yaml in `feed/` (`feed-item.yaml`, pre-existing orphan)
also unblocked by the build-scanner fix in `@adia-ai/a2ui-compose`
§172.

### Maintenance — audit-script companion §174 ships in v0.5.4 (no source change to web-components)

Repo-level audit script targeting web-components drift:

- **§174 `check-form-element-label-opt-out.mjs`** (slot 15) — codifies the
  §170 audit recipe. For every form-bearing primitive (extends
  UIFormElement): if yaml declares `label:` as first-class, class.js MUST
  opt out via `static labelDeprecated = false;`. Catches future drift if a
  new form-bearing primitive is added without the opt-out.

No web-components source change in §174 — detects future drift in the
yaml-vs-class.js invariant established by §170.

### Changed — `version`: `0.5.3` → `0.5.4`.
## [0.5.3] - 2026-05-14

### Added — `requiredIcons` static export on 12 primitives + `installIconLoadersForRegistered()` helper + audit slot 11 (§154, FEEDBACK-06 §4 + FEEDBACK-07 §4 P1, v0.5.3)

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.

**Three surfaces**:

**(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:

```js
export class UISelect extends UIFormElement {
  static requiredIcons = ['caret-up-down'];
  // ...
}
```

**(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.

**(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:

```js
import { installIconLoadersForRegistered } from '@adia-ai/web-components/core/icons';

const modules = import.meta.glob('/node_modules/@phosphor-icons/core/assets/regular/*.svg', { query: '?raw' });
const discovered = installIconLoadersForRegistered(modules);
// discovered: ['caret-up-down', 'x', 'caret-right', 'check-circle', ...]
```

**(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:

1. **declared-not-stamped** — yaml/class.js says `X` but no runtime stamp site
2. **stamped-not-declared** — runtime stamps `X` but declaration omits it (consumer-failure risk)
3. **missing-declaration** — runtime stamps icons but no class.js `static requiredIcons` field
4. **yaml-class-mismatch** — yaml and class.js disagree on the set
5. **yaml-missing-block** / **class-missing-declaration** — one side has the declaration, the other doesn't

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**:

| Slot | Script |
|---|---|
| 1 | check-no-live-readers |
| 2 | check-changelog-coverage |
| 3 | check-browser-safe |
| 4 | smoke:genui-composition-retrieval |
| 5 | check-form-bearing-dts |
| 6 | check-core-barrel-dts |
| 7 | check-yaml-events-vs-runtime |
| 8 | check-free-form-eval-regression |
| 9 | check-standalone-html-phosphor |
| 10 | check-substitutable-set-drift |
| **11** | **check-required-icons — NEW v0.5.3** |

Verification: 95 components scanned; 0 drift. The 12 auto-stamping primitives all match yaml ↔ class.js ↔ runtime.

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.

### Added — `responsive-shell-sidebar` pattern (§157, FEEDBACK-06 §3 P1, v0.5.3)

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.

**The pattern** ships:

- `.wide-only` / `.narrow-only` CSS convention (display: contents / display: none with breakpoint media query)
- Dual-mount inline sidebars (wrapped in `.wide-only`) + drawer twins outside shell slots
- Toolbar trigger buttons (inside `.narrow-only`) opening the drawer twins
- **MediaQueryList listener** that auto-closes drawers on viewport widening (the gap FEEDBACK-06 §3 flagged for the peer agent)
- Signal-based state sharing recipe (single signal source, parent reads `.value` once, passes to both mount points)
- Dual-mount caveat documented (signal-bound = safe; local-DOM-state = lost on resize)
- 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

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.

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`.

### Fixed — yaml-events drift cleanup on 5 components + audit-script false-positive fix (§144 + §162, v0.5.3)

`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.

**§144 — Phantom events dropped** (yaml declared but runtime never dispatched):

- **`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`.
- **`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.
- **`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.)
- **`row`** — phantom `drag-end` → dropped. Same as card; trait-emitted events may dispatch from droppable trait but not card/row directly.

**§162 — Audit-script false-positive fix + table `row-collapse` declared**:

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.

- **`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).
- **`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`.

`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).

### Added — `<slider-ui>` function-typed `.value` dev-mode guard (§153, FEEDBACK-07 §3 P1, v0.5.3)

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.

**Fix**: `slider-ui` `render()` checks `typeof this.value === 'function'` and emits a descriptive `console.warn` naming the two correct patterns:

```
[slider-ui] .value received a function. Did you mean:
  .value=${fn()}            ← call the function to get the current value
  .value=${signal.value}     ← read the signal's current value
Functions are not auto-invoked at render time; the slider reads
.value synchronously. See USAGE.md "Reactive binding" for the
parent-template-re-read pattern that works across frameworks.
```

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.

**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).

§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.

### Added — `<drawer-ui>` `close` event detail carries `reason` (§156, FEEDBACK-06 §2 P2, v0.5.3)

`<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.

**New type** in `drawer.d.ts`:

```ts
export type DrawerCloseReason = 'escape' | 'backdrop' | 'close-button' | 'programmatic';
export interface DrawerCloseEventDetail { reason: DrawerCloseReason; }
export type DrawerCloseEvent = CustomEvent<DrawerCloseEventDetail>;
```

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.

`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.

**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.

### Fixed — Template `inTag()` HTML-comment handling (§155, FEEDBACK-06 §1 P0, v0.5.3)

`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.

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).

**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.

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.

**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.

### Added — Template partial-attribute interpolation runtime guard (§152, FEEDBACK-07 §2 P0, v0.5.3)

`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.

**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.

**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.

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.

### Fixed — CodeMirror transitive dep declarations (§151, FEEDBACK-07 §1 P0, v0.5.3)

`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.

**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.

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.

No runtime behavior change.

### Fixed — `installIconLoaders` tolerates non-canonical loader-map key paths (§140, v0.5.3-staged)

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.

**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.

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.

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.
## [0.5.2] - 2026-05-13

### Changed — `toast.yaml` drops phantom `events.close` declaration (§130, v0.5.2)


`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`).

**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.

Companion regen via `npm run components --force`:
- `toast.a2ui.json` — `x-adiaui.events` block scrubbed
- `toast.d.ts` — `ToastCloseEvent` + `ToastCloseEventDetail` types stripped
- Catalog `catalog-a2ui_0_9.json` — Toast component events block synced (regen ride-along)

Type API change technically MAJOR-shaped under strict SemVer (consumer types narrow), but runtime behavior is identical → PATCH appropriate.
## [0.5.1] - 2026-05-13

_Lockstep ride-along (no source change)._
## [0.5.0] - 2026-05-13

**v0.5.0-staged** — staged for the next minor cut.

### Fixed — `core/element.d.ts` missing runtime re-exports (§102, FEEDBACK-05 P1)

`core/element.js` re-exports the reactive + template helpers from sibling modules:

```js
export { signal, computed, effect, batch, untracked, isSignal } from './signals.js';
export { html, css, repeat } from './template.js';
```

`core/element.d.ts` only declared the `UIElement` class + a few interface types. The canonical USAGE.md import pattern:

```ts
import { UIElement, html, signal, computed, effect } from '@adia-ai/web-components/core/element';
```

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.

**Fix:** `core/element.d.ts` now mirrors the runtime re-exports:

```ts
export { signal, computed, effect, batch, untracked, isSignal } from './signals.js';
export type { ReadonlySignal, Signal, ComputedSignal, EffectOptions, EffectDisposer } from './signals.js';
export { html, css, repeat } from './template.js';
export type { TemplateResult } from './template.js';
```

Consumers can delete their `declare module` shim after upgrading.

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.

### Added — `core/data-stream.d.ts` (NEW, §102 sibling-fix)

`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>`.

`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.

### Added — `scripts/release/check-core-barrel-dts.mjs` audit (§102)

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.

Heuristic limits documented in the script header:
- Only matches `export { X, Y } from '…'` form. Misses `export * from '…'` re-bundles (covered by `tsc --strict` round-trips).
- Type-only re-exports (`export type { X } from '…'`) match either form in the `.d.ts` — the audit only cares that the name is reachable.
- Files without a sibling `.d.ts` are skipped (codegen'd elsewhere or genuinely typeless).

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).
## [0.4.9] - 2026-05-13

### Added — `<swatch-ui>` multi-badge + auto-contrast label (§99a + §99b)

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.

### Added — `<color-picker-ui>` generation constraints (§100b, FEEDBACK-02 #7)

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:

- `[max-chroma]` (number, default `Infinity`) — clamp OKLCH chroma channel to at most this value.
- `[max-l]` (number, default `1`) — clamp OKLCH lightness to at most this value.
- `[min-l]` (number, default `0`) — clamp OKLCH lightness to at least this value (symmetric companion to `[max-l]`).
- `[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°.
- `[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.

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.

**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.

Types: `ColorPickerClampRecord`, `ColorPickerConstraintClampEventDetail`, `ColorPickerConstraintClampEvent` added to `color-picker.d.ts` + re-exported from the package barrel.

### Added — yaml event-detail blocks across 26 non-form-bearing primitives (§100c)

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:

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).

After regen, codegen now produces `CustomEvent<<NamedDetail>>` types for 30+ additional events. TypeScript consumers reading event details get typed access instead of `unknown`.

### Added — `cross_package_composes:` yaml block + check-composes-coverage extension (§99d)

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:

```yaml
# canvas.yaml
composes: []  # same-package siblings (none)
cross_package_composes:
  - tag: a2ui-root
    from: '@adia-ai/web-modules/runtime'
    note: Hosts A2UI rendering. Required when canvas mounts content.
  - tag: theme-ui
    from: '@adia-ai/web-modules/theme'
    note: Optional — only read when [theme-wrapper] is set.
```

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.

### Changed — `<editor-sidebar>` `[name]` → `[persist]` template sweep (§99c)

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/`.

### Audit summary — non-form primitives' yaml events vs runtime (§100a)

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).

### Fixed — `color-picker.d.ts` event detail shape mismatch (§90, FEEDBACK-04 P0)

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:

```html
<swatch-ui
  shape="block" size="lg"
  color="oklch(0.53 0.18 240)"
  label="500"
  detail="oklch(0.53 0.18 240)"
  badge="apca-pass"
  copyable
  selectable
></swatch-ui>
```

**Six new props** (all additive — existing consumers unchanged):

- `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").
- `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.
- `copyable` — boolean. Renders an inline copy-to-clipboard button (⧉ → ✓ on success, ⚠ on failure; auto-resets after 1.2s). Uses `navigator.clipboard.writeText`.
- `copy-value` — string. Override what gets copied; defaults to `[color]`.
- `selectable` — boolean. Makes the host keyboard-focusable + clickable. Sets `role="button"` + `tabindex="0"`; wires Enter / Space activation.
- `selected` — boolean reflected. Visual selected state with focus ring; sets `aria-pressed`.

**One new event:**

- `select` — fires when a selectable swatch is activated (click / Enter / Space). `detail: { value, color, label }` where `value` is `[color]` (or `[label]` as fallback).

**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.

**Backwards compat:** every new prop defaults off / empty. Existing usage (`<swatch-ui shape="dot" color="..." label="...">`) is byte-equivalent in stamping and behavior.

**Migration for the Tokens Studio FEEDBACK-04 #2 helper:**

```diff
- const swatch = renderSwatch({ color, label, badge, copyValue, selected })
- // ...70 LOC of manual stamping + badge SVG + copy state + a11y
+ html`<swatch-ui
+   shape="block" size="lg"
+   color=${color} label=${label} detail=${detail}
+   badge=${gamutBadge} copyable selectable
+   ?selected=${selected.value}
+   @select=${(e) => onSelect(e.detail.color)}
+ ></swatch-ui>`
```

### Added — `complete` event declaration on `<otp-input-ui>` (§91b, audit sweep)

§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.

### Added — `save` + `language-load-error` event declarations on `<code-ui>` (§91b, audit sweep)

Same audit. `code.d.ts` was missing two events the runtime has emitted since the primitive shipped:

- `save` (at `class.js:435`) — fires on Mod+S keybind; `cancelable: true`; `detail: { value }`.
- `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 }`.

Added `CodeSaveEvent` + `CodeSaveEventDetail` + `CodeLanguageLoadErrorEvent` + `CodeLanguageLoadErrorEventDetail` types + both `addEventListener` overloads. Re-exported from `packages/web-components/index.d.ts` barrel.

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`).

### Audit summary — hand-authored `.d.ts` cleanliness as of v0.4.9

After §90 + §91b sweeps, **all 17 form-bearing primitives' hand-authored `.d.ts` files are runtime-matched.** Audit recipe captured in `.claude/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.

### Documentation — USAGE.md "Derived reactive bindings" subsection (§91d, FEEDBACK-04 #3)

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.

New subsection at `USAGE.md § Property binding patterns › Derived reactive bindings` shows four patterns (two correct, two broken) with explicit annotations:

```js
// ✅ Derived reactive — re-evaluates whenever minL changes
html`<slider-ui .value=${() => minL.value * 100} min="0" max="100" />`
// ✅ Direct signal — re-evaluates whenever minL changes
html`<slider-ui .value=${minL} min="0" max="1" step="0.01" />`
// ❌ Static snapshot — reads minL.value ONCE at render time; thumb never moves
html`<slider-ui .value=${minL.value * 100} />`
// ❌ Function as the value — slider receives the function object, not its result
html`<slider-ui .value=${(v) => v * 100} />`
```

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.

**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).

### Fixed — `color-picker.d.ts` event detail shape mismatch (§90, FEEDBACK-04 P0)

`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.

**Fix:** updated `ColorPickerChangeEventDetail` to match the runtime:

```ts
export interface ColorPickerChangeEventDetail {
  value: string;   // format-respecting (hex when [format="hex"], oklch when [format="oklch"])
  l: number;       // OKLCH lightness (0–1)
  c: number;       // OKLCH chroma (0–~0.4)
  h: number;       // OKLCH hue (0–360°)
  hex: string;     // hex string (always present)
  oklch: string;   // oklch string (always present)
}
```

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).

`ColorFormat` simplified from `'hex' | 'rgb' | 'hsl' | 'oklch'` to `'hex' | 'oklch'` to match the yaml + runtime (the two formats actually supported).

### Changed — color-picker.d.ts JSDoc surfaces the dual-form invariant

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.
## [0.4.8] - 2026-05-12

### Changed — `core/icons.js` split into pluggable registry + opt-in Phosphor side-effect (§89, v0.4.8)

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.

**Split shape:**

- `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.
- `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()`.

**Three barrels side-effect-import `icons-phosphor.js` to preserve zero-config:**

- `index.js` — main barrel
- `core/index.js` — core barrel
- `components/index.js` — components barrel (used by site/docs)

**Consumer-facing impact:**

| Import path | Phosphor auto-loaded? | Bundle |
|---|---|---|
| `import '@adia-ai/web-components'` | ✅ Yes (unchanged) | full set |
| `import '@adia-ai/web-components/core'` | ✅ Yes (unchanged) | full set |
| `import '@adia-ai/web-components/components'` | ✅ Yes (unchanged) | full set |
| `import '@adia-ai/web-components/components/button'` | ❌ **CHANGED** — was full set, now registry-only | scoped |

**Migration for piecemeal consumers** with bundle-size SLOs:

```js
// Either: side-effect-import the full set explicitly
import '@adia-ai/web-components/components/button';
import '@adia-ai/web-components/core/icons-phosphor';

// Or: register only the icons you use (brace-list glob = static-analyzed → N chunks)
import '@adia-ai/web-components/components/button';
import { installIconLoaders } from '@adia-ai/web-components/core/icons';
installIconLoaders({
  regular: import.meta.glob(
    '/node_modules/@phosphor-icons/core/assets/regular/{caret-right,caret-up-down,moon,sun}.svg',
    { query: '?raw', import: 'default', eager: true }
  ),
});
```

`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.

**Measured impact** (Tokens Studio consumer, 4-icon app, post-fix):

| Metric | Pre-fix | Post-fix (scoped) |
|---|---|---|
| Files emitted under `dist/assets/` | ~9,074 | ~10 (target — verified consumer-side) |
| Total `dist/assets/` on disk | ~38 MB | ~30 KB (target) |
| Main `index-*.js` post-tree-shake | 1.48 MB | ~50-100 KB (target) |

`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.

### Added — `test/refactor-class-subpath.test.js` (§85b, v0.4.8)

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).

### Added — event-types codegen for 38 non-form primitives (§80, v0.4.8)

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.

**What consumers see after upgrading:**

```ts
import type { UIModal, UITimeline, TimelineToggleEvent } from '@adia-ai/web-components';

const modal = document.querySelector('modal-ui')!;
modal.addEventListener('close', (e) => { /* e: CustomEvent<unknown> — name typed */ });

const timeline = document.querySelector('timeline-ui')!;
timeline.addEventListener('timeline-toggle', (e: TimelineToggleEvent) => {
  const id: string = e.detail.id;       // typed
  const open: boolean = e.detail.open;   // typed
});
```

**Codegen extension:** `scripts/build/dts-codegen.mjs` (+89 LOC) reads `x-adiaui.events` from each component's `.a2ui.json` sidecar and emits:
- `<Title><EventPascal>EventDetail` interface (when yaml `detail:` block is populated)
- `<Title><EventPascal>Event = CustomEvent<<Detail>>` type alias (`unknown` detail if no `detail:` block)
- Per-class `addEventListener` overloads (one generic `HTMLElementEventMap` overload + one specific per typed event)

Naming convention dedups doubled prefixes — `timeline-toggle` on `UITimeline` → `TimelineToggleEvent`, not `TimelineTimelineToggleEvent`.

**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`.

**Tally:** 91 typed event aliases + 21 detail interfaces emitted across the 73 codegen'd `.d.ts` files. ~23% of events carry detail typing.

**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.

### Coverage closure — 90 of 95 primitives typed

| Primitive class | Property types | Event types |
|---|---|---|
| 17 form-bearing | ✅ v0.4.6 §68 (hand-authored) | ✅ v0.4.6 §68 (rich event detail types) |
| 38 non-form event-emitters | ✅ v0.4.7 §74 (codegen) | ✅ **v0.4.8 §80 (this release — codegen)** |
| 35 non-form non-emitters | ✅ v0.4.7 §74 (codegen) | n/a (no events to type) |
| 5 CSS-only elements | n/a (no class) | n/a (no class) |

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.
## [0.4.7] - 2026-05-12

### Refactored — class.js helper extractions in agent + toolbar primitives (post-§77 follow-up)

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:

- **`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.
- **`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.
- **`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`.

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.

### Added — `.d.ts` codegen for 73 non-form primitives (v0.4.7, §74)

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:

- `document.querySelector('button-ui')` returns `UIButton` (not `Element`)
- `createElement('card-ui')` returns `UICard`
- 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.
- JSDoc descriptions surface in IDE tooltips

**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.

**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.

**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.

### Added — non-side-effect `class` subpath per component (v0.4.7)

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:

```js
// Auto-register (default; tag registered immediately):
import '@adia-ai/web-components/components/slider';

// Class import (no auto-register; consumer decides when to define):
import { UISlider } from '@adia-ai/web-components/components/slider/class';
```

**Use cases enabled:**
- **Test isolation** — subclass + register under a unique per-test tag without clobbering the global `slider-ui` registration
- **Tag-name override** — register `<my-slider>` for a subclass while `<slider-ui>` remains the canonical
- **Selective composition** — import the class for inheritance without forcing tag registration on the host page

### Refactored — `packages/web-components/components/` (87 single-class + multi-class)

Mechanical refactor across all components with `customElements.define`:

**Before** — `<name>.js` had imports + class definition(s) + `customElements.define` calls + export.

**After** — split into two files:
- **`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.
- **`<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.

**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.

**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.

**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).

### Changed — `package.json` exports

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):

```json
"./components/*/class": {
  "types": "./components/*/*.d.ts",
  "default": "./components/*/class.js"
}
```

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.

### Changed — `USAGE.md § Registration` updated

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`.

### Verification

| Gate | Result |
|---|---|
| `vitest packages/web-components` | 483 passed / 0 failed (byte-equivalent to pre-refactor — every existing test consumes the auto-register path, which still works) |
| `node -e \"require.resolve(...)\"` for new subpaths | 4/4 representative paths resolve correctly (button/class, slider/class, menu/class multi-class, input/class) |
| `customElements.define('tag', class)` not called when `./class` subpath imported alone | Verified — `class.js` files have zero side effects |
| Component count post-refactor | 87 processed + 11 skipped (CSS-only / helpers / button-as-proof) = 98 component dirs; 87 paired with `class.js` |

### Changed — 17 component yamls populate `synonyms.tags` (§72)

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`.

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`.

### Coverage closure on FEEDBACK-adia-packages.md (final)

| # | Friction | Status |
|---|---|---|
| 1 | Typed events | ✅ v0.4.5 (CustomEvent.detail) + v0.4.6 (per-primitive `.d.ts`) |
| 2 | `disconnected()` discoverability | ✅ v0.4.6 (USAGE.md § Lifecycle) |
| 3 | Property reactivity discoverability | ✅ v0.4.6 (USAGE.md § Property reactivity) |
| 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)** |
| 5 | USAGE.md reference | ✅ v0.4.6 (430-line guide) |
| 6 | CSS via package specifier | ✅ v0.4.5 |
| 7 | Registration helpers + non-side-effect class import | ✅ **v0.4.7 (this release — class subpath)** + v0.4.6 (`defineIfFree`) |
| 8 | Form-associated discoverability | ✅ v0.4.6 (USAGE.md § Form participation) |

**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.
## [0.4.6] - 2026-05-12

### Fixed — `<pane-ui>` resize tracking 1:1 + pointer-capture under touch

`<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()`:

- **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.
- **`isPrimary` guard** rejects secondary touches / scroll-pointers.
- **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.
- **`touch-action: none`** on the grabber suppresses native scroll-pan during touch drags.
- **`[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.

Same shape pattern as `<admin-sidebar>` (mirrored — keeps the two resize behaviors consistent).

### Added — `<toggle-scheme-ui>` primitive

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`.

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"`.

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.

### Added — TypeScript declarations + consumer guide + registration helpers (§68 consumer-feedback v0.4.6 deferrals)

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.

**`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`.

**Hand-written `.d.ts` declarations** for the public API. Adding the package to dependencies now picks up TypeScript types for:

- **Core types** at `core/*.d.ts` — `UIElement`, `UIFormElement`, signal/computed/effect/batch/untracked, html/css/repeat/stamp templating, register/defineIfFree/isRegistered helpers
- **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
- **`HTMLElementTagNameMap` augmentation** at `index.d.ts` — `document.querySelector('slider-ui')` now returns `UISlider`, not `Element`
- **`package.json` `types` field + `exports` types conditions** — module resolution under `bundler` / `node16` / `nodenext` picks up the declarations automatically

Eliminates the `(e.target as any).value` pattern at compile time:

```ts
import type { SliderChangeEvent } from '@adia-ai/web-components';
slider.addEventListener('change', (e: SliderChangeEvent) => {
  const v: number = e.detail.value;   // typed; would error if value were string
});
```

Verified with negative type tests — string-to-number assignment errors fire; wrong-tag inference fires; wrong detail-shape fires.

**`core/register.js` + `core/register.d.ts`** — explicit registration helpers exported from the `core` barrel:

- `register(tagName, ctor)` — alias for `customElements.define` (no-magic; provided for symmetry)
- `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
- `isRegistered(tagName)` — returns `true` if the tag is taken

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.

**`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).

### Coverage

- 19 new `.d.ts` files (1 root + 6 core + 17 per-component – overlap = 19 total)
- 1 new helper module (`core/register.js`, 56 LOC) + its `.d.ts`
- 1 new documentation file (`USAGE.md`, ~430 lines)
- Updated `README.md` consumer-guide section (collapsed to a quick-reference card + pointer to `USAGE.md`)
- Updated `package.json` `types`, `exports`, `files` to expose the types properly

### Refreshed (§69 stale-audit sweep) — `traits/` docs currency

- `traits/CATEGORIES.md` — primitive count updated 93 → 95 (current verified count).
- `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).
## [0.4.5] - 2026-05-12

### Added — `CustomEvent.detail` sweep across 17 form-bearing primitives (§61)

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.

Driven by consumer feedback from Design Tokens Studio (2026-05-12) — eliminates the `(e.target as any).value` pattern in TypeScript consumers.

**Value-semantic primitives swept** (`detail: { value }`):
- `<input-ui>` (input.js — 9 dispatch sites)
- `<select-ui>` (select.js — 2 sites)
- `<slider-ui>` (slider.js — 4 sites)
- `<textarea-ui>` (textarea.js — 2 sites)
- `<range-ui>` (range.js — 3 sites)
- `<rating-ui>` (rating.js — 1 site)
- `<search-ui>` (search.js — 2 sites)
- `<otp-input-ui>` (otp-input.js — 3 sites)
- `<calendar-picker-ui>` (calendar-picker.js — 1 site)
- `<upload-ui>` (upload.js — 1 site; detail also carries `files: this.files`)

**Boolean-semantic primitives swept** (`detail: { value, checked }`):
- `<switch-ui>` (switch.js — 1 site)
- `<check-ui>` (check.js — 1 site)
- `<radio-ui>` (radio.js — 1 site)
- `<option-card-ui>` (option-card.js — 1 site)

**Already canonical** (no change needed):
- `<segmented-ui>` — emits `detail.value` since pre-v0.4.0
- `<code-ui>` — emits `detail.value` (CodeMirror state)
- `<color-picker-ui>` — emits multiple typed events with structured detail
- `<demo-toggle-ui>`, `<toggle-group-ui>`, `<tabs-ui>`, `<swiper-ui>` — already typed

### Added — `slider.test.js` locks in property reactivity (§61)

7-case vitest spec for `<slider-ui>` validating the contract that the consumer feedback questioned (claim #5: "doesn't react to property changes"):

1. Initial render places thumb at correct % for `value=50`
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)
3. `setAttribute('value', '25')` also propagates through `attributeChangedCallback` → property setter → signal
4. `[slot="value"]` readout text syncs on value change
5. `change` event is a `CustomEvent` with `detail.value` on keyboard step (`ArrowRight`)
6. `input` event is a `CustomEvent` with `detail.value` on keyboard step (`ArrowUp`)
7. `aria-valuenow` reflects current value for screen readers

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.

### Added — `/site/tokens/colors` design-tokens export panel (§52)

In-page exporter that snapshots the live OKLCH token surface as Figma-
importable JSON. Supports W3C DTCG (Figma native direct-import, default
— two files: `light.tokens.json` + `dark.tokens.json` with structured
`{colorSpace, components, alpha, hex}` color objects at float precision),
Variables Pro legacy format (single file, `modes` wrapper, hex/rgba
strings matching the canonical `parseColor` regex), and a float-RGB
sub-byte-precision experimental path. Polarity-collapsed tonal primitives
(`neutral.50` is a single Figma variable with per-mode values; the
underlying `-tint` / `-shade` cssVars are internal implementation detail
that don't surface in the export). Pure-JS OKLCH → OKLab → linear-sRGB
→ sRGB → HSL math (Björn Ottosson's matrices), no canvas quantization.

- **`styles/design-tokens-export.js`** (new ~430 lines) — extraction module:
  CSSOM scanner with authoritative-selector filter (`:root, theme-ui,
  [theme]` only — skipping attribute-scoped overrides that would
  pollute the cssVar map under last-wins semantics), symbolic
  `var()` / `light-dark()` chain walker, OKLCH color-math pipeline, four
  output formatters (DTCG / hex / float-RGB / HSL-decimal). Public API:
  `buildFigmaJson({ format })`, `buildDtcgFiles()`, `getExportStats()`,
  `downloadJson(filename, data)`, `copyJson(data)`.
- **`styles/colors.examples.html`** — export panel section: 5-button row +
  stats banner + workflow documentation for Figma's manual multi-mode
  import (add mode → right-click → "Import mode").
- **`styles/colors.examples.js`** — wires the export panel: DTCG (Light +
  Dark) primary download, Light-only / Dark-only single-mode, Variables Pro
  legacy hex, clipboard copy. Theme-aware filenames (`design-tokens-${theme}-
  hex.json` for legacy; DTCG path keeps `light.tokens.json` /
  `dark.tokens.json` because Figma derives mode names from filename).
- **`styles/colors.html`** (standalone path) — loads `button-ui` +
  `swatch-ui` CSS+JS for the panel chrome.

### Fixed — SPA-router-vs-download-anchor interaction (§52)

`UIRouter` in `core/provider.js:185` installs a document-level click listener
that intercepts `<a href>` clicks for client-side navigation. Programmatic
download anchors with `blob:` URLs would trigger `history.pushState(blob:...)`
which throws cross-origin. The export's `downloadJson` helper now dispatches
a non-bubbling synthetic click (`a.dispatchEvent(new MouseEvent('click',
{ bubbles: false }))`) — browser's `<a download>` default action still
fires because it's bound to the event target, not propagation. Worth
mirroring in any future in-page download workflow.
## [0.4.4] - 2026-05-12

### Added — corpus drift yaml gaps closed (§44, §50, §51)

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).

**Text strong attribute (§43):**
- **`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.

**Renderer-auto-routed textContent props (§44):**
- **`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.
- **`button.yaml` + `button.js` — `textContent: string`.** Same pattern.
- **`tag.yaml` + `tag.js` — `textContent: string`.** Same pattern.
- **`code.yaml` + `code.js` — `textContent: string`.** Same pattern.

**Universal layout grow attribute (§44):**
- **`col.yaml` + `col.js` — `grow: boolean, reflect: true`.** `:scope[grow]` selector at col.css line 38 supports it; yaml under-declared.
- **`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).

**Pass-through HTML/ARIA attributes (§44):**
- **`button.yaml` — `aria-label: string`.** Pass-through ARIA attribute, native HTML, no js change needed.
- **`textarea.yaml` — `name: string`.** Inherited from UIFormElement; yaml under-declared.

**#JS_PROPS-resident table/select props (§44):**
- **`table.yaml` — `columns: array` + `data: array`.** Already in renderer's `#JS_PROPS` set (line 233) but yaml under-declared.
- **`select.yaml` — `options: array`.** Runtime supports both prop AND children patterns.

**Universal [size] system (§50):**
- **`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.

**HTML standard input attributes (§51):**
- **`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.
- **`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`.

  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.

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.

See root [CHANGELOG.md `[Unreleased]`](../../CHANGELOG.md) for the cross-cutting arc narrative + `.claude/docs/journal/2026/05/2026-05-12.md` §§ 43 / 44 / 50 / 51 for per-§ details.
## [0.4.3] - 2026-05-11

### Added

- **`<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.
- **`<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`.
- **`<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.

### Fixed

- `<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.
- `<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`).

### Lockstep

9-package coordinated PATCH cut to v0.4.3 (per [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/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` — 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.
## [0.4.2] - 2026-05-11

### Changed

- **`<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.
- `<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 ↑/↓.
- 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.

### Added

- `<input-ui type="number">` — four new props:
  - `min` (Number, default `null`) — minimum value. Drives `aria-valuemin` + the `[−]` button's disabled state.
  - `max` (Number, default `null`) — maximum value. Drives `aria-valuemax` + the `[+]` button's disabled state.
  - `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.
  - `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"`.
- `<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`.
- `<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.
- `<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.
- `<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).
- 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.
- 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`.

### Fixed

- `<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.
- `<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).
- `<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.

### Known limitations

- ~~`<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.
- Scientific notation (`"1e3"`) is rejected by the filter. Deliberately out of scope.
- ~~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.

### Lockstep

9-package coordinated PATCH cut to v0.4.2 (per [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/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`) 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.
## [0.4.1] - 2026-05-10

### Ride-along (no source changes)

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.

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.
## [0.4.0] - 2026-05-10

### Ride-along (no source changes)

Lockstep MINOR cut alongside `@adia-ai/web-modules@0.4.0` (ADR-0024 legacy shell shapes retired). Source byte-identical to v0.3.6.

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.
## [0.3.6] - 2026-05-10

### Ride-along (no source changes)

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.
## [0.3.5] - 2026-05-07

### Ride-along (no source changes)

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.
## [0.3.4] - 2026-05-07

### Ride-along (no source changes)

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.
## [0.3.3] - 2026-05-07

**Lockstep cut.** All 9 published `@adia-ai/*` packages now share version `0.3.3`, governed by [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/docs/specs/package-architecture.md#15-versioning-policy).

### Changed

- `version`: `0.3.2` → `0.3.3`.
- `dependencies["@adia-ai/...]`: `^0.3.0` (patch-cut asymmetry — caret already covers 0.3.x; ranges unchanged).

### No source changes

`@adia-ai/web-components` source is byte-identical to `0.3.2`. The cut is a version bump + ride-along on the lockstep policy.
## [0.3.2] - 2026-05-06

**9-package lockstep patch cut to v0.3.2.** All lockstep members share
one version per [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/docs/specs/package-architecture.md#15-versioning-policy).
Internal `@adia-ai/*` dep ranges unchanged at `^0.3.0`.

### No source changes

This package's source is byte-identical to v0.3.1. The cut bumps
version only.

### Changed

- `version`: `0.3.1` → `0.3.2`.
## [0.3.1] - 2026-05-06

**9-package lockstep patch cut + folder-per-trait restructure.** All 9 published `@adia-ai/*` packages bump 0.3.0 → 0.3.1 per [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/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).

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.

### Changed

- **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.
- **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.
- `components/card/card.js` + `components/row/row.js` updated to import `traits/draggable/draggable.js` (was `traits/draggable.js`).
- `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).

### Why

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).

### Repo-only (not in tarball — but commit graph for transparency)

- `packages/web-components/traits/` directory restructure: ~280 file moves preserving `git mv` history.
- `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).
## [0.3.0] - 2026-05-05

**9-package lockstep cut.** All 9 published `@adia-ai/*` packages bump 0.2.5 → 0.3.0 per [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges bump `^0.2.0` → `^0.3.0`.

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).

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).

### Changed

- `version`: `0.2.5` → `0.3.0`.
- `dependencies["@adia-ai/a2ui-utils"]` removed from any consumer; consumers requiring the runtime should now depend on `@adia-ai/a2ui-runtime`.

### Repo-only (not in tarball)

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.

- `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.
- `core/` — `streams-bridge.js` import path updated for the runtime rename (`@adia-ai/a2ui-utils` → `@adia-ai/a2ui-runtime`).
- `traits/` — `tilt-hover.examples.html` regenerated from the components build (slot-marker convention update). No trait API changes.
- `styles/` — no source changes; touched only via the build's catalog-regeneration sweep.
- `patterns/` — no source changes since v0.2.5.
## [0.2.5] - 2026-05-04

**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 [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/docs/specs/package-architecture.md#15-versioning-policy). Patch cut — **no BREAKING changes**.

### Added

- **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.
  - `components/fields/fields.yaml` — declarative API with 2 examples (3-up stacked, inline-search-form) + 2 anti-patterns flagged.
  - `components/fields/fields.js` — `UIFields extends UIElement`. Properties `[columns]` (Number, default 6, reflective) + `[inline]` (Boolean, reflective). MutationObserver re-syncs `[inline]` propagation on `childList` changes.
  - `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).
  - `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**.

### Fixed

- **`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.

### Changed

- `version`: `0.2.4` → `0.2.5`.
- `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.5` under semver — patch-cut asymmetry).
- `components/index.js` adds `UIFields` export.
- `styles/components.css` adds `@import "../components/fields/fields.css"`.
## [0.2.4] - 2026-05-04

**Lockstep cut + Tier 4 capability adds + 2 architectural rewrites.** All 8 published `@adia-ai/*` packages bump 0.2.3 → 0.2.4 per [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.

### Changed

- `version`: `0.2.3` → `0.2.4`.
- `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.4`).
- New `components/demo-toggle/` directory; `components/index.js` adds `UIDemoToggle` export; `styles/components.css` adds `@import "../components/demo-toggle/demo-toggle.css"`.
- `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.

The substantive content of the next cut, surfaced from the
2026-05-04 trait-library lift initiative (`.claude/docs/PLAN.md`,
`.claude/docs/reports/traits-creative-director-audit-2026-05-04.md`).
**No BREAKING changes.** Every public tag, prop, event, and
declarative `traits=` attribute from v0.2.3 still works.

### Added — 11 new traits (Tier 4 capability adds)

Catalog: 41 → 56 traits across 9 categories (12 net new + droppable
already in flight from the Tasks UI parallel session). 100% test
coverage maintained; trait-suite footprint **59 files / 365 tests**
(was 44 / 169 at v0.2.3).

- **`layout-animation`** (motion-positioning) — FLIP primitive:
  capture old bounds, project transform from old to new, hand to a
  spring tick loop. Triggered by parent `MutationObserver` (sortable
  lists) or by toggling `data-layout-animate-trigger`. Closes the
  most-asked-for Framer Motion feature.
- **`drop-target`** (motion-positioning) — declarative drop zone with
  hit-testing + accept-reject (file types, MIME csv list). Counterpart
  to `draggable`/`drag-ghost`; emits `drop-enter` / `drop-leave` /
  `drop-receive` / `drop-rejected`.
- **`view-transition`** (motion-positioning) — wraps
  `document.startViewTransition()` for morph animations between DOM
  states. Sets `view-transition-name` on the host; installs a
  `host.startTransition(callback)` method. Same-document baseline
  Chromium 111+ / Safari 18+ / Firefox 129+; graceful-degrade path
  fires start+end synchronously when the API is missing.
- **`input-mask`** (forms-data) — locale-aware as-you-type formatter
  with 6 built-in patterns (phone-us, phone-intl, credit-card,
  date-iso, time-24h, cvv) and caret preservation against entered
  chars (not mask chars). Closes the structurally-thinnest category
  per the audit.
- **`long-press`** (input-interaction) — press-and-hold trigger with
  configurable duration / tolerance / progress events; suppresses the
  trailing `click` and the native `contextmenu` while in flight.
- **`scroll-progress`** (layout-measurement) — page or element scroll
  progress as `data-scroll-progress` (0..1) + CSS variable + event.
  Three modes: `in-view` (default), `page`, `scrolled`.
- **`error-shake`** (animation-feedback) — horizontal lateral
  oscillation on validation failure. Composes with `validation`
  trait via the `validated` event.
- **`success-checkmark`** (animation-feedback) — SVG stroke-draw
  checkmark on validation success. **Renders to body-level
  `position: fixed`** so the trait can't be clipped by the host's
  `overflow: hidden` ancestor (cards, buttons). Composes with
  `validation` via `validated` event.
- **`focus-restore`** (keyboard-navigation) — captures
  `document.activeElement` on connect, restores via `el.focus({
  preventScroll: true })` on disconnect. Falls back to body when the
  captured target is removed/disabled. Closes the modal-close-leaves-
  focus-dangling a11y bug class.
- **`arrow-grid-nav`** (keyboard-navigation) — 2D arrow-key navigation
  for grids/calendars/menubars (APG `grid` pattern). Two structural
  modes: `row-flat` (infers row/col by index ÷ columns) and
  `row-nested` (each direct child is a row container).
- **`announcer`** (audio-haptics-sensory) — singleton aria-live mirror
  with polite/assertive routing + throttling. Two body-level
  `aria-live` regions (`#adia-live-polite`, `#adia-live-assertive`)
  shared across all announcer instances. Lifts every audio/haptic/
  visual trait above WCAG 4.1.3.

Plus four traits that landed via the parallel Tasks UI initiative,
exported through this package's barrel: `droppable` (input-
interaction), `droppable-collection` (input-interaction),
`draggable-list-item` (motion-positioning), `keyboard-reorderable`
(keyboard-navigation).

### Added — `<demo-toggle-ui>` primitive

New custom element at `components/demo-toggle/`. A side-by-side "with
trait / without trait" comparison wrapper that any trait detail page
(or example) can drop into a hero. Reflects `data-state="on"|"off"`
on the host; emits a `change` event with `detail.state`. Optional
`data-mode="overlay"` stacks the slots in place so layout doesn't
shift between states. 9/9 tests passing. Integrated into 3 trait
pages (`parallax`, `shimmer-loading`, `noise-texture`) and ready for
broader retrofit.

### Changed — `confetti` + `confetti-burst` rendered to top-layer

Both traits now render particles into a singleton body-level
`<div popover="manual">` stage at z-index 99999, derived from the
host's `getBoundingClientRect()` at fire time. Particles escape any
`overflow: hidden` ancestor (modals, drawers, cards), no z-index
wars, no per-trait container. Both traits share the stage via a new
private `confetti-stage.js` helper. Disconnect cleans up the
attribute and the listener; in-flight particles outlive the trait
and self-remove on `animationend` (matching the spec's "disconnect
doesn't yank particles" requirement).

### Changed — `anchor-positioning` uses native CSS Anchor Positioning where supported

On Chromium 125+ and Safari 18.0+, the trait now writes
`anchor-name` on the anchor + `position-anchor` / `position-area` on
the host, calls `host.showPopover()` to promote into the top layer,
and lets the browser handle reflow on its own (no JS scroll/resize
loop). The v0 JS-positioning path remains for Firefox + Safari
< 18.0 as a fallback. The host carries
`data-anchor-mode="native"|"fallback"` so consumers and DevTools
sessions can see which path actually ran. Public attribute API
(`data-anchor`, `data-anchor-placement`, `data-anchor-gap`) is
unchanged.

### Changed — `count-up` adds Intl.NumberFormat + prefix/suffix/decimals/locale

The trait now formats the displayed value via `Intl.NumberFormat` so
`2438000` renders as `2,438,000` by default. Four new optional
config attrs:
- `data-count-prefix` — string prepended to the formatted value
  (e.g., `$`).
- `data-count-suffix` — string appended (e.g., `%`).
- `data-count-decimals` — fraction-digit count for currency
  (`$1,234.56`) or percentage (`12.5%`) cases. Default 0.
- `data-count-locale` — BCP 47 language tag override.

Reduced-motion path also formats via the formatter so the snapped
value matches the animated path's final frame.

### Changed — `noise-texture` strength attribute + visibility default

Earlier defaults stacked SVG rect opacity (0.08) × overlay div
opacity (0.5) = effective 0.04, visually no grain. New defaults: rect
opacity = 0.15, overlay's CSS opacity removed (single multiplier).
New `data-noise-strength` config attr (0..1, default 0.15) lets
consumers dial intensity without touching the trait source.

### Changed — Trait helpers dropped underscore prefix

`_motion.js` → `motion.js`, `_test-helpers.js` → `test-helpers.js`,
`_announcer-stage.js` → `announcer-stage.js`, `_confetti-stage.js`
→ `confetti-stage.js`. The catalog walker
(`scripts/build/traits-catalog.mjs`) switched from filename-prefix
detection to an explicit `HELPERS` Set so the convention can keep a
uniform naming style. Internal consumers updated; no public API
change.

### Changed — `--a-section-weight` flipped semibold → normal

Visible on every `<h2>` and `<h3>` consuming the `section` role
token. Lighter weight reads less heavy at the doc-section level —
closer to a label than a title — and pairs better with the `heading`
role above it (already at semibold). Single-line CSS change at
`packages/web-components/styles/typography.css:259`. No API surface
change; pure visual.

### Fixed — `inertia-drag` / `tossable` / `draggable` translate-read

`parseTranslate()` now reads `style.translate` first (the trait
writes to it), falling back to `style.transform` matrix. Same class
as the v0.2.3 `spring-animate` fix (commit `07fb2841`). Pre-fix:
every drag after the first read the wrong CSS property and got
`m41 = 0`, so subsequent drags jumped the host back near the origin
instead of moving from its current position.

### Fixed — `drag-ghost` rect-snap pixel-identical clone

Position the cloned ghost over the host's exact viewport rect at
`position: fixed; left/top/width/height; box-sizing: border-box`,
copy host's `transform`/`transform-origin`, then call
`setDragImage(ghost, clickX, clickY)`. Pre-fix: ghost was offscreen
with default content-box width/height; for content-box hosts, the
ghost rendered larger than the host and the offset coordinates
landed at the wrong bitmap pixel — the ghost drifted off the cursor
as the click moved away from the host's top-left corner. Hide via
`visibility: hidden` after the synchronous `setDragImage` snapshot.

### Fixed — `<list-item-ui>` `[data-active]` CSS state

`list.css` — added `[data-active]` block inside
`@scope (list-item-ui)`: `--a-accent-muted` background,
`--a-accent-strong` text + icon, 2px accent-strong inset rail.
Reuses the same token pair as the existing
`list-ui[selectable] [aria-selected="true"]` rule so the two
selection mechanisms paint consistently. Closes the keyboard-nav
demo "broken" report at v0.2.3 — events fired correctly, the active
state had no visible style.

### Fixed — `list-item-ui` empty items collapsed to 0 height

Empty `<list-item-ui></list-item-ui>` (no `text`, no `icon`, no
children) rendered with 0 height and silently disappeared from the
layout. Add a 24px floor via `min-height: var(--a-space-6)` so empty
items always show a visible row. Aligns with WCAG 2.2 SC 2.5.8
(Target Size, Minimum) for `list-ui[selectable]` where the row is
the click target.

### Fixed — `<button-ui type="reset">` triggers `form.requestReset()`

Previously a no-op (only `type="submit"` was handled). Pre-fix:
`<button-ui type="reset">` clicks fired `press` but never the form's
`reset` event, so the `resettable` trait's reset chain dead-ended at
the click.

### Fixed — `<input-ui>` / `<textarea-ui>` programmatic value sync

`render()` now syncs `this.value` into the inner contenteditable
when they diverge (preserving caret if already in sync). Pre-fix:
programmatic `host.value = "x"` updated the reactive prop but not
the visible content, so the `resettable` trait's snap-back wrote a
value the user couldn't see.

### Fixed — `resettable` discriminates checkable controls

Previously wrote `host.value = initialValue` for everything, which
silently no-op'd on `switch-ui`/`check-ui`/`radio-ui` (whose primary
state is `checked`, not `value`). The trait now detects boolean
controls (native `[type=checkbox|radio]` + custom elements
declaring `checked`) and resets via `host.checked` for those.

### Fixed — `confetti-burst` fires on press, not just connect

Refactored `setup()` to extract `fireBurst()` and install it as a
`press` listener (composes with the `pressable` trait's published
event surface). Old behavior fired exactly once at
`connectedCallback` and was inert thereafter. Multiple in-flight
bursts tracked in a `Set` so rapid clicks don't clobber each other.

### Fixed — `droppable` self-recursion (RangeError stack overflow)

The trait listened for `dnd-drop` on `document` AND dispatched
`dnd-drop` on the host with `bubbles: true`. The bubbled event
re-reached `document` → re-fired the same handler → recursion to
stack overflow. Renamed the host-emitted event to `dnd-drop-receive`
(distinct from the document-level signal). `droppable-collection`
also updated to listen for `dnd-drop-receive` on its host. Memory
entry filed at `feedback_event_name_collision_recursion.md`.

### Fixed — Icon registry: ~25 new aliases across multiple commits

`core/icons.js` aliases mapping consumer-friendly names to canonical
Phosphor names. Suppresses repeated `[icon-ui] Icon "X" not found`
warnings on every page that referenced these:

- `bold` / `italic` / `underline` → `text-b` / `text-italic` /
  `text-underline`
- `reset` / `undo` → `arrow-counter-clockwise`; `redo` →
  `arrow-clockwise`
- `success-checkmark` → `seal-check`; `circuit` → `circuitry`;
  `contrast` / `theme` → `circle-half`; `dark-mode` → `moon`;
  `light-mode` → `sun`
- `alert-triangle` / `alert-octagon` / `alert-diamond` → Phosphor
  `warning` / `warning-octagon` / `warning-diamond`
- `number` / `numeric` → `hash`; `numbered-list` → `list-numbers`
- `circle-warning` → `warning-circle`; `circle-x` → `x-circle`;
  `circle-check` → `check-circle`; `circle-info` → `info`;
  `circle-question` → `question`; `circle-plus` → `plus-circle`;
  `circle-minus` → `minus-circle`
- `fade` / `fade-presence` → `gradient` (from v0.2.3 cut)

---
## [0.2.3] - 2026-05-04

**Lockstep cut + accumulated fixes.** All 8 published `@adia-ai/*`
packages bump 0.2.2 → 0.2.3 per
[`.claude/docs/specs/package-architecture.md` § 15](../../.claude/docs/specs/package-architecture.md#15-versioning-policy).
Patch cut — no breaking changes. The substantive content is
`web-components`-only; the other 7 packages have no source change.

### Changed

- `version`: `0.2.2` → `0.2.3`.
- `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.3`).

### Fixed — `<list-item-ui>` slot layout CSS (2026-05-02)

`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.

### Fixed — `traits/confetti-burst.js` fires on press, not connect (2026-05-02)

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.

### Fixed — Icon registry: `fade` / `fade-presence` aliases (2026-05-04)

`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.

### Fixed — Trait runtime bugs surfaced by user testing on `/site/traits/*` (2026-05-02)

- `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.
- `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.

### Changed — Legacy tag-name sweep across nav cluster + 2 docs (2026-05-04)

Removed 11 references to deleted tag names (`<section-nav-item-ui>`,
`<section-nav-group-ui>`) that survived ADR-0015 + nav consolidation
in non-historical files. Source files swept:
`components/nav-item/{nav-item.yaml,nav-item.css,nav-item.a2ui.json}`,
`components/nav-group/{nav-group.yaml,nav-group.css,nav-group.a2ui.json}`.
Description text + cascade-context comments rephrased to drop the
legacy tag names without losing the design rationale ("section variant
doesn't reserve icon space when absent; section-variant groups render
as kicker labels with always-visible children"). Catalog regenerated
to match.

### Changed — `<agent-trace-ui>` STAGE column + detail alignment (2026-05-04)

`agent-trace.css` row track plan tightened so multi-word stage labels
("Rows returned", "Query duration", "Drift vs. SFDC") stay on one line:
`--agent-trace-row-label-col` shifts from a fixed `80px` to
`minmax(7rem, max-content)`, letting the longest label set the track.
The DETAIL column's text and its column header (`[data-trace-aux]`,
`[data-trace-header]:nth-of-type(3)`) are right-aligned so 1-3 word
qualifiers ("warehouse", "reconciled") sit flush with the row's right
edge instead of leaving the column reading as dead width. Per-row
`subgrid` keeps the alignment in lockstep across every row.

---
## [0.2.2] - 2026-05-02

**Lockstep cut + trait coverage 100% + `<traits-host>` wrapper.** All 8 published `@adia-ai/*` packages bump 0.2.1 → 0.2.2 per [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.

### Changed

- `version`: `0.2.1` → `0.2.2`.
- `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.2`).
- `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.

### Added — Trait test coverage at 100% + `<traits-host>` wrapper (2026-05-02)

Follow-up landing on top of the v0.2.1 trait-system work — closes the two follow-up items from `ADR-0018`.

- **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`).
- **`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.
- **`<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=`.

---
## [0.2.1] - 2026-05-02

**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 [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.

### Changed

- `version`: `0.2.0` → `0.2.1`.
- `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.1`).
- `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.
- `styles/components.css` — `@import` swept to the new `<stat-ui>` filename (see Fixed below).

### Added — Trait system source-of-truth + declarative `traits=` attribute (2026-05-02)

Rolls up under `ADR-0018`. Consumer-visible additions and one core change to UIElement's lifecycle.

- **`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.
- **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).
- **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`.
- **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).
- **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.

### Fixed — Trait brittleness bugs surfaced by new tests (2026-05-02)

- **`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.

### Test surface

- 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`.

### Added — `resettable` trait

- 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.

### Changed — UI field hover-bg flattened to default

`--a-ui-bg-hover` was `var(--a-canvas-2-scrim)` (an elevation-2 lift on
hover); it's now `var(--a-canvas-0-scrim)` (same as the default). The
hover affordance for FIELD UI surfaces (`<input-ui>`, `<textarea-ui>`,
`<nav-item-ui>`, `<chat-input-ui>`) is now carried by border-color +
foreground transitions, not bg lift. Reduces hover noise on dense
form surfaces; matches the visual flatness of the default-elevation
canvas.

Affects 4 components (1-line `semantics.css` change). No API change;
no component code change. `--a-ui-bg-active` (canvas-0) and
`--a-ui-bg-disabled` (canvas-1) unchanged — active still drops,
disabled still raises.

### Changed — `<chat-input-ui>` color isolation

The composite host now owns every color the inner `<textarea-ui>`
ships with. Previously, textarea-ui's hover (bg/border/color), focus
(color brightening + redundant ring), invalid focus, disabled (color),
and placeholder treatments all leaked through `<chat-input-ui>` because
each was wired to the generic `--a-ui-*` family with no host-level
override. Result: a hover bg flash on the editable area, a brightened
fg on focus, two stacked focus rings under invalid state, and a
disabled-bg rectangle inside the host's own disabled chrome.

`chat-input.css` now suppresses every textarea-ui state rule and
re-paints them with `--chat-input-*` tokens:

- `--chat-input-fg` (was implicit via `--a-ui-text`).
- `--chat-input-bg-hover`, `-border-hover`, `-fg-hover` — default to
  `--a-ui-bg-hover` / `--a-ui-border-hover` / `--a-fg`, matching
  `<input-ui>`'s hover affordance. The host paints the lift on
  `chat-input-ui:not([disabled]):hover`; the inner textarea-ui's own
  hover bg/border is forced transparent so the alpha doesn't compound.
- `--chat-input-bg-disabled`, `-fg-disabled` — inner-text disabled
  color is owned here; host-level disabled chrome unchanged.
- `--chat-input-placeholder-fg` — was leaking `--a-ui-text-placeholder`.

Hover lives outside the `@scope` block (Safari 17.x bug — `:scope …
[descendant]:hover` doesn't match in Safari 17.x; same workaround
input.css and textarea.css already use). Focus / invalid / disabled /
placeholder rules stay inside the `@scope` and use the
`:scope textarea-ui:STATE [slot="text"]:STATE` (0,4,1) pattern to beat
textarea.css's matching rules (0,3,0).

`--chat-input-duration` shifted from `--a-duration` to `--a-duration-fast`
(120 ms) to match `<input-ui>`'s transition speed; the host now
transitions `background`, `border-color`, `color`, and `box-shadow`
together — same 4-property surface `<input-ui>` and `<select-ui>` use —
so hover / focus / invalid / disabled all interpolate as one.

Inner `[slot="text"]` color overrides changed from explicit
`var(--chat-input-fg)` to `color: inherit` so the host's transitioned
`color` cascades to the contenteditable. The host's `:scope[disabled]`
now sets `color: var(--chat-input-fg-disabled)` directly; the inner
inherits, and the disabled-fg shift fades on the host's `color`
transition rather than snapping. No JS changes; no API changes.
## [0.2.0] - 2026-05-02

**Lockstep cut.** All 8 published `@adia-ai/*` packages now share one
version, governed by [`.claude/docs/specs/package-architecture.md` § 15
(Versioning Policy)](../../.claude/docs/specs/package-architecture.md#15-versioning-policy)
and enforced by `npm run check:lockstep` + the
`lockstep-versioning` job in
[`.github/workflows/docs-lint.yml`](../../.github/workflows/docs-lint.yml).

Big version jump (0.0.34 → 0.2.0) signals the lockstep era starts here;
this is **not** a breaking change to the component surface — every tag
+ prop + event from 0.0.34 still works identically. The jump aligns
this package with the highest sibling version (a2ui-mcp@0.1.3 → 0.2.0)
and avoids the `^0.0.x` caret-lock trap that bit `a2ui-mcp@0.1.3 →
a2ui-corpus@0.0.6` (the new `^0.X.0` ranges work properly under npm
pre-1.0 semver — see § 15 for the math).

### Changed

- `version`: `0.0.34` → `0.2.0`.
- `dependencies["@adia-ai/a2ui-utils"]`: `^0.0.2` → `^0.2.0`.

### No source changes

This package's source tree is byte-identical to 0.0.34. The cut bumps
version and the internal dep range only. Consumers who want the
substantive 0.0.34 work (the `<step-progress-ui>` primitive) get it
via either 0.0.34 or 0.2.0 — both ship the same component code.


---

## Pre-0.2.0 history

The 0.0.x release series (0.0.34 → 0.0.1, 2026-04-21 to 2026-05-02) is
archived in [`CHANGELOG-pre-0.2.0.md`](./CHANGELOG-pre-0.2.0.md). That
file covers every entry shipped before the lockstep cut.
