# Codex Prompt: Phase D2 — 6 个摆拍页补真交互 demo

Phase A 合规基线发现 6 个组件页**完全没有 v-model / 真交互 demo**——全是 prop 锁死的摆拍。本任务给这 6 页各加至少一个 v-model 绑定 + 实时回显的 demo card。

---

## 必读前置

1. [docs/internal/component-page-conformance-baseline-2026-04-28.md](../component-page-conformance-baseline-2026-04-28.md)
2. [playground/docs/pages/InputNumberPage.vue](../../../playground/docs/pages/InputNumberPage.vue)（90 分参考，line 110-130 是 v-model + 实时回显的样板）
3. [playground/docs/pages/SwitchPage.vue](../../../playground/docs/pages/SwitchPage.vue)（100 分参考，line 217-220）
4. [playground/docs/pages/RatingPage.vue](../../../playground/docs/pages/RatingPage.vue)（90 分参考，"Value Buttons" 段是交互桥接样板）

---

## 任务范围（精确 6 页）

| # | 页 | 路径 | 当前总分 |
|---|---|---|:--:|
| 1 | BadgePage | `playground/docs/pages/BadgePage.vue` | 70 |
| 2 | NotificationPage | `playground/docs/pages/NotificationPage.vue` | 60 |
| 3 | ProgressPage | `playground/docs/pages/ProgressPage.vue` | 60 |
| 4 | TablePage | `playground/docs/pages/TablePage.vue` | 60 |
| 5 | TopBarPage | `playground/docs/pages/TopBarPage.vue` | 60 |
| 6 | PromptMessagePage | `playground/docs/pages/PromptMessagePage.vue` | 60 |

每页加一个交互 demo 后，A4 维度 +20，5 页直接过 80 分线。

---

## 任务 1：每页加一个 "Try it" 交互 demo card

### 1.1 通用模板

在每个 page 的 §3 Capability 段（或对应位置）末尾，加一个 docs-demo-card：

```vue
<article class="docs-demo-card docs-demo-card--wide">
  <span class="docs-demo-card__title">{{ t('Try it', 'Try it 真交互试用') }}</span>
  <p class="docs-demo-card__summary">{{ t('Use the controls below to see <X> respond in real time.', '用下方控件实时驱动 <X> 状态变化。') }}</p>
  <!-- 真组件 + v-model -->
  <!-- 实时回显当前值 -->
</article>
```

### 1.2 每页具体 demo 设计

#### BadgePage（color × text 切换）

Badge 没 v-model（它是展示型组件），所以"交互"通过外部按钮控制 props：

```vue
<script setup lang="ts">
import { ref } from 'vue'
const badgeColor = ref<'Green' | 'Blue' | 'Red' | 'Orange' | 'Black'>('Green')
const badgeText = ref('NEW')
</script>

<template>
<article class="docs-demo-card docs-demo-card--wide">
  <span class="docs-demo-card__title">{{ t('Try it', 'Try it 真交互试用') }}</span>
  <Badge :color="badgeColor">{{ badgeText }}</Badge>
  <div style="display: flex; gap: 8px; margin-top: 12px">
    <button v-for="c in ['Green', 'Blue', 'Red', 'Orange', 'Black']" :key="c"
      @click="badgeColor = c as any"
      :style="{ fontWeight: badgeColor === c ? 'bold' : 'normal' }">
      {{ c }}
    </button>
  </div>
  <input v-model="badgeText" placeholder="text" style="margin-top: 8px" />
  <p style="margin-top: 8px; font-size: 12px; color: var(--text-2)">
    color = "{{ badgeColor }}", text = "{{ badgeText }}"
  </p>
</article>
</template>
```

#### NotificationPage（status × theme 受控切换）

```vue
<script setup lang="ts">
import { ref } from 'vue'
const notifStatus = ref<'info' | 'warning' | 'error'>('info')
const notifTheme = ref<'light' | 'dark'>('light')
const notifShown = ref(true)
</script>

<template>
<article class="docs-demo-card docs-demo-card--wide">
  <span class="docs-demo-card__title">{{ t('Try it', 'Try it 真交互试用') }}</span>
  <Notification v-if="notifShown" :status="notifStatus" :theme="notifTheme" @close="notifShown = false">
    {{ t('This is a sample notification message.', '这是一条示例通知消息。') }}
  </Notification>
  <p v-else>{{ t('(closed)', '(已关闭)') }} <button @click="notifShown = true">{{ t('Reopen', '重新打开') }}</button></p>
  <div style="display: flex; gap: 8px; margin-top: 12px">
    <button v-for="s in ['info', 'warning', 'error']" :key="s" @click="notifStatus = s as any">{{ s }}</button>
    <button @click="notifTheme = notifTheme === 'light' ? 'dark' : 'light'">toggle theme</button>
  </div>
  <p style="margin-top: 8px; font-size: 12px; color: var(--text-2)">
    status = "{{ notifStatus }}", theme = "{{ notifTheme }}"
  </p>
</article>
</template>
```

#### ProgressPage（slider 控制 value）

```vue
<script setup lang="ts">
import { ref } from 'vue'
const progressValue = ref(45)
</script>

<template>
<article class="docs-demo-card docs-demo-card--wide">
  <span class="docs-demo-card__title">{{ t('Try it', 'Try it 真交互试用') }}</span>
  <Progress :value="progressValue" />
  <input type="range" v-model.number="progressValue" min="0" max="100" style="width: 100%; margin-top: 12px" />
  <p style="margin-top: 8px; font-size: 12px; color: var(--text-2)">
    value = {{ progressValue }}%
  </p>
</article>
</template>
```

#### TablePage（点击行回显）

```vue
<script setup lang="ts">
import { ref } from 'vue'
const selectedRow = ref<Record<string, unknown> | null>(null)
const sampleData = [
  { name: 'Alice', role: 'Admin', status: 'Active' },
  { name: 'Bob', role: 'Editor', status: 'Inactive' },
  { name: 'Carol', role: 'Viewer', status: 'Active' },
]
</script>

<template>
<article class="docs-demo-card docs-demo-card--wide">
  <span class="docs-demo-card__title">{{ t('Try it', 'Try it 真交互试用') }}</span>
  <Table :columns="[
    { key: 'name', title: 'Name' },
    { key: 'role', title: 'Role' },
    { key: 'status', title: 'Status' },
  ]" :data="sampleData" />
  <div style="margin-top: 12px; display: flex; gap: 8px">
    <button v-for="row in sampleData" :key="row.name" @click="selectedRow = row">{{ row.name }}</button>
  </div>
  <p v-if="selectedRow" style="margin-top: 8px; font-size: 12px; color: var(--text-2)">
    selected: {{ selectedRow }}
  </p>
</article>
</template>
```

> **注意**：Table 当前没有原生 row click 事件，本 demo 用外部按钮触发 `selectedRow` 模拟行选择交互——这是合理的"外部受控真交互"。**不要**给 Table 组件本体加新 emit（超出 D2 范围）。

#### TopBarPage（title 输入 + tag 切换）

```vue
<script setup lang="ts">
import { ref } from 'vue'
const topbarTitle = ref('My Application')
const topbarTag = ref<'After Login' | 'Before Login'>('After Login')
</script>

<template>
<article class="docs-demo-card docs-demo-card--wide">
  <span class="docs-demo-card__title">{{ t('Try it', 'Try it 真交互试用') }}</span>
  <TopBar :title="topbarTitle" :tag="topbarTag">
    <template #left><span>TVU</span></template>
    <template #right><span>•••</span></template>
  </TopBar>
  <div style="display: flex; gap: 8px; margin-top: 12px">
    <input v-model="topbarTitle" placeholder="title" />
    <button @click="topbarTag = topbarTag === 'After Login' ? 'Before Login' : 'After Login'">toggle tag</button>
  </div>
  <p style="margin-top: 8px; font-size: 12px; color: var(--text-2)">
    title = "{{ topbarTitle }}", tag = "{{ topbarTag }}"
  </p>
</article>
</template>
```

#### PromptMessagePage（status + closable）

```vue
<script setup lang="ts">
import { ref } from 'vue'
const promptStatus = ref<'info' | 'success' | 'warning' | 'error'>('info')
const promptShown = ref(true)
</script>

<template>
<article class="docs-demo-card docs-demo-card--wide">
  <span class="docs-demo-card__title">{{ t('Try it', 'Try it 真交互试用') }}</span>
  <PromptMessage v-if="promptShown" :status="promptStatus" closable @close="promptShown = false">
    {{ t('This is a sample prompt message.', '这是一条提示信息。') }}
  </PromptMessage>
  <p v-else>{{ t('(dismissed)', '(已关闭)') }} <button @click="promptShown = true">{{ t('Show again', '再次显示') }}</button></p>
  <div style="display: flex; gap: 8px; margin-top: 12px">
    <button v-for="s in ['info', 'success', 'warning', 'error']" :key="s" @click="promptStatus = s as any">{{ s }}</button>
  </div>
  <p style="margin-top: 8px; font-size: 12px; color: var(--text-2)">
    status = "{{ promptStatus }}"
  </p>
</article>
</template>
```

> **注意 PromptMessageRuntime 是 dirty WIP**：本任务**只改 docs page**，不要动 [src/canonical/PromptMessage.vue](../../../src/canonical/PromptMessage.vue) 或 [src/components/PromptMessage/PromptMessage.vue](../../../src/components/PromptMessage/PromptMessage.vue)。如果 PromptMessage 组件目前 close 事件不工作，本 demo 仍可视觉运转——`@close` handler 设状态即可，不需要等组件本体修复。

---

## 任务 2：每页 grep 自验

```bash
# 每页都应至少出现一次 v-model
for f in BadgePage NotificationPage ProgressPage TablePage TopBarPage PromptMessagePage; do
  echo "=== $f ==="
  grep -c "v-model" "playground/docs/pages/${f}.vue"
done

# 期望每页输出 ≥ 1
```

任一页输出 0，**STOP 报告**。

---

## 任务 3：自验

```bash
pnpm exec vitest run tests/RemainingCanonicalPages.test.ts tests/Canonical.test.ts 2>&1 | tail -15
```

期望：测试全部通过。

如果某页加 demo 后旧测试失败（罕见），优先看是否本轮新加的 ref 名跟现有 demo 冲突——按需重命名，不改测试。

---

## 任务 4：execution report

输出：`docs/internal/phase-D2-execution-report.md`

```markdown
# Phase D2 Execution Report

跑时间：YYYY-MM-DD HH:MM:SS

## 6 页交互 demo 添加

### BadgePage
- 新增 `<script setup>` ref：badgeColor, badgeText
- Demo card：颜色按钮切换 + text 输入
- v-model 出现位置：line X

### NotificationPage
... [每页一段]

## v-model 验证
| Page | v-model 出现次数 |
|---|---:|
| Badge | N |
| ...

## 自验
- pnpm exec vitest tests/RemainingCanonicalPages tests/Canonical: [N / N]

## 异常项（如有）
[空 / 描述]
```

---

## 任务 5：⚠️ 不要 commit，留 dirty 给主 Session

**本任务严格禁止任何 git commit / git add 操作**——主 Session（用户 + Claude）会在所有 D 系列批次跑完后统一审查 + 一刀 commit。

具体禁止：
- ❌ 不要 `git add` 任何文件
- ❌ 不要 `git commit` 任何东西
- ❌ 不要 `git stash`
- ❌ 不要重置任何已有 dirty 文件
- ❌ 不要 `git restore` 已有 dirty 文件（除非确认是 audit 脚本副作用产生的，且本任务范围之外）

完成后工作区应该是：
- 6 个本任务改的 docs page 处于 dirty (modified) 状态
- 1 个新增的 execution report（untracked）
- 用户原有 8 个 dirty WIP 文件不受影响
- 其它原本 untracked 的 prompt 草稿 / spec 文件不受影响

如果发现 audit 脚本副作用产生 dirty 文件（如 `figma-data/published/icons/manifest.json` 仅时间戳变化），可以 `git restore` 该单一文件——但只限明确确认的 audit 副作用，不要扩散。

### 为最终统一 commit 准备的 message draft（**不要现在用**，仅记录在 execution report 里供未来引用）

```
docs(demo): add real interactive demos to 6 static pages (Phase D2)

Phase A baseline found 6 component pages with zero v-model
demos (all prop-locked static showcases): Badge, Notification,
Progress, Table, TopBar, PromptMessage.

Each page now has a "Try it" demo card with:
- Real component instance with v-model or controlled props
- Interactive controls (buttons / inputs / sliders)
- Live state readout below

Verified: each page has ≥1 v-model occurrence; focused vitest
tests/RemainingCanonicalPages.test.ts + tests/Canonical.test.ts
all green.
```

---

## 三段式自报告

按 [docs/docs-site-dx-parity-spec.md](../../docs-site-dx-parity-spec.md) §7 要求：

```markdown
## 已动手
- 6 页加 "Try it" demo card 含 v-model + 实时回显
- 写了 D2 execution report

## 仅诊断未动手
- [如有]

## 已知未解决
- [如有]
```

---

## 禁止

- ❌ 不动任何 `src/` 下组件
- ❌ 不动用户 WIP 的 8 个 dirty 文件
- ❌ 不要给组件本体加新 emit / prop（demo 只用现有 API）
- ❌ 不要把交互逻辑实现在组件内部——demo 用外部 ref 控制
- ❌ 不要修测试（除非 demo ref 名冲突）
- ❌ 不要 sneak D1（API 表）/ D3（5 段结构）/ D4（FormItem）的工作进来
- ❌ 不要给 PromptMessageRuntime 修 bug——只用 docs demo

---

## 完成后

1. 写完 execution report 后 **STOP**
2. **不要** commit，工作区保持 dirty
3. 把执行报告路径告诉用户
4. 报告里**显式列出**改动的 6 个 page 文件路径，方便主 Session 后续审 diff 时定位
5. 等主 Session 决定下一步（可能是继续 D3 / D4，也可能开始统一 commit 审查）

完成后 STOP。
