diff --git a/.gitignore b/.gitignore
index d799a1d..c35a2fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,10 +68,7 @@ __screenshots__
.env
# Claude
-.claude/settings.local.json
-.claude/config.json
-.claude/.env
-.claude/tools/node_modules
+.claude/
/libs/ui/scripts/output/*
/libs/ui/.size-limit.json
/libs/web-components/bundle-size-report.json
@@ -81,6 +78,7 @@ __screenshots__
# step 6 of the walkthrough in the root README.
/libs/web-components/harness/ssr-dsd-static.html
/libs/web-components/harness/ssr-dsd-hydrated.html
+libs/web-components/bundle-size-baseline.json
# Vitest artifacts
.vitest-artifacts
@@ -92,8 +90,9 @@ plans/
AGENTS.md
/stats.html
/libs/web-components/screenshots/
+/libs/web-components/migration-ir/
-# CTORNDSD-646b platform fixtures — build output and generated types only; the fixture
+# Platform fixtures — build output and generated types only; the fixture
# sources themselves are tracked. See docs/webcomponents-migration/08-react-and-nextjs.md.
/fixtures/*/.next/
/fixtures/*/next-env.d.ts
diff --git a/CLAUDE.md b/CLAUDE.md
index dad2b01..74c0398 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -44,8 +44,6 @@ npm run build-storybook
npm run verify:ui:full # All 10 verification phases + Verdaccio smoke test
npm run verify:ui:ci # CI gate (non-zero exit on failure)
-# Scaffolding
-npm run crc ComponentName # Interactive scaffold for a new component (prompts for tier)
```
## Architecture
@@ -107,6 +105,5 @@ The form-configurator packages use Jest (`jest.config.ts` per package).
- **Commits**: Conventional Commits (`feat:`, `fix:`, `chore:`, etc.) enforced by commitlint.
- **Pre-commit**: prettier format check + lint-staged (ESLint + prettier on staged `libs/**` files, markdownlint on `*.md`).
-- **New components**: `npm run crc ComponentName` launches an interactive prompt to choose the tier (atoms/molecules/organisms/widget) and scaffolds all required files.
- **Styling**: never write raw hex or pixel literals in component styles. Access values through the theme object (`get(theme, 'component.variant', {})`). Use the Emotion `css` prop, not inline `style` or CSS modules.
- **SSR compatibility**: components must render without errors in a Node environment. Mark client-only hooks with `'use client'` directive. The `ssr-check` and `rsc-render-check` scripts in `verify:ui` catch violations.
diff --git a/DEMO.md b/DEMO.md
deleted file mode 100644
index e6ea5f0..0000000
--- a/DEMO.md
+++ /dev/null
@@ -1,119 +0,0 @@
-# GridKit Demo — Run Card
-
-Hi there!
-
-Three weeks of work in short:
-
-we rebuilt five GridKit components twice — as Web Components in Lit, and as a React Native app on iOS — and pulled their shared token and state logic into `gd-design-core`, now covered by 121 tests.
-
-Alongside, two new Dynamic Builder checklists document
-
-- what runtime AI-generated UI costs and
-- how to pick a model.
-
-## Pre-flight — Stops 3–8 only
-
-```bash
-npm install && npm run demo:setup # ~3 min, do it before the meeting
-npm run demo:harness # :5173 — Stops 3–5
-npm run check:web-components-ssr && npm run check:web-components-size # SSR pages + warm size cache
-npm run demo:next # :5373 — Stop 5 insert
-```
-
-**Tabs to open in advance:** [1 · fidelity](http://localhost:5173/harness/fidelity-check.html) · [2 · shell-isolation](http://localhost:5173/harness/shell-isolation-check.html) · [3 · form-participation](http://localhost:5173/harness/form-participation-check.html) · [Next.js fixture](http://localhost:5373/) — plus the Simulator window and the two screenshots (Tabs 4–6).
-
-## Stop 1 — Dynamic Builder: performance
-
-- **Open** — [Performance Improvement Checklist](https://griddynamics.atlassian.net/wiki/spaces/RNDM/pages/4685791299/Dynamic+Builder+Performance+Improvement+Checklist)
-- **Why** — one A2UI page load: ~74.6k tokens, ~21s, ~$0.09
-- **Shows** — nine levers:
- - send only the schema a screen needs
- - merge repeated component groups
- - cap nesting
- - patch in the browser instead of regenerating
- - cache stable inputs
- - feature-flag new surfaces
- - ask whether the screen needs generated UI
-- **Plus** — a proposed SLA: ≤8s, ≤8k tokens, ≤$0.02 per call. Not ratified — that is the decision to ask for
-
-## Stop 2 — Dynamic Builder: model choice
-
-- **Open** — [Model Benchmark & Selection Checklist](https://griddynamics.atlassian.net/wiki/spaces/RNDM/pages/4685955158/Dynamic+Builder+Model+Benchmark+Selection+Checklist)
-- **Why** — we pick models by inheritance, not evidence
-- **Shows** — which model for which job:
- - classification and extraction — Haiku-class or o3-mini
- - hard reasoning — Gemini 2.5 Pro or Sonnet-class
- - A2UI — Flash Lite with a scoped schema
- - preview models — not yet
-- **Behind it** — timings from 38.65s to 204.57s, plus public pricing. No controlled study exists, and Haiku 3.5 is already retired
-
-## Stop 3 — Fidelity check
-
-- **Open** — Tab 1 · [localhost:5173/harness/fidelity-check.html](http://localhost:5173/harness/fidelity-check.html)
-- **Why** — sets the visual bar before Stop 4 strips the theme. Keep it to 15 seconds
-- **Shows**
- - five components rebuilt in Lit — button, checkbox, typography, input, select
- - running on the real GridKit theme, not lookalikes
-- **Look for** — gold primary button, secondary/outlined/disabled/loading, gold checkboxes, Fira Sans headings, a working select
-- **Note** — skip this and Stop 4 looks like a broken port
-
-## Stop 4 — Shell isolation
-
-- **Open** — Tab 2 · [localhost:5173/harness/shell-isolation-check.html](http://localhost:5173/harness/shell-isolation-check.html)
-- **Why** — CTORNDSD-286: a host app's global CSS leaked into our components
-- **Shows**
- - at the top, a global reset wrecks the plain Emotion button — the old bug, reproduced
- - the Lit component beside it is untouched
-- **Read the JSON, not the buttons** — the theme is empty on purpose, so both render grey
-- **Last two lines** — the Lit-shell shortcut blocked its own styling too. Why we dropped it
-
-### ↳ `npm run check:web-components-size`
-
-- **Why** — bundle cost is the main upside
-- **Shows** — 7–16× smaller per component:
- - Button 2.05 vs 18.66 kB
- - Checkbox 2.11 vs 23.22 kB
- - Typography 1.09 vs 17.64 kB
- - Input 2.53 vs 18.51 kB
- - Select 2.91 vs 27.69 kB
-- **Headline** — all five together, **11.20 kB against 105.72 kB**. 18.08 kB counting Lit's runtime, which most React apps have not already paid. The last line is a CI gate
-
-## Stop 5 — Form participation
-
-- **Open** — Tab 3 · [localhost:5173/harness/form-participation-check.html](http://localhost:5173/harness/form-participation-check.html)
-- **Why** — proves these are real form controls, not a mock-up
-- **Shows**
- - the app's own CSS reaches in where we allow it — `::part()` gives the magenta ring, cyan fill, red checkbox
- - a plain descendant selector still cannot — 4 pixels changed, not 99
-- **Also** — they behave like inputs: listed in `form.elements`, submitted into `FormData`, block empty submit, reset cleanly
-
-### ↳ `curl -s http://localhost:5373/ | grep -c shadowrootmode`
-
-- **Against** — the Next.js fixture at [localhost:5373](http://localhost:5373/)
-- **Why** — the honest cost. Lead with it
-- **Shows**
- - both counts come back `0` — no Declarative Shadow DOM, no server-rendered heading
- - with JavaScript off, unstyled text and no headings — worse than React today for SEO and first paint
-- **Two more** — our token barrel breaks inside a Server Component; mount is 2.3–3.4× slower; nested theming has no equivalent yet
-
-## Stop 6 — iOS Simulator, live
-
-```
-xcrun simctl boot "iPhone 16" && open -a Simulator # ~30s
-npm run dev:react-native -- --ios --localhost
-```
-
-- **Turn to** — Tab 4 · the Simulator window, and scroll it. Live, not a screenshot
-- **Why** — can we leave the DOM, not just React?
-- **Shows** — the same five components off the same `gd-design-core`:
- - gold button, GridKit type scale
- - input with label and helper text
- - select with chevron
-- **The point** — one source, two outputs: 1.68 MB of native bytecode for the device, 392 kB of JavaScript for a browser. No DOM-specific code
-
-## If it breaks
-
-- Stops 3–5: `npm run verify:web-components` · Stops 6–8: `npm test --workspace=libs/react-native` + `npm run test:design-core`
-- Ports: `kill $(lsof -ti:5173)` · `:5273` · `:5373` · `:8081` — teardown: `pkill -f "expo start"; xcrun simctl shutdown all`
-
-Full detail: `docs/webcomponents-migration/README.md` · `libs/react-native/FINDINGS.md` · the two Confluence pages above.
diff --git a/README.md b/README.md
index 57d956a..f3f08f3 100644
--- a/README.md
+++ b/README.md
@@ -12,10 +12,10 @@ Nx monorepo containing the GridKit design system packages.
Not published, and under active investigation:
-| Package | Description |
-| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
-| `gd-design-core` (`libs/design-core`) | Framework-agnostic state stores + token resolution. No React, no Lit, no `gd-design-library` dependency |
-| `web-components` (`libs/web-components`) | Lit custom-element port of 5 GridKit atoms. `private: true` — see [Web Components spike](#web-components-spike-ctorndsd-646) |
+| Package | Description |
+| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
+| `gd-design-core` (`libs/design-core`) | Framework-agnostic state stores + token resolution. No React, no Lit, no `gd-design-library` dependency |
+| `web-components` (`libs/web-components`) | Lit custom-element port GridKit atoms. `private: true` — see [Web Components](libs/web-components/README.md) |
## Quick start
@@ -83,203 +83,3 @@ npm run build:ui && npm run publish:ui
# form-configurator — via GitHub Actions (publish-form-configurator.yaml) or:
npm run build:form-configurator && npm run publish:form-configurator
```
-
-## Scaffold a new component
-
-```bash
-npm run crc ComponentName
-```
-
-## Web Components spike (CTORNDSD-646)
-
-An investigation into porting GridKit from React to Lit custom elements. **Every demo below runs from
-the repo root — you never need to `cd` into a package or fixture.**
-
-### One-time setup
-
-```bash
-npm install # if you haven't already
-npm run demo:setup # builds dist/ + installs the two fixtures (~2-3 min)
-```
-
-`demo:setup` builds `gd-design-library`, `gd-design-core`, and `web-components`, then installs
-`fixtures/react19-check` and `fixtures/next-ssr-check`. Some demos need those build artifacts; the
-demo index tells you which.
-
-### Test it step by step
-
-Twelve steps, in order, all from the repo root. Each one lists the command, what you should see, and
-which finding it proves. **Steps 1–6 are non-interactive** — run them and read the terminal. **Steps
-7–11 open a browser.** Step 12 runs the whole automated set at once.
-
-If you only have five minutes, run **step 12**.
-
-#### 1. Type-check
-
-```bash
-npm run type-check:web-components
-echo $? # 0
-```
-
-**Success is silent** — `tsc` prints nothing and exits `0`. Any output at all means a failure. This
-checks two projects: the shipped library and the harness. The harness one was broken for a while and is
-now covered.
-
-#### 2. Lint
-
-```bash
-npx nx lint web-components
-npx nx lint design-core
-```
-
-Expect `Successfully ran target lint` from both, with no rule violations listed above it. `nx` prints a
-problem count only when there are problems.
-
-#### 3. Framework-agnostic core tests
-
-```bash
-npm run test:design-core
-```
-
-Expect **73 passed (6 files)**. Includes the `resetTo` action added because form reset was silently
-keeping a checkbox checked — see `FINDINGS.md` §17.1.
-
-#### 4. Component tests in a real browser
-
-```bash
-npm run test:web-components
-```
-
-Expect **35 passed (4 files)**, running in real Chromium. This is the suite that found the `gd-input`
-accessibility bug on its first run (§18.2). It covers the Input cursor guard, the checkbox
-`attribute: false` constraint, form participation, the shared-stylesheet cache, and axe.
-
-> Real Chromium is a constraint, not a preference — jsdom does not reliably implement Constructable
-> StyleSheets, the `popover` attribute, or Declarative Shadow DOM.
-
-#### 5. Bundle size and the regression gate
-
-```bash
-npm run check:web-components-size
-```
-
-Prints a per-atom Lit-vs-React table, then expect `✓ no bundle-size regressions`. Ballpark: 5 atoms at
-**~11 kB** gzip (**~18 kB** including the `lit` runtime) against **~106 kB** for React+Emotion —
-§3, §18.5.
-
-Exact totals drift by a few dozen bytes between builds because Rollup redistributes shared-helper bytes
-between chunks whenever any one chunk changes. That is why the gate's tolerance is 10% rather than 0 —
-see [`10-performance-report.md`](./docs/webcomponents-migration/10-performance-report.md).
-
-To prove the gate actually fails, edit a number down in
-`libs/web-components/bundle-size-baseline.json` and re-run: it should exit `1`.
-
-#### 6. Server rendering with zero client JavaScript
-
-```bash
-npm run check:web-components-ssr
-```
-
-Generates `libs/web-components/harness/ssr-dsd-static.html` (**0** `
-```
-
-A real theme is **mandatory**, not cosmetic: the components resolve the real token files, whose own
-fallbacks are debug placeholder strings, so a themeless render produces invalid CSS (**measured**,
-§§13, 16).
-
-### Lit
-
-```ts
-html`Save`;
-```
-
-### React
-
-```tsx
-import { createComponent } from '@lit/react';
-import * as React from 'react';
-import { GdCheckbox as GdCheckboxElement } from 'gd-design-web';
-
-export const GdCheckbox = createComponent({
- tagName: 'gd-checkbox',
- elementClass: GdCheckboxElement,
- react: React,
- events: { onGdChange: 'gd-change' },
-});
-
- setChecked(e.detail.checked)}>
- Accept terms
-;
-```
-
-The `events` map is required on **both** React 18 and 19.
-
-### Next.js (App Router)
-
-```tsx
-// app/page.tsx — server component: renders tags, imports nothing from the package
-Save
-
-```
-
-```tsx
-// app/client-island.tsx
-'use client';
-import { defaultTheme } from 'gd-design-library/tokens';
-import 'gd-design-web';
-```
-
-`'use client'` is **mandatory** — the token barrel cannot be imported in a server component
-(**measured**, §17.4). See `08-react-and-nextjs.md`.
-
-### Vue
-
-```vue
-
- Save
-
-```
-
-Vue handles custom elements natively; `.prop` forces property assignment for objects, and `@gd-change`
-binds custom events directly. **Reasoned, not verified** — no Vue fixture was built.
-
-### Angular
-
-```html
-Save
-```
-
-Requires `CUSTOM_ELEMENTS_SCHEMA` in the consuming module. **Reasoned, not verified** — no Angular
-fixture was built.
diff --git a/docs/webcomponents-migration/05-native-html-guidelines.md b/docs/webcomponents-migration/05-native-html-guidelines.md
deleted file mode 100644
index 34bb71d..0000000
--- a/docs/webcomponents-migration/05-native-html-guidelines.md
+++ /dev/null
@@ -1,158 +0,0 @@
-# 05 — Native HTML Versus Custom Element Guidelines
-
-**Owner:** CTORNDSD-646a · **Answers:** CTORNDSD-646 acceptance criterion 6 · **Status:** Delivered
-
-CTORNDSD-646 asks whether _all_ existing GridKit components should become custom elements. The
-answer is **no**, and the dividing line is empirical rather than stylistic.
-
-This analysis uses only components that already exist. No component was built for it.
-
-## The question that actually decides it
-
-Shadow DOM is not free. It buys style isolation — the measured fix for CTORNDSD-286 (**measured**,
-`FINDINGS.md` §1) — and it charges three things:
-
-1. **Light-DOM discoverability.** `document.querySelector('h1')` cannot reach a heading rendered
- inside a shadow root (**measured**, §5). The accessibility tree is unaffected — screen readers see
- a real heading — so this is a DOM-query gap, not an a11y regression. But SEO crawlers, link
- checkers, browser extensions, analytics selectors, E2E selectors, and testing-library shortcuts
- all use light-DOM queries.
-2. **A containing block per element.** A percentage width on a shadow-DOM child resolves against the
- host's box, and `:host` defaults to `auto`. This is not theoretical: `gd-select` collapsed to
- icon-only width and clipped its dropdown text for exactly this reason, fixed by setting an
- explicit width on the host (**measured**, §10).
-3. **Per-instance setup cost.** Custom-element upgrade plus shadow-root attachment, paid per node. At
- 300 instances this is measurable (**measured**, §14).
-
-So the decision rule is not "is this a component?" but **"does this element own behavior worth paying
-isolation for, and does anything outside need to find its internals?"**
-
-## Decision rule
-
-Apply in order; stop at the first match.
-
-| Ship | When |
-| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **Lit custom element** | It owns interaction state or behavior the browser does not provide natively, **and** it renders a visual surface that must survive a hostile host reset, **and** nothing external needs to query its internals by light-DOM tag or attribute |
-| **Native element + shared token CSS** | It renders a real semantic element whose **discoverability matters** (SEO, crawlers, link checkers, analytics, E2E selectors) — even if it carries a little behavior. Behavior small enough to express as a documented pattern or a thin hook does not justify a shadow root |
-| **No abstraction — shared utility CSS** | It carries no behavior **and** no visual surface of its own. Pure layout and spacing wrappers |
-| **Documentation only** | The value is a naming convention over CSS that already exists |
-| **React wrapper** | Orthogonal, not exclusive — applies **on top of** a Lit element whenever consumers are React. See `08-react-and-nextjs.md` |
-
-## Verdicts
-
-Behavior signals below are **measured** — line counts and hook/state/effect counts read from each
-component's `.tsx`. Bundle figures are **measured** from `FINDINGS.md` §3.
-
-| Group | Existing component(s) | Behavior signal | Verdict | Confidence |
-| --------------- | ----------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------- | --------------- |
-| Button | `atoms/Button` | 82 lines, 0 state | **Lit custom element** | High |
-| Input | `atoms/Input` | 106 lines, 0 state, 7 hooks | **Lit custom element** — conditional | High |
-| Select | `atoms/Select` | 533 lines, 10 state, 6 effects, 5 context | **Lit custom element** | High |
-| Checkbox | `atoms/Checkbox` | 97 lines, 2 state, 3 effects | **Lit custom element** — conditional | High |
-| Link | `atoms/Link` | 58 lines, 0 state | **Native `` + shared token CSS** | High |
-| Image | `atoms/Image` | 91 lines, 3 state | **Native `` + shared token CSS** | Medium |
-| Typography | `atoms/Typography` | 46 lines, 0 state | **Native element + shared token CSS** | High — measured |
-| Layout and grid | `layout/Row`, `layout/Column`, `layout/FlexContainer` | 38 / 41 / 30 lines, 0 state each | **No abstraction — shared utility CSS** | High — measured |
-| Containers | `atoms/Box`, `atoms/Wrapper` | 41 / 22 lines, 0 state | **No abstraction — shared utility CSS** | High |
-| Containers | `layout/ChatContainer` | 163 lines, 2 state, `useMediaQuery`, `useImperativeHandle` | **Lit custom element** — it is not a container | Medium |
-
-### Ship as Lit custom elements
-
-**Button.** The most-used interactive primitive, and CTORNDSD-286 was a button bug — style isolation
-is the whole point here. It carries variant, loading, and focus-ring states that native `