fischer-agentkit/docs/plans/2026-07-02-001-refactor-rem...

406 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "refactor: Remove all emoji from agentkit"
date: 2026-07-02
type: refactor
status: approved
approved_at: 2026-07-02
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 颜色样式。
执行顺序按 4 个批次分阶段落地,每批可独立提交/回滚。
## 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`(首字母)则原样渲染。
### KTD3CLI 状态标记用 Rich 文本标签 + 颜色,弃用 `✓` `✗` `⚠`
**决策**`OK` / `FAIL` / `WARN` 作为文本标签,配合 Rich `[green]` / `[red]` / `[yellow]` 颜色。横幅标题用 `[OK 验收结果]` / `[WARN 风险标记]` 形式保留 emoji 缺失的视觉强度。
**理由**
- 终端字体差异Windows Terminal / 容器 tty / 旧版 macOS Terminal 对 Unicode 符号渲染不一致
- 颜色已能传达同样信息量
- 与 Python `rich` 库的最佳实践一致(标签 + 颜色)
### KTD4Bitable 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 备份快照或导出/导入,可能需要清理脚本。本次不处理。
### KTD5App.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 字符移除(不含文档注释)
- 4 批次的实施顺序与每批对应的文件清单
- 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:212,271``avatar: '⚖'``avatar: AuditOutlined`shell.avatar 改 Component
- `src/agentkit/server/frontend/src/components/chat/MessageShell.vue` — 检查 avatar 字段类型Component 走 `<component :is="..." />`string 走原样
- `src/agentkit/server/frontend/src/App.vue:109-110` — 字体回退删 emoji 字体声明
**Approach**
- 引入 `BankOutlined` / `AuditOutlined` / `TeamOutlined` / `CheckOutlined` / `SwapOutlined` / `MinusOutlined` / `QuestionOutlined`(均已在 `@ant-design/icons-vue` 中,无需新装)
- `BoardBannerCard` / `DebateBannerCard``<span class="...__icon">X</span>` 改为 `<component :is="X" class="...__icon" />`
- `UserBubble` 的 command card 改造:`icon` prop 改为 `Component` 类型
- `useMessageRenderer``shell.avatar` 类型由 `string` 改为 `Component`(仅这两处),其余 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`,确认横幅显示 `<BankOutlined />` 矢量图标(非 emoji
- 触发辩论确认 `DebateBannerCard` 显示 `<AuditOutlined />`
---
### U3. CLI 控制台输出 — admin / chat / skill / benchmark
**Goal**:把 Rich 输出中的 `✓` `✗` `⚠` 改为 `OK`/`FAIL`/`WARN` 文本标签 + Rich 颜色。
**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` — 所有 `✓`/`✗`/`⚠` 收敛
**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. 端到端验证 + 防止复发lint 规则 + 文档)
**Goal**:确认全仓无残留 emoji并加入 CI 守卫。
**Files**
- 新增 `frontend/eslint-rules/no-emoji.cjs`(自定义 ESLint 规则,禁止源码中的 emoji 字符范围)
- `frontend/.eslintrc.cjs` 注册规则
- `pyproject.toml``ruff` 自定义规则(`RUF900` 禁用 emoji或依赖 `pre-commit` 钩子
- `.pre-commit-config.yaml`(若不存在则创建)— 添加 `agentkit-no-emoji` 钩子
- `docs/solutions/style/no-emoji-style-guide.md`(新增)— 记录替换规则与原因
**Approach**
- 自定义 ESLint 规则用正则 `[\x{1F000}-\x{1FFFF}\x{2600}-\x{27BF}\x{2300}-\x{23FF}\x{2B00}-\x{2BFF}]` 匹配所有 emoji 范围
- 规则名 `no-emoji-in-source`,错误级别 `error`,白名单文件:注释(通过 `// allow-emoji` 注解豁免,仅在文档/迁移脚本等场景)
- ruff 通过 `RUF` 自定义检查或第三方 `flake8-no-emoji` 工具
- pre-commit 在 commit 阶段跑前端 + 后端双检查
- 风格指南文档说明三套替代策略KTD1-3
**Test scenarios**
- **Happy path**`npx eslint src/` 无 emoji 错误
- **Happy path**`ruff check src/` 无 emoji 警告
- **Error path**:故意在某 `.vue` 文件中加一个 emoji 字符ESLint 报错并阻止 build
- **Integration**:本地 commit 含 emoji 的文件被 pre-commit 阻止
**Verification**
- `npm run lint` 全通过
- `ruff check src/` 全通过
- `pre-commit run --all-files` 全通过
- 新建一个临时 `.vue` 文件含 emoji确认 ESLint 报错
**Deferred to Follow-Up**CI 工作流(`.github/workflows/` 或 Gitea Actions添加 emoji 检查步骤 — 等 lint 规则稳定后再加,避免一次性改动过多。
---
## 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 依赖 U1chatStore.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已默认开启
## 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**: Standard5 个 implementation units跨前后端+CLI+DB+测试+lint 守卫)
- **Expected commits**: 5每个 U 一个 commitU5 包含 lint 规则可能拆 2 个)
- **Risk profile**: 中(无安全/支付/外部 API 风险,主要是视觉/UX 风险)
- **Origin**: 用户直接在 ce-plan 中提出(无 upstream brainstorm doc
- **Confidence**: 中高KTD1-4 都有项目内既有模式可遵循KTD5/6 引入新机制U5 留验证空间)