# v0.11.0 Release 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:** Ship one breaking-minor `0.11.0` that closes the publish gap (308 unreleased commits), splits Chart to a `./chart` subpath export so echarts leaves the main chunk, and adds `sideEffects` for consumer tree-shaking.

**Architecture:** Chart moves out of the barrel main entry into a dedicated `src/chart.ts` ESM subpath, built by a **second vite build pass** (`vite.chart.config.ts`, `emptyOutDir:false`) so index's existing ESM+UMD artifacts stay byte-identical. Chart CSS ships self-contained at `./chart/style.css`. tree-shaking verified by a repeatable isolated-consumer smoke that asserts echarts symbols are absent from `import { Button }`.

**Tech Stack:** Vue 3 SFC, Vite lib build, vite-plugin-dts, changesets, vitest, echarts (inline, now only in chart chunk).

## Global Constraints

- **Version bump:** `0.11.0` (minor). Pre-1.0 convention: breaking → minor. Do NOT hand-edit `package.json` version — changesets computes it from the minor changeset(s).
- **No self-commit (project override of the generic plan template):** Per `AGENTS.md` §标准闭环, an executor does NOT `git commit`/`push`. Each task's final step is: run verification, report diff + stdout, STOP for review. The plan owner commits after owner ack. (The `git commit` blocks below are the intended message/scope for that later commit, not an executor action.)
- **Figma is SoT:** no Chart visual changes; this is a packaging/export refactor only. No component prop/value edits.
- **Determinism:** all verification is build-artifact assertions + vitest + isolated-consumer smoke. No heuristic checks.
- **UMD preserved:** index's `dist/tvu-design-system.js` (ESM) + `.umd.cjs` (UMD) must remain unchanged. Chart is ESM-only (`dist/chart.js`).
- **Don't touch generated files by hand:** `src/tokens/variables.css` is emitted by `figma-sync/generate-tokens.mjs` — never hand-edit it.

---

## File Structure

- **Create** `src/chart.ts` — chart subpath entry: re-export Chart + import chart CSS.
- **Create** `vite.chart.config.ts` — second build pass for the chart entry (ESM-only, `emptyOutDir:false`).
- **Create** `.changeset/echarts-chart-subpath.md`, `.changeset/token-exports.md`, `.changeset/composition-exports.md`, `.changeset/i18n-locale.md`.
- **Create** `scripts/smoke-consumer-treeshake.mjs` — repeatable isolated-consumer tree-shaking assertion.
- **Modify** `src/index.ts` — remove Chart import (L23), named export (L67), `app.component('Chart', …)` (L134).
- **Modify** `package.json` — add `./chart` + `./chart/style.css` exports, `sideEffects`; append chart build pass to `build`/`prepare` scripts.
- **Modify** `docs/API_STABILITY.md` (L22 Chart row), `docs/MIGRATION_TO_V1.md` (0.11.0 section), `scripts/generate-render-verification-manifest.mjs` (L504-519 stale "Chart.js/canvas" comment).
- **Modify** docs-site `playground/docs/pages/ChartPage.vue` + `playground/docs/navigation.ts` only if they import Chart from the main entry (verify in Task 6).

---

## Task 1: Move Chart out of the barrel main entry

**Files:**
- Create: `src/chart.ts`
- Modify: `src/index.ts` (remove L23, L67, L134)
- Test: `tests/exports/chart-subpath.test.ts`

**Interfaces:**
- Produces: `src/chart.ts` default-less named export `{ Chart }`; main entry `src/index.ts` no longer exports `Chart` nor registers it in `install()`.

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

```ts
// tests/exports/chart-subpath.test.ts
import { describe, it, expect } from 'vitest'
import * as main from '../../src/index'
import * as chart from '../../src/chart'

describe('Chart subpath split', () => {
  it('main entry no longer exports Chart', () => {
    expect((main as Record<string, unknown>).Chart).toBeUndefined()
  })
  it('main install() does not register Chart', () => {
    const registered: string[] = []
    const fakeApp = { component: (name: string) => { registered.push(name); return fakeApp } }
    ;(main.default as { install: (a: unknown) => void }).install(fakeApp)
    expect(registered).not.toContain('Chart')
  })
  it('./chart entry exports Chart component', () => {
    expect(chart.Chart).toBeDefined()
  })
})
```

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

Run: `pnpm vitest run tests/exports/chart-subpath.test.ts`
Expected: FAIL — `./src/chart` cannot be resolved (file not created yet) / `main.Chart` is defined.

- [ ] **Step 3: Create `src/chart.ts`**

```ts
// Chart is a heavy component (echarts). It ships as a dedicated subpath export
// (`@nancyzeng0210/tvu-design-system/chart`) so echarts never enters the main
// bundle chunk for consumers who don't use charts. (v0.11.0, INFRA-F61.)
import './tokens/variables.css'
export { default as Chart } from './canonical/Chart.vue'
```

- [ ] **Step 4: Edit `src/index.ts` — remove the three Chart references**

Remove line 23: `import Chart from './canonical/Chart.vue'`
Remove `  Chart,` from the named-export block (currently line 67).
Remove `    app.component('Chart', Chart)` from `install()` (currently line 134).

- [ ] **Step 5: Run test, verify it passes**

Run: `pnpm vitest run tests/exports/chart-subpath.test.ts`
Expected: PASS (3 tests).

- [ ] **Step 6: Typecheck**

Run: `pnpm vue-tsc --noEmit`
Expected: no errors (no dangling `Chart` reference in `src/index.ts`).

- [ ] **Step 7: Report diff + stdout, STOP for review** (no self-commit; intended commit message: `feat(v0.11.0)!: split Chart to ./chart subpath, drop from main barrel`)

---

## Task 2: Second build pass for the chart entry (ESM + self-contained CSS)

**Files:**
- Create: `vite.chart.config.ts`
- Modify: `package.json` (`build` + `prepare` scripts)
- Test: build-artifact assertions (manual run in steps)

**Interfaces:**
- Consumes: `src/chart.ts` from Task 1.
- Produces: `dist/chart.js` (ESM), `dist/chart.d.ts`, `dist/chart/style.css`. Index artifacts (`dist/tvu-design-system.js`, `.umd.cjs`, `dist/style.css`) unchanged.

- [ ] **Step 1: Create `vite.chart.config.ts`**

```ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import dts from 'vite-plugin-dts'

// Second build pass: chart subpath entry. Runs AFTER the main `vite build`
// with emptyOutDir:false so it augments dist/ without wiping index artifacts.
// ESM-only (multi-format UMD is reserved for the single-entry index build).
export default defineConfig({
  plugins: [
    vue(),
    dts({ include: ['src/chart.ts'], insertTypesEntry: true }),
  ],
  build: {
    emptyOutDir: false,
    lib: {
      entry: fileURLToPath(new URL('src/chart.ts', import.meta.url)),
      formats: ['es'],
      fileName: () => 'chart.js',
    },
    cssCodeSplit: false,
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: { vue: 'Vue' },
        assetFileNames: (info) =>
          info.name && info.name.endsWith('.css') ? 'chart/style.css' : 'assets/[name]-[hash][extname]',
      },
    },
  },
})
```

- [ ] **Step 2: Append the chart pass to `build` and `prepare` scripts in `package.json`**

In `scripts.build`, insert `&& vite build --config vite.chart.config.ts` immediately after the main `&& vite build` and before `&& node figma-sync/generate-token-exports.mjs`. Do the same in `scripts.prepare`. Result (build):

```
"build": "vue-tsc --noEmit && vite build && vite build --config vite.chart.config.ts && node figma-sync/generate-token-exports.mjs && node figma-sync/generate-composition-exports.mjs && node figma-sync/build-icon-dist.mjs && pnpm build:playground",
```

- [ ] **Step 3: Run the full build**

Run: `pnpm build`
Expected: completes exit 0; second pass logs a chart build.

- [ ] **Step 4: Assert chart artifacts exist + index unchanged + echarts confined**

Run:
```bash
ls -la dist/chart.js dist/chart.d.ts dist/chart/style.css
grep -c "coordinateSystem\|getZr" dist/tvu-design-system.js  # index main ESM
grep -c "coordinateSystem\|getZr" dist/chart.js
```
Expected: three files exist; **index main ESM count = 0** (echarts gone from index); chart.js count > 0 (echarts present in chart).

> If index still contains echarts symbols, echarts is being pulled by something other than Chart in the barrel — stop and grep `src/index.ts` transitive imports for `echarts` before proceeding.

- [ ] **Step 5: Report diff + build stdout + artifact assertions, STOP for review** (intended commit: `feat(v0.11.0): chart second build pass — ESM + self-contained chart/style.css`)

---

## Task 3: package.json exports + sideEffects

**Files:**
- Modify: `package.json` (`exports`, add `sideEffects`)
- Test: `tests/exports/package-exports.test.ts`

**Interfaces:**
- Consumes: `dist/chart.js`, `dist/chart.d.ts`, `dist/chart/style.css` from Task 2.
- Produces: resolvable `./chart` + `./chart/style.css` subpaths; `sideEffects` field.

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

```ts
// tests/exports/package-exports.test.ts
import { describe, it, expect } from 'vitest'
import pkg from '../../package.json'

describe('0.11.0 exports', () => {
  it('exposes ./chart with import + types', () => {
    expect(pkg.exports['./chart']).toEqual({
      import: './dist/chart.js',
      types: './dist/chart.d.ts',
    })
  })
  it('exposes ./chart/style.css', () => {
    expect(pkg.exports['./chart/style.css']).toBe('./dist/chart/style.css')
  })
  it('declares sideEffects preserving CSS', () => {
    expect(pkg.sideEffects).toContain('**/*.css')
  })
})
```

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

Run: `pnpm vitest run tests/exports/package-exports.test.ts`
Expected: FAIL — keys undefined.

- [ ] **Step 3: Edit `package.json`**

Add to `exports` (after the `./composition/js` block):
```json
    "./chart": {
      "import": "./dist/chart.js",
      "types": "./dist/chart.d.ts"
    },
    "./chart/style.css": "./dist/chart/style.css"
```
Add top-level field (after `"exports": { … }`):
```json
  "sideEffects": ["**/*.css"],
```

- [ ] **Step 4: Run test, verify it passes**

Run: `pnpm vitest run tests/exports/package-exports.test.ts`
Expected: PASS (3 tests).

- [ ] **Step 5: Report diff + stdout, STOP for review** (intended commit: `feat(v0.11.0): add ./chart exports + sideEffects for tree-shaking`)

---

## Task 4: tree-shaking regression smoke (core DoD)

**Files:**
- Create: `scripts/smoke-consumer-treeshake.mjs`

**Interfaces:**
- Consumes: `pnpm build` output + `npm pack` tarball.
- Produces: a repeatable script asserting `import { Button }` bundles WITHOUT echarts, and `./chart` bundles WITH echarts. Uses only Node stdlib + `npm`/`npx` shells (respects `audit:scripts-stdlib`).

- [ ] **Step 1: Create `scripts/smoke-consumer-treeshake.mjs`**

```js
#!/usr/bin/env node
// Repeatable tree-shaking regression: pack HEAD, install into a temp consumer,
// build two variants, assert echarts confinement. (v0.11.0 DoD, INFRA-F61.)
import { execSync } from 'node:child_process'
import { mkdtempSync, writeFileSync, mkdirSync, readFileSync, readdirSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'

const repo = process.cwd()
const tgz = execSync('npm pack --ignore-scripts --silent', { cwd: repo }).toString().trim().split('\n').pop()
const tgzAbs = join(repo, tgz)

function buildVariant(appVue) {
  const dir = mkdtempSync(join(tmpdir(), 'tvu-ts-'))
  mkdirSync(join(dir, 'src'))
  writeFileSync(join(dir, 'package.json'), JSON.stringify({
    name: 'ts-smoke', private: true, type: 'module', version: '0.0.0',
    scripts: { build: 'vite build' },
    dependencies: { '@nancyzeng0210/tvu-design-system': `file:${tgzAbs}`, vue: '^3.5.34' },
    devDependencies: { '@vitejs/plugin-vue': '^6.0.6', vite: '^8.0.12' },
  }))
  writeFileSync(join(dir, 'vite.config.js'),
    "import {defineConfig} from 'vite'\nimport vue from '@vitejs/plugin-vue'\nexport default defineConfig({plugins:[vue()]})\n")
  writeFileSync(join(dir, 'index.html'),
    '<!doctype html><html><body><div id=app></div><script type=module src=/src/main.js></script></body></html>')
  writeFileSync(join(dir, 'src/main.js'),
    "import {createApp} from 'vue'\nimport App from './App.vue'\ncreateApp(App).mount('#app')\n")
  writeFileSync(join(dir, 'src/App.vue'), appVue)
  execSync('npm install --no-fund --no-audit', { cwd: dir, stdio: 'ignore' })
  execSync('npx vite build', { cwd: dir, stdio: 'ignore' })
  const assets = join(dir, 'dist/assets')
  let echarts = 0
  for (const f of readdirSync(assets).filter((n) => n.endsWith('.js'))) {
    const s = readFileSync(join(assets, f), 'utf8')
    echarts += (s.match(/coordinateSystem|getZr/g) || []).length
  }
  return echarts
}

const buttonOnly = buildVariant(
  "<script setup>\nimport { Button } from '@nancyzeng0210/tvu-design-system'\n</script>\n<template><Button>Hi</Button></template>")
const withChart = buildVariant(
  "<script setup>\nimport { Chart } from '@nancyzeng0210/tvu-design-system/chart'\n</script>\n<template><Chart :option=\"{}\" /></template>")

console.log(`[treeshake] Button-only echarts symbols: ${buttonOnly}`)
console.log(`[treeshake] Chart-subpath echarts symbols: ${withChart}`)
if (buttonOnly !== 0) { console.error('FAIL: echarts leaked into Button-only bundle'); process.exit(1) }
if (withChart === 0) { console.error('FAIL: Chart subpath did not bundle echarts'); process.exit(1) }
console.log('[treeshake] PASS')
```

- [ ] **Step 2: Run it against the current build**

Run: `pnpm build && node scripts/smoke-consumer-treeshake.mjs`
Expected: `Button-only echarts symbols: 0` / `Chart-subpath echarts symbols: >0` / `PASS`.

- [ ] **Step 3: Verify it respects the stdlib gate**

Run: `pnpm audit:scripts-stdlib`
Expected: PASS (script uses only `node:*` + shelling `npm`/`npx`; no disallowed imports).

- [ ] **Step 4: Report script + smoke stdout, STOP for review** (intended commit: `test(v0.11.0): repeatable consumer tree-shaking regression smoke`)

---

## Task 5: Changesets for the publish gap

**Files:**
- Create: `.changeset/echarts-chart-subpath.md`, `.changeset/token-exports.md`, `.changeset/composition-exports.md`, `.changeset/i18n-locale.md`

**Interfaces:**
- Consumes: nothing (documentation of shipped work).
- Produces: 4 changesets that, with the existing 4, roll the version to `0.11.0`.

- [ ] **Step 1: Create the four changeset files**

`.changeset/echarts-chart-subpath.md`:
```md
---
"@nancyzeng0210/tvu-design-system": minor
---

**BREAKING: Chart engine migrated chart.js → echarts; peerDeps reduced to `vue` only.** `chart.js` and `vue-chartjs` are removed from `peerDependencies`; consumers no longer install any chart peer library (echarts is bundled internally). **BREAKING: `Chart` moved to the `./chart` subpath export** — import it as `import { Chart } from '@nancyzeng0210/tvu-design-system/chart'` (+ `import '@nancyzeng0210/tvu-design-system/chart/style.css'`). The global `app.use(TVU)` install no longer registers `<Chart>`; register it locally. This keeps echarts out of the main bundle for consumers who don't use charts.
```

`.changeset/token-exports.md`:
```md
---
"@nancyzeng0210/tvu-design-system": minor
---

Add design-token subpath exports: `./tokens` (DTCG JSON, alias-preserving, dual-theme) and `./tokens/js` (resolved TS with `TokenName` union + `.d.ts`). Lets non-CSS toolchains consume the token tier from a single source.
```

`.changeset/composition-exports.md`:
```md
---
"@nancyzeng0210/tvu-design-system": minor
---

Add composition-contract subpath exports: `./composition` (JSON) and `./composition/js` (TS). Machine-readable component-composition rules (contains / contained_by / do-not-hand-compose) for page-level AI synthesis.
```

`.changeset/i18n-locale.md`:
```md
---
"@nancyzeng0210/tvu-design-system": minor
---

Add i18n locale module: `provideTvuLocale` / `useLocale` / `defaultLocale` / `TVU_LOCALE_KEY` + type `TvuLocale` (provide/inject). Component hardcoded aria-labels and pagination unit now read from the locale dictionary. Non-breaking — defaults are byte-equal to the previous English strings.
```

- [ ] **Step 2: Dry-run version to confirm 0.11.0**

Run: `pnpm changeset status --verbose`
Expected: aggregates to a `minor` bump → next version `0.11.0`.

- [ ] **Step 3: Report the four files + status output, STOP for review** (intended commit: `docs(v0.11.0): changesets for echarts/chart-subpath, token/composition exports, i18n`)

---

## Task 6: Documentation sync + README/GETTING_STARTED drift fix (Codex #1 + #4)

**Files:**
- Modify: `docs/API_STABILITY.md` (Chart row), `docs/MIGRATION_TO_V1.md` (0.11.0 section)
- Modify: `scripts/generate-render-verification-manifest.mjs` (L504-519 stale comment)
- Verify/modify: `playground/docs/pages/ChartPage.vue`, `playground/docs/navigation.ts` (Chart import path)
- Modify: `README.md` (broken `Input` import example L50 → real component; stale "no TS/JSON token export" L42 → shipped `./tokens`; React scope note), `docs/GETTING_STARTED.md` (token statement sync + React scope note)

**Interfaces:**
- Consumes: the export changes from Tasks 1-3.

> **Scope note (owner-approved 2026-07-13, Codex analysis):** F61 smoke + Codex both confirmed README L50 imports a non-existent `Input` and L42 says "no TS/JSON token export" though `./tokens` now ships. #4: React is icon-only cross-framework; components are internal experiment — say so, don't imply a `./react` component API.

- [ ] **Step 1: Check docs-site Chart import source**

Run: `grep -rn "Chart" playground/docs/pages/ChartPage.vue playground/docs/navigation.ts | grep -i import`
If they import Chart from `@/src/index` or the main barrel, repoint to `../../../src/chart` (or the docs-site's canonical alias). If they import from `src/canonical/Chart.vue` directly, no change needed.

- [ ] **Step 2: Update `docs/API_STABILITY.md`**

Find the Chart row (~L22) and append the subpath note, e.g. change `Chart (4 types …)` to `Chart (4 types …) — imported from \`./chart\` subpath (v0.11.0)`.

- [ ] **Step 3: Add 0.11.0 migration section to `docs/MIGRATION_TO_V1.md`**

Add a section documenting: (a) Chart import path change + chart.js/vue-chartjs peerDep removal; (b) F59 prop renames with NO runtime fallback — table:
```
Button  style→fill        (React: variant→fill)
Badge   tag→fill
Tab/TabList/TabItem  type→fill, property2→color; TabItem property1→state
Steps/StepItem  stepStyle→type; StepItem: style prop removed
InputNumber  property1→type
```
Note: enum values and Figma property names unchanged; old prop names now fall through to attrs (silently inert).

- [ ] **Step 4: Fix stale manifest comment**

In `scripts/generate-render-verification-manifest.mjs` (~L504-519), update the Chart entry comment that says "Chart.js"/canvas to reflect echarts SVG renderer.

- [ ] **Step 5: Fix README token/import drift (Codex #1)**

In `README.md`: (a) replace the broken `import { Button, Input } from ...` example (L~50) — `Input` is not exported; use a real component, e.g. `import { Button, InputBoxLine } from '@nancyzeng0210/tvu-design-system'` and update the surrounding snippet to use `<InputBoxLine>`. (b) Replace the L~42 statement "There is **no TS/JSON token export** yet …" with the shipped reality: tokens ship as CSS variables (`./style.css`) AND as a machine-readable export — DTCG JSON at `./tokens` + resolved TS at `./tokens/js`. Keep it factual; do not oversell.

- [ ] **Step 6: Sync GETTING_STARTED token statement + add React scope note (Codex #4)**

In `docs/GETTING_STARTED.md`: align any "token only ships as CSS" wording with the `./tokens` reality (mirror README Step 5). In both README and GETTING_STARTED, where React is mentioned, add an explicit scope note: **icons are cross-framework (React/ESM + static SVG), but the Vue components are not published as a React component API — React component support is an internal experiment, there is no `./react` export.** Don't remove existing accurate icon-in-React guidance; only bound the components claim.

- [ ] **Step 7: Run doc-sync + stale-anchor gates**

Run: `pnpm audit:doc-sync && pnpm audit:stale-anchors`
Expected: PASS. (If doc-sync flags a new cross-ref from the edits, follow its fix hint.)

- [ ] **Step 8: Report diffs + gate stdout, STOP for review** (intended commit: `docs(v0.11.0): chart subpath API/migration + fix README Input/token drift + React scope note`)

---

## Task 7: consumer-facing contract gate (Codex #1)

**Files:**
- Create: `scripts/audit-consumer-contract.mjs` (node stdlib only — respects `audit:scripts-stdlib`)
- Create: `tests/audit-consumer-contract.test.ts`
- Modify: `package.json` (add `audit:consumer-contract` script + append to `prepublishOnly`), `.husky/pre-commit` (run on README/GETTING_STARTED/package.json changes)

**Interfaces:**
- Consumes: real export surface from Tasks 1-3 (`src/index.ts` named exports + `src/chart.ts` + `package.json` `exports` keys).
- Produces: `checkConsumerContract(docText, { rootExports, subpaths, forbiddenPhrases })` pure fn returning `{ violations: string[] }`; exit 1 if any doc has violations.

**Goal:** mechanically prevent the drift F61/Codex found — a doc code example importing a name the package doesn't export, importing from a subpath that isn't declared, or asserting a capability that's no longer true.

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

```ts
// tests/audit-consumer-contract.test.ts
import { describe, it, expect } from 'vitest'
import { checkConsumerContract } from '../scripts/audit-consumer-contract.mjs'

const ctx = {
  rootExports: new Set(['Button', 'InputBoxLine']),
  subpaths: new Set(['.', './chart', './style.css']),
  forbiddenPhrases: ['no TS/JSON token export'],
}

describe('consumer contract', () => {
  it('flags an import of a name not exported from root', () => {
    const doc = "```ts\nimport { Button, Input } from '@nancyzeng0210/tvu-design-system'\n```"
    expect(checkConsumerContract(doc, ctx).violations.join(' ')).toMatch(/Input/)
  })
  it('flags an import from an undeclared subpath', () => {
    const doc = "```ts\nimport { X } from '@nancyzeng0210/tvu-design-system/nope'\n```"
    expect(checkConsumerContract(doc, ctx).violations.join(' ')).toMatch(/nope/)
  })
  it('flags a forbidden stale capability phrase', () => {
    const doc = 'Text: there is no TS/JSON token export yet.'
    expect(checkConsumerContract(doc, ctx).violations.join(' ')).toMatch(/no TS\/JSON token export/)
  })
  it('passes a clean doc', () => {
    const doc = "```ts\nimport { Button, InputBoxLine } from '@nancyzeng0210/tvu-design-system'\n```"
    expect(checkConsumerContract(doc, ctx).violations).toHaveLength(0)
  })
})
```

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

Run: `pnpm vitest run tests/audit-consumer-contract.test.ts`
Expected: FAIL — `checkConsumerContract` not exported.

- [ ] **Step 3: Implement `scripts/audit-consumer-contract.mjs`**

Export a pure `checkConsumerContract(docText, ctx)`:
- Scan fenced code blocks for `import { A, B } from '@nancyzeng0210/tvu-design-system<sub>'`. For each: resolve `<sub>` → subpath key (`''`→`'.'`, `/chart`→`'./chart'`). If subpath not in `ctx.subpaths` → violation. Else for a root (`.`) import, each named symbol not in `ctx.rootExports` → violation (skip type-only imports of documented types if needed — keep it simple: names only).
- Scan full text for each `ctx.forbiddenPhrases` substring → violation.
- Return `{ violations }`.

`main()`: build `ctx` from the real surface — `rootExports` by parsing `src/index.ts` export blocks (reuse the same regex style as existing audits) + include `src/chart.ts` names for `./chart`; `subpaths` = `Object.keys(pkg.exports)`; `forbiddenPhrases` = `['no TS/JSON token export', 'no TS/JSON token']`. Run `checkConsumerContract` over `README.md` + `docs/GETTING_STARTED.md`; print violations; exit 1 if any. Use only `node:fs`/`node:path`.

- [ ] **Step 4: Run test + run gate against real docs**

Run: `pnpm vitest run tests/audit-consumer-contract.test.ts` (4 pass), then `node scripts/audit-consumer-contract.mjs` (Expected PASS — Task 6 already fixed the real docs; if it flags something real, that's a Task 6 gap → report it).

- [ ] **Step 5: Wire into package.json + pre-commit**

Add `"audit:consumer-contract": "node scripts/audit-consumer-contract.mjs"` to scripts; append `&& pnpm run audit:consumer-contract` to `prepublishOnly`. In `.husky/pre-commit`, run it when `README.md`/`docs/GETTING_STARTED.md`/`package.json` are staged (mirror an existing conditional gate block).

- [ ] **Step 6: Verify stdlib + report, STOP for review**

Run `pnpm audit:scripts-stdlib` (PASS). Report. (intended commit: `feat(v0.11.0): consumer-facing contract gate — doc examples must import real exports`)

---

## Task 8: Full pre-flight verification

**Files:** none (verification only)

- [ ] **Step 1: Full build**

Run: `pnpm build`
Expected: exit 0.

- [ ] **Step 2: Full test suite**

Run: `pnpm vitest run`
Expected: all pass (≥ 515 + new tests from Tasks 1/3).

- [ ] **Step 3: prepublishOnly gate chain (dry)**

Run: `pnpm prepublishOnly`
Expected: all audits pass. If `audit:published-vs-code` flags the not-yet-published 0.11.0 delta, note it for the release step (expected, since publish happens after).

- [ ] **Step 4: tree-shaking regression**

Run: `node scripts/smoke-consumer-treeshake.mjs`
Expected: `PASS`.

- [ ] **Step 5: Consumer exports resolution smoke**

Pack + install into a temp consumer and node-resolve every subpath:
```bash
node -e "for (const s of ['.','./style.css','./chart','./chart/style.css','./tokens','./tokens/js','./composition','./composition/js','./eslint-plugin']) { try { require.resolve('@nancyzeng0210/tvu-design-system/'+s.replace(/^\.\/?/,'').replace(/^$/,'')) } catch(e){} }"
```
(Adapt to ESM `import.meta.resolve` in the temp consumer; the goal is: no `ERR_PACKAGE_PATH_NOT_EXPORTED` for any documented subpath.)

- [ ] **Step 6: Report full pre-flight results, STOP for owner ack.**

After owner ack, the release itself (changeset version + tag + publish CI) is a **separate owner-gated action** — NOT part of this plan. Do not publish here.

---

## Self-Review

**Spec coverage:** §2 goals → Tasks 1-3 (Chart split + sideEffects), Task 5 (publish gap changesets); §4.1 Chart split → Task 1; §4.2 vite build (D3) → Task 2; §4.3 CSS (D4, with documented safer deviation: keep chart token in main style.css too) → Task 2; §4.4 exports → Task 3; §4.5 changesets → Task 5; §4.6 migration → Task 6; §5 file sync → Task 6; §6 verification (tree-shaking DoD) → Tasks 4 + 7; §7 risks → covered by Task 2 Step 4 guard + Task 4/7 asserts. No gaps.

**Placeholder scan:** No TBD/TODO; every code step has concrete content. Task 6 Step 1/6-Step-5 are conditional (verify-then-edit) but give the exact grep + repoint target — acceptable (real codebase state must be read first).

**Type consistency:** `Chart` named export used consistently (Task 1 produces it in both `src/chart.ts` and asserts absence in `src/index`); `smoke-consumer-treeshake.mjs` echarts-symbol grep (`coordinateSystem|getZr`) matches Task 2 Step 4 assertion. Export keys `./chart`, `./chart/style.css` consistent across Tasks 2/3/7.

**Deviation from spec (noted):** §4.3/D4 suggested removing chart token from main `style.css`; plan keeps it (duplicate same-value CSS var is harmless, avoids editing generated `variables.css`). Chart remains self-contained via its own `./chart/style.css`.
