@adia-ai/web-components 0.7.24 → 0.7.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +46 -25
  2. package/README.md +2 -2
  3. package/USAGE.md +4 -4
  4. package/components/accordion/accordion-item.a2ui.json +30 -1
  5. package/components/accordion/accordion-item.yaml +33 -0
  6. package/components/accordion/accordion.a2ui.json +9 -0
  7. package/components/accordion/accordion.class.js +53 -3
  8. package/components/accordion/accordion.css +61 -3
  9. package/components/accordion/accordion.d.ts +12 -0
  10. package/components/accordion/accordion.yaml +17 -0
  11. package/components/code/code.class.js +1 -1
  12. package/components/drawer/drawer.class.js +2 -2
  13. package/components/empty-state/empty-state.yaml +5 -3
  14. package/components/feed/feed.a2ui.json +1 -1
  15. package/components/feed/feed.class.js +1 -1
  16. package/components/feed/feed.d.ts +1 -1
  17. package/components/feed/feed.yaml +2 -2
  18. package/components/field/field.class.js +1 -1
  19. package/components/loading-overlay/loading-overlay.class.js +1 -1
  20. package/components/loading-overlay/loading-overlay.yaml +1 -1
  21. package/components/modal/modal.class.js +1 -1
  22. package/components/nav/nav.class.js +1 -1
  23. package/components/nav/nav.examples.md +4 -4
  24. package/components/popover/popover.class.js +1 -1
  25. package/components/spinner/spinner.class.js +1 -1
  26. package/components/spinner/spinner.yaml +1 -1
  27. package/components/toast/toast.class.js +1 -1
  28. package/custom-elements.json +1 -1
  29. package/dist/theme-provider.min.js +1 -1
  30. package/dist/web-components.min.css +1 -1
  31. package/dist/web-components.min.js +1 -1
  32. package/dist/web-components.sheet.js +1 -1
  33. package/package.json +1 -1
  34. package/traits/CATEGORIES.md +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog — @adia-ai/web-components
2
2
 
3
+ ## [0.7.26] — 2026-07-04
4
+
5
+ ### Fixed
6
+ - **`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.
7
+
8
+ ## [0.7.25] — 2026-07-03
9
+
10
+ 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.
11
+
12
+ ### Added
13
+ - **`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)
14
+
15
+ ### Fixed
16
+ - **`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)
17
+ - **`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)
18
+ - **`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).
19
+
20
+ ### Maintenance
21
+ - **`dist/` bundles regenerated** (`web-components.min.{css,js}`, `web-components.sheet.js`, `theme-provider.min.js`) to carry the accordion changes above.
22
+ - **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.
23
+
3
24
  ## [0.7.24] — 2026-07-01
4
25
 
5
26
  Upstream consumer findings from `adia-pay` against installed `0.7.23` (F1–F6).
@@ -41,7 +62,7 @@ Upstream consumer findings from `adia-pay` against installed `0.7.23` (F1–F6).
41
62
  ## [0.7.19] — 2026-06-10
42
63
 
43
64
  ### Fixed
44
- - **`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 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.
65
+ - **`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.
45
66
 
46
67
  ## [0.7.18] — 2026-06-09
47
68
 
@@ -99,7 +120,7 @@ Upstream consumer findings from `adia-pay` against installed `0.7.23` (F1–F6).
99
120
 
100
121
  ### Changed
101
122
 
102
- - **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 `docs/MIGRATION GUIDE.md`.
123
+ - **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`.
103
124
  - **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.
104
125
 
105
126
  ### Fixed
@@ -211,7 +232,7 @@ Upstream consumer findings from `adia-pay` against installed `0.7.23` (F1–F6).
211
232
 
212
233
  ### Changed
213
234
 
214
- - **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; `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).
235
+ - **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).
215
236
  - **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).
216
237
  - **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.
217
238
 
@@ -336,7 +357,7 @@ Upstream consumer findings from `adia-pay` against installed `0.7.23` (F1–F6).
336
357
  ### Changed — global styles assigned to `@layer` cascade layers (ADR-0038)
337
358
 
338
359
  - `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).
339
- - **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 + `docs/specs/cascade-layer-architecture.md`.
360
+ - **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`.
340
361
 
341
362
  ### Added — `<select-ui>` per-option `icon` / `avatar`, reflected in the trigger
342
363
 
@@ -344,7 +365,7 @@ Upstream consumer findings from `adia-pay` against installed `0.7.23` (F1–F6).
344
365
 
345
366
  ### Changed — block-level display by default + universal `[inline]` attribute (ADR-0037)
346
367
 
347
- - 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 + `docs/specs/attribute-api-system.md` §5.
368
+ - 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.
348
369
 
349
370
  ### Changed — `chevron` → `caret` terminology sweep (FEEDBACK-90, ADR-0036 follow-up)
350
371
 
@@ -1118,11 +1139,11 @@ Three pre-v0.4.0 `patterns/*.examples.html` files used legacy markup retired by
1118
1139
  ## [0.6.15] — 2026-05-21
1119
1140
 
1120
1141
  ### Fixed
1121
- - **`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 `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`).
1142
+ - **`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`).
1122
1143
  - **`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).
1123
1144
 
1124
1145
  ### Docs
1125
- - **Quick-start + migration guide examples corrected (FB-09).** `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.). `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.
1146
+ - **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.
1126
1147
 
1127
1148
  ### Verification
1128
1149
  - `verify:traits` 56/56 clean.
@@ -1356,7 +1377,7 @@ Files: `slider.css`, `slider.class.js`, `slider.examples.html`, `slider.yaml`, `
1356
1377
 
1357
1378
  ## [0.5.15] - 2026-05-16
1358
1379
 
1359
- > **Scope note:** §325 + §326 were originally planned as a separate v0.5.16 cycle (see retired `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.
1380
+ > **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.
1360
1381
 
1361
1382
  ### v0.5.15 §326 — `<swatch-ui>` `[slot="chrome"]` lost in template contexts (FB-37; P1)
1362
1383
 
@@ -2763,7 +2784,7 @@ Also fixed `code.yaml`: removed a phantom `copy` event declaration (no runtime e
2763
2784
 
2764
2785
  ### Audit summary — hand-authored `.d.ts` cleanliness as of v0.4.9
2765
2786
 
2766
- After §90 + §91b sweeps, **all 17 form-bearing primitives' hand-authored `.d.ts` files are runtime-matched.** Audit recipe captured in `docs/plans/0.4.9-release-plan.md` §91b for future cycles — automatable as `scripts/release/check-form-bearing-dts.mjs` in a later cycle if drift returns.
2787
+ 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.
2767
2788
 
2768
2789
  ### Documentation — USAGE.md "Derived reactive bindings" subsection (§91d, FEEDBACK-04 #3)
2769
2790
 
@@ -3213,7 +3234,7 @@ Companion to the corpus simplification arc in `@adia-ai/a2ui-{runtime,compose,co
3213
3234
 
3214
3235
  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.
3215
3236
 
3216
- See root [CHANGELOG.md `[Unreleased]`](../../CHANGELOG.md) for the cross-cutting arc narrative + [docs/journal/2026/05/2026-05-12.md](../../docs/journal/2026/05/2026-05-12.md) §§ 43 / 44 / 50 / 51 for per-§ details.
3237
+ See root [CHANGELOG.md `[Unreleased]`](../../CHANGELOG.md) for the cross-cutting arc narrative + [.claude/docs/journal/2026/05/2026-05-12.md](../../.claude/docs/journal/2026/05/2026-05-12.md) §§ 43 / 44 / 50 / 51 for per-§ details.
3217
3238
  ## [0.4.3] - 2026-05-11
3218
3239
 
3219
3240
  ### Added
@@ -3229,7 +3250,7 @@ See root [CHANGELOG.md `[Unreleased]`](../../CHANGELOG.md) for the cross-cutting
3229
3250
 
3230
3251
  ### Lockstep
3231
3252
 
3232
- 9-package coordinated PATCH cut to v0.4.3 (per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy)). Internal `@adia-ai/*` dep ranges stay at `^0.4.0` (patch-cut asymmetry — `^0.4.0` covers `0.4.x` under semver). Ratifies [ADR-0027](../../.brain/adrs/0027-cross-primitive-composition-imports.md) — cross-primitive composition imports are the consumer's responsibility; the primitive's `.js` does NOT side-effect-import composed primitives. Source files touched: `components/input/input.{js,css,yaml,a2ui.json}`. See root [CHANGELOG.md `## [0.4.3]`](../../CHANGELOG.md) for the cut narrative.
3253
+ 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](../../.brain/adrs/0027-cross-primitive-composition-imports.md) — cross-primitive composition imports are the consumer's responsibility; the primitive's `.js` does NOT side-effect-import composed primitives. Source files touched: `components/input/input.{js,css,yaml,a2ui.json}`. See root [CHANGELOG.md `## [0.4.3]`](../../CHANGELOG.md) for the cut narrative.
3233
3254
  ## [0.4.2] - 2026-05-11
3234
3255
 
3235
3256
  ### Changed
@@ -3266,7 +3287,7 @@ See root [CHANGELOG.md `[Unreleased]`](../../CHANGELOG.md) for the cross-cutting
3266
3287
 
3267
3288
  ### Lockstep
3268
3289
 
3269
- 9-package coordinated PATCH cut to v0.4.2 (per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy)). Internal `@adia-ai/*` dep ranges stay at `^0.4.0` (patch-cut asymmetry — `^0.4.0` covers `0.4.x` under semver). ADR-0025 ([`.brain/adrs/0025-no-native-form-controls.md`](../../.brain/adrs/0025-no-native-form-controls.md)) ratified — codifies the "no native or suppressed-native interactive widgets in primitives" principle that the `<input-ui type="number">` rewrite implements; `password` remains the documented exception (physically requires `<input type="password">` for `-webkit-text-security`). See root [CHANGELOG.md `## [0.4.2]`](../../CHANGELOG.md) for the cut narrative.
3290
+ 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`](../../.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.
3270
3291
  ## [0.4.1] - 2026-05-10
3271
3292
 
3272
3293
  ### Ride-along (no source changes)
@@ -3298,7 +3319,7 @@ Lockstep version bump only — source byte-identical to v0.3.4. Internal `@adia-
3298
3319
  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.
3299
3320
  ## [0.3.3] - 2026-05-07
3300
3321
 
3301
- **Lockstep cut.** All 9 published `@adia-ai/*` packages now share version `0.3.3`, governed by [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy).
3322
+ **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).
3302
3323
 
3303
3324
  ### Changed
3304
3325
 
@@ -3311,7 +3332,7 @@ Lockstep version bump only — source byte-identical to v0.3.3. Internal `@adia-
3311
3332
  ## [0.3.2] - 2026-05-06
3312
3333
 
3313
3334
  **9-package lockstep patch cut to v0.3.2.** All lockstep members share
3314
- one version per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy).
3335
+ one version per [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/docs/specs/package-architecture.md#15-versioning-policy).
3315
3336
  Internal `@adia-ai/*` dep ranges unchanged at `^0.3.0`.
3316
3337
 
3317
3338
  ### No source changes
@@ -3324,7 +3345,7 @@ version only.
3324
3345
  - `version`: `0.3.1` → `0.3.2`.
3325
3346
  ## [0.3.1] - 2026-05-06
3326
3347
 
3327
- **9-package lockstep patch cut + folder-per-trait restructure.** All 9 published `@adia-ai/*` packages bump 0.3.0 → 0.3.1 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges remain at `^0.3.0` (covers `0.3.1` under semver — patch-cut asymmetry).
3348
+ **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).
3328
3349
 
3329
3350
  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.
3330
3351
 
@@ -3345,7 +3366,7 @@ Symmetry with `components/<name>/` (already folder-per for months) — easier to
3345
3366
  - `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).
3346
3367
  ## [0.3.0] - 2026-05-05
3347
3368
 
3348
- **9-package lockstep cut.** All 9 published `@adia-ai/*` packages bump 0.2.5 → 0.3.0 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges bump `^0.2.0` → `^0.3.0`.
3369
+ **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`.
3349
3370
 
3350
3371
  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).
3351
3372
 
@@ -3367,7 +3388,7 @@ The following directories saw significant in-repo changes between v0.2.5 and v0.
3367
3388
  - `patterns/` — no source changes since v0.2.5.
3368
3389
  ## [0.2.5] - 2026-05-04
3369
3390
 
3370
- **Lockstep cut + new `<fields-ui>` form-grid primitive + `draggable-list-item` last-item drop-zone fix.** All 8 published `@adia-ai/*` packages bump 0.2.4 → 0.2.5 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — **no BREAKING changes**.
3391
+ **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**.
3371
3392
 
3372
3393
  ### Added
3373
3394
 
@@ -3389,7 +3410,7 @@ The following directories saw significant in-repo changes between v0.2.5 and v0.
3389
3410
  - `styles/components.css` adds `@import "../components/fields/fields.css"`.
3390
3411
  ## [0.2.4] - 2026-05-04
3391
3412
 
3392
- **Lockstep cut + Tier 4 capability adds + 2 architectural rewrites.** All 8 published `@adia-ai/*` packages bump 0.2.3 → 0.2.4 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
3413
+ **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.
3393
3414
 
3394
3415
  ### Changed
3395
3416
 
@@ -3399,8 +3420,8 @@ The following directories saw significant in-repo changes between v0.2.5 and v0.
3399
3420
  - `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.
3400
3421
 
3401
3422
  The substantive content of the next cut, surfaced from the
3402
- 2026-05-04 trait-library lift initiative (`docs/PLAN.md`,
3403
- `docs/reports/traits-creative-director-audit-2026-05-04.md`).
3423
+ 2026-05-04 trait-library lift initiative (`.claude/docs/PLAN.md`,
3424
+ `.claude/docs/reports/traits-creative-director-audit-2026-05-04.md`).
3404
3425
  **No BREAKING changes.** Every public tag, prop, event, and
3405
3426
  declarative `traits=` attribute from v0.2.3 still works.
3406
3427
 
@@ -3653,7 +3674,7 @@ warnings on every page that referenced these:
3653
3674
 
3654
3675
  **Lockstep cut + accumulated fixes.** All 8 published `@adia-ai/*`
3655
3676
  packages bump 0.2.2 → 0.2.3 per
3656
- [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy).
3677
+ [`.claude/docs/specs/package-architecture.md` § 15](../../.claude/docs/specs/package-architecture.md#15-versioning-policy).
3657
3678
  Patch cut — no breaking changes. The substantive content is
3658
3679
  `web-components`-only; the other 7 packages have no source change.
3659
3680
 
@@ -3707,7 +3728,7 @@ edge instead of leaving the column reading as dead width. Per-row
3707
3728
  ---
3708
3729
  ## [0.2.2] - 2026-05-02
3709
3730
 
3710
- **Lockstep cut + trait coverage 100% + `<traits-host>` wrapper.** All 8 published `@adia-ai/*` packages bump 0.2.1 → 0.2.2 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
3731
+ **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.
3711
3732
 
3712
3733
  ### Changed
3713
3734
 
@@ -3726,7 +3747,7 @@ Follow-up landing on top of the v0.2.1 trait-system work — closes the two foll
3726
3747
  ---
3727
3748
  ## [0.2.1] - 2026-05-02
3728
3749
 
3729
- **Lockstep cut + 41-trait library overhaul + `<stat-ui>` ad-blocker rename.** All 8 published `@adia-ai/*` packages bump 0.2.0 → 0.2.1 per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
3750
+ **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.
3730
3751
 
3731
3752
  ### Changed
3732
3753
 
@@ -3818,8 +3839,8 @@ transition rather than snapping. No JS changes; no API changes.
3818
3839
  ## [0.2.0] - 2026-05-02
3819
3840
 
3820
3841
  **Lockstep cut.** All 8 published `@adia-ai/*` packages now share one
3821
- version, governed by [`docs/specs/package-architecture.md` § 15
3822
- (Versioning Policy)](../../docs/specs/package-architecture.md#15-versioning-policy)
3842
+ version, governed by [`.claude/docs/specs/package-architecture.md` § 15
3843
+ (Versioning Policy)](../../.claude/docs/specs/package-architecture.md#15-versioning-policy)
3823
3844
  and enforced by `npm run check:lockstep` + the
3824
3845
  `lockstep-versioning` job in
3825
3846
  [`.github/workflows/docs-lint.yml`](../../.github/workflows/docs-lint.yml).
package/README.md CHANGED
@@ -117,7 +117,7 @@ web-components/
117
117
  │ <traits-host> wrapper for raw-HTML declarative
118
118
  │ composition. Generated catalog at _catalog.json
119
119
  │ drives the MCP get_traits tool + per-trait demo
120
- │ pages. Full contract in docs/specs/traits.md.
120
+ │ pages. Full contract in .claude/docs/specs/traits.md.
121
121
 
122
122
  ├── a2ui/ — deprecation shim for one release
123
123
  │ └── index.js Re-exports @adia-ai/a2ui-runtime with a
@@ -247,7 +247,7 @@ class MyPanel extends UIElement {
247
247
 
248
248
  The `el` parameter is the element instance — every signal-backed property is reactively read.
249
249
 
250
- Full authoring contract: [`docs/specs/component-token-contract.md`](../../docs/specs/component-token-contract.md).
250
+ Full authoring contract: [`.claude/docs/specs/component-token-contract.md`](../../.claude/docs/specs/component-token-contract.md).
251
251
  The `adia-ui-authoring` skill encodes the 20 non-negotiable rules.
252
252
 
253
253
  ## Card-n / drawer-ui composition parity
package/USAGE.md CHANGED
@@ -466,7 +466,7 @@ export class UIModal extends UIElement {
466
466
  }
467
467
  ```
468
468
 
469
- Reference: [`docs/conventions/component-events.md`](../../docs/conventions/component-events.md) for the 1-page convention summary; [`scripts/build/dts-codegen.mjs`](../../scripts/build/dts-codegen.mjs) for the codegen logic that reads `x-adiaui.events`; [`scripts/release/check-form-bearing-dts.mjs`](../../scripts/release/check-form-bearing-dts.mjs) for the drift audit.
469
+ Reference: [`.claude/docs/conventions/component-events.md`](../../.claude/docs/conventions/component-events.md) for the 1-page convention summary; [`scripts/build/dts-codegen.mjs`](../../scripts/build/dts-codegen.mjs) for the codegen logic that reads `x-adiaui.events`; [`scripts/release/check-form-bearing-dts.mjs`](../../scripts/release/check-form-bearing-dts.mjs) for the drift audit.
470
470
 
471
471
  ---
472
472
 
@@ -1631,8 +1631,8 @@ If your design needs label-on-tile (e.g. Tokens Studio's C1.3 hand-rolled `<div
1631
1631
  - [`README.md`](./README.md) — package overview + authoring guide
1632
1632
  - [Component yaml contracts](./components/) — every primitive's complete API (props, events, slots, examples)
1633
1633
  - [Live demos](https://ui-kit.exe.xyz/site/components/) — every primitive in action
1634
- - [`docs/specs/component-token-contract.md`](../../docs/specs/component-token-contract.md) — full authoring contract
1635
- - [`docs/specs/component-implementation-patterns.md`](../../docs/specs/component-implementation-patterns.md) — render strategies
1636
- - [`docs/specs/traits.md`](../../docs/specs/traits.md) — composable behaviors
1634
+ - [`.claude/docs/specs/component-token-contract.md`](../../.claude/docs/specs/component-token-contract.md) — full authoring contract
1635
+ - [`.claude/docs/specs/component-implementation-patterns.md`](../../.claude/docs/specs/component-implementation-patterns.md) — render strategies
1636
+ - [`.claude/docs/specs/traits.md`](../../.claude/docs/specs/traits.md) — composable behaviors
1637
1637
 
1638
1638
  For consumer questions not covered here, file an issue against [adiahealth/gen-ui-kit](https://github.com/adiahealth/gen-ui-kit/issues). This document is the canonical answer — if it doesn't have your answer, it's a doc gap worth fixing.
@@ -24,6 +24,15 @@
24
24
  "text": {
25
25
  "description": "Header text — the clickable label that toggles the section.",
26
26
  "type": "string"
27
+ },
28
+ "variant": {
29
+ "description": "Per-item posture override. Items normally inherit their host\naccordion-ui's [variant] — set this directly on ONE item only to\nopt it out of (`flat`) or into (`contained`) the host's posture.",
30
+ "type": "string",
31
+ "enum": [
32
+ "flat",
33
+ "contained"
34
+ ],
35
+ "default": "flat"
27
36
  }
28
37
  },
29
38
  "required": [
@@ -66,6 +75,9 @@
66
75
  "body": {
67
76
  "description": "The section's collapsible content. Renders below the header; hidden when `[open]` is unset. Author-fills with prose, lists, embedded forms, or any rich markup the panel needs."
68
77
  },
78
+ "caret": {
79
+ "description": "The disclosure indicator icon. Auto-stamped (a caret-down icon-ui)\nwhen absent — under BOTH the default header and an authored\n[slot=\"header\"]. Author your own [slot=\"caret\"] element (even empty)\nto opt out of the auto-stamp; no separate attribute needed."
80
+ },
69
81
  "header": {
70
82
  "description": "Custom header content. By default `[text]` renders as a plain header\nlabel, but a `[slot=\"header\"]` override lets consumers author rich\nheaders (icon + title + action buttons + caret)."
71
83
  }
@@ -81,7 +93,24 @@
81
93
  ]
82
94
  },
83
95
  "tag": "accordion-item-ui",
84
- "tokens": {},
96
+ "tokens": {
97
+ "--accordion-item-bg": {
98
+ "description": "Item surface background.",
99
+ "default": "transparent"
100
+ },
101
+ "--accordion-item-body-pt": {
102
+ "description": "Open body top padding.",
103
+ "default": 0
104
+ },
105
+ "--accordion-item-border": {
106
+ "description": "Item surface border (full shorthand — mirrors --card-border).",
107
+ "default": "none"
108
+ },
109
+ "--accordion-item-divider": {
110
+ "description": "Border between header and open body when expanded.",
111
+ "default": "none"
112
+ }
113
+ },
85
114
  "traits": [],
86
115
  "version": 1
87
116
  }
@@ -26,6 +26,17 @@ props:
26
26
  description: Whether the section is expanded.
27
27
  type: boolean
28
28
  default: false
29
+ variant:
30
+ description: |-
31
+ Per-item posture override. Items normally inherit their host
32
+ accordion-ui's [variant] — set this directly on ONE item only to
33
+ opt it out of (`flat`) or into (`contained`) the host's posture.
34
+ type: string
35
+ default: flat
36
+ enum:
37
+ - flat
38
+ - contained
39
+ reflect: true
29
40
  slots:
30
41
  header:
31
42
  description: |-
@@ -39,6 +50,12 @@ slots:
39
50
  `[slot="actions"]`, or marked `[data-no-toggle]`) are excluded from
40
51
  the toggle-on-click cascade — clicking them fires their own handler
41
52
  without also toggling the section.
53
+ caret:
54
+ description: |-
55
+ The disclosure indicator icon. Auto-stamped (a caret-down icon-ui)
56
+ when absent — under BOTH the default header and an authored
57
+ [slot="header"]. Author your own [slot="caret"] element (even empty)
58
+ to opt out of the auto-stamp; no separate attribute needed.
42
59
  body:
43
60
  description: >-
44
61
  The section's collapsible content. Renders below the header; hidden
@@ -52,6 +69,20 @@ events:
52
69
  type: boolean
53
70
  description: New open state.
54
71
 
72
+ tokens:
73
+ --accordion-item-bg:
74
+ description: Item surface background.
75
+ default: transparent
76
+ --accordion-item-border:
77
+ description: Item surface border (full shorthand — mirrors --card-border).
78
+ default: none
79
+ --accordion-item-divider:
80
+ description: Border between header and open body when expanded.
81
+ default: none
82
+ --accordion-item-body-pt:
83
+ description: Open body top padding.
84
+ default: 0
85
+
55
86
  keywords:
56
87
  - accordion-item
57
88
  - collapsible
@@ -75,3 +106,5 @@ a2ui:
75
106
  reason: 'Built-in cascade; do not wire onclick yourself.'
76
107
  - rule: 'For single-button disclosures (one expanding panel without a list) use <details>/<summary> or compose <button-ui> + <section-ui>.'
77
108
  reason: 'Accordion-item assumes sibling-context.'
109
+ - rule: 'An item inherits its host accordion-ui''s [variant]; set variant="flat" or variant="contained" directly on ONE item to override just that item against the host.'
110
+ reason: 'Per-item posture override.'
@@ -20,6 +20,15 @@
20
20
  "description": "Allow multiple panels to be open simultaneously",
21
21
  "type": "boolean",
22
22
  "default": false
23
+ },
24
+ "variant": {
25
+ "description": "Visual posture, inherited by child accordion-item-ui elements unless a\nchild sets its own [variant]. `flat` (default) renders items with no\nchrome — settings pages, FAQ blocks, anywhere the accordion sits\ndirectly on the page canvas. `contained` gives every item a bounded\nsurface (background + border + radius spanning header and open body,\nplus a divider between them when open) — the composition to reach for\nwhen nesting the accordion inside <card-ui>.",
26
+ "type": "string",
27
+ "enum": [
28
+ "flat",
29
+ "contained"
30
+ ],
31
+ "default": "flat"
23
32
  }
24
33
  },
25
34
  "required": [
@@ -38,6 +38,12 @@ export class UIAccordion extends UIElement {
38
38
 
39
39
  static properties = {
40
40
  multiple: { type: Boolean, default: false, reflect: true },
41
+ // `reflect: true` is load-bearing: the CSS ancestor selector
42
+ // (accordion.css `@scope (accordion-item-ui)`) matches
43
+ // `accordion-ui[variant="contained"]`. Default 'flat' ⇒ absent-attribute
44
+ // = current behavior. No render/JS logic keys off it — inheritance of
45
+ // the contained posture into child items is pure CSS.
46
+ variant: { type: String, default: 'flat', reflect: true }, // 'flat' | 'contained'
41
47
  };
42
48
 
43
49
  static template = () => null;
@@ -70,6 +76,10 @@ export class UIAccordionItem extends UIElement {
70
76
  static properties = {
71
77
  text: { type: String, default: '', reflect: true },
72
78
  open: { type: Boolean, default: false, reflect: true },
79
+ // Per-item posture override against the host accordion-ui's [variant]
80
+ // (see UIAccordion.variant above). Default 'flat'; CSS-only, pure
81
+ // token re-point in contained (accordion.css).
82
+ variant: { type: String, default: 'flat', reflect: true }, // 'flat' | 'contained'
73
83
  };
74
84
 
75
85
  static template = () => null;
@@ -77,13 +87,24 @@ export class UIAccordionItem extends UIElement {
77
87
  #bound = false;
78
88
 
79
89
  connected() {
80
- // Stamp header if not present
81
- if (!this.querySelector('[slot="header"]')) {
90
+ // Stamp header if not present; otherwise restore the disclosure caret
91
+ // (FN-1) — a consumer's custom [slot="header"] previously suppressed it
92
+ // entirely because the caret only ever got stamped alongside the header.
93
+ const existingHeader = this.querySelector('[slot="header"]');
94
+ if (!existingHeader) {
82
95
  const header = document.createElement('div');
83
96
  header.setAttribute('slot', 'header');
97
+ // SPEC-R8: the auto-stamped header was mouse-only (no tabindex/keydown).
98
+ // Make it keyboard-operable; a consumer-authored header keeps owning
99
+ // its own semantics (data-autofocusable marks the header WE own).
100
+ header.setAttribute('tabindex', '0');
101
+ header.setAttribute('role', 'button');
102
+ header.setAttribute('aria-expanded', 'false');
103
+ header.dataset.autofocusable = '';
84
104
 
85
105
  const label = document.createElement('span');
86
106
  label.setAttribute('slot', 'header-text');
107
+ label.dataset.autolabel = ''; // marks THIS label as prop-driven (FN-2)
87
108
  header.appendChild(label);
88
109
 
89
110
  const caret = document.createElement('icon-ui');
@@ -92,6 +113,13 @@ export class UIAccordionItem extends UIElement {
92
113
  header.appendChild(caret);
93
114
 
94
115
  this.prepend(header);
116
+ } else if (!existingHeader.querySelector('[slot="caret"]')) {
117
+ // Scope to the HEADER, not the whole item — a caret placed in
118
+ // [slot="body"] must not suppress this (Finding 10).
119
+ const caret = document.createElement('icon-ui');
120
+ caret.setAttribute('name', 'caret-down');
121
+ caret.setAttribute('slot', 'caret');
122
+ existingHeader.appendChild(caret); // restore the disclosure signifier
95
123
  }
96
124
 
97
125
  // Stamp content wrapper if not present
@@ -112,17 +140,26 @@ export class UIAccordionItem extends UIElement {
112
140
  if (!this.#bound) {
113
141
  this.#bound = true;
114
142
  this.addEventListener('click', this.#onClick);
143
+ this.addEventListener('keydown', this.#onKeydown);
115
144
  }
116
145
  }
117
146
 
118
147
  disconnected() {
119
148
  this.removeEventListener('click', this.#onClick);
149
+ this.removeEventListener('keydown', this.#onKeydown);
120
150
  this.#bound = false;
121
151
  }
122
152
 
123
153
  render() {
124
- const label = this.querySelector('[slot="header-text"]');
154
+ // Only the auto-stamped label follows [text] — an authored
155
+ // [slot="header-text"] (no data-autolabel marker) is never touched (FN-2).
156
+ const label = this.querySelector('[slot="header-text"][data-autolabel]');
125
157
  if (label) label.textContent = this.text;
158
+
159
+ // Keep aria-expanded in sync on the auto-stamped header — `open` is a
160
+ // reactive prop, so render() re-runs on every toggle (SPEC-R8).
161
+ const hdr = this.querySelector('[slot="header"][data-autofocusable]');
162
+ if (hdr) hdr.setAttribute('aria-expanded', String(this.open));
126
163
  }
127
164
 
128
165
  #onClick = (e) => {
@@ -140,4 +177,17 @@ export class UIAccordionItem extends UIElement {
140
177
  detail: { open: this.open },
141
178
  }));
142
179
  };
180
+
181
+ // SPEC-R8: Enter/Space on the auto-stamped header toggles it. Scoped to
182
+ // OUR stamped header only (data-autofocusable) — never a consumer control
183
+ // inside a custom header. Routes through header.click() → #onClick, so
184
+ // there is exactly one toggle code path (DRY).
185
+ #onKeydown = (e) => {
186
+ const header = this.querySelector('[slot="header"][data-autofocusable]');
187
+ if (!header || e.target !== header) return;
188
+ if (e.key === 'Enter' || e.key === ' ') {
189
+ e.preventDefault(); // Space: no page scroll
190
+ header.click();
191
+ }
192
+ };
143
193
  }