<!-- GENERATED COPY — do not edit. Canonical source:
     .claude/docs/MIGRATION GUIDE.md (framework monorepo). Regenerate:
     npm run check:migration-sync -- --fix -->

# Migration Guide: @agent-ui-kit → @adia-ai

Migrating from `@agent-ui-kit/web-components` to the current
`@adia-ai/*` 9-package monorepo (web-components, web-modules,
llm, a2ui-runtime, a2ui-compose, a2ui-corpus, a2ui-mcp, a2ui-retrieval,
a2ui-validator).

Current versions: all 9 `@adia-ai/*` packages at `0.7.1` (lockstep,
2026-06-01). Internal `@adia-ai/*` ranges are caret-locked at `^0.7.0`
(PATCH-cut asymmetry: `^0.6.0` covers all `0.6.x` per npm pre-1.0
semver; minor cuts bump ranges, patch cuts hold). Base class is
`UIElement` (was `AdiaElement` pre-0.0.x); design tokens use the
`--a-*` prefix.

Version cuts and migration scope:
- **`0.0.5` through `0.0.11`** — additive polish / hotfix; no migration needed.
- **`0.0.12` through `0.0.19`** — additive (table-toolbar primitive, smart filters, Construct Canvas, more docs); no breaking changes.
- **`0.0.20`** — large BREAKING cut. Safari floor raise, ui-audit-coherence fixes, Phase 6 contract decisions, and the deprecation-window closure. Use the [§ 0.0.x → 0.0.20 section below](#migrating-to-adia-aiweb-components0020-2026-04-27) — it's the most comprehensive break in the `0.0.x` line.
- **`0.0.21`** — same-day patch. Pure source-doc / JSDoc refresh + repo-tooling. **No runtime change.** No migration needed beyond the `0.0.20` sweep; bumping to `0.0.21` is a drop-in upgrade.
- **`0.0.29` / `web-modules@0.0.1`** — three-tier extraction. Use the [§ 0.0.29 + web-modules section below](#migrating-to-adia-aiweb-components0029--adia-aiweb-modules001-2026-05-01).
- **`0.3.0` through `0.3.6`** — lockstep series. Additive: ADR-0023 bespoke shell-tier children landed in `web-modules@0.3.4-0.3.6` with backwards compatibility — both legacy and bespoke authoring shapes coexisted. No breaking change at the cluster boundary, though new authoring should use bespoke (see § v0.3.6 below).
- **`0.4.0`** — large BREAKING cut. **Legacy shell authoring shapes RETIRED** per `ADR-0024`. Closes the ADR-0023 arc. See [§ v0.4.0 section below](#migrating-to-adia-aiv040-2026-05-10) — it's the most comprehensive break since `0.0.20`.
- **`0.5.0`** — MINOR lockstep cut. Internal ranges `^0.4.0` → `^0.5.0`. **Free-form composer graduation** (cov 21% → 92%) + 5 FEEDBACK rounds from Tokens Studio closed. No code-breaking changes; soft-API graduation only — see [§ v0.5.0 section below](#migrating-to-adia-aiv050-2026-05-13).
- **`0.5.1`** — PATCH cut. Internal ranges stay at `^0.5.0`. Free-form maturation (cov 92% → 93%, F1 59 → 62) via Haiku-pin + INTENT-PARAPHRASE prompt + Mode-C corpus. **Soft-API graduations:** `result.usedIngredients` + `result.rationale` lifted from `_debug.*` to top-level on `generateFreeFormAdapter`. See [§ v0.5.1 section below](#migrating-to-adia-aiv051-2026-05-13).
- **`0.5.2`** — PATCH cut. Internal ranges stay at `^0.5.0`. Audit-script family extended to 10 trip-wires (added §128 `check-standalone-html-phosphor` + §135 `check-substitutable-set-drift`). **Soft-API addition:** `result.plan` lifted from `_debug.plan` to top-level on `generateFreeFormAdapter` (the `_debug.plan` fallback was already redundant; v0.5.2 just removes the dead read path). See [§ v0.5.2 section below](#migrating-to-adia-aiv052-2026-05-13).
- **`0.5.3`** — PATCH cut. Internal ranges stay at `^0.5.0`. **FEEDBACK-06 + FEEDBACK-07 response cycle**: §140 `resolveLoader` filename-suffix fallback (handles pnpm / yarn-berry / custom-Vite-root loader-map keys); §154 NEW `installIconLoadersForRegistered()` + `static requiredIcons` declarations (consumers can auto-discover the icon set their imported primitives need instead of hand-curating brace-lists); §143 8 new UI-primitive corpus chunks closing §127's coverage gap; §142 broadened smoke retrieval probes; §144 yaml-events drift cleanup; §153 slider function-typed `.value` dev-mode guard; §157 responsive-shell-sidebar pattern; §162 yaml-events audit false-positive fix + table row-collapse declaration. **No breaking changes.** Recommended migration for color-app and similar consumers: replace hand-curated `installIconLoaders` brace-lists with `installIconLoadersForRegistered`. See [§ v0.5.3 section below](#migrating-to-adia-aiv053-2026-05-13).
- **`0.5.4` through `0.5.21`** — PATCH series. Consumer-feedback close-out (FB-29 through FB-53), drift-class lockdown graduation (7 surfaces, 33 trip-wires), `<editor-canvas-toolbar>` module, `<color-input-ui>` form-bearing primitive, `<color-scale-ui>` token composer, and 20 UI-primitive improvements. **No breaking changes.** Upgrade is a drop-in; no migration needed beyond recommended icon-registration upgrade from v0.5.3.
- **`0.6.0`** — MINOR lockstep cut. Internal ranges `^0.5.0` → `^0.6.0`. **BREAKING:** `stat-ui.{js,css}` renamed to `stat.{js,css}` — deep-importers must update paths. TypeScript substrate Phase 0+1 landed (tsconfig scaffold, `exports.types` on all 9 packages, 38 stub `.d.ts` files). See [§ v0.6.0 section below](#migrating-to-adia-aiv060-2026-05-18).
- **`0.6.1`** — PATCH cut. Internal ranges stay at `^0.6.0`. **BREAKING:** `<link-ui>` token rename `--link-color-*` → `--link-fg-*`. TypeScript Phase 2 (`@adia-ai/llm` → `.ts`) + Phase 3 (`@adia-ai/a2ui-compose` → `.ts`). See [§ v0.6.1 section below](#migrating-to-adia-aiv061-2026-05-18).
- **`0.6.2` through `0.6.6`** — PATCH series. TypeScript Phase 4 (`@adia-ai/a2ui-retrieval` + `@adia-ai/a2ui-mcp` → `.ts`), Phase 5 (real `.d.ts` + `a2ui.schema.json` codegen + branded IDs), Phase 6 (strict-flag ratchet sealed, ADR-0029). `<field-ui align>` prop (§304). `mcp/server.ts` thin-dispatcher refactor. **No breaking changes.** Upgrade is a drop-in.

- **`0.6.7`** — PATCH cut (2026-05-19). Internal ranges stay at `^0.6.0`. **Closed 17 repo-improvement findings (§367)** — adds shipping artifacts and quality-of-life upgrades; no breaking changes. Highlights:
  - **`@adia-ai/web-components`** ships 96 `.examples.md` files, a W3C-spec `custom-elements.json` manifest, and an `adia-ui-doc` CLI bin (`npx adia-ui-doc <name>`).
  - **`@adia-ai/web-modules`** ships a new `/generative` subpath barrel — single import registers `canvas-ui` + `a2ui-root` + re-exports `registerResolver` (closes the ADR-0027 cross-package barrel footgun).
  - **`@adia-ai/a2ui-mcp`** now exposes 30 tools (was 24): adds `list_patterns`, `refine_ui`, `server_status`. Dual stdio + StreamableHTTP transport (set `MCP_HTTP_PORT` to enable HTTP at `/mcp`). Real MCP sampling support — when run via Claude Code / Cursor / Hermes, the host's LLM is used via `server.createMessage()` instead of falling back to `.env` keys.
  - **Tarball hygiene fixes**: `@adia-ai/web-modules` no longer leaks 28 `.examples.html` authoring artifacts; `@adia-ai/a2ui-mcp` / `a2ui-compose` / `a2ui-retrieval` no longer leak 24 `.ts` source files.
  - **Recommended consumer migration:** if you were using `@adia-ai/web-modules/runtime/a2ui-root/a2ui-root.js` + `@adia-ai/web-components/components/canvas/canvas.js` for generative UI surfaces, collapse to a single `import '@adia-ai/web-modules/generative'`. Both old paths still work.
  - See [§ v0.6.7 section below](#migrating-to-adia-aiv067-2026-05-19).

- **Upcoming (`<theme-provider>` cut)** — **BREAKING:** the token-theme attribute is renamed `data-theme` → `theme` repo-wide. Replace `<html data-theme="ocean">` → `<html theme="ocean">` and any `[data-theme="…"]` selectors → `[theme="…"]`. Mechanical: a word-boundary find-replace of `data-theme` → `theme` (preserve compound attrs like `data-theme-slug` / `data-themed`). Pre-1.0 PATCH cadence. Also new: the **`<theme-provider>`** element adopts the AdiaUI foundation into the document from anywhere (no `<head>` stylesheet link), with opt-in `theme="…"` (named preset) + `scale="verse|prose"` (typographic register) attributes that fetch their layer on demand.
  - **`theme` ≠ `scheme` — don't conflate the two axes.** This rename is purely the *theme* axis (a named preset: ocean / forest / slate / …). A light/dark *mode* is the orthogonal *scheme* axis — it belongs on `color-scheme` / `data-scheme`, **never** on `[theme]`. If you'd encoded a mode as `data-theme="dark"`, that was a category error: after the rename it is a no-op on *both* axes. Move it to the scheme axis — `<html style="color-scheme: dark">`, `data-scheme`, or `<toggle-scheme-ui>`. (Surfaced by color-app, 2026-06-07.)

- **`0.8.0`** — MINOR lockstep cut (2026-07-15). Internal ranges `^0.7.0` → `^0.8.0`. **BREAKING (3 items):** the Material color-token adoption — `--md-sys-color-*` (a static export) is now the color system's source of truth and public API. Removed: the 19 `--a-accent-*` back-compat aliases (→ `--a-primary-*`), the 189 raw per-family `--a-{family}-{0..6}-{tint-|shade-}scrim` primitives (→ the static `--md-sys-color-{family}-scrim-*` ladder), and the `primitives-accent.css` deep-import path (→ `primitives-primary.css`). **Behavioral:** the 8 named `[theme]` presets no longer re-color components (only brand hue + radius/density/shadow knobs still respond). See [§ v0.8.0 section below](#migrating-to-adia-aiv080-2026-07-15).

Live demos and per-component docs at [ui-kit.exe.xyz/site/](https://ui-kit.exe.xyz/site/). The `adia-ui-migration` skill (in `.agents/skills/`) automates most of the mechanical sweeps in this guide.

---

## Migrating to `@adia-ai/*@v0.8.0` (2026-07-15)

**MINOR cut.** All 9 packages bumped `0.7.x → 0.8.0`; internal `@adia-ai/*` ranges **bump to `^0.8.0`**. The headline: **AdiaUI adopted the Material Design token system as its color source of truth** (`.claude/docs/specs/material-color-bridge-contract.md`, Phases 1–5). `--md-sys-color-*` roles (59 per palette, 8 palettes, static OKLCH literals in `colors/material-static.css`) are the public color API; the `--a-*` semantic layer remains fully functional as a thin alias layer reading FROM it. 3 breaking items + 1 behavioral change.

### `--a-accent-*` tokens removed (`--a-accent-*` → `--a-primary-*`)

The `accent` family was renamed `primary` (matches Material's single `primary` family and the pre-existing L3 CTA names); the deprecated alias block shipped during the transition has been removed now that its zero-consumers condition was met. Pure rename — same values, same roles.

**Audit:**
```bash
git grep -nE -- '--a-accent'
```

**Sweep (mechanical):**
```bash
git grep -lE -- '--a-accent' | xargs perl -i -pe 's/--a-accent(?![a-z0-9])/--a-primary/g'
```

(The `(?![a-z0-9])` guard keeps suffixed forms like `--a-accent-strong` → `--a-primary-strong` correct while never touching unrelated names. Prefer consuming `--md-sys-color-primary*` directly in new code.)

### Raw per-family scrim primitives removed (`--a-{family}-{0..6}-scrim` → `--md-sys-color-{family}-scrim-*`)

`scrims.css` (189 computed alpha-ramp declarations: `--a-{family}-N-tint-scrim` / `-shade-scrim` / `-scrim` for 9 families) is retired — zero in-repo consumers remained once the semantic layer moved to the static export's own 7-step scrim ladder. **Note:** the semantic `--a-canvas-{0..6}-scrim` and `--a-scrim-{weak|toast|default|dialog|strong}` role names still exist and keep working — only the RAW per-family primitives are gone.

**Audit:**
```bash
git grep -nE -- '--a-(neutral|primary|secondary|tertiary|brand|info|success|warning|danger)-[0-9]-(tint-scrim|shade-scrim|scrim)\b'
```

**Sweep: manual review** — the old ramp ran 10–70% alpha over an adaptive near-black/near-white base; the static ladder runs 5–60% over each family's fixed mid-tone. Match by ALPHA VALUE, not name position: `-1-scrim` (20%) → `-scrim-weak`, `-2-` (30%) → `-scrim`, `-3-` (40%) → `-scrim-strong`, `-4-` (50%) → `-scrim-stronger`, `-5/-6-` (60/70%) → `-scrim-strongest` (60% is now the ceiling). A blind rename would shift every overlay one step lighter.

### `primitives-accent.css` file removed (deep-import path)

Deep importers of `@adia-ai/web-components/styles/colors/primitives-accent.css` must retarget `primitives-primary.css` (same ramp, renamed family). Consumers of the barrel (`styles/colors/index.css`, `styles/index.css`, `host.css`) are unaffected.

**Audit:**
```bash
git grep -n 'primitives-accent'
```

**Sweep (mechanical):**
```bash
git grep -l 'primitives-accent' | xargs perl -i -pe 's/primitives-accent/primitives-primary/g'
```

### Behavioral: named `[theme]` presets no longer re-color (manual review)

The 8 `[theme="default|ocean|forest|sunset|lavender|rose|slate|midnight"]` presets worked by overriding parametric `--a-{family}-hue/-c-min/-c-max` knobs that the calc()-based engine consumed. The static export doesn't read those knobs, so the presets no longer change component colors — only `--a-brand-hue` and the non-color knobs (`--a-radius-k`, `--a-density`, `--a-shadow-intensity`) still respond to `[theme]`. **No symbol was removed** — `themes.css` still ships and applying `[theme]` is harmless — but a consumer relying on live re-theming must now theme by overriding `--md-sys-color-*` roles directly (or regenerate a static export per theme). Operator-accepted tradeoff of the adoption; there is no mechanical sweep.

**Audit (find consumers that set a theme):**
```bash
git grep -nE 'theme="(ocean|forest|sunset|lavender|rose|slate|midnight)"'
```

### Verify the sweep landed (all four items)

```bash
git grep -cE -- '--a-accent' || echo clean                 # expect clean (item 1)
git grep -c 'primitives-accent' || echo clean              # expect clean (item 3)
git grep -cE -- '--a-[a-z]+-[0-9]-(tint-|shade-)?scrim\b' || echo clean   # expect clean (item 2)
```

Then the browser gate on the migrated screens (`adia-verify`): zero console errors,
non-zero bounding boxes, and read the screenshot — the scrim remap (item 2) and the
`[theme]` behavioral change are visual; only the pixels prove overlay weights and
brand color survived. Compare a before/after screenshot of one themed page if the
app set a named theme.

---

## Migrating to `@adia-ai/*@0.6.0` (2026-05-18)

**MINOR cut.** All 9 packages bumped `0.5.x → 0.6.0`. Internal `@adia-ai/*` ranges **bump to `^0.6.0`** (minor-cut asymmetry: all consumers must update their lockstep ranges). TypeScript substrate landed.

### Breaking changes

#### `@adia-ai/web-components` — `stat-ui.{js,css}` → `stat.{js,css}` filename rename

`<stat-ui>` was the only primitive with a `-ui` suffix in its CSS/JS filenames. Corrected to match the rest of the catalog.

**Consumers who deep-import the stat primitive must update:**

```js
// Before
import '@adia-ai/web-components/components/stat/stat-ui.js';
// After
import '@adia-ai/web-components/components/stat/stat.js';
```

```css
/* Before */
@import '@adia-ai/web-components/components/stat/stat-ui.css';
/* After */
@import '@adia-ai/web-components/components/stat/stat.css';
```

Consumers who import via the barrel (`@adia-ai/web-components/components/index.js`) or via the component auto-register path (`@adia-ai/web-components/components/stat`) are **not affected** — those paths were already updated.

### Non-breaking additions

- **TypeScript substrate Phase 0+1:** all 9 packages now ship `exports.types` pointing to real `.d.ts` declarations. TypeScript consumers get IDE autocomplete and type checking without any config change. No import path changes.
- **`verify:exports-conditionals` CI gate** added — ensures every `exports.import` has a matching `exports.types` entry.

---

## Migrating to `@adia-ai/*@0.6.1` (2026-05-18)

**PATCH cut.** All 9 packages bumped `0.6.0 → 0.6.1`. Internal ranges **stay at `^0.6.0`**.

### Breaking changes

#### `@adia-ai/web-components` — `<link-ui>` CSS token rename

`--link-color-*` tokens renamed to `--link-fg-*` to match the property-first naming convention used by all other primitives.

| Old token | New token |
|---|---|
| `--link-color` | `--link-fg` |
| `--link-color-hover` | `--link-fg-hover` |
| `--link-color-visited` | `--link-fg-visited` |

**Action required if you customise `<link-ui>` colours in CSS:**

```css
/* Before */
link-ui { --link-color: var(--brand-blue); }
link-ui:hover { --link-color-hover: var(--brand-blue-dark); }

/* After */
link-ui { --link-fg: var(--brand-blue); }
link-ui:hover { --link-fg-hover: var(--brand-blue-dark); }
```

If you do not override these tokens (i.e. you use the default theme colours), **no action needed**.

### Non-breaking additions

- **`@adia-ai/llm` TypeScript Phase 2:** sources moved to `src/*.ts`; real `.d.ts` files generated; import paths unchanged (`@adia-ai/llm`, `@adia-ai/llm/bridge`, `@adia-ai/llm/stub`, `@adia-ai/llm/adapters/*` all work as before). TypeScript consumers now get accurate parameter and return types.
- **`@adia-ai/a2ui-compose` TypeScript Phase 3:** internal sources typed; no public API change.

---

## Migrating to `@adia-ai/*@0.6.7` (2026-05-19)

**PATCH cut.** Internal `@adia-ai/*` ranges stay at `^0.6.0`. No breaking changes; upgrade is a drop-in from any `0.6.x`. This release closes 17 repo-improvement findings (§367) — most user-visible changes are net-new shipping artifacts and quality-of-life upgrades.

### What's new in the published packages

**`@adia-ai/web-components` now ships:**

- **`components/<name>/<name>.examples.md`** (96 files, ~67KB total). 1-3 canonical fragments per component as plain markdown. Previously `.examples.html` was monorepo-only; consumers had no first-class markup reference. Read with `cat node_modules/@adia-ai/web-components/components/<name>/<name>.examples.md` or via `npx adia-ui-doc <name>`.
- **`custom-elements.json`** (W3C Custom Elements Manifest v2.1.0, 110 modules, ~300KB). Enables VS Code LSP autocomplete and Storybook/wider WC-tool interop without configuration.
- **`bin/doc.mjs`** + `bin` field — exposes a `npx adia-ui-doc` CLI:
  ```bash
  npx adia-ui-doc                    # list all 96 components
  npx adia-ui-doc field              # summary + live demo URL + first example
  ```

**`@adia-ai/web-modules` ships a new `generative` subpath:**

```js
// One import registers <canvas-ui> + <a2ui-root> + re-exports registerResolver
import '@adia-ai/web-modules/generative';
import { registerResolver, Canvas, A2UIRoot } from '@adia-ai/web-modules/generative';
```

Closes the ADR-0027 "cross-package primitives don't auto-register" footgun for the generative-UI use case. Both the previous-form imports continue to work:

```js
// Still supported (more verbose; 0.6.6 and earlier shape)
import '@adia-ai/web-modules/runtime/a2ui-root/a2ui-root.js';
import '@adia-ai/web-components/components/canvas/canvas.js';
import { registerResolver } from '@adia-ai/a2ui-runtime';
```

**`@adia-ai/a2ui-mcp` adds three tools (24 → 30):**

- `list_patterns({ domain?, category? })` — enumerate the corpus with optional filters. Useful when you want to browse rather than keyword-search.
- `refine_ui({ intent, previousMessages, validationErrors })` — re-run monolithic generation feeding validation errors back to the LLM. Closes the missing-refine path for the monolithic engine (zettel already had `refine_composition`).
- `server_status()` — returns `{ version, transport, sampling, corpus: { totalComponents, compositionCount, chunkCount } }`. Useful for health-checks and observability.

**Dual transport — set `MCP_HTTP_PORT` to enable HTTP:**

```bash
# stdio (default) — for Claude Code / Cursor / Hermes IDE integration
node node_modules/.bin/adiaui-mcp

# HTTP — for deployed services, exposed at <host>:<port>/mcp
MCP_HTTP_PORT=3460 node node_modules/.bin/adiaui-mcp
```

The HTTP transport implements the MCP Streamable HTTP protocol (MCP spec 2025-11-25). The Node-side `mcpStream()` adapter in `@adia-ai/a2ui-runtime` was also extended to dispatch by URL scheme — `ws://` / `wss://` use the original WebSocket path, `http://` / `https://` use the new Streamable HTTP client.

**MCP sampling is now wired through** — when run via Claude Code / Cursor / Hermes, `generate_ui` and `plan_app_state` will request inference from the host's LLM via `server.createMessage()` instead of falling back to `.env` API keys. The host LLM choice (model + budget + provider) is transparently used. Falls back to `.env` keys if the client doesn't declare sampling capability.

### Tarball hygiene fixes

These don't change the API but eliminate authoring-artifact bloat in published tarballs:

- **`@adia-ai/web-modules`** — was leaking 28 `.examples.html` files into the tarball. Now filtered (matches `@adia-ai/web-components` convention via `!**/*.examples.html` in `files`).
- **`@adia-ai/a2ui-mcp` / `@adia-ai/a2ui-compose` / `@adia-ai/a2ui-retrieval`** — were leaking 24 TypeScript source files alongside the emitted `.js` (a hangover from the Phase-4/5 TS migration). Now filtered via `!**/*.ts` + re-include `**/*.d.ts` in `files`.

### Recommended consumer migration (optional, non-breaking)

If you have a generative-UI surface and were using the verbose three-import pattern, collapse to the new subpath:

```diff
- import '@adia-ai/web-modules/runtime/a2ui-root/a2ui-root.js';
- import '@adia-ai/web-components/components/canvas/canvas.js';
- import { registerResolver } from '@adia-ai/a2ui-runtime';
+ import '@adia-ai/web-modules/generative';
+ import { registerResolver } from '@adia-ai/web-modules/generative';
```

No further changes needed; `<canvas-ui>` and `<a2ui-root>` register identically.

If you have an MCP server registration in `.claude/settings.json` / `.cursor/mcp.json`, **no change required**. Existing stdio registration continues to work and now picks up the new tools + sampling capability automatically.

### Verification gates added

- `node scripts/verify-pack.mjs` — validates tarball shape across all 9 packages (yaml/a2ui.json/d.ts count parity, no .ts source leaks, required artifacts present, CSS-only + child components allowlisted). Wired into the `npm run check` umbrella.
- `npm run smoke:mcp-extended` — three smokes covering HTTP transport initialize handshake, sampling capability detection, new-tool callability. Bundled into `npm run mcp:smoke`.

---

## Migrating to `@adia-ai/*@0.6.2` – `0.6.6` (2026-05-18)

**PATCH series.** No breaking changes. Upgrade is a drop-in from any `0.6.x`.

### New in `0.6.2` — TypeScript Phase 4

`@adia-ai/a2ui-retrieval` and `@adia-ai/a2ui-mcp` sources converted to TypeScript. TS-MIG-001 `skipLibCheck` override closed — all packages fully type-checked. No public API change.

### New in `0.6.3` – `0.6.4` — TypeScript Phase 5 (real runtime declarations)

`@adia-ai/a2ui-runtime` ships real `.d.ts` declarations replacing Phase 1 stub files. Includes:
- `A2UIMessage` discriminated union type (all 6 message kinds: `createSurface`, `updateComponents`, `updateDataModel`, `wireComponents`, `deleteSurface`, `meta`)
- `A2UIComponent` interface with full property set
- `a2ui.schema.json` (JSON Schema 2020-12) as the authoritative source; `a2ui.schema.d.ts` generated from it
- Branded ID types: `FragmentId`, `ChunkId`, `CompositionId` in `@adia-ai/a2ui-compose`
- Subpath exports: `@adia-ai/a2ui-runtime/schema` → `a2ui.schema.d.ts`; `@adia-ai/a2ui-runtime/types` → `types.d.ts`

TypeScript consumers writing custom compose strategies or MCP tools can now import the A2UI protocol types directly:

```ts
import type { A2UIMessage, A2UIComponent } from '@adia-ai/a2ui-runtime/schema';
```

### New in `0.6.5` — TypeScript Phase 6 sealed

All 6 TypeScript migration phases complete. `tsconfig.base.json` is now normative — zero untracked strict-flag overrides across all 9 packages. See `.claude/docs/specs/typescript-first-substrate.md` and ADR-0029.

### New in `0.6.6` — `<field-ui align>` prop

`<field-ui>` gains an `align` attribute (`start` | `end`, default `start`). `align="end"` right-aligns the label within its grid cell — useful in multi-field forms where labels should be flush to the control column:

```html
<!-- Default: label left-aligned -->
<field-ui label="Email"><input-ui type="email"></input-ui></field-ui>

<!-- Trailing-flush label alignment -->
<field-ui label="Email" align="end"><input-ui type="email"></input-ui></field-ui>
```

CSS-only; no layout structure change.

---

## Migrating to `@adia-ai/*@0.5.3` (2026-05-13)

**PATCH cut.** All 9 packages bumped `0.5.2 → 0.5.3`. Internal `@adia-ai/*` ranges **stay at `^0.5.0`** per PATCH-cut asymmetry. **No breaking changes.**

Headline response: **FEEDBACK-06 + FEEDBACK-07** from external consumers (Tokens Studio / color-app). Two complementary icon-registry fixes + the broader v0.5.3 cycle's quality + audit work.

### 🌟 Recommended icon-registration upgrade (FEEDBACK-06 §4 / FEEDBACK-07 §4 / §154)

Consumers who use `installIconLoaders` with a hand-curated brace-list can now switch to **`installIconLoadersForRegistered`** — auto-discovers the icon set from every primitive's `static requiredIcons` declaration. No more "I added a new component and got `warnMissingIcon` for an icon I didn't even author."

**Before (v0.5.2):**

```ts
import { installIconLoaders } from '@adia-ai/web-components/core/icons'
import '@adia-ai/web-components/components/select'  // needs caret-up-down internally
import '@adia-ai/web-components/components/tag'     // needs x for dismiss

installIconLoaders({
  regular: import.meta.glob(
    '/node_modules/@phosphor-icons/core/assets/regular/{caret-up-down,x,sun,moon}.svg',
    { query: '?raw', import: 'default', eager: true }
  ),
})
```

Pitfall: every time you add a primitive, you must figure out which icons it auto-stamps + extend the brace-list. Easy to miss (`<color-picker-ui>` needs `check`/`warning`/`copy`; `<select-ui>` needs `caret-up-down`; `<tag-ui>` needs `x`; etc.).

**After (v0.5.3):**

```ts
import { installIconLoadersForRegistered } from '@adia-ai/web-components/core/icons'
import '@adia-ai/web-components/components/select'
import '@adia-ai/web-components/components/tag'

// Builds the loader map by walking customElements + reading
// `static requiredIcons` on each defined primitive. Add or remove
// component imports + the icon set stays correct automatically.
installIconLoadersForRegistered({
  regular: import.meta.glob('/node_modules/@phosphor-icons/core/assets/regular/*.svg', {
    query: '?raw',
    import: 'default',
    eager: true,
  }),
});
```

The `static requiredIcons` declaration is a class-level array on every primitive that auto-stamps an `<icon-ui>` internally:

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

For consumers who prefer to keep a narrow brace-list (smallest bundle), the `discover()` helper returns the discovered list so you can build a tighter glob:

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

const required = installIconLoadersForRegistered.discover()
// required = ['caret-up-down', 'caret-right', 'x', 'check', 'warning', 'copy', ...]
```

### §140 — `resolveLoader` filename-suffix fallback

Loader-map keys no longer need to match the canonical `/node_modules/@phosphor-icons/core/assets/<weight>/<filename>` prefix. pnpm hoist (`/node_modules/.pnpm/@phosphor-icons+core@.../`), yarn berry PnP virtual paths, custom Vite roots, and manually-built loader maps now resolve correctly. Public API unchanged; relaxes lookup-key discipline only. **You don't need to do anything** — your existing `installIconLoaders(modules)` call will work regardless of key prefix.

### Other arcs in v0.5.3 (no consumer action)

- §142: smoke retrieval probes broadened post-§125 chunk refactor
- §143: 8 new UI-primitive corpus chunks (calendar, color-picker, slider, pagination, popover, avatar-group, video-player, +1) closing v0.5.2 §127's Haiku-vs-Opus coverage gap
- §144: yaml-events drift cleanup on 4 components
- §153: slider `.value` function-typed dev-mode guard (FEEDBACK-07 §3)
- §157: NEW `<responsive-shell-sidebar>` pattern (FEEDBACK-06 §3)
- §162: `check:yaml-events` audit false-positive fix + table row-collapse event declaration

### Recipe

```bash
npm install -E @adia-ai/web-components@0.5.3 \
  @adia-ai/web-modules@0.5.3 \
  @adia-ai/a2ui-runtime@0.5.3 \
  @adia-ai/a2ui-compose@0.5.3 \
  @adia-ai/a2ui-corpus@0.5.3 \
  @adia-ai/a2ui-mcp@0.5.3 \
  @adia-ai/a2ui-retrieval@0.5.3 \
  @adia-ai/a2ui-validator@0.5.3 \
  @adia-ai/llm@0.5.3
```

If you have a hand-curated `installIconLoaders` brace-list, consider switching to `installIconLoadersForRegistered` (see above). Otherwise drop-in upgrade.

---

## Migrating to `@adia-ai/*@0.5.2` (2026-05-13)

**PATCH cut.** All 9 packages bumped `0.5.1 → 0.5.2`. Internal `@adia-ai/*` ranges **stay at `^0.5.0`** per the PATCH-cut asymmetry — `^0.5.0` covers all `0.5.x` automatically; only minor cuts bump the floor.

**No breaking changes.** Audit-script family extended + small soft-API addition; consumers stay drop-in.

### Soft-API addition (`@adia-ai/a2ui-compose`)

`generateFreeFormAdapter(ctx)` return value now exposes `result.plan` as a top-level field (was previously read from `result._debug?.plan`). Internal consumers updated:

- `eval-diff.mjs` line 147: `const plan = result.plan || result._debug?.plan || null` → `result.plan || null` (the dead fallback is gone).

External consumers reading `result._debug?.plan` will get `undefined` after upgrading to `@adia-ai/a2ui-compose@0.5.2`. **Recommended migration**: read `result.plan` directly. Defensive optional chaining keeps the path safe either way.

### Audit-script family extensions

Two new pre-flight trip-wires added (slots 9 + 10 in the existing §72 / §81 / §83 / §84 / §98 / §100a / §102 / §115 family):

| Script | Closes |
|---|---|
| `npm run check:standalone-html-phosphor` | Standalone-HTML phosphor wiring drift (§107 bug class — `<icon-ui>` blank in iframed demo HTMLs) |
| `npm run check:substitutable-set-drift` | `SUBSTITUTABLE` set drift between `eval-diff.mjs` and `transpile.js` SoTs |

Both are wired into the `lockstep-release` SKILL Step 1 pre-flight. No consumer action required.

### Toast → feed facade soak verdict

Per the soak window (2026-05-01 → 2026-05-15), `<toast-ui>` facade was reviewed for stability. Verdict: **PASS-WITH-CAVEAT** (zero internal consumers exercised it in-window; facade is bug-free but data sparse). v0.6.0 will include a "toast-ui element deprecation review" gated on external-consumer audit. Until then: no consumer action; `<toast-ui>` element + `UIToast.show()` API both continue to work.

### `_debug.*` volatility contract

The `_debug` block on `generateFreeFormAdapter`'s return value is now formally documented as volatile / dialog-recorder-gated:

```js
const result = await generateFreeFormAdapter(ctx);

// ✓ Stable first-class fields (post-§109 / §107a)
result.usedIngredients   // array of chunk names
result.rationale         // LLM rationale text
result.plan              // LLM plan { ingredients: [...] }

// ⚠ Volatile, dialog-recorder-gated; do not depend on shape
result._debug?.attempts
result._debug?.warnings
result._debug?.systemPrompt
result._debug?.rawLLMResponse
result._debug?.tokens
```

The `_debug.*` block remains; only the API contract is documented as volatile. v0.6.0 schedules folding `attempts` + `warnings` into first-class fields.

### Recurring eval gate

`.github/workflows/free-form-eval-weekly.yml` now schedules monthly (`cron: '0 6 1 * *'`, 1st of each month at 06:00 UTC). Cost: ~$4/yr post-§118 Haiku-pin. Flip to weekly (`0 6 * * 1`) for ~$16/yr if higher cadence is needed.

### Recipe

Drop-in upgrade:

```bash
npm install -E @adia-ai/web-components@0.5.2 \
  @adia-ai/web-modules@0.5.2 \
  @adia-ai/a2ui-runtime@0.5.2 \
  @adia-ai/a2ui-compose@0.5.2 \
  @adia-ai/a2ui-corpus@0.5.2 \
  @adia-ai/a2ui-mcp@0.5.2 \
  @adia-ai/a2ui-retrieval@0.5.2 \
  @adia-ai/a2ui-validator@0.5.2 \
  @adia-ai/llm@0.5.2
```

If you read `_debug.plan` directly, switch to `result.plan` (one-line fix; optional chaining keeps old code safe).

---

## Migrating to `@adia-ai/*@0.5.1` (2026-05-13)

**PATCH cut.** All 9 packages bumped `0.5.0 → 0.5.1`. Internal `@adia-ai/*` ranges **stay at `^0.5.0`** per PATCH-cut asymmetry. **No breaking changes.**

### Soft-API graduations (`@adia-ai/a2ui-compose`)

§109 + §118 lifted two fields from `result._debug.*` to top-level on `generateFreeFormAdapter`:

```js
// v0.5.0 — fields read from _debug.* (silently undefined when not recording)
result._debug?.usedIngredients   // could be undefined
result._debug?.rationale          // could be undefined

// v0.5.1 — fields are first-class (always populated)
result.usedIngredients            // array of chunk names
result.rationale                  // LLM rationale text
```

The `_debug.*` block keeps `plan`, `attempts`, `warnings`, `systemPrompt`, `rawLLMResponse`, `tokens` (those gate on the recording state). External consumers reading `result._debug?.usedIngredients` / `result._debug?.rationale` will get `undefined` after upgrade — optional chaining handles this safely, but the recommended migration is to read the top-level fields.

Internal consumers updated (commit `8c07a007`):
- `apps/genui/app/factory-chat/factory-chat.contents.js:674`
- `apps/genui/app/gen-ui/_handlers/handleAuto.js:107`

### Free-form composer maturation (no consumer action)

- **Coverage**: 92% → 93% on full-100 held-out set (via §117 INTENT-PARAPHRASE block + paraphrase-retry on empty-plan)
- **F1**: 59 → 62 (via §107a/b ALWAYS-substitute prompt push + substitution-coverage measurement infra)
- **Model**: free-form ingredient picker now uses Claude Haiku 4.5 by default (was Opus). ~5× cheaper per intent (~$0.015 → ~$0.003). No quality regression expected. Empirical A/B deferred to v0.5.2 §127.
- **Corpus**: 10 new annotated chunks via `catalog/ui-patterns/v050-mode-c-blocks/`

### Audit-script family (no consumer action)

- §115: NEW `npm run check:free-form-eval-regression` — reads `eval-diff` output's free-form coverage/F1/avgScore, gates against the v0.5.1 baseline.
- §128: NEW `.github/workflows/free-form-eval-weekly.yml` — manual-only at v0.5.1 (schedule block commented); v0.5.2 flips to monthly cron.

### Recipe

```bash
npm install -E @adia-ai/web-components@0.5.1 @adia-ai/web-modules@0.5.1 \
  @adia-ai/a2ui-runtime@0.5.1 @adia-ai/a2ui-compose@0.5.1 @adia-ai/a2ui-corpus@0.5.1 \
  @adia-ai/a2ui-mcp@0.5.1 @adia-ai/a2ui-retrieval@0.5.1 @adia-ai/a2ui-validator@0.5.1 \
  @adia-ai/llm@0.5.1
```

If reading `_debug.usedIngredients` or `_debug.rationale`: switch to top-level. One-line fix per call site.

---

## Migrating to `@adia-ai/*@0.5.0` (2026-05-13)

**MINOR lockstep cut.** All 9 packages bumped `0.4.9 → 0.5.0`. Internal `@adia-ai/*` ranges bumped `^0.4.0` → `^0.5.0` (MINOR convention).

**No code-breaking changes.** First MINOR since the lockstep era began at v0.2.0; the graduation cut for the v0.4.x consumer-feedback arc.

### What v0.5.0 closed

- **5 rounds of FEEDBACK from Tokens Studio** (consumer ID: `colorapp`) — 27 total claims closed across v0.4.5–v0.5.0:
  - FEEDBACK-01: initial v0.4.x integration audit (8 claims)
  - FEEDBACK-02: color-picker constraint surface (7 claims)
  - FEEDBACK-03: package import surface + `core/icons.js` registry split (3 claims)
  - FEEDBACK-04: color-picker.d.ts P0 + isFocusInInput utility (7 claims)
  - FEEDBACK-05: `core/element.d.ts` runtime re-export mirror (1 claim)

- **Audit-script family complete on every known hand-authored drift class** — 7 trip-wires at v0.5.0: §72 + §81 + §83 + §84 + §98 + §100a + §102. v0.5.1 adds §115; v0.5.2 adds §128 + §135.

- **Free-form composer graduation** (cov 21% → 92% on full-100):
  - §93: layout-chunk regrowth (15 chunks)
  - §94: forms + data + navigation regrowth (~10 chunks per domain)
  - §95: picker promotion (free-form available alongside zettel + monolithic)
  - §97: threshold rebaseline against the honest 92% baseline
  - §103: auto-grouping (Column / Row / Grid root container per LLM-emitted `plan.layout`)
  - §104: 22 new annotated chunks across 6 catalog/ui-patterns/v050-*-blocks/ pages
  - §105: structural substitutions (Button.text / Badge.text / Tag.text / Icon.name / Image.alt / Link.href / Kbd.textContent)

### Soft-API additions (no breaking changes)

`generateFreeFormAdapter` (in `@adia-ai/a2ui-compose`) is the headline new strategy. It composes UI from the LLM-driven free-form picker against the 86-chunk corpus. Auto-engine routes intents to it as one of multiple strategies (zettel / free-form / monolithic).

If you previously called `generateAdapter(ctx)` directly: no change. The strategy registry handles routing.

### Recipe

```bash
npm install -E @adia-ai/web-components@0.5.0 @adia-ai/web-modules@0.5.0 \
  @adia-ai/a2ui-runtime@0.5.0 @adia-ai/a2ui-compose@0.5.0 @adia-ai/a2ui-corpus@0.5.0 \
  @adia-ai/a2ui-mcp@0.5.0 @adia-ai/a2ui-retrieval@0.5.0 @adia-ai/a2ui-validator@0.5.0 \
  @adia-ai/llm@0.5.0
```

For consumers on `^0.4.0`, this is a single `npm install` away (internal ranges bump from `^0.4.0` to `^0.5.0` per MINOR convention). No code changes required.

### Pre-v0.5.0 cleanup landed

- `<icon-ui>` registry split (§89, FEEDBACK-03): three barrels (`web-components/index.js` / `core/index.js` / `components/index.js`) auto-load `core/icons-phosphor.js`. Piecemeal-component consumers either import a barrel OR explicitly import `core/icons-phosphor` OR call `installIconLoaders()` with a scoped glob. Standalone-template demo HTMLs got phosphor wiring in v0.5.1 §107 (followed by a v0.5.2 §128 trip-wire to prevent the regression).

---

## Migrating to `@adia-ai/*@0.4.0` (2026-05-10)

**⚠️ First non-patch release in the 0.x line.** All 9 published `@adia-ai/*` packages bumped to `0.4.0` as a lockstep cut. Internal `@adia-ai/*` ranges bumped from `^0.3.0` to `^0.4.0`. The legacy shell authoring shapes (across all 3 shell hosts) are RETIRED. Consumers MUST use the bespoke vocabulary established by ADR-0023 in v0.3.4–v0.3.6.

**Migration window**: 9 days. Bespoke landed in `web-modules@0.3.4` (2026-05-10), all 6 in-repo consumers migrated in `web-modules@0.3.6` (2026-05-10), legacy retired in `0.4.0` (2026-05-10). Any consumer pinned to `0.3.x` had backwards-compat reads available for the entire transition window.

### Removed (BREAKING)

#### admin cluster — `<admin-shell>`
- `<aside-ui slot="leading|trailing">` — replaced by `<admin-sidebar slot="leading|trailing">`
- `<aside data-sidebar="leading|trailing">` — replaced by `<admin-sidebar slot="leading|trailing">`
- `<dialog data-command>` — replaced by `<admin-command>`
- `[data-resize]` external handle children — resize now internal to `<admin-sidebar>`
- Shell host no longer reads any of the legacy paths

#### chat cluster — `<chat-shell>`
- `<section data-chat-messages>` — replaced by `<chat-thread>`
- `<chat-input-ui data-chat-input>` — replaced by `<chat-composer>` wrapping `<chat-input-ui>`
- `<empty-state-ui data-chat-empty>` — replaced by `<chat-empty>` wrapping `<empty-state-ui>`
- `<header>` with `[data-chat-name]` / `[data-chat-status]` — replaced by `<chat-header>` with `<chat-status>`
- `<footer>` with `<chat-input-ui>` — replaced by `<chat-composer>`
- The shell only listens for `composer-submit` events now (was: `submit` + `composer-submit` dual)

#### editor cluster — `<editor-shell>`
- `<header>` toolbar — replaced by `<editor-toolbar>`
- `<div data-editor-body>` wrapper — dropped entirely (use direct grid children)
- `<pane-ui data-left|data-right>` — replaced by `<editor-sidebar slot="leading|trailing">` wrapping `<pane-ui resizable>`
- `<div data-canvas>` — replaced by `<editor-canvas>`
- `<footer>` statusbar — replaced by `<editor-statusbar>`

### Migration recipe per cluster

#### admin cluster diff
```diff
- <admin-shell>
-   <aside-ui slot="leading">                  ← or <aside data-sidebar="leading">
-     <header-ui>…</header-ui>
-     <section-ui>…</section-ui>
-     <footer-ui>…</footer-ui>
-     <div data-resize></div>
-   </aside-ui>
-   <main>…</main>
-   <dialog data-command>
-     <command-ui></command-ui>
-   </dialog>
- </admin-shell>
+ <admin-shell>
+   <admin-sidebar slot="leading" resizable collapsible>
+     <header-ui>…</header-ui>
+     <section-ui>…</section-ui>
+     <footer-ui>…</footer-ui>
+     <!-- resize handle now internal to <admin-sidebar> -->
+   </admin-sidebar>
+   <main>…</main>
+   <admin-command>
+     <command-ui></command-ui>
+   </admin-command>
+ </admin-shell>
```

#### chat cluster diff
```diff
- <chat-shell>
-   <header>
-     <span data-chat-name>Title</span>
-     <span data-chat-status></span>
-   </header>
-   <section data-chat-messages>
-     <empty-state-ui data-chat-empty>…</empty-state-ui>
-   </section>
-   <footer>
-     <chat-input-ui data-chat-input></chat-input-ui>
-   </footer>
- </chat-shell>
+ <chat-shell>
+   <chat-header>
+     <span slot="name">Title</span>
+     <chat-status slot="status"></chat-status>
+   </chat-header>
+   <chat-thread>
+     <chat-empty>
+       <empty-state-ui>…</empty-state-ui>
+     </chat-empty>
+   </chat-thread>
+   <chat-composer>
+     <chat-input-ui></chat-input-ui>
+   </chat-composer>
+ </chat-shell>
```

#### editor cluster diff
```diff
- <editor-shell>
-   <header>Toolbar</header>
-   <div data-editor-body>
-     <pane-ui data-left side="leading" resizable>Inspector</pane-ui>
-     <div data-canvas>Workspace</div>
-     <pane-ui data-right side="trailing" resizable>Properties</pane-ui>
-   </div>
-   <footer>Status</footer>
- </editor-shell>
+ <editor-shell>
+   <editor-toolbar>Toolbar</editor-toolbar>
+   <editor-sidebar slot="leading" collapsible>
+     <pane-ui side="leading" resizable>Inspector</pane-ui>
+   </editor-sidebar>
+   <editor-canvas>Workspace</editor-canvas>
+   <editor-sidebar slot="trailing" collapsible>
+     <pane-ui side="trailing" resizable>Properties</pane-ui>
+   </editor-sidebar>
+   <editor-statusbar>Status</editor-statusbar>
+ </editor-shell>
```

### JS selector migration

Queries against legacy data-attributes need to change:

```diff
- chat.querySelector('[data-chat-input]')      → chat.querySelector('chat-input-ui')
- chat.querySelector('[data-chat-messages]')   → chat.querySelector('chat-thread')
- chat.querySelector('[data-chat-empty]')      → chat.querySelector('empty-state-ui')
- chat.querySelector('[data-chat-status]')     → chat.querySelector('chat-status')
- document.querySelector('[data-canvas]')      → document.querySelector('editor-canvas')
- document.querySelector('[data-editor-body]') → (drop — bespoke layout doesn't need wrapper)
- document.querySelector('[data-sidebar="leading"]')  → document.querySelector('admin-sidebar[slot="leading"]')
- document.querySelector('[data-sidebar="trailing"]') → document.querySelector('admin-sidebar[slot="trailing"]')
```

### Trigger attributes that still work

These attributes are NOT removed — they still target the bespoke shell children:

- `[data-sidebar-toggle="leading|trailing"]` — buttons that toggle the matching `<admin-sidebar>` rail (forwards to `.toggle()`)
- `[data-command-trigger]` — buttons that open `<admin-command>` (forwards to `.show()`)
- `[data-toolbar-action="<name>"]` — buttons inside `<editor-toolbar>` that bubble `toolbar-action` events

### Sweep recipes

```bash
# 1. Find all legacy shell shape usages
git grep -nE '<aside-ui slot|<aside data-sidebar|<dialog data-command'
git grep -nE 'data-chat-messages|data-chat-input|data-chat-empty|data-chat-status|data-chat-name'
git grep -nE 'data-canvas[^-]|data-editor-body|<pane-ui data-(left|right)'

# 2. Mechanical Perl rewrite for chat (review before applying)
perl -i.bak -pe '
  s/<section data-chat-messages>/<chat-thread>/g;
  s|</section>(\s*<!--.*chat-messages.*-->)?|</chat-thread>|g;
  s/<chat-input-ui data-chat-input/<chat-composer><chat-input-ui/g;
  s|</chat-input-ui>(\s*</footer>)|</chat-input-ui></chat-composer>$1|g;
  s/data-chat-empty/<chat-empty>/g;
  s/data-chat-status//g;
' apps/**/*.html

# 3. Verify with the smoke probe (Playwright-based)
npm run smoke:consumers
```

### What to test after migrating

The repo includes a Playwright probe (`scripts/smoke-consumer-migrations.mjs`, wired to `npm run smoke:consumers`) that verifies bespoke-only markup renders correctly. Run it after each consumer migrates. The probe:

1. Loads each migrated URL in headless Chromium
2. Verifies expected bespoke elements appear in DOM (e.g., `chat-thread`, `chat-composer`, `chat-empty`)
3. Watches for console errors
4. Watches for HTTP failures

Non-zero exit on regression. Used internally for the 6 in-repo consumers — passed 6/6 at v0.4.0.

### Architectural payoff (information for downstream consumers)

If you maintain your own shell-style components in your fork or extension:
- `<admin-shell>` host: 305 → **87 LOC** (-71%) at v0.4.0
- The bespoke pattern moves resize / collapse / persistence / dialog behavior INTO each child. The host coordinates only.
- See [`bespoke-shell-children` skill](../../.agents/skills/adia-author/SKILL.md) for the canonical decomposition recipe.

### Don't roll back

Once on `0.4.0`, the legacy paths cannot be reactivated by config — they're physically removed from the source. Pin to `0.3.x` if you need legacy behavior; commit to bespoke if you upgrade.

---

## Author-error sweep — `<grid-ui cols="N">` was never functional

`grid-ui` has only ever accepted `columns="N"` (see [`packages/web-components/components/grid/grid.js:12`](../../packages/web-components/components/grid/grid.js#L12)). Pages authored with `cols=` were silently rendering as the default 3-column grid — the attribute was ignored. Sweep:

```bash
git grep -nlE '<grid-ui[^>]*\bcols=' \
  | xargs perl -i -pe 's/(<grid-ui[^>]*?)\bcols=/$1columns=/g'
```

Then re-run `npm run harvest:chunks` if any of the touched pages live under `site/pages/` and are tagged with `data-chunk` (extracts to `packages/a2ui/corpus/chunks/`).

---

## Migrating to `@adia-ai/web-components@0.0.29` + `@adia-ai/web-modules@0.0.1` (2026-05-01)

**The first BREAKING release since `0.0.20`.** All composite custom
elements (the former `patterns/` directory) move out of
`@adia-ai/web-components` into a new sibling package
`@adia-ai/web-modules`, organized into clusters via subpath exports.
Decision in `ADR-0012`;
full session narrative in `.claude/docs/journal/2026/05/2026-05-01.md` §18.

**Install:**

```diff
- npm install @adia-ai/web-components@^0.0.20
+ npm install @adia-ai/web-components@^0.0.29
+ npm install @adia-ai/web-modules@^0.0.1
```

`web-modules` has a peer-dep on `>=0.0.28` of `web-components` — the
two install together. If you don't use any composite element
(app-shell, app-nav, adia-chat, adia-editor, gen-ui, a2ui-root)
you can stay on web-components alone.

### Three-tier architecture

```
@adia-ai/web-components   primitives (button, table, card, …)
@adia-ai/web-modules      composites (shell + chat + editor + runtime)
@adia-ai/web-themes       (future) token sets per vendor / brand
```

Lateral siblings — `web-modules` and `web-themes` peer-depend
`web-components` only. No cross-tier imports above primitives;
composition happens at your site, not inside the library.

### What moved (cluster mapping)

| Cluster | Elements (current per ADR-0015 / 0.0.31+) |
|---|---|
| `shell` | `<admin-shell>` (nav primitives below live in `web-components`) |
| `chat` | `<chat-shell>` |
| `editor` | `<editor-shell>` |
| `runtime` | `<gen-root>`, `<a2ui-root>` |

> **Update — 0.0.31 (ADR-0015 § Element renames).** Module tags moved
> to the `<namespace-thing>` two-word convention: `<adia-chat-ui>` →
> `<chat-shell>`; `<adia-editor-ui>` → `<editor-shell>`; `<app-shell-ui>`
> → `<admin-shell>`; `<gen-ui-ui>` → `<gen-root>`. Cluster directories
> + class names + CSS sub-files renamed in lockstep.

> **Update — 0.0.33 (ADR-0015 § Nav consolidation).** The 6-element
> nav family (`<app-nav-ui>`, `<app-nav-group-ui>`, `<app-nav-item-ui>`,
> `<section-nav-ui>`, `<section-nav-group-ui>`, `<section-nav-item-ui>`)
> consolidated into 3 primitives in `web-components` —
> `<nav-ui>`, `<nav-group-ui>`, `<nav-item-ui>` — with
> `[variant="primary"|"section"]` driving visual treatment. Section-
> variant migration: `<section-nav-ui …>` → `<nav-ui variant="section" …>`.
> The old per-element CSS link rels under
> `web-modules/shell/{app-nav*,section-nav*}/` are gone — nav CSS now
> ships in `@adia-ai/web-components/css`.

**Tags, CSS class names, CSS variables, yaml + a2ui.json contracts
for non-nav modules — apart from the ADR-0015 renames listed above —
are unchanged.** Beyond the rename map + nav consolidation, only the
import path moves.

### Migration mapping (deep imports)

```diff
# JS — every cluster, individual element
- import '@adia-ai/web-components/patterns/app-shell/app-shell.js';
+ import '@adia-ai/web-modules/shell/app-shell/app-shell.js';

- import { AdiaA2UIRoot } from '@adia-ai/web-components/patterns/a2ui-root/a2ui-root.js';
+ import { AdiaA2UIRoot } from '@adia-ai/web-modules/runtime/a2ui-root/a2ui-root.js';

- import '@adia-ai/web-components/patterns/adia-chat/adia-chat.js';
+ import '@adia-ai/web-modules/chat/adia-chat/adia-chat.js';
```

```diff
<!-- CSS — site/<link rel="stylesheet"> tags -->
- href="node_modules/@adia-ai/web-components/patterns/app-shell/app-shell.css"
+ href="node_modules/@adia-ai/web-modules/shell/app-shell/app-shell.css"

- href="node_modules/@adia-ai/web-components/patterns/adia-chat/adia-chat.css"
+ href="node_modules/@adia-ai/web-modules/chat/adia-chat/adia-chat.css"
```

**Idiomatic — import the cluster, let the bundler tree-shake:**

```js
import '@adia-ai/web-modules';            // every cluster (barrel)
import '@adia-ai/web-modules/shell';      // app-shell + nav family
import '@adia-ai/web-modules/chat';       // adia-chat
import '@adia-ai/web-modules/editor';     // adia-editor
import '@adia-ai/web-modules/runtime';    // gen-ui + a2ui-root
```

### What was removed from `@adia-ai/web-components`

```diff
# package.json — exports
-   "./patterns":     "./patterns/index.js",
-   "./patterns/*":   "./patterns/*/*.js",

# package.json — files
-     "patterns/",

# package.json — sideEffects
-     "./patterns/**/*.js",

# index.js
- export * from './patterns/index.js';
```

Plus deletion of `packages/web-components/styles/layouts/admin.css`
(orphaned shell-CSS aggregator with no active consumers; would have
created a reverse-dep CSS-import edge from web-components into
web-modules).

### `import '@adia-ai/web-components'` no longer registers patterns

The barrel side-effect dropped:

```diff
# Before — barrel registered every primitive AND every pattern
- import '@adia-ai/web-components';

# After — barrel registers primitives only; modules are explicit
+ import '@adia-ai/web-components';        // primitives
+ import '@adia-ai/web-modules';           // every cluster
```

If you were relying on the `0.0.28` barrel registering
`<admin-shell>` etc. for free, add the corresponding
`web-modules` import.

### Sweep recipes

```bash
# Find every deep-import of patterns/
git grep -nE "@adia-ai/web-components/patterns/"

# Find every CSS link href referencing patterns/
git grep -nE "web-components/patterns/[^\"']+\.css"

# Mechanical Perl rewrite (review before applying):
perl -i -pe 's{\@adia-ai/web-components/patterns/(app-shell\|app-nav(?:-group\|-item)?\|section-nav(?:-group\|-item)?)/}{\@adia-ai/web-modules/shell/$1/}g' $(git grep -lE "@adia-ai/web-components/patterns/")
perl -i -pe 's{\@adia-ai/web-components/patterns/(adia-chat)/}{\@adia-ai/web-modules/chat/$1/}g'    $(git grep -lE "@adia-ai/web-components/patterns/")
perl -i -pe 's{\@adia-ai/web-components/patterns/(adia-editor)/}{\@adia-ai/web-modules/editor/$1/}g' $(git grep -lE "@adia-ai/web-components/patterns/")
perl -i -pe 's{\@adia-ai/web-components/patterns/(a2ui-root\|gen-ui)/}{\@adia-ai/web-modules/runtime/$1/}g' $(git grep -lE "@adia-ai/web-components/patterns/")
```

### Stay on `0.0.28` to defer

The [`0.0.28`](https://github.com/adiahealth/gen-ui-kit/releases/tag/web-components-v0.0.28)
snapshot still ships `patterns/` in-place. If the migration window
isn't yours, pin to `0.0.28` and migrate when convenient — the
0.0.28 → 0.0.29 path is purely import-rewrite, no semantic deltas.

### Additive features bundled in `0.0.29`

Three additive features ride along (no migration required):

- `<feed-item-ui>[action]` — focus-trap + `role="alertdialog"`. Phase 2 of [`feed-channel.md`](../../.claude/docs/specs/feed-channel.md).
- `<editor-shell>` (formerly `<adia-editor-ui>`) Phase C — multi-select + localStorage clipboard.
- Construct Canvas adapter-preview tabs (OD-001).
- `<toast-ui>` is now a thin facade over `<feed-ui>` — API surface preserved; small visual delta on lane styling, themable via `--feed-item-*`.

---

## Migrating to `@adia-ai/web-components@0.0.20` (2026-04-27)

The biggest BREAKING release in the `0.0.x` line. Ten attribute / event surface changes across the catalog, plus a Safari-floor raise. Full release narrative in `.claude/docs/journal/2026/04/2026-04-27.md` §§5–13. ADR for the Safari floor: `ADR-0007`.

**Install:**

```diff
- npm install @adia-ai/web-components@^0.0.10
+ npm install @adia-ai/web-components@^0.0.20
```

The npm publish is gated on the visual-smoke runbook (`.brain/archive/2026-Q2/visual-smoke-polyfill-cleanup-2026-04-27.md`) — check whether `0.0.20` is on the registry before pinning.

### Browser baseline

```diff
- Chromium 125+ / Safari 17.4+ / Firefox 129+
+ Chromium 125+ / Safari 18.0+ / Firefox 129+
```

Safari raised by one major. Closes two engine-level WebKit bugs:

- `color-mix(in oklch, …, transparent)` red-shift below Safari 18 (the transparent operand's hue resolves to 0).
- iOS Safari `popover="auto"` light-dismiss broken on 17.0–18.2 (residual on iOS 18.0–18.2 documented as a known quirk).

If your target audience still includes Safari 17.4–17.7, the `core/polyfills.js` opt-in loader remains for consumers extending below the floor. See the live [breaking-changes reference](https://ui-kit.exe.xyz/site/reference/breaking-changes) for the full migration path.

### 1. `<button-ui>` — variant axis split (variant=danger removed)

`variant` is now style-only across the catalog; semantics ride a new `[color]` attribute. The two compose: `<button-ui variant="solid" color="danger">` is "filled destructive button"; `<button-ui variant="outline" color="success">` is "outlined success button".

```diff
- <button-ui variant="danger" text="Delete" icon="trash"></button-ui>
+ <button-ui variant="solid" color="danger" text="Delete" icon="trash"></button-ui>
```

Sweep:
```bash
# Find all uses
git grep -nE 'button-ui[^>]*variant="danger"'
# Migration (Perl in-place)
perl -i -pe 's/(<button-ui[^>]*?)variant="danger"/$1color="danger"/g' path/to/file.html
```

`progress-row-ui` `variant="primary"` is renamed to `variant="accent"` in the same release — no consumers in the wild, but if you hand-set the variant, swap.

`<button-ui>` accepts the full `[color]` enum: `default | accent | info | success | warning | danger`. Demos at [/site/components/button](https://ui-kit.exe.xyz/site/components/button).

### 2. Stage-progress — single `status` enum replaces multi-Boolean

Three components now expose `status="idle | active | completed | error"` instead of legacy Boolean attributes. Mutually-exclusive states prevent the `active && completed` footgun.

| Component | Before | After |
|---|---|---|
| [`<timeline-item-ui>`](https://ui-kit.exe.xyz/site/components/timeline) | `[completed]`, `[active]`, `[error]` | `status="completed | active | error | idle"` |
| [`<stepper-item-ui>`](https://ui-kit.exe.xyz/site/components/stepper) | `[completed]`, `[active]` | `status="…"` |
| [`<pipeline-status-ui>`](https://ui-kit.exe.xyz/site/components/pipeline-status) | `[complete]` (legacy spelling — `complete`, not `completed`) | `status="completed"` |

```diff
- <timeline-item-ui completed text="Submitted"></timeline-item-ui>
- <timeline-item-ui active text="Processing"></timeline-item-ui>
- <timeline-item-ui error text="Failed"></timeline-item-ui>
+ <timeline-item-ui status="completed" text="Submitted"></timeline-item-ui>
+ <timeline-item-ui status="active" text="Processing"></timeline-item-ui>
+ <timeline-item-ui status="error" text="Failed"></timeline-item-ui>
```

`<timeline-item-ui spinner>` is **retained** — it's a presentation hint that animates the active dot as a spinning ring; orthogonal to `status`. Combine: `<timeline-item-ui status="active" spinner>`.

Sweep:
```bash
git grep -nE '<timeline-item-ui[^>]*\b(completed|active|error)\b'
git grep -nE '<stepper-item-ui[^>]*\b(completed|active)\b'
git grep -nE '<pipeline-status-ui[^>]*\bcomplete\b'
```

### 3. `<table-toolbar-ui>` Boolean affordances inverted

The four flags `filterable`, `sortable`, `columns`, `searchable` defaulted to `true` — unrepresentable in HTML's attribute model (authors couldn't turn the affordance OFF via attribute). Renamed to opt-out form.

| Before (default `true`) | After (default `false`) |
|---|---|
| `filterable` | `noFilter` (attr: `no-filter`) |
| `sortable` | `noSort` (attr: `no-sort`) |
| `columns` | `noColumns` (attr: `no-columns`) |
| `searchable` | `noSearch` (attr: `no-search`) |

```diff
- <table-toolbar-ui for="users"></table-toolbar-ui>
  <!-- Bare element — same visual; all four affordances shown by default -->
+ <table-toolbar-ui for="users"></table-toolbar-ui>

- <table-toolbar-ui for="users" searchable="false"></table-toolbar-ui>
  <!-- Explicit suppression — the legacy form was always-truthy -->
+ <table-toolbar-ui for="users" no-search></table-toolbar-ui>
```

Note: bare `<table-toolbar-ui>` renders identically to before. The migration only matters if you were explicitly hiding affordances — `searchable="false"` was always-truthy under the old contract anyway, so it likely was rendering them all.

### 4. `<chat-input-ui>` `[busy]` → `[loading]`

```diff
- <chat-input-ui busy></chat-input-ui>
+ <chat-input-ui loading></chat-input-ui>
```

```diff
- chatInput.busy = true;
+ chatInput.loading = true;
```

`busy` was the only component in the catalog using that name for the in-flight / streaming state — every other component uses `loading`. Direct rename.

### 5. `error` variant alias removed (toast / alert / tag)

```diff
- <toast-ui variant="error" text="Save failed"></toast-ui>
+ <toast-ui variant="danger" text="Save failed"></toast-ui>

- <alert-ui variant="error">…</alert-ui>
+ <alert-ui variant="danger">…</alert-ui>

- <tag-ui variant="error" text="Failed"></tag-ui>
+ <tag-ui variant="danger" text="Failed"></tag-ui>
```

Affects [`<toast-ui>`](https://ui-kit.exe.xyz/site/components/toast), [`<alert-ui>`](https://ui-kit.exe.xyz/site/components/alert), [`<tag-ui>`](https://ui-kit.exe.xyz/site/components/tag). `error` was a deprecated alias for `danger` since `0.0.x` — `0.0.20` removes it.

### 6. Event-name renames — drop the prefix

```diff
- chat.addEventListener('chat-submit', (e) => { … });
+ chat.addEventListener('submit', (e) => { … });

- legend.addEventListener('legend-toggle', (e) => { … });
+ legend.addEventListener('toggle', (e) => { … });

- swiper.addEventListener('slide-change', (e) => { … });
+ swiper.addEventListener('change', (e) => { … });
```

Detail shape is identical for all three. Rule of thumb in `0.0.20`: components dispatch canonical short verbs (`change`, `toggle`, `submit`, `select`, `press`, etc.) when one fits the host's semantic. Prefixed names (`chart-hover`, `cell-click`, `noodle-connect`, `feedback-rate`, etc.) are retained when the event carries domain-specific payload that isn't covered by the canonical verb. See [`component-token-contract.md` §"Event-naming convention"](../../.claude/docs/specs/component-token-contract.md).

If you compose `<chart-ui>` + `<chart-legend-ui>` and the chart's internal listener was wiring `legend-toggle`, no consumer change needed — the chart's listener migrated too. The break only affects consumer code listening directly.

### 7. `<field-ui>.error` removed (read child's error)

The validation message belongs to the input, not the wrapper. `field-ui` now reads its slotted control's `error` property and mirrors into its `[data-field-error]` slot — `AdiaFormElement.error` is the single source of truth.

```diff
- <field-ui label="Email" error="Invalid format">
-   <input-ui type="email"></input-ui>
- </field-ui>
+ <field-ui label="Email">
+   <input-ui type="email" error="Invalid format"></input-ui>
+ </field-ui>
```

Field subscribes to the child's `invalid | change | input` events and keeps its message in sync. Programmatic API: set `inputEl.error = "…"`.

### 8. `<agent-trace-ui>` `[open]` → `[collapsed]`

Default-visibility flipped. Bare `<agent-trace-ui>` now shows expanded by default, matching the rest of the agent-* family ([`<agent-reasoning-ui>`](https://ui-kit.exe.xyz/site/components/agent-reasoning), [`<agent-artifact-ui>`](https://ui-kit.exe.xyz/site/components/agent-artifact)).

```diff
- <agent-trace-ui open></agent-trace-ui>   <!-- was needed to expand -->
+ <agent-trace-ui></agent-trace-ui>          <!-- expanded by default -->

- <agent-trace-ui></agent-trace-ui>          <!-- was hidden -->
+ <agent-trace-ui collapsed></agent-trace-ui> <!-- explicit hide -->
```

Event payload also renamed: `trace-toggle` events now carry `{ collapsed }` instead of `{ open }` in the detail.

### 9. agent-questions-ui / agent-feedback-bar-ui — kebab-string → camelCase prop keys

If you were setting these props programmatically, the JS access form changed:

```diff
- el['submit-label'] = 'Continue';
+ el.submitLabel = 'Continue';

- el['save-label'] = 'Bookmark';
- el['save-icon'] = 'star';
+ el.saveLabel = 'Bookmark';
+ el.saveIcon = 'star';
```

The HTML attribute name is unchanged (`<agent-questions-ui submit-label="Continue">` still works). Only the JS property name flipped to canonical camelCase.

### 10. New `[color]` axis on `<button-ui>` — composes with `[variant]`

Not a break, but pairs with §1. `<button-ui>` now accepts `[color="default | accent | info | success | warning | danger"]` alongside `[variant]`. The two compose orthogonally — see [§ Variants and color](https://ui-kit.exe.xyz/site/components/button) for the full matrix.

### Quick search-and-replace summary

```bash
# 1. button variant=danger → color=danger (sweep HTML)
git grep -nlE 'button-ui[^>]*variant="danger"' \
  | xargs perl -i -pe 's/(<button-ui[^>]*?)variant="danger"/$1color="danger"/g'

# 2. timeline-item-ui Booleans → status enum
git grep -nlE '<timeline-item-ui[^>]*\bcompleted\b' \
  | xargs perl -i -pe 's/(<timeline-item-ui[^>]*?)\bcompleted\b/$1status="completed"/g'
git grep -nlE '<timeline-item-ui[^>]*\bactive\b' \
  | xargs perl -i -pe 's/(<timeline-item-ui[^>]*?)\bactive\b/$1status="active"/g'
git grep -nlE '<timeline-item-ui[^>]*\berror\b' \
  | xargs perl -i -pe 's/(<timeline-item-ui[^>]*?)\berror\b/$1status="error"/g'

# 3. stepper-item-ui similarly
git grep -nlE '<stepper-item-ui[^>]*\bcompleted\b' \
  | xargs perl -i -pe 's/(<stepper-item-ui[^>]*?)\bcompleted\b/$1status="completed"/g'

# 4. pipeline-status-ui complete → status=completed (note spelling)
git grep -nlE '<pipeline-status-ui[^>]*\bcomplete\b' \
  | xargs perl -i -pe 's/(<pipeline-status-ui[^>]*?)\bcomplete\b/$1status="completed"/g'

# 5. event listeners
git grep -nE "addEventListener.*'chat-submit'" \
  | xargs perl -i -pe "s/'chat-submit'/'submit'/g"
git grep -nE "addEventListener.*'legend-toggle'" \
  | xargs perl -i -pe "s/'legend-toggle'/'toggle'/g"
git grep -nE "addEventListener.*'slide-change'" \
  | xargs perl -i -pe "s/'slide-change'/'change'/g"

# 6. error → danger on toast/alert/tag (one component at a time so other variant=error don't collide)
git grep -nlE '<toast-ui[^>]*variant="error"' \
  | xargs perl -i -pe 's/(<toast-ui[^>]*?)variant="error"/$1variant="danger"/g'
git grep -nlE '<alert-ui[^>]*variant="error"' \
  | xargs perl -i -pe 's/(<alert-ui[^>]*?)variant="error"/$1variant="danger"/g'
git grep -nlE '<tag-ui[^>]*variant="error"' \
  | xargs perl -i -pe 's/(<tag-ui[^>]*?)variant="error"/$1variant="danger"/g'

# 7. chat-input busy → loading
git grep -nlE '<chat-input-ui[^>]*\bbusy\b' \
  | xargs perl -i -pe 's/(<chat-input-ui[^>]*?)\bbusy\b/$1loading/g'
git grep -nlE 'chatInput\.busy' \
  | xargs perl -i -pe 's/chatInput\.busy/chatInput.loading/g'

# 8. agent-trace open → no attr (default visible) OR collapsed
#    Manual review: default-visible is the new default; consumers that wrote
#    <agent-trace-ui open> get the same visual now without the attr; consumers
#    that wrote bare <agent-trace-ui> wanting it hidden need [collapsed].
git grep -nE '<agent-trace-ui'

# 9. avatar-ui name= → text= (deprecated alias, scheduled for removal)
#    `name=` still works in 0.0.20+ but fires a one-shot console warning
#    per page. Sweep before the next major to avoid the upcoming break.
git grep -nlE '<avatar-ui[^>]*\bname=' \
  | xargs perl -i -pe 's/(<avatar-ui[^>]*?\s)name=/\1text=/g'
```

The `adia-ui-migration` skill in `.claude/skills/adia-ui-migration/` automates these sweeps and runs the verification gate after.

### Visual changes (no API change)

These are not breaks but are user-visible if you upgrade:

- **Shadows now paint.** `--a-shadow-{sm,md,lg}` were silently `none` for every consumer (`tooltip`, `card`, `drawer`, `segmented`, `chart`, `toast`) due to invalid CSS color/alpha syntax. Switched to relative-color form. Visual change wherever shadows are referenced — soft 4-layer Material-style lift now visible.
- **`--a-chrome-light` / `--a-chrome-dark` are scheme-aware.** Were absolute `oklch(1 0 0)` / `oklch(0 0 0)`; now resolve via `--a-neutral-0-tint` / `--a-neutral-0-shade` so dark mode flips polarity correctly. If you read these in a custom theme override, the value resolves slightly differently in dark mode.
- **Code-editor selection / active-line** no longer red-shifts on Safari < 18.
- **`<swiper-ui>`** no longer renders double pagination dots on Chrome 135+.
- **`<input-ui>` icon-affixes** no longer render as literal text on non-Vite static deploys.

---

## Migrating from `@adia-ai/web-components@0.0.4` to `0.0.10` / `0.0.11`

`0.0.5` through `0.0.11` were additive polish + hotfix releases. No migration needed — upgrade by bumping the dependency version. New components and tokens added; existing surface unchanged. See [packages/web-components/CHANGELOG.md](../../packages/web-components/CHANGELOG.md) for per-version detail.

---

## Migrating from `@adia-ai/web-components@0.0.2` to `0.0.4` (2026-04-22)

The A2UI runtime (renderer, registry, streams, surface manifest, wiring
primitives, dockable base classes, controllers) was extracted into its
own package `@adia-ai/a2ui-utils`. The `<a2ui-root>` declarative custom
element moved to the `patterns/` directory. `0.0.3` (web-components)
and `0.0.1` (a2ui-utils) shipped this split but were broken — the
lazy controller imports in `wiring-registry.js` couldn't resolve
because the `controllers/` subdir still lived under `web-components/`.
`0.0.4` / `0.0.2` ship the fix (controllers moved into `a2ui-utils`,
imports retargeted). **Upgrade past `0.0.3` / `0.0.1` if you're on
them; any surface declaring a controller 500's on the broken cut.**

**Install the new leaf package:**

```diff
- npm install @adia-ai/web-components@^0.0.2
+ npm install @adia-ai/web-components@^0.0.4 @adia-ai/a2ui-utils@^0.0.2
```

(`@adia-ai/web-components@0.0.4` declares `@adia-ai/a2ui-utils` as a
runtime dependency, so `npm install @adia-ai/web-components@^0.0.4`
alone also pulls `a2ui-utils` transitively. The explicit dep is still
recommended for consumers importing from it directly.)

**Runtime imports:**

```diff
- import { A2UIRenderer, registry, sseStream, wsStream } from '@adia-ai/web-components/a2ui';
+ import { A2UIRenderer, registry, sseStream, wsStream } from '@adia-ai/a2ui-utils';

- import { SurfaceManifest, WiringEngine } from '@adia-ai/web-components/a2ui';
+ import { SurfaceManifest, WiringEngine } from '@adia-ai/a2ui-utils';

- import { ActionDock, A2UI_EVENT_TO_DOM } from '@adia-ai/web-components/a2ui';
+ import { ActionDock, A2UI_EVENT_TO_DOM } from '@adia-ai/a2ui-utils';
```

**`<a2ui-root>` custom element:**

```diff
- import { AdiaA2UIRoot } from '@adia-ai/web-components/a2ui';
+ import { AdiaA2UIRoot } from '@adia-ai/web-components/patterns';
  // or
  import '@adia-ai/web-components/patterns/a2ui-root/a2ui-root.js';
```

Markup is unchanged:

```html
<a2ui-root src="/api/stream" transport="sse"></a2ui-root>
```

**Softened break.** The old `@adia-ai/web-components/a2ui` subpath
still resolves in `0.0.4` via a re-export shim that prints a
one-time deprecation warning. The subpath will be removed in `0.1.0`;
update imports at your convenience before then.

**Removed.** `ManifestRuntime` and the `./context-store` subpath were
dropped — the implementations depended on a `context-store.js` module
that had never been authored. The context-store surface will return
in a later release once designed.

---

## Within `0.0.x` — additive polish (0.0.4 → 0.0.5 → 0.0.6 → 0.0.7 → 0.0.8 → 0.0.9 → 0.0.10)

No migration required. Each release adds new authoring primitives
alongside the existing ones; legacy hooks stay supported. Upgrade by
bumping the dependency version.

- **`0.0.5`** — `app-shell-ui` adopts the `icon|heading|description|action`
  slot contract shared with `card-ui` / `drawer-ui` / `adia-editor-ui`.
  `pane-ui` gains a `side="leading|trailing"` attribute for one-edge
  border + grabber + resize-direction flip. `--a-chrome-{app,pane}-
  {header,footer,toolbar}-height` design tokens added. Internal
  `--nav-item-child-*` tokens relocated into `app-nav-group`'s own
  `@scope`. `pane-ui` section-content left-edge drift fixed.
- **`0.0.6`** — `slot="action-leading"` added on `drawer-ui` /
  `card-ui` / `adia-editor-ui` / `app-shell-ui` footers. Pairs with
  the existing `slot="action"` to produce a dual-cluster footer
  (destructive / secondary cluster on the leading edge, primary
  cluster on the trailing edge):

  ```diff
    <footer slot="footer">
  -   <button-ui text="Delete" variant="danger" />
  -   <span data-spacer />
  -   <button-ui text="Cancel" />
  -   <button-ui text="Save" variant="primary" />
  +   <span slot="action-leading">
  +     <button-ui text="Delete" variant="danger" />
  +   </span>
  +   <span slot="action">
  +     <button-ui text="Cancel" />
  +     <button-ui text="Save" variant="primary" />
  +   </span>
    </footer>
  ```

  `<span data-spacer>` stays supported for non-slotted toolbars
  (e.g. `toolbar-ui`, `row-ui` inside a filter-bar) where the
  footer-slot vocabulary doesn't apply. Migrate at your own pace.

- **`0.0.7`** — `<a2ui-root>` gains a `doc` property so authors can
  drive the renderer from a static message array without opening a
  transport. A new `doc-replaced` event fires after each full
  reset + replay:

  ```js
  const root = document.querySelector('a2ui-root');
  root.doc = [
    { type: 'createSurface', surfaceId: 'root', root: 'root' },
    { type: 'updateComponents', surfaceId: 'root', components: [
      { id: 'root', component: 'Heading', text: 'Hi' }
    ]},
  ];
  ```

  Existing stream-based callers (`src` + `transport`) are unaffected.
  Also in `0.0.7`: `modal-ui` silently wiped author content on connect
  in every release 0.0.2–0.0.6 — `0.0.7` fixes this. If you saw empty
  modal bodies, upgrade.

- **`0.0.8`** — hotfix + compat. `0.0.7` introduced a bad `typeof
  import.meta.glob === 'function'` guard in `icons.js` that always
  evaluated false under Vite; every icon disappeared in dev. `0.0.8`
  swaps to try/catch (Vite's AST rewrites the calls to literals, so
  the try succeeds; static serving falls back to a manifest loader).
  Also lands browser-compat guards: try/catch around `popover-ui`'s
  `showPopover`/`hidePopover`, `typeof X === 'undefined'` guards on
  `ResizeObserver` / `IntersectionObserver` traits, and a
  `replaceChildren()` polyfill for Safari 14.0. **Upgrade from 0.0.7
  to restore icons in dev.**

- **`0.0.9`** — follow-up hotfix for static deploys. On non-Vite
  hosts, `icons.js` initialises `EMPTY_WEIGHTS` synchronously and
  async-loads the icon manifest. `icon-ui` elements that rendered
  during that gap silently gave up when `resolveLoader` returned
  null — the manifest arrived ~100ms later but nothing re-requested
  the icons, so they stayed blank. `0.0.9` walks every
  `icon-ui[name]` in the DOM after the manifest swap-in and
  re-requests each. Vite dev was unaffected. **Upgrade if you host
  the library on plain static serving (exe.dev, Cloudflare Pages,
  Vercel static, etc.).**

- **`0.0.10`** — two targeted fixes. `AdiaElement` now wraps
  `this.connected()` in `untracked(...)` so element setup can't leak
  reactive subscriptions into the ancestor effect that caused the
  appendChild. Symptom this resolves: any time a parent effect
  `appendChild`s a custom element whose `connected()` reads its own
  reactive properties (via template-literal interpolation), the
  ancestor effect was silently subscribed to the child's signals —
  child state changes would then re-trigger the parent. Most
  visibly this made the Inspector in the A2UI Editor destroy + rebuild
  on every keystroke, blurring the focused input. Second fix:
  `A2UIRenderer` now honors `createSurface.root` (was hard-coding the
  attach id to `'root'`); the fallback preserves the old behavior
  for docs without an explicit root field. **Upgrade if you compose
  custom elements dynamically inside reactive render loops, or if
  you author A2UI docs with non-'root' root ids.**

---

## 1. Installation & Imports

```diff
- npm install @agent-ui-kit/web-components
+ npm install @adia-ai/web-components
```

```diff
// CSS — use the `/css` subpath export (resolves to index.css)
- import "@agent-ui-kit/web-components/dist/agent-ui.css";
+ import "@adia-ai/web-components/css";

// JS (register all components)
- import "@agent-ui-kit/web-components";
+ import "@adia-ai/web-components";

// Core element — the base class is AdiaElement (not AgentElement)
- import { AgentElement } from "@agent-ui-kit/web-components/element";
+ import { AdiaElement } from "@adia-ai/web-components/core";

// Traits — no dedicated subpath export; importing the root registers all
- import "@agent-ui-kit/web-components/traits";
+ import "@adia-ai/web-components";
```

The package's public exports map (from `package.json`) is:
`.` → `./index.js`, `./css` → `./index.css`, `./core` → `./core/index.js`,
`./a2ui` → `./a2ui/index.js`. Traits are registered via the default
module import; there is no separate `/traits` subpath.

---

## 2. Tag Name Changes

All tags changed from `aui-` prefix to `-ui` suffix.

### Direct Renames

| Old | New |
|-----|-----|
| `aui-button` | `button-ui` |
| `aui-input` | `input-ui` |
| `aui-textarea` | `textarea-ui` |
| `aui-select` | `select-ui` |
| `aui-option` | `<option>` (native) |
| `aui-optgroup` | `<optgroup>` (native) |
| `aui-checkbox` | `check-ui` |
| `aui-radio` | `radio-ui` |
| `aui-radio-group` | `radio-group-ui` |
| `aui-switch` | `switch-ui` |
| `aui-range` | `slider-ui` |
| `aui-dialog` | `modal-ui` + `card-ui` | No dedicated `dialog-ui` custom element in v0.0.1; `dialog-ui` is only an A2UI protocol alias that resolves to `modal-ui`. See §3 dialog for structure. |
| `aui-drawer` | `drawer-ui` |
| `aui-modal` | `modal-ui` |
| `aui-toast` | `toast-ui` |
| `aui-tooltip` | `tooltip-ui` |
| `aui-popover` | `popover-ui` |
| `aui-alert` | `alert-ui` |
| `aui-accordion` | `accordion-ui` |
| `aui-tabs` | `tabs-ui` |
| `aui-tab` | `tab-ui` |
| `aui-breadcrumb` | `breadcrumb-ui` |
| `aui-pagination` | `pagination-ui` |
| `aui-segmented-control` | `segmented-ui` |
| `aui-segment` | `segment-ui` |
| `aui-command` | `command-ui` |
| `aui-table` | `table-ui` |
| `aui-sidebar` | `app-shell [slot="sidebar"]` |
| `aui-divider` | `divider-ui` |
| `aui-avatar` | `avatar-ui` |
| `aui-avatar-group` | `avatar-group-ui` |
| `aui-badge` | `badge-ui` |
| `aui-icon` | `icon-ui` |
| `aui-image` | `image-ui` |
| `aui-progress` | `progress-ui` |
| `aui-skeleton` | `skeleton-ui` |
| `aui-kbd` | `kbd-ui` |
| `aui-code-block` | `code-ui` |
| `aui-calendar-picker` | `calendar-picker-ui` |
| `aui-app-shell` | `app-shell` (no `-ui` suffix) |
| `aui-nav-item` | `app-nav-item` (inside `app-shell [slot="sidebar"]`) |
| `aui-grid` | `grid-ui` |
| `aui-chip` | `tag-ui` |
| `aui-input-otp` | `otp-input-ui` |
| `aui-file-upload` | `upload-ui` |
| `aui-spinner` | `progress-ui variant="spinner"` |
| `aui-menu` | `menu-ui` |
| `aui-text` | `text-ui` |

### Structural Renames (changed composition)

| Old | New | Notes |
|-----|-----|-------|
| `aui-hstack` | `row-ui` | Gaps now use semantic tokens (`xs`–`xl`) |
| `aui-vstack` | `col-ui` | Same |
| `aui-stepper` | `steps-ui` | Parent manages child state via data attributes |
| `aui-step` | `step-ui` | `text`, `description`, `icon` properties |
| `aui-form` | `form-container-ui` | Dispatches `form-submit` event; supports `method="agent"` |
| `aui-heading` | `<h1 data-heading="title">` | No component — use native element + attribute |
| `aui-progress-circle` | `progress-ui variant="spinner"` | Merged into progress |

### Removed (use native HTML or composition)

| Old | Replacement |
|-----|-------------|
| `aui-container` | `card-ui` | Use `card-ui raw` for chromeless wrapper, or standard `card-ui` with `<header>` / `<section>` / `<footer>` |
| `aui-content` | `<section>` inside `card-ui` | Card children are native `<header>`, `<section>`, `<footer>` — styled by card.css via `@scope` |
| `aui-header` | `<header>` inside `card-ui` | Header slots: `heading`, `description`, `icon`, `action`. Or use `text` attr on drawer-ui |
| `aui-footer` | `<footer>` inside `card-ui` | Footer slots: `description`, `action`. Drawer has no footer — place buttons in body |
| `aui-field` | Use `label` attribute on form controls directly |
| `aui-fieldset` | Native `<fieldset>` |
| `aui-link` | Native `<a>` |
| `aui-page` | `app-shell` |
| `aui-page-main` | `app-shell [slot="content"]` |
| `aui-page-context` | `app-shell [slot="header"]` |
| `aui-tab-panel` | `<div>` with manual visibility | **`panel-ui` is an accordion panel, NOT a tab panel.** See Tabs section below |
| `aui-accordion-item` | `panel-ui` inside `accordion-ui` |

### Not yet in new lib

| Old | Status |
|-----|--------|
| `aui-tree` / `aui-tree-item` | Not available |
| `aui-carousel` | Not available |
| `aui-sparkline` | Use `chart-ui` |
| `aui-stat` | Compose with `card-ui` + `text-ui` |
| `aui-empty-state` | Compose with `col-ui` + `text-ui` + `icon-ui` |
| `aui-hover-card` | Use `popover-ui` |
| `aui-meter` | Not available |
| `aui-panes` / `aui-pane` | Not available |
| `aui-description-list` | Use native `<dl>` |
| `aui-notification-center` | Not available |
| `aui-tag-group` | Compose with `row-ui` + `tag-ui` |
| `aui-button-group` | Compose with `row-ui` + `button-ui` |
| `aui-checkbox-group` | Compose with `col-ui` + `check-ui` |
| `aui-disclosure` / `aui-disclosure-group` | Use `accordion-ui` |
| `aui-tile` | Use `card-ui` |
| `aui-bar` | Use `row-ui` |

---

## 2b. Card Content Model (Critical)

The biggest structural change. Cards now use native `<header>`, `<section>`, `<footer>` — no custom sub-elements.

```diff
- <aui-container>
-   <aui-header>Title</aui-header>
-   <aui-content>Body text</aui-content>
-   <aui-footer>
-     <aui-button label="Save"></aui-button>
-   </aui-footer>
- </aui-container>

+ <card-ui>
+   <header>
+     <h3 data-heading="section">Title</h3>
+     <span slot="description">Subtitle</span>
+   </header>
+   <section>
+     <col-ui gap="md">
+       <p>Body text</p>
+     </col-ui>
+   </section>
+   <footer>
+     <button-ui text="Save" variant="primary" slot="action"></button-ui>
+   </footer>
+ </card-ui>
```

**Rules:**
- Header, Section, Footer are direct children of Card — never nest them inside each other
- Section content must be wrapped in `col-ui` — never put content directly in section
- Header slots: `heading` (on `<span>` only), `description`, `icon`, `action`
- Footer slots: `description`, `action`
- Card sizes: `xs`, `sm`, `md` (default), `lg`, `xl` — controls inset, radius, gap, typography
- `[raw]` attribute strips background, border, shadow (structural wrapper only)

---

## 3. Attribute Changes by Component

### button

```diff
- <aui-button label="Save" icon="check"></aui-button>
+ <button-ui text="Save" icon="check"></button-ui>
```

| Old attr | New attr | Notes |
|----------|----------|-------|
| `label` | `text` | |
| `icon-leading` | `icon-leading` | Same |
| `icon-trailing` | `icon-trailing` | Same |
| `text-leading` | — | Removed |
| `text-trailing` | — | Removed |
| `href` | — | Removed; wrap in `<a>` |
| `loading` | `loading` | Same |
| `disabled` | `disabled` | Same |
| `type` | `type` | Same |
| — | `variant` | New: `solid`, `outline`, `ghost`, `danger`, `primary`, `secondary`, `soft` |
| — | `size` | New: `xs`, `sm`, `md`, `lg`, `xl` |
| — | `block` | New: full-width mode |

### input

```diff
- <aui-input placeholder="Email" type="email" clearable></aui-input>
+ <input-ui placeholder="Email" type="email"></input-ui>
```

| Old attr | New attr | Notes |
|----------|----------|-------|
| `clearable` | — | Removed |
| `autofocus` | — | Removed |
| `font` | — | Removed |
| `debounce` | — | Removed |
| `no-clear` | — | Removed |
| `prefix` | — | Removed; use `icon-leading` |
| `suffix` | — | Removed; use `icon-trailing` |
| — | `icon-leading` | New |
| — | `icon-trailing` | New |
| — | `min`, `max` | New: for number type |
| — | `autocomplete` | New |

### select

```diff
- <aui-select label="Country" placeholder="Choose...">
-   <aui-option value="us">United States</aui-option>
-   <aui-optgroup label="Europe">
-     <aui-option value="de">Germany</aui-option>
-   </aui-optgroup>
- </aui-select>
+ <select-ui label="Country" placeholder="Choose...">
+   <option value="us">United States</option>
+   <optgroup label="Europe">
+     <option value="de">Germany</option>
+   </optgroup>
+ </select-ui>
```

| Old attr | New attr | Notes |
|----------|----------|-------|
| `mode` | — | Removed; dropdown only |
| `orientation` | — | Removed |
| `transparent` | — | Removed |
| `fill` | — | Removed |
| `padding` | — | Removed |
| `hide-label` | — | Removed |
| `target` | — | Removed |

### dialog

**Status as of `@adia-ai/web-components@0.0.1`:** there is no standalone
`dialog-ui` custom element — the name appears only as an A2UI protocol
alias that resolves to `modal-ui` at render time. Both simple
confirm/cancel dialogs and dialogs with custom content migrate to
`modal-ui` + structured children.

`modal-ui`'s real API surface (per `components/modal/modal.js`): `text`
(header title), `size` (`sm`/`md`/`lg`), `open` (Boolean). No built-in
`description` / `confirm-text` / `cancel-text` / `variant="danger"` /
`closable` / `confirm` / `cancel` event contract. Compose any of those
using children.

```diff
# Simple confirm
- <aui-dialog open size="md">
-   <aui-header><aui-heading>Confirm</aui-heading></aui-header>
-   <p>Are you sure?</p>
- </aui-dialog>
+ <modal-ui text="Confirm" size="md" open>
+   <section>Are you sure?</section>
+   <footer>
+     <button-ui text="Cancel" variant="ghost" slot="action"></button-ui>
+     <button-ui text="Confirm" variant="primary" slot="action"></button-ui>
+   </footer>
+ </modal-ui>
```

**Dialogs with custom content** (input fields, forms, tables) — same
structure; just richer body:

```diff
# Custom content → use modal-ui wrapping card-ui
- <aui-dialog open style="--aui-dialog-width: 28rem">
-   <aui-header><aui-heading>Deactivate User</aui-heading></aui-header>
-   <aui-content>
-     <aui-input placeholder="Reason..."></aui-input>
-   </aui-content>
-   <aui-footer>
-     <aui-button label="Cancel"></aui-button>
-     <aui-button label="Confirm"></aui-button>
-   </aui-footer>
- </aui-dialog>
+ <modal-ui open>
+   <card-ui style="max-width: 28rem; width: 100%; padding: var(--a-space-md);">
+     <h3 data-heading="section">Deactivate User</h3>
+     <input-ui placeholder="Reason..."></input-ui>
+     <row-ui justify="end" gap="sm" style="margin-top: var(--a-space-md);">
+       <button-ui variant="ghost" text="Cancel"></button-ui>
+       <button-ui variant="primary" text="Confirm"></button-ui>
+     </row-ui>
+   </card-ui>
+ </modal-ui>
```

**Note:** `--aui-dialog-width` has no equivalent token. Use `max-width` on `card-ui` inside `modal-ui`.

`modal-ui` real attribute surface (v0.0.1):

| attr | type | notes |
|------|------|-------|
| `text` | string | Header title; stamped into the header slot |
| `size` | `sm` / `md` / `lg` | Preset width buckets |
| `open` | Boolean | Show/hide; pair with `modal.show()` / `modal.hide()` |

Events: `close` fires after the modal finishes its closing animation.
There are no built-in `confirm` / `cancel` events in v0.0.1 — wire
button clicks yourself (as shown in the "Simple confirm" diff above).
For custom widths, use `max-width` on the content subtree; there is no
`--modal-width` token.

### drawer

The drawer's internal structure changed completely. There are no `header`/`content`/`footer` sub-regions. The `text` attribute provides the header title, the drawer auto-stamps its own header and close button, and children become the body content.

```diff
- <aui-drawer side="right" open style="--aui-drawer-width: 40rem">
-   <aui-header>
-     <aui-heading>Filters</aui-heading>
-     <aui-button variant="ghost" icon-leading="x" label="Close"></aui-button>
-   </aui-header>
-   <aui-content>
-     <p>Filter content here</p>
-   </aui-content>
-   <aui-footer>
-     <aui-button label="Clear all"></aui-button>
-   </aui-footer>
- </aui-drawer>
+ <drawer-ui text="Filters" side="right" open closable style="--drawer-width: 40rem">
+   <p>Filter content here</p>
+   <button-ui text="Clear all"></button-ui>
+ </drawer-ui>
```

**Key changes:**
- Header is set via `text` attribute — do NOT add your own `<header>` inside
- Close button is built-in via `closable` attribute — do NOT add your own
- Children are adopted into the body automatically — just place content directly inside
- Width token is `--drawer-width`, NOT `--a-drawer-width`

| Old attr | New attr | Notes |
|----------|----------|-------|
| `enable-scroll` | — | Removed |
| `enable-pointer` | — | Removed |
| — | `text` | New: header title (replaces `aui-header` + `aui-heading` inside) |
| — | `size` | New: `sm`, `md`, `lg` |
| — | `closable` | New: built-in close button (replaces manual close button) |
| `style="--aui-drawer-width"` | `style="--drawer-width"` | Token renamed (NOT `--a-drawer-width`) |

### toast

```diff
- <aui-toast message="Saved!" duration="3000" closeable></aui-toast>
+ <toast-ui text="Saved!" duration="3000" closable></toast-ui>
```

| Old attr | New attr | Notes |
|----------|----------|-------|
| `message` | `text` | Renamed |
| `closeable` | `closable` | Spelling fix |
| — | `variant` | New: `default`, `success`, `error`, `warning`, `info` |

### breadcrumb

```diff
- <aui-breadcrumb joiner="/">
-   <aui-breadcrumb-item>Home</aui-breadcrumb-item>
- </aui-breadcrumb>
+ <breadcrumb-ui separator="/">
+   <a href="/">Home</a>
+ </breadcrumb-ui>
```

| Old attr | New attr | Notes |
|----------|----------|-------|
| `joiner` | `separator` | Renamed |
| `label` | — | Removed; use `aria-label` |
| `max-depth` | — | Removed |

### table

The table API changed significantly — from declarative HTML sub-components to a data-driven JavaScript API.

**For plain-text tables**, use `table-ui` with the data-driven API:

```diff
- <aui-table sortable paginate="10">
-   <aui-table-column key="name" label="Name" sortable></aui-table-column>
-   <aui-table-column key="email" label="Email"></aui-table-column>
- </aui-table>

+ <table-ui sortable paginate="10"></table-ui>
+ <script>
+   const table = document.querySelector('table-ui');
+   table.columns = [
+     { key: 'name', label: 'Name' },
+     { key: 'email', label: 'Email' },
+   ];
+   table.data = [
+     { name: 'Alice', email: 'alice@example.com' },
+   ];
+ </script>
```

**For tables with rich cell content** (badges, icons, links, etc.), `table-ui` does NOT support custom cell renderers — it only renders plain text via `td.textContent`. Use native HTML `<table>` instead and style it yourself:

```diff
- <aui-table cols="1fr 2fr 1fr">
-   <aui-table-head>
-     <aui-table-row>
-       <aui-table-header>ID</aui-table-header>
-       <aui-table-header>Name</aui-table-header>
-       <aui-table-header>Status</aui-table-header>
-     </aui-table-row>
-   </aui-table-head>
-   <aui-table-body>
-     <aui-table-row>
-       <aui-table-cell>001</aui-table-cell>
-       <aui-table-cell><aui-text weight="medium">Alice</aui-text></aui-table-cell>
-       <aui-table-cell><aui-badge intent="success">Active</aui-badge></aui-table-cell>
-     </aui-table-row>
-   </aui-table-body>
- </aui-table>

+ <table>
+   <thead>
+     <tr>
+       <th>ID</th>
+       <th>Name</th>
+       <th>Status</th>
+     </tr>
+   </thead>
+   <tbody>
+     <tr>
+       <td>001</td>
+       <td><text-ui weight="medium">Alice</text-ui></td>
+       <td><badge-ui intent="success">Active</badge-ui></td>
+     </tr>
+   </tbody>
+ </table>
```

**Note:** The `cols` attribute (CSS grid column template) only works on `table-ui`, not native `<table>`. Native tables use standard column sizing. You will need to add your own table CSS for borders, padding, and hover states.

### sidebar

**Status as of `@adia-ai/web-components@0.0.1`:** there is no
`sidebar-ui` / `sidebar-item-ui` / `sidebar-section-ui` custom element.
The closest equivalent is the `app-nav` / `app-nav-group` / `app-nav-item`
patterns (see `packages/web-components/patterns/app-nav*/`) composed
inside the `app-shell` pattern (see §7 Layout Migration below). Those
patterns do not auto-navigate either — wrap items in `<a>` for real
routing.

```diff
- <aui-sidebar width="240" collapsible collapsed>
-   <aui-sidebar-header>
-     <aui-nav-item><aui-avatar label="A"></aui-avatar> My App</aui-nav-item>
-   </aui-sidebar-header>
-   <aui-sidebar-content>
-     <a href="/dashboard"><aui-nav-item active><aui-icon name="house"></aui-icon> Dashboard</aui-nav-item></a>
-     <a href="/settings"><aui-nav-item><aui-icon name="gear"></aui-icon> Settings</aui-nav-item></a>
-   </aui-sidebar-content>
-   <aui-sidebar-footer>
-     <aui-nav-item><aui-avatar label="U"></aui-avatar> User</aui-nav-item>
-   </aui-sidebar-footer>
- </aui-sidebar>
+ <app-shell>
+   <div slot="sidebar">
+     <app-nav-item text="My App" icon="grid"></app-nav-item>
+     <app-nav-group>
+       <a href="/dashboard"><app-nav-item icon="house" text="Dashboard" active></app-nav-item></a>
+       <a href="/settings"><app-nav-item icon="gear" text="Settings"></app-nav-item></a>
+     </app-nav-group>
+     <app-nav-item text="User" icon="user"></app-nav-item>
+   </div>
+   <div slot="content">Main content</div>
+ </app-shell>
```

| Old | New | Notes |
|-----|-----|-------|
| `aui-sidebar` | `app-shell [slot="sidebar"]` | Sidebar is a slot on `app-shell`, not a standalone component in v0.0.1 |
| `aui-sidebar-header` | `app-nav-item` as the first child | Place the branding item at the top of the slot |
| `aui-sidebar-content` | `app-nav-group` | Groups items; supports multiple groups |
| `aui-sidebar-footer` | `app-nav-item` as the last child | Same pattern as the header |
| `aui-nav-item` | `app-nav-item` | Attributes: `icon`, `text`, `active`. Does NOT auto-navigate — wrap in `<a>` |

App-shell supports modes: `default`, `panel`, `collapsed`, `full`. The
old sidebar attributes (`width`, `min-width`, `max-width`, `collapsible`,
`side`) do not map 1:1 — width is a CSS custom-property tweak on the
shell rather than a component attribute. See §7 Layout Migration.

### tabs

**`panel-ui` is an accordion panel, NOT a tab panel.** There is no built-in tab panel component. `tabs-ui` only manages tab active state — you must implement panel visibility yourself.

Attribute changes: `value=` → `active=` on `tabs-ui`, `value=` → `name=` on `tab-ui`.

```diff
- <aui-tabs value="tab1">
-   <aui-tab value="tab1">First</aui-tab>
-   <aui-tab value="tab2">Second</aui-tab>
- </aui-tabs>
- <aui-tab-panel value="tab1">Content 1</aui-tab-panel>
- <aui-tab-panel value="tab2">Content 2</aui-tab-panel>

+ <tabs-ui active="tab1">
+   <tab-ui name="tab1">First</tab-ui>
+   <tab-ui name="tab2">Second</tab-ui>
+ </tabs-ui>
+ <div data-tab-panel="tab1">Content 1</div>
+ <div data-tab-panel="tab2" style="display: none;">Content 2</div>
+
+ <script>
+   // You must wire up panel switching yourself
+   document.querySelectorAll("tabs-ui").forEach((tabs) => {
+     tabs.addEventListener("click", (e) => {
+       const tab = e.target.closest("tab-ui");
+       if (!tab) return;
+       const name = tab.getAttribute("name") || "";
+       let el = tabs.nextElementSibling;
+       while (el?.hasAttribute("data-tab-panel")) {
+         el.style.display = el.getAttribute("data-tab-panel") === name ? "" : "none";
+         el = el.nextElementSibling;
+       }
+     });
+   });
+ </script>
```

| Old attr | New attr | Notes |
|----------|----------|-------|
| `aui-tabs value="X"` | `tabs-ui active="X"` | Renamed |
| `aui-tab value="X"` | `tab-ui name="X"` | Renamed |
| `aui-tab-panel value="X"` | `<div data-tab-panel="X">` | **No component** — use div + manual show/hide |

### accordion

```diff
- <aui-accordion multiple>
-   <aui-accordion-item>
-     <aui-header>Section 1</aui-header>
-     <p>Content</p>
-   </aui-accordion-item>
- </aui-accordion>
+ <accordion-ui multiple>
+   <panel-ui text="Section 1">
+     <p>Content</p>
+   </panel-ui>
+ </accordion-ui>
```

### heading

```diff
- <aui-heading size="lg">Dashboard</aui-heading>
+ <h1 data-heading="title">Dashboard</h1>
```

**`data-heading` is mandatory on all heading elements.** Without it, `h1-h6` are reset to `font-size: inherit` — they're semantic-only. All visual heading styles come from `[data-heading]` in `resets.css`.

Heading variants: `display`, `title`, `subtitle`, `section`, `label`, `eyebrow`, `caption`.

Apply to any element:
```html
<span data-heading="eyebrow">STATUS</span>
<h2 data-heading="section">Settings</h2>
<h3 data-heading="section">Card Title</h3>
<span data-heading="label">Revenue</span>
<span data-heading="caption">Updated 2h ago</span>
```

**Inside `card-ui` headers:** Card CSS auto-targets native `h1-h6` and `small`/`p` inside `<header>` for grid positioning — so `slot="heading"` is only needed on non-heading elements like `<span>`:
```html
<card-ui>
  <header>
    <h3 data-heading="section">Title</h3>          <!-- no slot needed -->
    <small>Subtitle</small>                          <!-- no slot needed -->
    <span slot="heading" data-heading="section">Title</span>  <!-- span needs slot -->
  </header>
</card-ui>
```

### text

```diff
- <aui-text>Some content</aui-text>
+ <text-ui text="Some content"></text-ui>
```

Or use the `text` attribute for CSS `attr()` rendering:
```html
<text-ui text="Some content"></text-ui>
```

### hstack / vstack

```diff
- <aui-hstack gap="4">...</aui-hstack>
- <aui-vstack gap="6">...</aui-vstack>
+ <row-ui gap="md">...</row-ui>
+ <col-ui gap="md">...</col-ui>
```

Gap values changed from numeric units to semantic tokens: `none`, `xs`, `sm`, `md`, `lg`, `xl`.

New attributes: `justify`, `align`, `wrap` (row-ui only).

### chip → tag

```diff
- <aui-chip removable>Beta</aui-chip>
+ <tag-ui text="Beta" removable></tag-ui>
```

New: `variant` (`default`, `success`, `warning`, `error`, `info`), `disabled`.

### switch → toggle

```diff
- <aui-switch label="Notifications" checked></aui-switch>
+ <toggle-ui label="Notifications" checked></toggle-ui>
```

API is largely the same. New: `hint` property.

---

## 4. Token Changes

### Namespace

```diff
- --aui-*
+ --a-*
```

### Spacing

| Old | New |
|-----|-----|
| `--aui-space-xs` | `--a-space-xs` |
| `--aui-space-sm` | `--a-space-sm` |
| `--aui-space-md` | `--a-space-md` |
| `--aui-space-lg` | `--a-space-lg` |
| `--aui-space-xl` | `--a-space-xl` |

New lib also has numeric scale: `--a-space-1` through `--a-space-16`, plus `--a-space-unit` (0.25rem base) and `--a-space-k` multiplier.

### Sizing

| Old | New |
|-----|-----|
| `--aui-size-xs` | Use `[size="xs"]` attribute |
| `--aui-size-sm` | Use `[size="sm"]` attribute |
| `--aui-size-md` | Use `[size="md"]` attribute |
| `--aui-size-lg` | Use `[size="lg"]` attribute |
| `--aui-size-xl` | Use `[size="xl"]` attribute |
| `--aui-control-height-*` | `--a-content-height` (set by `[size]`) |

### Typography

| Old | New |
|-----|-----|
| `--aui-font-family` | `--a-font-family` |
| `--aui-font-family-display` | `--a-font-display` |
| `--aui-font-family-mono` | `--a-font-mono` |
| `--aui-font-xs` | `--a-font-size-xs` |
| `--aui-font-sm` | `--a-font-size-sm` |
| `--aui-font-md` | `--a-font-size-md` (default) |
| `--aui-font-lg` | `--a-font-size-lg` |
| `--aui-font-xl` | `--a-font-size-xl` |
| `--aui-font-weight-regular` | `--a-font-weight-normal` |
| `--aui-font-weight-medium` | `--a-font-weight-medium` |
| `--aui-font-weight-semibold` | `--a-font-weight-semibold` |
| `--aui-font-weight-bold` | `--a-font-weight-bold` |

### Radius

| Old | New |
|-----|-----|
| `--aui-radius-sharp` | `--a-radius-xs` (or `[radius="sharp"]` → k=0) |
| `--aui-radius-rounded` | `--a-radius-md` (or `[radius="rounded"]` → k=1.5) |
| `--aui-radius-round` | `--a-radius-xl` (or `[radius="round"]` → k=2) |
| `--aui-radius` (component) | `--a-radius` (set by `[radius]` attr or `[size]`) |

New lib uses t-shirt scale: `--a-radius-xs` through `--a-radius-xl`, plus `--a-radius-full` (pill). Dynamic via `clamp(--a-radius-min, --a-radius-k × --a-radius-base, --a-radius-max)`.

### Shadows

| Old | New |
|-----|-----|
| `--aui-shadow-xs` | `--a-shadow-xs` |
| `--aui-shadow-sm` | `--a-shadow-sm` |
| `--aui-shadow-md` | `--a-shadow-md` |
| `--aui-shadow-lg` | `--a-shadow-lg` |
| `--aui-shadow-xl` | `--a-shadow-xl` |

New: `--a-shadow-intensity` multiplier, semantic aliases (`--a-shadow-dropdown`, `--a-shadow-dialog`).

### Z-Index

| Old | New |
|-----|-----|
| `--aui-z-base` (0) | `--a-z-base` (0) |
| `--aui-z-raised` (1) | `--a-z-raised` (10) |
| `--aui-z-sticky` (10) | `--a-z-sticky` (200) |
| `--aui-z-overlay` (100) | `--a-z-overlay` (300) |
| `--aui-z-popover` (200) | `--a-z-popover` (500) |
| `--aui-z-modal` (1000) | `--a-z-modal` (400) |
| `--aui-z-toast` (9999) | `--a-z-toast` (600) |

### Colors

| Old | New |
|-----|-----|
| `--aui-surface-text` | `--a-canvas-text` |
| `--aui-surface-*` | `--a-canvas-*` |
| `--aui-accent-*` | `--a-accent-*` |
| `--aui-solid-*` | `--a-accent-strong` / `--a-accent-default` |

Color system changed from stepped primitives (`--aui-accent-500`) to OKLCH computational surfaces (`--a-accent-tint-50`, `--a-accent-shade-50`).

---

## 5. Theme System

```diff
- <html theme="forest">
+ <html data-theme="forest">
```

| Old theme | New theme | Notes |
|-----------|-----------|-------|
| `forest` | `forest` | Similar personality, different values |
| `rose` | `rose` | Similar |
| `zinc` | `slate` | Renamed |
| — | `default` | New baseline |
| — | `ocean` | New |
| — | `sunset` | New |
| — | `lavender` | New |
| — | `midnight` | New |

New lib also supports `<theme-ui>` component for parametric control:
```html
<theme-ui preset="ocean" space-scale="0.75" chroma="1.5">
  <!-- themed content -->
</theme-ui>
```

---

## 6. Global Attributes

| Old | New | Notes |
|-----|-----|-------|
| `[density="compact"]` | `[density="compact"]` | Same name, new multiplier values |
| `[density="spacious"]` | `[density="spacious"]` | Same |
| `[radius="sharp"]` | `[radius="sharp"]` | Same |
| `[radius="rounded"]` | `[radius="rounded"]` | Same |
| `[radius="round"]` | `[radius="round"]` | Same |
| `[scaling="compact"]` | `[size="sm"]` | Renamed + new semantic |
| `[scaling="large"]` | `[size="lg"]` | Renamed |
| `[shadow="flat"]` | — | Removed |
| `[shadow="elevated"]` | — | Removed |
| `[motion="none"]` | — | Removed |
| `[motion="smooth"]` | — | Removed |

---

## 7. Layout Migration

### App Shell

```diff
- <aui-app-shell>
-   <aui-sidebar slot="sidebar">
-     <aui-sidebar-header>Logo</aui-sidebar-header>
-     <aui-sidebar-content>
-       <aui-nav-item>Dashboard</aui-nav-item>
-     </aui-sidebar-content>
-     <aui-sidebar-footer>Settings</aui-sidebar-footer>
-   </aui-sidebar>
-   <aui-page>
-     <aui-page-context>
-       <aui-breadcrumb>...</aui-breadcrumb>
-     </aui-page-context>
-     <aui-page-main>
-       Content here
-     </aui-page-main>
-   </aui-page>
- </aui-app-shell>

+ <app-shell>
+   <div slot="sidebar">
+     <app-nav-item text="My App"></app-nav-item>
+     <app-nav-group>
+       <app-nav-item icon="house" text="Dashboard" active></app-nav-item>
+     </app-nav-group>
+   </div>
+   <div slot="header">
+     <breadcrumb-ui>...</breadcrumb-ui>
+   </div>
+   <div slot="content">
+     Content here
+   </div>
+ </app-shell>
```

New `app-shell` supports modes: `default`, `panel`, `collapsed`, `full`.

---

## 8. Quick Search & Replace

For most projects, start with these global replacements:

```
# Tag renames (most common)
aui-button     →  button-ui
aui-input      →  input-ui
aui-textarea   →  textarea-ui
aui-select     →  select-ui
aui-option     →  option          (native element)
aui-optgroup   →  optgroup        (native element)
aui-checkbox   →  check-ui
aui-radio      →  radio-ui
aui-switch     →  switch-ui
aui-icon       →  icon-ui
aui-text       →  text-ui
aui-badge      →  badge-ui
aui-avatar     →  avatar-ui
aui-divider    →  divider-ui
aui-dialog     →  modal-ui               (dialog-ui is only an A2UI protocol alias — no dedicated custom element)
aui-drawer     →  drawer-ui
aui-toast      →  toast-ui
aui-tooltip    →  tooltip-ui
aui-alert      →  alert-ui
aui-tabs       →  tabs-ui
aui-tab        →  tab-ui
aui-accordion  →  accordion-ui
aui-breadcrumb →  breadcrumb-ui
aui-pagination →  pagination-ui
aui-sidebar    →  app-shell [slot="sidebar"]  (no sidebar-ui — use the app-nav-* pattern family inside the slot)
aui-table      →  table-ui
aui-progress   →  progress-ui
aui-skeleton   →  skeleton-ui
aui-kbd        →  kbd-ui
aui-chip       →  tag-ui
aui-hstack     →  row-ui
aui-vstack     →  col-ui
aui-grid       →  grid-ui
aui-command    →  command-ui
aui-app-shell  →  app-shell              (NOTE: no -ui suffix — only exception)
aui-segmented-control → segmented-ui
aui-segment    →  segment-ui

# Attribute renames
label=          →  text=           (on button, alert, toast, drawer ONLY — NOT on form controls)
message=        →  text=           (on toast)
joiner=         →  separator=      (on breadcrumb)
closeable       →  closable        (on toast, dialog)

# ⚠️ label= is STILL CORRECT on form controls (select-ui, input-ui, etc.)
# Do NOT blindly replace all label= → text=
# Also update JS: setAttribute("label", ...) → setAttribute("text", ...) on button-ui elements

# Theme attribute
theme=          →  data-theme=

# Token prefix
--aui-          →  --a-

# Heading
<aui-heading>   →  <h2 data-heading="section">  (choose appropriate level + variant)
```

### Attributes that need manual review

These can't be search-and-replaced — they require understanding the context:

- `aui-button label="..."` → `button-ui text="..."` (but `label` is still used on form controls like `select-ui`)
- `aui-button label="..."` in JS → `setAttribute("text", ...)` — also update runtime JS, not just HTML
- `aui-container` → `card-ui` (raw wrapper, no header/content/footer sub-structure)
- `aui-header` / `aui-footer` → removed inside drawers (use `text` attr) and cards (style children yourself)
- `aui-field` → move `label`, `error`, `required` directly onto the form control
- `aui-table` sub-components → `table-ui` data-driven API (plain text only) or native `<table>` (rich content)
- `aui-tab-panel` → `<div>` with manual show/hide — `panel-ui` is for accordions, NOT tabs
- `aui-dialog` with custom content → `modal-ui` + `card-ui`, NOT `dialog-ui`
- `aui-page-*` hierarchy → flatten into `app-shell` slots
- `aui-nav-item` → `app-nav-item` — does NOT navigate, wrap in `<a>` for real links
- `--aui-dialog-width` / `--aui-drawer-width` → component tokens are unprefixed (`--drawer-width`), NOT `--a-*`

---

## 9. Event Changes

| Old event | New event | Component |
|-----------|-----------|-----------|
| `press` | `press` | button (same — normalized click/tap/keyboard) |
| `search` | `input` (native) | input |
| `submit` | `form-submit` | form |
| `select` | `select` | select (same) |
| `change` | `change` | form controls (same) |
| `remove` | `remove` | chip/tag (same) |
| — | `confirm` | dialog (new) |
| — | `cancel` | dialog (new) |
| — | `cell-edit` | table spreadsheet mode (new) |

---

## 10. Component CSS Token Names

Design system tokens use the `--a-` prefix (`--aui-space-md` → `--a-space-md`). But **component-specific tokens are unprefixed** — they do NOT use `--a-`:

| Old | Wrong | Correct |
|-----|-------|---------|
| `--aui-drawer-width` | ~~`--a-drawer-width`~~ | `--drawer-width` |
| `--aui-dialog-width` | ~~`--a-dialog-width`~~ | No token — use `max-width` on `card-ui` inside `modal-ui` |
| `--aui-table-*` | ~~`--a-table-*`~~ | `--table-font-size`, `--table-padding-x`, etc. |

The pattern: design tokens are `--a-*`, component tokens are `--{component}-*`.
