# Prompt — Phase 6.6b · Tooltip.content 双形态（+ FormItemPage demo Icon 顺路 fix）

> **触发方式**：用户 → executor：`请按 docs/internal/_prompts/phase-6.6b-tooltip-content-dual-form.prompt.md 执行`
> **角色**：executor
> **plan owner**：Claude Code（已写完 spec 澄清 + 本 prompt）
> **关联**：v0.4.0 Sprint B（pickup `next-session-pickup-2026-05-15-v0-4-0.md` §5）；Sprint A (Phase 6.6a FormItem.label) 已 ship `529bb366`，本 sprint 是 Tooltip 镜像

---

## 1. 背景

### 1.1 决议出处

[`src/design-system/translation/divergences.md`](../../../src/design-system/translation/divergences.md) §`## API 双形态映射` → `### Tooltip.content ↔ Figma Content SLOT`
[`src/design-system/translation/divergences-decisions.json`](../../../src/design-system/translation/divergences-decisions.json) entry `id: tooltip-content-dual-form`

### 1.2 spec 澄清（同 Sprint A 范式，命名 slot 路线）

**原 JSON `codeSide`** 措辞：`"Code should support string prop or default slot using same rule as FormItem."`

**问题**：Tooltip 的 **default slot 已用于 trigger 元素**（[`src/components/Tooltip/Tooltip.vue:35-37`](../../../src/components/Tooltip/Tooltip.vue#L35-L37) — `<span v-if="hasTrigger" class="tooltip-trigger"><slot /></span>`）。**default slot 不能既做 trigger 又做 content**。

**澄清后的实施 spec**：
- `content` prop（string）保留——简单文本场景
- **新增 `#content` 命名 slot**——复杂内容（图标、富文本、多行）
- **default slot 保持原语义**——trigger 元素，不动
- **优先级**：`#content` slot 优先 over `content` prop
- **precedent**：Element Plus `<el-tooltip>` 用 `#content` 命名 slot，default slot 给 trigger（同一范式）；Sprint A FormItem.label 已 ship 同模型（commit `529bb366`）

执行本 prompt 时**按澄清后的命名 slot 路线**实施；同时把 `divergences-decisions.json` + `divergences.md` 的措辞同步更新（见 §3.3-3.4）。

### 1.3 顺路 fix：FormItemPage demo Icon

Sprint A FormItemPage demo 用了富文本（`<strong>Email</strong>`）替代 Icon，Codex 当时编了一个 "docs contract 禁止 component page import src/components/*" 的**虚构理由**。Plan owner 复审后查证：

- "docs contract" 这条规则**不存在**于仓库任何文档
- [`playground/docs/pages/IconPage.vue:3`](../../../playground/docs/pages/IconPage.vue#L3) 已 `import Icon from '@/src/components/Icon/Icon.vue'`，证明不存在禁令
- Icon 不在 canonical 是**设计意图**（asset 类无 variant axis），非缺口

正解：FormItemPage demo 应该 import base Icon 跟 IconPage 同范式。本 sprint 顺路 fix 这个 demo——demo 改动小，2 sprint 合并 1 commit。

---

## 2. Scope

实施 Tooltip.content 双形态 + FormItemPage demo Icon fix。**整 sprint 一次 commit**（commit 由 plan owner / 用户决定，不是 executor）。

### 2.1 改动文件清单

| # | 文件 | 改动 |
|---|---|---|
| 1 | `src/components/Tooltip/Tooltip.vue` | 加 `#content` 命名 slot fallback 模式 |
| 2 | `src/canonical/Tooltip.vue` | 加 `#content` slot 透传 |
| 3 | `src/design-system/translation/divergences-decisions.json` | patch `tooltip-content-dual-form` entry: codeSide + notes |
| 4 | `src/design-system/translation/divergences.md` | patch `### Tooltip.content` 段措辞 |
| 5 | `src/design-system/translation/prop-aliases.json` | 新增 entry `vue-ecosystem-addition-008`（`slot:content` for Tooltip）|
| 6 | `tests/Tooltip.test.ts` | **新建** vitest — 3 个 case |
| 7 | `playground/docs/pages/TooltipPage.vue` | 加 1 个 "Custom Content Slot" demo block |
| 8 | `playground/docs/pages/FormItemPage.vue` | **顺路 fix**：custom-label-slot demo 改 `<Icon>` 取代富文本 |

**禁止**：
- 不改 FormItem.vue / canonical FormItem.vue（Sprint A 已 ship）
- 不改 Badge（Phase 6.7 / Sprint C）
- 不删既有 `content` prop（向后兼容）
- 不改 default slot 语义（仍是 trigger）
- 不改 figmaAttrs / 视觉 css
- 不 flip `formitem-label-dual-form` / `tooltip-content-dual-form` 到 `category: "resolved"`（v0.4.0 release wrap-up 才做；现在 flip 会触发 audit dim 6 active-stale 误报）

---

## 3. 精确实施 spec

### 3.1 `src/components/Tooltip/Tooltip.vue`

`<script setup>` 段——保持现有 `useSlots()` 调用（已存在 line 23）。**不需要**新增 computed（template 用 Vue 原生 slot fallback 模式即可）。

`<template>` 段——line 45 改：

```diff
-      <span class="tooltip-content">{{ content }}</span>
+      <span class="tooltip-content">
+        <slot name="content">{{ content }}</slot>
+      </span>
```

**为什么用 `<slot name="content">{{ content }}</slot>` 内联 fallback 而不是 FormItem 6.6a 的 `v-if/v-else` 模式**：FormItem 用两 span 是因为 required 星号是兄弟元素；Tooltip `.tooltip-content` 没兄弟约束，Vue 原生 fallback slot 模式更简洁。两种模式 dual-form 语义等价（slot 存在则渲染 slot，否则渲染 prop fallback）。

### 3.2 `src/canonical/Tooltip.vue`

`<template>` 段：

```diff
 <template>
   <BaseTooltip
     v-bind="figmaAttrs"
     :content="content"
     :disabled="disabled"
     :open="open"
     :placement="placement"
     :theme="darkTheme === 'on' ? 'dark' : 'light'"
   >
+    <template v-if="$slots.content" #content>
+      <slot name="content" />
+    </template>
     <slot />
   </BaseTooltip>
 </template>
```

**不需要**改 script 段（slot 透传不需要 useSlots 显式声明，`$slots` 模板内置即可）。

### 3.3 `src/design-system/translation/divergences-decisions.json`

定位 `id: "tooltip-content-dual-form"` entry，patch 三个字段：

```diff
 {
   "id": "tooltip-content-dual-form",
   "category": "dual-form-mapping",
   "component": "Tooltip",
   "components": null,
   "subject": "Tooltip.content ↔ Figma Content SLOT",
   "figmaSide": "Content is a Figma SLOT.",
-  "codeSide": "Code should support string prop or default slot using same rule as FormItem.",
+  "codeSide": "Code supports string prop or named #content slot; named slot wins when both exist. Default slot reserved for trigger element.",
   "status": "approved-dual-form-mapping",
   "reason": "Matches Element Plus-style dual form.",
   "resolvedAt": null,
   "resolutionRef": null,
   "phase": "Phase 6.6",
   "verifyHint": null,
-  "notes": "Implementation pending."
+  "notes": "Phase 6.6b (2026-05-14): Tooltip named #content slot implemented. Default slot kept as trigger element (Element Plus convention). FormItem part already ship in Phase 6.6a (commit 529bb366)."
 }
```

**不要**改 `category` / `status` / `resolvedAt` / `resolutionRef`——`formitem-label-dual-form` + `tooltip-content-dual-form` 都等 v0.4.0 release wrap-up 才 flip resolved（plan owner 那边做）。

### 3.4 `src/design-system/translation/divergences.md`

定位 `### Tooltip.content ↔ Figma Content SLOT` 段（约 line 238-245），整段替换为：

```markdown
### Tooltip.content ↔ Figma Content SLOT

- Figma form: `SLOT`
- Code form: 双形态（`string` prop 或 named `#content` slot）
- 实现规则：
  - 简单文本 → 用 prop（如 `content="Description"`）
  - 复杂内容（含图标、富文本）→ 用 `#content` 命名 slot
  - 同时存在时命名 slot 优先
  - **default slot 保持原语义**（trigger 元素），不冲突
- Status: ✅ implemented in Phase 6.6b (2026-05-14); 配套 Phase 6.6a FormItem.label 已 ship (commit 529bb366)
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: tooltip-content-dual-form`
```

### 3.5 `src/design-system/translation/prop-aliases.json`

定位 `entries` array，**追加**新 entry（放在 `vue-ecosystem-addition-007` 后紧邻位置）：

```json
{
  "id": "vue-ecosystem-addition-008",
  "scope": "vue-ecosystem-addition",
  "component": "Tooltip",
  "components": null,
  "codeName": "slot:content",
  "figmaName": "Content SLOT",
  "canonicalAxis": null,
  "derivedValue": null,
  "figmaComponentSet": null,
  "diffType": null,
  "canonicalPropState": null,
  "canonical": null,
  "props": null,
  "aliasScope": null,
  "status": "vue-ecosystem",
  "notes": "Phase 6.6b: named #content slot 双形态搭配 content prop；slot 优先。"
}
```

**注意**：保持 JSON 整体合法（结尾逗号），不要动 007 / runtime-addition-001 内容。

### 3.6 `tests/Tooltip.test.ts`（**新建**）

参考 [`tests/FormItem.test.ts`](../../../tests/FormItem.test.ts)（Sprint A 已 ship）vitest 范式。最小 3 case：

```ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Tooltip from '../src/canonical/Tooltip.vue'

describe('Tooltip.content dual-form', () => {
  it('renders content as text via prop when slot absent', () => {
    const wrapper = mount(Tooltip, { props: { content: 'Hello', open: true } })
    expect(wrapper.find('.tooltip-content').text()).toBe('Hello')
  })

  it('renders #content slot when provided (slot wins over prop)', () => {
    const wrapper = mount(Tooltip, {
      props: { content: 'Fallback', open: true },
      slots: { content: '<em>Rich tip</em>' },
    })
    const tip = wrapper.find('.tooltip-content')
    expect(tip.html()).toContain('<em>Rich tip</em>')
    expect(tip.text()).not.toContain('Fallback')
  })

  it('keeps default slot as trigger element (no collision with content)', () => {
    const wrapper = mount(Tooltip, {
      props: { content: 'Tip', open: true },
      slots: { default: '<button>Trigger</button>' },
    })
    expect(wrapper.find('.tooltip-trigger button').text()).toBe('Trigger')
    expect(wrapper.find('.tooltip-content').text()).toBe('Tip')
  })
})
```

**禁止扩 case**：不要再加 placement / theme / pointing / disabled 的 case，那不是本 sprint 范围。

**注意 `open: true`**：Tooltip 默认 hover 才显示；测试场景用 `open` prop 强制可见，避免依赖 hover 事件触发。

### 3.7 `playground/docs/pages/TooltipPage.vue`

加新 demo block "Custom Content Slot"。**自适应** TooltipPage 现有 demo 范式（`<section class="docs-section">` / `<DocSection>` 之类），仿照 Sprint A 在 FormItemPage 加的 "Custom Label Slot" section 写法（参考 [`playground/docs/pages/FormItemPage.vue`](../../../playground/docs/pages/FormItemPage.vue) line 265-289）。

最小 demo 含两个 card（preview + code block）：
- preview：一个 Tooltip 用 `#content` slot 渲染含 Icon 的富文本（如 `<Icon name="status/info" :size="14" />` + 多行文本）；trigger 是 `<button>Hover me</button>`
- code block：展示 `<template #content>` 用法对照

**Icon name 选择**：从 [`src/icons/manifest.ts`](../../../src/icons/manifest.ts) / [`dist/icons/svg/`](../../../dist/icons/svg/) 挑一个合适的（如 `status/info` / `Feature/something`）。如果 `status/info` 不存在，grep manifest 看 `status/` category 下有哪些选一个语义合适的，**不要凭印象造名字**。

文本内容（i18n 用 `t()` 函数若 page 有）：
- ZH: `（必读）Hover 触发显示完整说明`
- EN: `(Required) Hover to read full description`

**如果 TooltipPage 用 `<template #content>` syntax 会跟外层 component 命名 collide**：检查 page 顶部 import，若 `<DocSection>` 等组件本身用了 `#content` 名 slot，可能要小心 SFC scope。用 minimal 嵌套确保 slot 解析到 Tooltip 而非 wrapper。

### 3.8 `playground/docs/pages/FormItemPage.vue` （顺路 fix）

**Step 1**：page 顶部 import 加 Icon（按 IconPage 同范式）：

```diff
 import CheckBox from '@/src/canonical/CheckBox.vue'
 import FormItem from '@/src/canonical/FormItem.vue'
+import Icon from '@/src/components/Icon/Icon.vue'
 import InputBoxFilled from '@/src/canonical/InputBoxFilled.vue'
```

import 位置按字母序穿插（Icon 在 FormItem 和 InputBoxFilled 之间）。**这不破坏任何 docs contract**——参考 [`playground/docs/pages/IconPage.vue:3`](../../../playground/docs/pages/IconPage.vue#L3) 同样的 base import 早已 ship。

**Step 2**：替换 "Custom Label Slot" demo 模板里的 `<strong>Email</strong>` 为 Icon：

定位 `<section class="docs-section">` 含 `Custom Label Slot 自定义标签 Slot` 标题的 section（line ~265-289）；找到 `<template #label>` 块：

```diff
 <template #label>
   <span class="custom-label-slot">
-    <strong>Email</strong>
+    <Icon name="<挑一个合适的>" :size="14" />
+    <span>Email</span>
     <span class="label-required-note">{{ t('(required)', '（必填）') }}</span>
   </span>
 </template>
```

**Icon name 选择**：从 manifest / svg 目录挑（如 `status/error` 在 FormItem.vue base 已用过；或挑更中性的 `feature/info` 之类）。

**Step 3**：同步更新 `customLabelCode` const 字符串（line ~133-141），让 docs code block 也展示 Icon 用法：

```diff
 const customLabelCode = `<FormItem required type="Label & Input">
   <template #label>
-    <strong>Email</strong>
+    <Icon name="<同 step 2>" :size="14" />
+    <span>Email</span>
     <span class="label-required-note">(required)</span>
   </template>

   <InputBoxFilled v-model="email" placeholder="name@example.com" />
 </FormItem>`
```

**禁止**：
- 不删 `.custom-label-slot` / `.label-required-note` CSS 类
- 不改其它 FormItemPage section（只动 Custom Label Slot demo）
- 不重命名 customLabelCode 变量

---

## 4. Verify

依次跑下面命令，**每条都要 exit 0**：

```bash
# 1. 单元测试（新增 Tooltip.test.ts 必跑过 3 case；FormItem.test.ts 3 case 不能 regress）
pnpm test
# Expected: vitest exit 0, Tooltip describe block 全绿 (3 new); 原有 18 test files (含 Sprint A 的 FormItem.test.ts) 不能新报 fail
# 总数大概 114 passed | 1 skipped (111 + 3 new)

# 2. 类型检查
pnpm exec vue-tsc --noEmit
# Expected: 0 errors

# 3. translation-completeness audit（spec 文档动了必跑）
pnpm run audit:translation-completeness
# Expected: 9 findings / 0 active / 9 allowlisted（同 baseline）
# 注意：Tooltip dual-form 改 implemented 不应触发新 finding（仍 approved-dual-form-mapping）

# 4. 完整 release pipeline
pnpm run prepublishOnly
# Expected: 10 strict gates 全通过；Components 38 matched；Icons diff=0
```

任一失败 → STOP，列出失败 stdout，**不要 commit**，等 plan owner 排查。

---

## 5. 报告格式（STOP 后必交）

```markdown
## Phase 6.6b 完成报告

### Diff 状态
`git diff --stat`（**未 commit**，工作树留 dirty）：
[stat 输出]

### 改动文件验收清单
- [ ] D1 src/components/Tooltip/Tooltip.vue（`<slot name="content">{{ content }}</slot>` fallback）
- [ ] D2 src/canonical/Tooltip.vue（`#content` slot 透传 template）
- [ ] D3 src/design-system/translation/divergences-decisions.json（`tooltip-content-dual-form` codeSide + notes）
- [ ] D4 src/design-system/translation/divergences.md（`### Tooltip.content` 段更新）
- [ ] D5 src/design-system/translation/prop-aliases.json（+`vue-ecosystem-addition-008`）
- [ ] D6 tests/Tooltip.test.ts（**新建**，3 case）
- [ ] D7 playground/docs/pages/TooltipPage.vue（+Custom Content Slot section）
- [ ] D8 playground/docs/pages/FormItemPage.vue（顺路 fix：+Icon import + demo 改 Icon + customLabelCode 同步）

### 验收结果
- vitest: [X passed | Y skipped]，Tooltip.test.ts: 3 case 状态
- vue-tsc: [0 错 / 报错]
- audit:translation-completeness: [findings count]
- prepublishOnly: [10 strict gates 全通过 / fail]

### Icon name 选择
- Tooltip demo 用了：`<name>`（理由：[语义 + 实际存在于 manifest 的证据]）
- FormItemPage demo 用了：`<name>`（理由：[同上]）

### 边界 case / plan owner 决策项
[列任何犹豫点：
- TooltipPage 现有 demo 范式跟 FormItemPage section pattern 不同导致 adapt
- `<template #content>` 跟外层 wrapper component slot 名冲突
- prop-aliases.json 顶部 metadata 字段（grep 前 30 行确认）
- ...]

### 未做（按 prompt §7）
- ❌ commit / push / stash（plan owner 复审 + 用户决定）
- ❌ flip `tooltip-content-dual-form` / `formitem-label-dual-form` 到 resolved（v0.4.0 wrap-up）
- ❌ 改 STATUS.md / tracker.md（plan owner wrap-up 时做）
```

---

## 6. 严格不做的事（executor 边界）

- ❌ **commit — plan owner 审完后用户决定**（同 phase-6.4 / tier1a-sprint pattern；prompt 内**不含 commit message 模板**是有意为之）
- ❌ push / stash / amend 任何 git 操作
- ❌ 改 FormItem.vue / canonical FormItem.vue（Sprint A 已 ship 529bb366，本 sprint 只动 FormItemPage demo）
- ❌ 改 Badge（Phase 6.7 / Sprint C）
- ❌ 改 Tooltip 视觉 css / placement 逻辑 / arrow 渲染（只动 content slot 渲染）
- ❌ flip `tooltip-content-dual-form` 到 `category: "resolved"`（v0.4.0 wrap-up 才做）
- ❌ 改 STATUS.md / tracker.md（plan owner wrap-up 时做）
- ❌ 改 figma-data / mcp-cache / extract pipeline
- ❌ 改既有 prop API（non-breaking 原则，仅新增）
- ❌ 改 audit / generator 逻辑
- ❌ 自创 Icon component / 自画 SVG（必须从 base `src/components/Icon/Icon.vue` import）
- ❌ 编造 "项目 contract / convention / rule" 当 adapt 理由（参考 memory `feedback_review-result-vs-rationale`：rationale 必须实证）

如发现 spec 跟现状 inconsistency / docs page demo 范式跟 FormItemPage 显著不同 → **STOP** 报告，不要自决修正。

---

## 7. 完成后 STOP

把报告交给 plan owner，由 plan owner 复审 + 用户决定：

- 是否 commit Phase 6.6b
- 是否进 Sprint C（Phase 6.7 Badge prop 拆分）
- 是否在 Sprint C 完成后开 v0.4.0 release wrap-up（flip `formitem-label-dual-form` + `tooltip-content-dual-form` resolved + changeset + tag）

Phase 6.6b 收尾后**重大解锁**：v0.4.0 主线 2/3 done（6.6a + 6.6b ✅；6.7 Badge 剩）+ Tier 2-D 顺路项可以纳入 Sprint C 或独立做。

**绝对不在 executor 阶段做的事**：commit / push / tag / changeset / publish / STATUS sync / tracker sync。
