# Dual-Framework Rollout — Plan 1: Generator + Low-Risk Batch Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build a config-driven code generator that turns any canonical Vue SFC into a registered Web Component + idiomatic React wrapper + types + smoke test, then use it to ship the low-risk batch of components to dual-framework (React) support, each passing the existing React render-verification gate with 0 `A_TRUE_DRIFT_CANDIDATE`.

**Architecture:** The pilot proved (findings 2026-06-29) that the WC-registration + React-wrapper layer is *highly mechanical*: every wrapper is the same `useRef`+`useEffect` property-setter loop + event-listener cleanup + `<tvu-x ref>{children}</tvu-x>` JSX. We extract that mechanical layer into a generator driven by a per-component config (the only per-component input: tag, prop list, events/v-model, slots, canonical source, figma codeComponent). The 3 pilot components become the generator's golden fixtures (regenerating them must keep the React gate green). Then the low-risk batch is `add config → run generator → review diff → run gate → commit`, one component at a time.

**Tech Stack:** Vue 3.5 `defineCustomElement`; Vite lib build (`vite.web-components.config.ts` → `dist-wc/tvu-web-components.js`); React 19 wrappers (`react-pilot/`); Playwright render-verification (`tests/render-verification-react/`); vitest (generator unit tests + wrapper smoke tests); Node ESM (`.mjs`) for the generator script.

## Global Constraints

- **Figma is the single source of truth.** React goes through the *same deterministic render-verification gate* as Vue (same comparator, same 5% / ±1px tolerance, same `expectedFromFigma`). No AI free-play; no heuristic verification.
- **Do not touch the published Vue package or its build.** No edits to `src/index.ts` main exports, `package.json` main `exports`/`main`/`module`, or the main `vite.config.ts`. The WC build (`vite.web-components.config.ts`) and `react-pilot/` are fully independent.
- **Tokens load at the document root and inherit through shadow DOM.** `:root` inside a shadow root does NOT match — tokens MUST be at document root.
- **`style` is a React-reserved prop name.** Any canonical axis literally named `style` (e.g. Button: `'filling' | 'ghost' | 'rimless'`) is renamed on the React surface to `variant` via a single-source reserved-name remap table. The underlying custom-element property stays `style`. Vue keeps `style` unchanged.
- **React token delivery:** the React package exports an importable stylesheet (`styles.css`) that the consumer imports once at app root (mirrors Vue's `import variables.css`). NO component-internal side-effect injection.
- **Each component MUST pass** `pnpm test:render-verification-react` with `A_TRUE_DRIFT_CANDIDATE === 0` before its commit. Pre-existing B-class schema gaps (e.g. `button-url-link`) are allowed only where the Vue gate also fails the identical check.
- **Commit discipline:** Owner commits direct to `master` and **push includes both remotes** (Gitea + GitHub). `.vue` / `.tsx` commits require `VISUAL_COMMIT_APPROVED=1` (Owner has approved visuals for this rollout). **Executor/implementer subagents NEVER commit/push** — they report diff + stdout + verification, plan owner + Owner ack, then commit. Serialize commits. Never parallel-dispatch implementers that touch the same files.
- **Subagent dispatch hard constraints (every implementer/reviewer prompt):** do the work yourself; do NOT spawn nested agents; do NOT return a plan — return real results + the exact files changed + verification stdout; if asked to commit, refuse (plan owner commits).

---

## File Structure

| File | Responsibility | New/Modify |
|---|---|---|
| `src/web-components/components.config.ts` | **Single source** of per-component generation configs (typed). Adding a component = add one entry here. | Create |
| `src/web-components/reserved-names.ts` | React reserved-name → safe-name remap table (`style`→`variant`, extensible). | Create |
| `scripts/generate-react-bindings.mjs` | Generator CLI: reads configs, emits `register.ts`, each `wrappers/<Name>.tsx`, `wrappers/types.ts`, `wrappers/<Name>.test.tsx`. Pure template functions exported for unit test. | Create |
| `scripts/lib/react-binding-templates.mjs` | Pure string-template functions (wrapper / types / register / test). Imported by the generator + unit-tested directly. | Create |
| `tests/generate-react-bindings.test.ts` | vitest unit tests on the pure template functions (config → expected code snippets). | Create |
| `scripts/audit-binding-config-parity.mjs` | Audit: every config's prop list matches the canonical SFC `defineProps` (drift guard — keeps SFC the true source). | Create |
| `src/web-components/register.ts` | Becomes **generated output** (header marks it generated). | Modify (regenerate) |
| `react-pilot/src/wrappers/*.tsx` + `types.ts` | Become **generated output**; Button gains `variant` (was `style`). | Modify (regenerate) |
| `tests/render-verification-react/lib/drift-compare.ts` | Replace verbatim copy with re-export of a shared module (Q10). | Modify |
| `tests/visual-verify/lib/drift-compare-core.ts` | **Shared comparator** extracted from `tests/visual-verify/manifest-verifier.spec.ts`, imported by both Vue + React specs. | Create |
| `react-pilot/styles.css` (or `src/styles-entry.css`) | Importable token entry re-exporting `src/tokens/variables.css` for React consumers (decision d). | Create |
| `react-pilot/harness/RenderHarness.tsx` | Extend `adaptProps` + component registry to cover Phase-1 components (currently hardcodes Button/InputBoxLine/FormItem). | Modify |
| `tests/render-verification-react/react-drift-full.spec.ts` | Extend the `codeComponent` filter allow-list as Phase-1 components land. | Modify |
| `package.json` | Add `gen:react-bindings`, `audit:binding-config-parity`, `test:render-verification-react` scripts. | Modify |

---

## PHASE 0 — Generator Infrastructure

### Task 1: Per-component config schema + reserved-name table

**Files:**
- Create: `src/web-components/reserved-names.ts`
- Create: `src/web-components/components.config.ts`
- Test: covered by Task 3 unit tests (this task is pure data + types)

**Interfaces:**
- Produces: `RESERVED_NAME_REMAP: Record<string,string>` and `applyReservedRemap(propName: string): string`.
- Produces: `ComponentConfig` type and `COMPONENT_CONFIGS: ComponentConfig[]`. Consumed by Tasks 3, 6+.

```ts
// ComponentConfig shape (the ONLY per-component input the generator needs)
export interface PropConfig {
  name: string          // canonical defineProps name, e.g. "color", "style"
  tsType: string        // e.g. "ButtonColor", "string", "boolean"
  jsdoc?: string        // optional doc line
}
export interface VModelConfig {
  reactValueProp: string   // e.g. "value"
  reactChangeProp: string  // e.g. "onChange"
  elementProperty: string  // e.g. "modelValue"
  updateEvent: string      // e.g. "update:modelValue"
  detailIndex: number      // e.g. 0 (CustomEvent.detail[0])
  valueType: string        // e.g. "string"
}
export interface EventConfig {
  reactHandlerProp: string // e.g. "onClick"
  domEvent: string         // e.g. "click"
  handlerType: string      // e.g. "(e: Event) => void"
}
export interface ComponentConfig {
  name: string             // React export name, e.g. "Button"
  tag: string              // custom element tag, e.g. "tvu-button"
  canonicalImport: string  // SFC import name, e.g. "ButtonBridge"
  canonicalPath: string    // e.g. "../canonical/ButtonBridge.vue"
  figmaCodeComponent: string // manifest filter key, e.g. "Button"
  props: PropConfig[]      // simple value props (set via element property)
  events: EventConfig[]    // native/custom event → React handler prop
  vModel: VModelConfig | null
  hasDefaultSlot: boolean  // children
  namedSlots: string[]     // e.g. ["label"] for FormItem
}
```

- [ ] **Step 1: Write `reserved-names.ts`**

```ts
// src/web-components/reserved-names.ts
// React reserves `style` (CSSProperties), `className`, `key`, `ref`, `children`.
// A canonical design axis literally named one of these is renamed on the React
// surface only; the underlying custom-element property keeps its original name.
// Single source — the generator applies this to every component (anti-pattern #2: no per-component special-case).
export const RESERVED_NAME_REMAP: Record<string, string> = {
  style: 'variant',
}
export function applyReservedRemap(propName: string): string {
  return RESERVED_NAME_REMAP[propName] ?? propName
}
```

- [ ] **Step 2: Write the 3 pilot configs in `components.config.ts`**

Author Button / InputBoxLine / FormItem configs by mirroring their canonical `defineProps` (read `src/canonical/ButtonBridge.vue`, `InputBoxLine.vue`, `FormItem.vue`). Button's `style` prop stays `name: 'style'` in config — the generator applies the remap. Example (Button — fill the real prop list from the SFC):

```ts
import type { ComponentConfig } from './reserved-names' // (move the types here or to a shared types file)
export const COMPONENT_CONFIGS: ComponentConfig[] = [
  {
    name: 'Button', tag: 'tvu-button', canonicalImport: 'ButtonBridge',
    canonicalPath: '../canonical/ButtonBridge.vue', figmaCodeComponent: 'Button',
    props: [
      { name: 'color', tsType: 'ButtonColor' },
      { name: 'fixedWidth', tsType: 'ButtonFixedWidth' },
      { name: 'icon', tsType: 'ButtonIcon' },
      { name: 'radius', tsType: 'ButtonRadius' },
      { name: 'size', tsType: 'ButtonSize' },
      { name: 'status', tsType: 'ButtonStatus' },
      { name: 'style', tsType: 'ButtonStyle', jsdoc: 'Design-variant enum (filling/ghost/rimless) — exposed to React as `variant`, NOT CSS.' },
      { name: 'theme', tsType: 'ButtonTheme' },
    ],
    events: [{ reactHandlerProp: 'onClick', domEvent: 'click', handlerType: '(e: Event) => void' }],
    vModel: null, hasDefaultSlot: true, namedSlots: [],
  },
  // InputBoxLine: vModel = { reactValueProp:'value', reactChangeProp:'onChange', elementProperty:'modelValue', updateEvent:'update:modelValue', detailIndex:0, valueType:'string' }
  // FormItem: namedSlots: ['label'], hasDefaultSlot: true
]
```

- [ ] **Step 3: Commit**

```bash
git add src/web-components/reserved-names.ts src/web-components/components.config.ts
git commit -m "feat(wc): per-component generation config schema + reserved-name remap table"
```

---

### Task 2: Pure template functions

**Files:**
- Create: `scripts/lib/react-binding-templates.mjs`
- Test: `tests/generate-react-bindings.test.ts` (Task 3)

**Interfaces:**
- Produces (all pure, `config => string`): `renderWrapper(config)`, `renderTypesInterface(config)`, `renderRegisterEntry(config)`, `renderSmokeTest(config)`, plus `renderRegisterFile(configs)`, `renderTypesFile(configs)`. Consumed by Task 3 generator + Task 3 unit tests.

- [ ] **Step 1: Write `renderWrapper(config)`**

Emit the exact pilot pattern, parameterized. The mechanical core (verbatim across all pilot wrappers): `useRef` + a `useEffect` that sets each prop as an element *property* (`(el as any)[elementName] = value` guarded by `!== undefined`), a `useEffect` per event with add/removeEventListener cleanup, v-model as a controlled `value`→`elementProperty` effect + an `updateEvent` listener reading `detail[detailIndex]`, and `<tag ref={ref}>{children}</tag>`. The React prop name comes from `applyReservedRemap(prop.name)`; the element property name stays `prop.name`.

```js
// scripts/lib/react-binding-templates.mjs  (excerpt — property-setter loop)
// For each prop:  const reactName = applyReservedRemap(prop.name)
//   if (reactName !== undefined) (el as any)['<prop.name>'] = <reactName>
// JSDoc note injected when reactName !== prop.name (the rename footgun warning).
export function renderWrapper(config) { /* returns full .tsx source string */ }
```

- [ ] **Step 2: Write `renderTypesInterface`, `renderRegisterEntry`, `renderSmokeTest`, and file-level `renderTypesFile`/`renderRegisterFile`**

`renderTypesInterface`: a `interface <Name>Props { ... }` where each prop is `<reactName>?: <tsType>`, plus `children?: React.ReactNode` if `hasDefaultSlot`, plus event handler props, plus v-model's `value`/`onChange`. `renderRegisterFile(configs)`: the full `register.ts` with imports + `defineCustomElement` consts + `registerTvuElements()` defining every `tag`, ending with a generated-file header comment.

- [ ] **Step 3: Commit**

```bash
git add scripts/lib/react-binding-templates.mjs
git commit -m "feat(wc): pure string-template functions for React bindings"
```

---

### Task 3: Generator CLI + unit tests (TDD)

**Files:**
- Create: `scripts/generate-react-bindings.mjs`
- Create: `tests/generate-react-bindings.test.ts`
- Modify: `package.json` (add `"gen:react-bindings": "node scripts/generate-react-bindings.mjs"`)

**Interfaces:**
- Consumes: `COMPONENT_CONFIGS` (Task 1), template fns (Task 2).
- Produces: CLI that writes `src/web-components/register.ts`, `react-pilot/src/wrappers/<Name>.tsx`, `react-pilot/src/wrappers/types.ts`, `react-pilot/src/wrappers/<Name>.test.tsx`. Supports `--component <Name>` (single) and `--all`.

- [ ] **Step 1: Write the failing unit test**

```ts
// tests/generate-react-bindings.test.ts
import { renderWrapper, renderTypesInterface } from '../scripts/lib/react-binding-templates.mjs'
import { describe, it, expect } from 'vitest'
const buttonCfg = { name:'Button', tag:'tvu-button', props:[{name:'style',tsType:'ButtonStyle'},{name:'color',tsType:'ButtonColor'}], events:[{reactHandlerProp:'onClick',domEvent:'click',handlerType:'(e: Event) => void'}], vModel:null, hasDefaultSlot:true, namedSlots:[] }
it('renames reserved `style` to `variant` on the React surface but keeps element property `style`', () => {
  const tsx = renderWrapper(buttonCfg)
  expect(tsx).toContain('variant')                 // React-facing prop
  expect(tsx).toContain("(el as any).style = variant") // element property unchanged
  expect(tsx).not.toMatch(/\bstyle\?:/)            // no `style?:` in the React interface path
})
it('types interface exposes `variant`, not `style`', () => {
  const i = renderTypesInterface(buttonCfg)
  expect(i).toContain('variant?: ButtonStyle')
  expect(i).not.toContain('style?:')
})
it('v-model component emits controlled value/onChange + listens on updateEvent', () => {
  const inputCfg = { name:'InputBoxLine', tag:'tvu-input', props:[], events:[], vModel:{reactValueProp:'value',reactChangeProp:'onChange',elementProperty:'modelValue',updateEvent:'update:modelValue',detailIndex:0,valueType:'string'}, hasDefaultSlot:false, namedSlots:[] }
  const tsx = renderWrapper(inputCfg)
  expect(tsx).toContain('.modelValue = value')
  expect(tsx).toContain("addEventListener('update:modelValue'")
  expect(tsx).toContain('detail?.[0]')
})
```

- [ ] **Step 2: Run test, verify it fails**

Run: `pnpm vitest run tests/generate-react-bindings.test.ts`
Expected: FAIL (template fns not returning expected strings yet / import error).

- [ ] **Step 3: Implement template fns (Task 2 bodies) + generator CLI until tests pass**

Generator reads `COMPONENT_CONFIGS`, applies `--component`/`--all`, writes files via `fs.writeFileSync`, prints each written path. Each generated file starts with `// AUTO-GENERATED by scripts/generate-react-bindings.mjs — do not edit by hand. Source: src/web-components/components.config.ts`.

- [ ] **Step 4: Run tests, verify pass**

Run: `pnpm vitest run tests/generate-react-bindings.test.ts`
Expected: PASS (all 3+).

- [ ] **Step 5: Commit**

```bash
git add scripts/generate-react-bindings.mjs scripts/lib/react-binding-templates.mjs tests/generate-react-bindings.test.ts package.json
git commit -m "feat(wc): config-driven React-binding generator + unit tests"
```

---

### Task 4: Regenerate the 3 pilot components (golden integration proof)

**Files:**
- Modify (regenerate): `src/web-components/register.ts`, `react-pilot/src/wrappers/{Button,Input,FormItem}.tsx`, `react-pilot/src/wrappers/types.ts`
- Modify: `react-pilot/src/wrappers/Button.test.tsx` (and any consumer of `style`) → use `variant`
- Modify: any showcase/demo using Button `style` prop → `variant`

**Interfaces:**
- Consumes: generator (Task 3). Produces: regenerated pilot bindings; proof = React gate still 0 A_TRUE_DRIFT.

- [ ] **Step 1: Run generator for the 3 pilot configs**

Run: `pnpm gen:react-bindings --all`
Expected: writes register.ts + 3 wrappers + types.ts. `git diff` shows Button's `style`→`variant` rename; Input/FormItem semantically identical to hand-written.

- [ ] **Step 2: Update Button `style`→`variant` call sites**

Grep React-side usages: `grep -rn "style=" react-pilot/src react-pilot/harness playground/docs/pages/DualFrameworkPilotPage.vue` — change Button design-axis usages (`style="filling"`) to `variant="filling"`. Do NOT touch real CSS `style={{...}}` on non-TVU elements. Update `react-pilot/harness/RenderHarness.tsx adaptProps` for Button if it forwards `style`.

- [ ] **Step 3: Build WC + run the React gate**

Run: `pnpm build:wc && pnpm test:render-verification-react`
Expected: PASS, `A_TRUE_DRIFT_CANDIDATE === 0` (180 manifest entries; the 4 `button-url-link` B-class fails remain, identical to Vue gate — allowed).

- [ ] **Step 4: Run wrapper smoke tests**

Run: `pnpm --dir react-pilot test`
Expected: PASS.

- [ ] **Step 5: Commit** (requires `VISUAL_COMMIT_APPROVED=1`)

```bash
VISUAL_COMMIT_APPROVED=1 git add src/web-components/register.ts react-pilot/src/wrappers/ react-pilot/harness/RenderHarness.tsx playground/docs/pages/DualFrameworkPilotPage.vue
VISUAL_COMMIT_APPROVED=1 git commit -m "refactor(wc): regenerate pilot bindings from generator; rename Button style->variant"
```

---

### Task 5: Extract shared comparator (Q10)

**Files:**
- Create: `tests/visual-verify/lib/drift-compare-core.ts`
- Modify: `tests/visual-verify/manifest-verifier.spec.ts` (import from core)
- Modify: `tests/render-verification-react/lib/drift-compare.ts` (re-export core)

**Interfaces:**
- Produces: `drift-compare-core.ts` exporting `buildChecks`, `classifyFailedCheck`, comparator constants. Both specs import the same module — single source.

- [ ] **Step 1: Move the comparator logic into `drift-compare-core.ts`** (the functions currently duplicated verbatim between the Vue spec and `tests/render-verification-react/lib/drift-compare.ts`).
- [ ] **Step 2: Re-point both specs** to import from core; delete the verbatim copy body, keep a thin re-export in the React `lib/drift-compare.ts`.
- [ ] **Step 3: Run BOTH gates**

Run: `pnpm test:render-verification && pnpm audit:render-drift-gate && pnpm build:wc && pnpm test:render-verification-react`
Expected: both PASS, both `A_TRUE_DRIFT === 0` (no behavior change — pure extraction).

- [ ] **Step 4: Commit**

```bash
git add tests/visual-verify/lib/drift-compare-core.ts tests/visual-verify/manifest-verifier.spec.ts tests/render-verification-react/lib/drift-compare.ts
git commit -m "refactor(test): extract shared render-verification comparator (Q10)"
```

---

### Task 6: React token entry + config-parity audit

**Files:**
- Create: `react-pilot/styles.css` (`@import '../src/tokens/variables.css';`) — the documented consumer import.
- Create: `scripts/audit-binding-config-parity.mjs` + `package.json` script `audit:binding-config-parity`.

**Interfaces:**
- Produces: `styles.css` (decision d) + parity audit (config props ⊆ canonical defineProps, names match → keeps SFC the true source; fails on drift).

- [ ] **Step 1: Create `react-pilot/styles.css`** and document in a comment that React consumers `import '@nancyzeng0210/tvu-react/styles.css'` once at app root (final package name TBD at packaging phase; pilot path is fine here).
- [ ] **Step 2: Write `audit-binding-config-parity.mjs`** — for each config, read its `canonicalPath` SFC, extract `defineProps` member names (regex/AST), assert config `props[].name` is a subset and types are mirrored; exit 1 on mismatch. Run it; expect PASS for the 3 pilot configs.
- [ ] **Step 3: Commit**

```bash
git add react-pilot/styles.css scripts/audit-binding-config-parity.mjs package.json
git commit -m "feat(wc): React token entry (styles.css) + config-vs-defineProps parity audit"
```

> **Phase 0 gate (ROI checkpoint):** generator green, 3 pilot components regenerated and passing the React gate at 0 A_TRUE_DRIFT, shared comparator in place, parity audit green. **Plan owner reports ROI to Owner before starting Phase 1.**

---

## PHASE 1 — Low-Risk Batch (generator-driven)

These components hit only pilot-proven axes (props + native events + single/named slot + `update:*` v-model). No Teleport, no provide/inject, no canvas. Per-component task is mechanical; the template below is repeated for each.

**Batch list (15 components), ordered simplest-first:**

| Order | Component | canonical | Notable axis (all pilot-proven) |
|---|---|---|---|
| 1 | Badge | `Badge.vue` | default slot |
| 2 | PillStatus | `PillStatus.vue` | default slot |
| 3 | PillCounter | `PillCounter.vue` | default slot |
| 4 | Progress | `Progress.vue` | props only |
| 5 | Rating | `Rating.vue` | props (+ event — confirm emit) |
| 6 | Switch | `Switch.vue` | `update:status` v-model |
| 7 | CheckBox | `CheckBox.vue` | `update:status` v-model |
| 8 | Radio | `Radio.vue` | `update:status` v-model + slot |
| 9 | InputBoxFilled | `InputBoxFilled.vue` | sibling of pilot Input (`update:modelValue`) |
| 10 | InputNumber | `InputNumber.vue` | `update:modelValue` |
| 11 | Slider | `Slider.vue` | `update:modelValue` |
| 12 | Breadcrumb | `Breadcrumb.vue` | default slot |
| 13 | BreadcrumbItem | `BreadcrumbItem.vue` | default slot |
| 14 | Pagination | `Pagination.vue` | `update:modelValue` / events |
| 15 | TopBar | `TopBar.vue` | default slot |

> Each must already have ≥1 entry in `figma-data/render-verification-manifest.json` (Vue side). **Before** generating, the implementer confirms manifest coverage; if a component has 0 manifest entries it is render-gate-invisible — flag to plan owner (do NOT silently skip; this mirrors the Vue-side INFRA-F41 coverage gap, log it).

### Per-component task template (repeat for each of the 15)

**Files (component X):**
- Modify: `src/web-components/components.config.ts` (add X config)
- Generate: `react-pilot/src/wrappers/X.tsx`, update `types.ts`, `register.ts`, `X.test.tsx`
- Modify: `react-pilot/harness/RenderHarness.tsx` (add X to component registry + any `adaptProps` case)
- Modify: `tests/render-verification-react/react-drift-full.spec.ts` (add X to `codeComponent` allow-list)

- [ ] **Step 1: Read `src/canonical/X.vue` `defineProps` + emits + slots.** Author the X `ComponentConfig` in `components.config.ts`, mirroring defineProps exactly (apply v-model config if it emits `update:*`).
- [ ] **Step 2: Run parity audit**: `pnpm audit:binding-config-parity` → PASS (config matches SFC).
- [ ] **Step 3: Generate**: `pnpm gen:react-bindings --component X`. Review the diff — wrapper/types/register should be mechanical.
- [ ] **Step 4: Register X in the harness** (`RenderHarness.tsx` import + render switch; `adaptProps` if v-model needs `modelValue`→`value`) and add `'X'` to the spec's `codeComponent` filter list.
- [ ] **Step 5: Build + gate**: `pnpm build:wc && pnpm test:render-verification-react`. Expected: PASS, `A_TRUE_DRIFT_CANDIDATE === 0` for X's manifest entries.
- [ ] **Step 6: Smoke test**: `pnpm --dir react-pilot test` → PASS.
- [ ] **Step 7: Commit** (one component per commit, serialized; `VISUAL_COMMIT_APPROVED=1`):

```bash
VISUAL_COMMIT_APPROVED=1 git add src/web-components/components.config.ts react-pilot/src/wrappers/ react-pilot/harness/RenderHarness.tsx tests/render-verification-react/react-drift-full.spec.ts
VISUAL_COMMIT_APPROVED=1 git commit -m "feat(react): add dual-framework binding for X (gate green, 0 drift)"
```

> **Phase 1 gate:** all 15 low-risk components green at 0 A_TRUE_DRIFT. This proves the generator's ROI on the bulk. Plan owner writes a short retrospection (any per-component quirk that needed a config knob the schema lacked → feeds the schema). Then start the high-risk plans below.

---

## Follow-up plans (NOT bite-sized here — they depend on Phase 0 generator output + Phase 1 learnings; writing fake TDD steps now would be placeholders)

Each gets its own `docs/superpowers/plans/` file once Phase 1 lands. Risk axes are now evidence-pinned (not findings §6's guesses):

- **Plan 2 — Teleport class:** PopupBox (`<Teleport to="body">` + `position:fixed` modal), SelectBoxBase + SelectBoxLine + SelectBoxFilled (Teleport dropdown + `update:modelValue`), DropDownListSelect (dropdown; confirm positioning), Tooltip (`position:fixed` anchored, no teleport — medium). **Key unknown:** teleported nodes land in document body (outside the custom element's shadow root) — verify they still inherit tokens (they will, tokens are at document root) and that the React gate can locate them (the comparator already has teleport selector-fallback experience). Button already teleports and passed → baseline de-risk.
- **Plan 3 — provide/inject composition:** Tab + TabList + TabItem, Steps + StepItem. **Key unknown:** if parent and children are *separate* custom elements, Vue `provide`/`inject` will NOT cross the shadow boundary between them. Options to validate: (a) register the whole family as ONE custom element (children are internal Vue components, not separate elements) so provide/inject stays inside one shadow root; (b) if React needs to compose them as separate elements, fall back to route-B for this family. This is the single most likely route-B trigger — verify early.
- **Plan 4 — canvas:** Chart (chart.js). **Key unknown:** canvas sizing/rendering inside shadow DOM. Verify the canvas gets correct dimensions and chart.js renders; render-verification on a canvas component is limited (computed style of the container, not pixel-diff of the canvas) — define the gate scope honestly.
- **Plan 5 (parallel, non-blocking) — node-level coverage (Q11 / INFRA-F41):** `data-figma-node-id` annotation deepens both Vue + React gates. Tracked with the Vue side; does not block the React rollout.

---

## Self-Review

**Spec coverage** (against findings §5–§7 + decisions): generator (decision 4) = Phase 0 Tasks 1–3; `style`→`variant` reserved-name remap (decision 2) = Task 1 + Task 3 tests + Task 4; React token entry (decision 3) = Task 6; shared comparator Q10 (decision 5) = Task 5; full-batch (decision 1) = Phase 1 low-risk + follow-up Plans 2–4; node coverage Q11 = Plan 5. Quirks Q3 (property-not-attribute), Q4 (v-model event name), Q5 (callback memoize — note in generated JSDoc), Q6 (`readonly` vs `readOnly`) = handled by generator templates/JSDoc. Q7 (bundle size) and Q8 (iframe theme) = packaging-phase, not this plan (noted, not dropped).

**Placeholder scan:** Phase 0 + Phase 1 contain real code/commands. Follow-up plans are deliberately NOT bite-sized and say so (with the concrete unknown each must resolve) — this is honest scoping, not a placeholder, because their steps genuinely depend on Phase 0 output.

**Type consistency:** `ComponentConfig` / `PropConfig` / `VModelConfig` / `EventConfig` used identically in Tasks 1, 2, 3, 6; `applyReservedRemap` defined Task 1, used Tasks 2–4; `renderWrapper`/`renderTypesInterface` defined Task 2, tested Task 3.

**Scope correction flagged to Owner:** real remaining count is **29** (32 canonical exports − 3 pilot), not 23; risk taxonomy corrected vs findings §6 (Notification/Message are inline, NOT overlay; provide/inject Tab/Steps is the real composition risk; only PopupBox/Select*/Tooltip+Button teleport/position-fixed).
