---
title: "refactor: Remove all emoji from agentkit"
date: 2026-07-02
type: refactor
status: in-progress
approved_at: 2026-07-02
progress:
  u1: done
  u2: mostly-done  # BoardBannerCard 已为纯文本，ReviewResultCard.vue + useMessageRenderer.ts:286/307/328 待改
  u3: mostly-done  # Python CLI 已完成，scripts/ 待补
  u4: done
  u5: not-started  # 简化为风格文档 + pre-commit grep
origin: user-direct (ce-plan invocation, 2026-07-02)
---

# refactor: Remove all emoji from agentkit

## Summary

`@board` 私董会数据恢复的修复已就位。但项目长期混用 emoji（专家头像、CLI 状态、私董会横幅、bitable 默认图标等），与既有的 Ant Design Vue Outlined 图标家族视觉风格不一致，且跨字体/OS 渲染不稳定。本次重构一次性把全部 emoji 收敛到三套等价替代：

- **头像/单字符位** → 统一走 `expertIdentity.ts` 风格的「CJK/ASCII 首字符大写」；YAML/DB 中仍是字符串字段，前端渲染照常。
- **横幅/卡片图标** → 改用 Ant Design Vue 组件（`BankOutlined`/`AuditOutlined`/`TeamOutlined` 等）。
- **CLI 状态标记** → Rich 文本标签（`OK`/`FAIL`/`WARN`） + Rich 颜色样式。

执行顺序按 5 个批次分阶段落地，每批可独立提交/回滚。

## Problem Frame

### 现状

全仓 22 个文件含 emoji 字符，覆盖 7 个语义类别（专家头像、bitable 默认、私董会横幅、辩论横幅、CLI 状态、终端提示、DB 默认值）。其中：

- `configs/experts/*.yaml` 15 个文件使用 emoji 作为 avatar
- `bitable/db.py` 用 `📋` 作 `icon` 字段的 DB default（与 `models.py:80` 已有 `"table"` 默认不一致）
- `chat.py` 把 `🏛️ 私董会开始：…` 写入 `SqliteConversationStore`，作为无障碍/纯文本回退
- `BoardBannerCard.vue`、`DebateBannerCard.vue`、`DebateConclusionCard.vue`、`UserBubble.vue` 直接渲染 emoji
- 4 个 CLI 文件用 `✓` `✗` `⚠` 作状态标记
- 3 个测试文件把 emoji 作为 fixture

### 触发原因

- 跨平台渲染不稳定：Linux 服务器无 Noto Color Emoji 时私董会横幅显示豆腐方块
- 视觉风格不一致：与项目其余 Ant Design Vue Outlined 图标家族（1.5px stroke）不协调
- DB schema 漂移：`bitable/models.py:80` 已迁到稳定 key `"table"`，但 `bitable/db.py:68,216` ORM/SQL 仍用 `📋`

### 期望效果

- 全部 emoji 字符从仓库代码中清除（注释、文档除外）
- 视觉风格统一为 Ant Design Vue Outlined + 字符首字母
- 旧 Bitable 行的 emoji 值通过前端 `resolveBitableIcon` 惰性回退到 `TableOutlined`，无需迁移
- CLI 输出在所有终端（包括无 emoji 字体）下稳定显示

## Key Technical Decisions

### KTD1：专家头像改为「首字符大写」策略，与 `expertIdentity.ts` 行为对齐

**决策**：YAML 中 `avatar` 字段从 emoji 改为对应专家名的首字符大写（中文取首个 CJK 字，英文取首字母大写）。保留 `IBoardExpert.avatar` 字段（不删），值由后端 `ExpertConfig` 解析或前端 `pickExpertIdentity` 兜底。

**理由**：
- `frontend/src/components/chat/helpers/expertIdentity.ts:55` 的 `pickExpertIdentity` 默认就是首字符策略
- 删除字段会破坏既有 WebSocket payload（`board_started.experts[].avatar`）契约
- 字符位渲染跨字体稳定

**取舍**：放弃了 emoji 头像的「人格化表达」，换取视觉一致性与跨平台稳定。Lead/主持人仍可通过 `color` token + tag 区分。

### KTD2：横幅/卡片图标改用 Ant Design Vue 组件，shell.avatar 改为 `Component` 类型

**决策**：`BoardBannerCard` / `DebateBannerCard` / `DebateConclusionCard` / `UserBubble` / `useMessageRenderer.ts` 引入 Ant Design Vue 组件（如 `<BankOutlined>`/`<AuditOutlined>`/`<TeamOutlined>`/`<CheckOutlined>`），把 shell.avatar 字段从 `string` 改为 `Component` 类型。

**理由**：
- 项目图标家族已统一为 Outlined（`Sidebar`、`Tabs`、`Tool call cards`）
- `useMessageRenderer.ts` 现有 `shell.avatar` 字段在 `MessageShell.vue` 渲染，与后端 `expert_avatar` 字段不冲突（那是单个专家的 avatar）

**影响**：`MessageShell.vue` 接收 shell 的 avatar 字段如果是 `Component` 类型则用 `<component :is="..." />`；如果是 `string`（首字母）则原样渲染。

### KTD3：CLI 状态标记用 Rich 文本标签 + 颜色，弃用 `✓` `✗` `⚠`

**决策**：`OK` / `FAIL` / `WARN` 作为文本标签，配合 Rich `[green]` / `[red]` / `[yellow]` 颜色。横幅标题用 `[OK 验收结果]` / `[WARN 风险标记]` 形式保留 emoji 缺失的视觉强度。

**理由**：
- 终端字体差异：Windows Terminal / 容器 tty / 旧版 macOS Terminal 对 Unicode 符号渲染不一致
- 颜色已能传达同样信息量
- 与 Python `rich` 库的最佳实践一致（标签 + 颜色）

### KTD4：Bitable DB 默认值从 `📋` 改为 `table`，旧数据惰性收敛

**决策**：
- `bitable/db.py:68` ORM `default="📋"` → `default="table"`
- `bitable/db.py:216` SQL `DEFAULT '📋'` → `DEFAULT 'table'`
- **不写迁移脚本** — 前端 `resolveBitableIcon` 已对未知值回退到 `TableOutlined`，下次访问旧行时自动收敛
- 新建行直接用 `table`；旧行 render 时也是 `TableOutlined`，零差异

**理由**：
- `bitable/models.py:80`（Pydantic）已经是 `"table"`，ORM 端对齐消除双默认值
- 避免引入 Alembic 迁移；旧数据下次访问收敛
- `bitable.ts:73` 和 `bitableIcons.ts:86` 已说明「legacy emoji 字符串自动回退」

**已知限制**：旧行在 DB 中仍是 `📋` 字符串。若未来要做 DB 备份快照或导出/导入，可能需要清理脚本。本次不处理。

### KTD5：App.vue 字体回退去掉 emoji 字体声明

**决策**：`App.vue:109-110` 中的 `'Apple Color Emoji'`、`'Segoe UI Emoji'`、`'Segoe UI Symbol'`、`'Noto Sans Emoji'` 字体回退删除。

**理由**：既然不再渲染任何 emoji 字符，浏览器/系统不会下载这些字体；删去减少 30KB+ 的潜在字体下载请求。保留 `sans-serif` 作为最终回退。

### KTD6：测试 fixture 同步更新，保持断言真实

**决策**：3 个测试文件中的 emoji fixture（`🤖` `🎯` `🐱` `💡` `🦊` `🐼` `🏛️`）改为对应的首字母大写（A/T/C/I/F/P 等），断言中的 `expect(avatars[0].textContent).toBe('🤖')` 同步改为 `'A'`。

**理由**：`restoreBoardStateFromMessages` 的 7 个新单元测试已锁定「缺 avatar/缺 color 走 fallback」行为，fixture 改字母不会绕过 fallback 链（仍由 `pickExpertIdentity(name)` 决定）。

## Scope Boundaries

### 包含

- 22 个源码文件中的 emoji 字符移除（不含文档注释）
- 5 批次的实施顺序与每批对应的文件清单
- DB 默认值从 emoji 改为稳定 key（无 schema migration）
- App.vue 字体回退清理
- 3 个测试文件 fixture 同步
- 验证：typecheck / pytest / vitest / 视觉冒烟

### 不包含（Deferred to Follow-Up Work）

- **emoji 自动检测**（CI 守卫 / pre-commit 钩子）— 防止新增 emoji 复发，作为后续独立计划
- **Bitable 旧行清理脚本**（DB 备份/导出场景下需要时再做）
- **`@ant-design/icons-vue` 包增量依赖** — 该包已是项目依赖（`bitableIcons.ts` 现有 import），无新增
- **CLI 国际化（i18n）** — `OK`/`FAIL`/`WARN` 目前硬编码中英混排；国际化是更大范围重构，本次不动
- **Tauri 桌面端 emoji 字体** — Tauri 客户端的 emoji 字体回退不在本计划范围
- **avatar 字段语义升级**（如改成 `avatar_url` 支持真实头像图片）— 是产品级决策，超出本次范围

## System-Wide Impact

| 受影响方 | 影响 |
| --- | --- |
| **终端用户（Web GUI）** | 私董会/辩论横幅、专家头像、Bitable 图标视觉变化；语义不变 |
| **终端用户（CLI）** | `agentkit` CLI 表格/状态标记从符号改为文本标签；颜色保留 |
| **终端用户（Tauri 桌面）** | 间接通过 Web GUI 同步 |
| **开发者** | 写新代码时不能直接用 emoji 字符；后续建议加 lint 规则 |
| **运营/部署** | 无（不涉及端口、配置、依赖增减） |
| **测试** | 3 个 vitest fixture 文件需同步更新断言；不影响测试覆盖度 |

## High-Level Technical Design

视觉风格统一（KTD1+KTD2）的目标是把混用状态收敛成两条规则：

```
┌─────────────────────────────────────────────────────┐
│  头像/单字符位                                       │
│  • YAML avatar 字段 → "S"/"B"/"P" 等首字符           │
│  • 前端 expertIdentity.ts 兜底（如缺 avatar）        │
│  • ExpertMessage/StickyModeHeader/BoardBannerCard   │
│    都按 string 渲染字符                              │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│  横幅/卡片图标                                       │
│  • BoardBannerCard: <BankOutlined />                │
│  • DebateBannerCard: <AuditOutlined />              │
│  • DebateConclusionCard: 4 个决策图标 = 4 个组件    │
│  • UserBubble command card: <BankOutlined /> 私董会 │
│                                <TeamOutlined /> 团  │
│  • useMessageRenderer shell.avatar 改为 Component   │
│    MessageShell 用 <component :is=...> 渲染          │
└─────────────────────────────────────────────────────┘
```

CLI 状态机收敛（KTD3）：

```
旧:                          新:
"✓" "✗" "⚠"                  "OK" "FAIL" "WARN"
[green]✓ X[/green]            [bold green]OK X[/bold green]
[red]✗ X[/red]                [bold red]FAIL X[/bold red]
[yellow]⚠ X[/yellow]          [bold yellow]WARN X[/bold yellow]
```

DB schema 收敛（KTD4）：

```
bitable/db.py:68      icon = Column(String, default="table")
bitable/db.py:216     icon VARCHAR DEFAULT 'table'
                                                  ↓
旧 DB 行: '📋'        → 前端 resolveBitableIcon 命中 DEFAULT_BITABLE_ICON='table'  → 渲染 TableOutlined
新 DB 行: 'table'     → 渲染 TableOutlined                                  （与旧行一致）
```

## Implementation Units

### U1. 数据/契约层 — avatar 字段、持久化文本、DB 默认值

**Goal**：把数据层/契约层的 emoji 全部收敛到字符串默认值或首字符。

**Files**：
- `configs/experts/*.yaml`（15 个文件：apple/private_board/team/tech_lead 等）— avatar 字段从 emoji 改首字符大写
- `src/agentkit/bitable/db.py:68` — ORM default `📋` → `table`
- `src/agentkit/bitable/db.py:216` — SQL `DEFAULT '📋'` → `DEFAULT 'table'`
- `src/agentkit/server/routes/chat.py:304,306` — `🏛️ 私董会开始：…` → `私董会开始：…`
- `src/agentkit/server/frontend/src/stores/chatStream.ts:1149` — `🏛️ 私董会开始：…` → `私董会开始：…`
- `src/agentkit/core/plan_schema.py:107,122` — `📋`/`⚠️` → `[Plan ...]`/`WARN`
- `src/agentkit/experts/registry.py:85` — 注释 `📊` 改为纯文字

**Approach**：
- YAML avatar 字段值：英文名取首字母大写（`steve_jobs` → `S`），中文名取首 CJK 字（`张三` → `张`）
- 后端 `chat.py` 与前端 `chatStream.ts` 的 `board_started` 持久化文本保持语义一致（去 emoji 字符本身不影响 message_type/board_started metadata）
- `plan_schema.py:to_readable()` 是给人工看的报告，文本用纯文字更稳

**Test scenarios**：
- **Happy path**：`configs/experts/steve_jobs.yaml` 加载后 `expert.avatar == "S"`
- **Happy path**：`FileModel` 新实例 `icon == "table"`
- **Happy path**：`_apply_v1_schema` 生成的表 `icon` 列默认值为 `'table'`
- **Edge case**：现有 emoji DB 行（如 `icon='📋'`）通过 `resolveBitableIcon` 仍渲染为 `TableOutlined`

**Verification**：
- `pytest tests/unit/experts -k "config or registry"` 通过
- `pytest tests/unit/bitable -k "schema or model"` 通过
- 启动 backend 调用 `@board`，确认 DB 中 `board_started` 消息 content 为 `私董会开始：...`（无 emoji）

---

### U2. 前端 UI 组件 — 横幅、卡片、命令 chip

**Goal**：把 emoji 图标改用 Ant Design Vue 组件。

**Files**：
- `src/agentkit/server/frontend/src/components/chat/messages/BoardBannerCard.vue` — `🏛️` → `<BankOutlined />`
- `src/agentkit/server/frontend/src/components/chat/messages/DebateBannerCard.vue` — `⚖` → `<AuditOutlined />`
- `src/agentkit/server/frontend/src/components/chat/messages/DebateConclusionCard.vue` — `decisionIcons` map 改为 Ant Design Vue 组件（`CheckOutlined`/`SwapOutlined`/`MinusOutlined`/`QuestionOutlined`）
- `src/agentkit/server/frontend/src/components/chat/messages/UserBubble.vue:143` — `'🏛️'`/`'👥'` 字符串 → `<BankOutlined />`/`<TeamOutlined />`
- `src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts:203,262` — **已完成**（`avatar: AuditOutlined`）
- `src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts:286` — `avatar: '◆'` → Ant Design 组件（如 `ApartmentOutlined` 用于协作图）
- `src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts:307` — `avatar: review.passed ? '\u2713' : '\u2717'` → `review.passed ? CheckOutlined : CloseOutlined`
- `src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts:328` — `avatar: '!'` → `WarningOutlined`（与 ReviewResultCard 的 risk_flagged 语义一致）
- `src/agentkit/server/frontend/src/components/chat/messages/ReviewResultCard.vue:42-44` — `'\u2713'`/`'\u2717'`/`'\u21bb'` → `CheckOutlined`/`CloseOutlined`/`ReloadOutlined`
- `src/agentkit/server/frontend/src/components/chat/messages/MessageShell.vue` — 检查 avatar 字段类型，Component 走 `<component :is="..." />`，string 走原样（已实现 Component vs string 分支）
- `src/agentkit/server/frontend/src/App.vue:109-110` — 字体回退删 emoji 字体声明

**Approach**：
- 引入 `BankOutlined` / `AuditOutlined` / `TeamOutlined` / `CheckOutlined` / `SwapOutlined` / `MinusOutlined` / `QuestionOutlined`（均已在 `@ant-design/icons-vue` 中，无需新装）
- `DebateBannerCard` 的 `<span class="...__icon">X</span>` 改为 `<component :is="X" class="...__icon" />`（BoardBannerCard 已为纯文本，不适用）
- `UserBubble` 的 command card 改造：`icon` prop 改为 `Component` 类型
- `useMessageRenderer` 中 `shell.avatar` 类型由 `string` 改为 `Component`（行 203/262 已完成，286/307 待改），其余 shell 仍用 string（首字母）
- `MessageShell.vue` 用 `v-if="typeof shell.avatar === 'string'"` 分支处理

**Test scenarios**：
- **Happy path**：`BoardBannerCard` 渲染时包含 `BankOutlined` SVG path
- **Happy path**：`DebateBannerCard` 渲染时包含 `AuditOutlined` SVG path
- **Happy path**：`DebateConclusionCard decision="adopt"` 渲染 `CheckOutlined`
- **Happy path**：`UserBubble` 解析 `@board:alice, bob` 时 command icon 为 `BankOutlined`
- **Edge case**：`shell.avatar` 是 string（如 `expert_initial`）时仍原样渲染，不被当作 component

**Verification**：
- `npm run typecheck` 通过
- `npx vitest run tests/unit/components/` 通过
- 浏览器打开 `/agent/chat` 触发 `@board`，确认横幅为纯文本（无 emoji）
- 触发辩论确认 `DebateBannerCard` 显示 `<AuditOutlined />`

---

### U3. CLI 控制台输出 + Shell/CI 脚本 — admin / chat / skill / benchmark / scripts

**Goal**：把 Rich 输出中的 `✓` `✗` `⚠` 改为 `OK`/`FAIL`/`WARN` 文本标签 + Rich 颜色；同时清理 shell/CI 脚本中的 emoji 状态标记。

**Files**：
- `src/agentkit/cli/admin.py:215,601` — 表格 `is_active` 列的 `✓`/`✗` → `OK`/`--`
- `src/agentkit/cli/chat.py:589,603,692,694,727,730` — 验收/阶段/风险标记全部收敛
- `src/agentkit/cli/skill.py:296` — `⚠ 以下为自动生成…` → `WARN 以下为自动生成…`
- `src/agentkit/cli/benchmark.py:895,989,2032,2733,2767,2784,2790` — 所有 `✓`/`✗`/`⚠` 收敛
- `scripts/dev-start.sh:88,91,123-132` — `✓`/`✗`/`○` → `[ OK ]`/`[FAIL]`/`[ .. ]`（与 KTD3 文本标签策略一致）
- `scripts/deploy.sh:16,21,36` — `❌`/`✅` → `[FAIL]`/`[OK]`
- `.gitea/workflows/deploy.yml:69` — `✅` → `[OK]`

**Approach**：
- 字符串替换策略：所有 `[green]✓ X[/green]` 改 `[bold green]OK X[/bold green]`；`[red]✗ X[/red]` 改 `[bold red]FAIL X[/red]`；`[yellow]⚠ X[/yellow]` 改 `[bold yellow]WARN X[/yellow]`
- 表格列中的 `✓`/`✗` 用 `OK`/`--`（失败用 `--` 而非 `FAIL` 因为列宽有限）
- 横幅标题的 `⚠ 风险标记` 改 `WARN 风险标记`（保留全部大写作为视觉强度替代）

**Test scenarios**：
- **Happy path**：`agentkit admin list-users` 输出包含 `OK` 而非 `✓`
- **Happy path**：`agentkit chat` 触发的 phase completion 打印 `[OK] 阶段名：summary`
- **Error path**：phase 失败时打印 `[FAIL] 阶段名：error`
- **Integration**：`agentkit benchmark` 在失败用例上打印 `[FAIL]`（无 emoji）

**Verification**：
- `pytest tests/unit/cli -k "admin or chat or skill or benchmark"` 通过
- 手动跑 `agentkit admin list-users` / `agentkit chat "test"` / `agentkit benchmark list` 确认输出无 emoji

---

### U4. 终端前端 + 测试 fixture + 字体回退

**Goal**：清理剩余的终端提示、测试 fixture、字体回退。

**Files**：
- `src/agentkit/server/frontend/src/stores/terminal.ts:251,262,266,270,275` — `⚠` `⏳` `✓` `✗` → `WARN` `PENDING` `OK` `REJECTED`/`TIMEOUT`
- `src/agentkit/server/frontend/src/components/terminal/CommandHistory.vue:20` — `✓`/`✗` → `OK`/`FAIL`
- `src/agentkit/server/frontend/tests/unit/stores/chatStream.test.ts:731` — `avatar: '🦊'` → `avatar: 'F'`
- `src/agentkit/server/frontend/tests/unit/stores/chatStore.test.ts:22` — `content: '🏛️ 私董会开始：…'` → `content: '私董会开始：…'`
- `src/agentkit/server/frontend/tests/unit/stores/chatStore.test.ts:33,40` — `avatar: '🦊'`/`'🐼'` → `avatar: 'F'`/`'P'`
- `src/agentkit/server/frontend/tests/unit/components/StickyModeHeader.test.ts` — 所有 emoji fixture 改首字母
- `src/agentkit/server/frontend/src/components/chat/messages/TeamPlanCard.vue:67,82,127` — 注释更新（已说明弃用，保留为历史说明即可，无运行影响）

**Approach**：
- 终端消息用 `[WARN]` / `[PENDING]` / `[OK]` / `[REJECTED]` / `[TIMEOUT]` 文本格式（与 CLI KTD3 保持一致）
- 测试 fixture 改为对应名称首字母（`StickyModeHeader.test.ts` 中 `avatar: '🤖'` → `avatar: 'A'`，`expect(avatars[0].textContent).toBe('A')`）
- `chatStore.test.ts` 的 `board_started` content 改 `私董会开始：AI 未来`（与 U1 后端持久化文本一致）

**Test scenarios**：
- **Happy path**：`CommandHistory` 渲染 exit_code=0 时显示 `OK`、非 0 时显示 `FAIL`
- **Happy path**：`StickyModeHeader.test.ts` 中 `expect(avatars[0].textContent).toBe('A')` 通过
- **Happy path**：`chatStore.test.ts` `restoreBoardStateFromMessages` 测试对新的非-emoji content 仍正确解析
- **Edge case**：终端 store 中 `appendOutput` 的 ANSI 颜色（`\x1b[33m`）保留，仅替换前缀文本

**Verification**：
- `npm run typecheck` 通过
- `npx vitest run tests/unit/` 全通过
- 终端面板审批流程人工冒烟

---

### U5. 端到端验证 + 防止复发（pre-commit grep 守卫 + 文档）

**Goal**：确认全仓无残留 emoji，并加入轻量防复发守卫。

**Files**：
- `scripts/check-no-emoji.sh`（新增）— 用 `rg` 扫描字面 emoji + 转义序列，覆盖 src/configs/tests/scripts
- `.pre-commit-config.yaml`（若不存在则创建）— 添加 `agentkit-no-emoji` 钩子（调用上述 shell 脚本）
- `docs/solutions/style/no-emoji-style-guide.md`（新增）— 记录三套替代策略（KTD1-3）与 Unicode 范围参考

**Approach**：
- **轻量方案**（替代原 ESLint + ruff 自定义规则，因项目无 ESLint 基础设施且 ruff 不支持自定义规则）：
  - shell 脚本用 `rg` 扫描两类模式：(a) 字面 emoji 字符 `[\x{1F000}-\x{1FFFF}\x{2600}-\x{27BF}\x{2300}-\x{23FF}\x{2500}-\x{25FF}\x{2190}-\x{21FF}\x{2B00}-\x{2BFF}]`；(b) 转义序列 `\\u(271[0-9a-fA-F]|26[0-9a-fA-F]{2}|2[0-5][0-9a-fA-F]{2}|21[0-9a-fA-F]{2})`
  - pre-commit 钩子在 commit 阶段跑该脚本，拦截含 emoji 的暂存文件
  - 白名单：`docs/`（文档允许 emoji）、`*.md`（markdown 注释允许）
- 风格指南文档说明三套替代策略（KTD1-3）+ 何时用 Ant Design 组件 vs 文本标签 vs 首字母

**Test scenarios**：
- **Happy path**：`bash scripts/check-no-emoji.sh` 退出码 0（无 emoji 残留）
- **Error path**：故意在某 `.ts` 文件中加 `\u2713` 转义序列，脚本检出并报错
- **Integration**：本地 commit 含 emoji 的文件被 pre-commit 阻止

**Verification**：
- `bash scripts/check-no-emoji.sh` 全通过
- `pre-commit run --all-files` 全通过（若 pre-commit 已安装）
- 手动在临时文件加 emoji 字符和 `\u2713` 转义，确认脚本检出

**Deferred to Follow-Up**：CI 工作流（`.gitea/workflows/`）添加 emoji 检查步骤 — 等 pre-commit 钩子稳定后再加。

---

## Risks & Dependencies

| 风险 | 缓解 |
| --- | --- |
| Ant Design Vue 图标视觉与原 emoji 风格差异大，用户不适应 | U2 完成后做视觉冒烟；如不接受可回退到 `string` 字符首字母策略（KTD1） |
| `useMessageRenderer.ts` 的 `shell.avatar` 改 `Component` 类型影响 `MessageShell.vue` 之外的消费者 | 全仓 grep `shell.avatar` / `message.shell.avatar` 确认仅 `MessageShell.vue` 消费 |
| CLI 输出改为 `OK`/`FAIL` 文本会改变宽度，可能破坏现有对齐 | 表格用 `--` 而非 `FAIL`（列宽更短） |
| Bitable 旧行不清理，未来 DB 导出/快照会有 `📋` 字符串 | 文档记录 KTD4 限制；后续可加迁移脚本 |
| pre-commit / 自定义 ESLint 规则本身可能误报（CJK 字符、注释中的 emoji） | 规则用明确的 Unicode range；白名单机制支持 `// allow-emoji` 行级豁免 |
| 测试 fixture 改首字母可能与某个测试断言的「fallback 行为」冲突 | U4 前先跑一遍 vitest 全量测试，建立基线；逐个 fixture 改并立即跑对应测试 |

**依赖关系**：
- U1 独立（数据/契约层）
- U2 独立（前端 UI）
- U3 独立（CLI）
- U4 依赖 U1（chatStore.test.ts content 需与 U1 后端持久化文本一致）
- U5 依赖 U1-U4 全部完成

**实施顺序**：U1 → U2 → U3 → U4 → U5。每批独立可提交/可回滚。

## Acceptance Examples

AE1. **私董会横幅显示矢量图标** — 触发 `@board 测试主题`，确认 `BoardBannerCard` 渲染 `<BankOutlined />` 矢量图标（SVG），不再显示 `🏛️` 字符。

AE2. **Bitable 默认图标稳定** — 创建新 Bitable 文件，`icon` 字段在 DB 中为 `'table'`，UI 渲染 `TableOutlined`。已存在的 emoji 行通过前端 `resolveBitableIcon` 自动收敛。

AE3. **CLI 状态无 emoji** — 运行 `agentkit admin list-users`，表格中 `is_active=true` 显示 `OK`，`is_active=false` 显示 `--`，无 `✓`/`✗` 字符。

AE4. **专家头像首字符** — `@board steve_jobs, charlie_munger` 私董会，`BoardBannerCard` 中 Steve Jobs 头像显示 `S`、Charlie Munger 头像显示 `C`（不再显示 `🍎`/`🧠`）。

AE5. **测试 fixture 与生产代码一致** — `npm run typecheck` + `npx vitest run` + `pytest tests/unit/` 三套全通过，无 emoji 残留。

AE6. **CI 守卫** — 故意在某 `.vue` 模板加 `🏛️` 字符，`npm run lint` 报错并阻止。

## Open Questions

1. **`useMessageRenderer.ts` 的 `shell.avatar` 类型升级是否需要更平滑**（保留 string 但用约定 `__ICON__BankOutlined`）？— 当前方案改为 `Component` 是更类型安全的，但若 `MessageShell.vue` 之外有第三方消费方可能破坏。**默认决定**：U2 实施前先全仓 grep 确认无外部消费者。

2. **Bitable `📋` 旧行是否要做一次性清理脚本**？— KTD4 决定惰性收敛，文档记录为 follow-up。如用户要求显式清理，U5 后可加一个 `agentkit bitable migrate-icons` 命令。

3. **`@ant-design/icons-vue` 包体积**（目前约 300KB+）— 项目已在用，仅新增 7 个组件，bundle size 影响可忽略。如未来有更激进的优化诉求可走按需 import + Vite tree-shaking（已默认开启）。

### ce-doc-review 2026-07-03 发现（P1-P2）

以下发现由 ce-doc-review 5 个 persona reviewer 在 2026-07-03 重新核查时识别，需在 U5 实施前决策。

**P1 发现（8 个）：**

4. **scripts/ 和 .gitea/ 中的 emoji 未纳入计划** — `scripts/dev-start.sh`（`✓`✗`○`）、`scripts/deploy.sh`（`❌`✅`）、`.gitea/workflows/deploy.yml`（`✅`）含与 CLI 同语义的状态标记，但不在 U1-U4 任何文件清单中。**决策**：将其加入 U3 或 U4 范围，沿用 KTD3 替换策略。

5. **"22 个文件"计数矛盾** — Problem Frame 声称 22 个文件，类别求和为 28，U1-U4 文件清单求和为 37。**决策**：重新统计并订正。

6. **pre-commit 钩子范围矛盾** — "不包含"段将 pre-commit 列为后续独立计划，但 U5 明确包含 `.pre-commit-config.yaml`。**决策**：从"不包含"删除 pre-commit 提及，明确 U5 包含 pre-commit，仅 CI 工作流延期。

7. **ESLint 未安装** — 仓库无 ESLint 配置、无 `eslint` 依赖、`package.json` 无 `lint` 脚本。U5 隐含 4 个未声明子任务（安装 ESLint、创建配置、编写规则、添加脚本）。**决策**：U5 拆分为 U5a（ESLint 基础设施）+ U5b（no-emoji 规则），或改用轻量 shell 脚本 + pre-commit grep 方案。

8. **ruff "自定义规则 RUF900" 不可行** — ruff 不支持用户通过 pyproject.toml 注册自定义规则，`RUF` 前缀是 ruff 保留命名空间。**决策**：删除 ruff 自定义规则选项，Python 侧统一走 pre-commit hook + rg 扫描。

9. **"全部 emoji 字符从仓库代码中清除"与范围不匹配** — 绝对承诺但 scripts/CI 文件遗漏、useMessageRenderer.ts 转义形式遗漏。**决策**：修改为"U1-U4 列出文件中的 emoji 清除"或扩展范围。

10. **useMessageRenderer.ts 3 处字符串 glyph 破坏 KTD2 一致性** — 行 286（`◆`）、307（`\u2713`/`\u2717`）、328（`!`）未转换，与 KTD2"图标家族统一为 Outlined"目标矛盾。（注：286/307 已在 P0 修复中加入 U2，328 的 `!` 需决策是否也改组件。）**决策**：将 `!` 也改为 `WarningOutlined` 或保留为 ASCII 文本标签。

11. **U5 过度工程** — 自定义 ESLint + ruff + pre-commit + 风格文档四件套，与剩余清理任务不相称。项目当前无 ESLint 基础设施，emoji 复发概率低。**决策**：U5 瘦身为"风格文档 + pre-commit grep 守卫"，删除自定义 ESLint/ruff 规则。

**P2 发现（6 个）：**

12. **无障碍决策未声明** — 图标替换全程未声明 aria-label/aria-hidden 决策。装饰性图标应 `aria-hidden="true"`，承载语义的图标（如 review pass/fail）需 `aria-label`。**决策**：在 KTD2 加无障碍子决策。

13. **计划状态字段未反映进度** — 仍标记 `status: approved`，但 U1-U4 已基本完成。**决策**：更新为 `status: in-progress`，加 progress 字段。

14. **BoardBannerCard 实现偏离计划但未记录** — 从"Ant Design 组件"变成"纯文本"。（注：P0 修复已更新 AE1/U2/KTD2 描述，但 KTD2 取舍段落仍提及"放弃了 emoji 头像换取视觉一致性"，需补充"无图标"理由。）**决策**：在 KTD2 取舍段补理由。

15. **"无图标"第三选项从未被计划考虑** — KTD2 选项空间只有"emoji vs Ant Design 组件"，实际实现了"无图标"。**决策**：在 KTD2 补"无图标"为显式选项并给取舍。

16. **KTD4 旧行 `📋` 无主动清理路径** — 被动等待"如用户要求显式清理"。**决策**：加主动触发条件（如"下次 schema migration 时顺带 UPDATE"）。

17. **U5 "阻止 build" 验收与现有 build 流程脱节** — `npm run build` 是 `vue-tsc --noEmit && vite build`，不跑 ESLint。**决策**：明确接入点（pre-commit 拦截 vs build 前置检查）。

## Sources & Research

- **本地参考**：
  - `frontend/src/components/chat/helpers/expertIdentity.ts` — 字符首字母策略的权威实现
  - `frontend/src/components/bitable/bitableIcons.ts:89` — `resolveBitableIcon` 的回退路径
  - `docs/plans/2026-06-19-001-feat-chat-area-vi-redesign-plan.md:192` — BoardBannerCard 的设计说明（含 `🏛️`）
  - `docs/plans/2026-07-01-001-feat-ui-ue-enhancement-plan.md:154-155` — StickyModeHeader 的 emoji 头像渲染说明
- **项目记忆**：
  - 「UI components must use consistent color tokens; avoid hardcoded blue fallback colors」— 沿用到图标风格统一
  - 「CSS token fallback values can cause unintended color changes when tokens load late」— Ant Design Vue 组件是设计良好的回退机制
- **无外部研究触发**：emoji 替换是纯内部风格收敛，无外部 API/技术依赖。

---

## Plan Metadata

- **Depth**: Standard（5 个 implementation units，跨前后端+CLI+DB+测试+lint 守卫）
- **Expected commits**: 5（每个 U 一个 commit；U5 包含 lint 规则可能拆 2 个）
- **Risk profile**: 中（无安全/支付/外部 API 风险，主要是视觉/UX 风险）
- **Origin**: 用户直接在 ce-plan 中提出（无 upstream brainstorm doc）
- **Confidence**: 中高（KTD1-4 都有项目内既有模式可遵循；KTD5/6 引入新机制，U5 留验证空间）
