---
date: 2026-07-01
type: feat
origin: docs/brainstorms/2026-07-01-ui-ue-enhancement-requirements.md
---

# feat: AgentKit UI/UE 增强 — 聊天流式 + Sticky 头部 + 日历重设计

## Summary

强化 AgentKit 聊天与日历模块的 UI/UE：让 agent 思考过程与团队/私董会结果真正流式可见，让 @team/@board 模式在对话顶部持久显式，去除常驻路由噪声，给用户消息补齐复制/删除/回填操作，并让日历回到 Notion 风格的统一设计语言。覆盖 22 个需求（R1-R22），含后端 `_phase_executor.py` 流式切换以打通 expert_result/team_synthesis 的流式链路。

---

## Problem Frame

当前聊天主界面的思考展示默认折叠且为纯文本，看不到 agent 推理过程；@team/@board 模式的顶部 banner 为静态文案且不可点击，无法回答"这次任务目标是什么、由哪些专家参与"；专家团与私董会的最终结果以完整事件块一次性到达前端，与 `final_answer` 的流式体验割裂；路由元信息 tag 常驻显示对终端用户是无意义噪声；用户消息气泡无任何悬停操作；日历模块硬编码颜色与 `tokens.css` 的 Notion 调色板完全脱节。

来源：`docs/brainstorms/2026-07-01-ui-ue-enhancement-requirements.md`（经 ce-doc-review 审查强化，含 9 个 Open Questions）。

---

## Requirements

### 需求追溯

| 需求组 | R-IDs | 实施单元 |
|--------|-------|----------|
| 思考展示 | R1-R3 | U1 |
| @team/@board 头部 | R4-R7 | U2 |
| 团队/私董会流式 | R8-R11 | U3, U4 |
| 路由标签 | R12-R13 | U5 |
| 用户消息悬停 | R14-R17 | U6 |
| 日历重设计 | R18-R22 | U7 |

完整需求清单见 origin 文档。关键决策（思考混合方案、sticky 持久条、FC+自定义外壳、流式方案待定）已由 brainstorm 确认。

### 成功标准

- 思考进行中时 `ThinkingBlock` 展开并显示流式光标，完成后收起为摘要条
- @team/@board 模式进入对话后顶部渲染 sticky 持久条，专家头像可点击查看详情
- `expert_result` 与 `team_synthesis` 事件按 token/文本块流式累加渲染，与 `final_answer` 体验对齐
- 路由元信息 tag 默认隐藏，悬停时淡入显示
- 用户消息气泡悬停时显示复制/删除/回填操作工具条
- 日历模块统一消费 `tokens.css`，无硬编码颜色值

---

## Key Technical Decisions

1. **ThinkingBlock 流式光标采用 CSS 伪元素 + watch content.length 截断展示**。后端已将 thinking chunks 累积到 `message.thinking`（`chatStream.ts:512-521`），前端无需改事件流，只需在 `ThinkingBlock.vue` 内 watch `content.length` 做截断展示 + 闪烁光标。完成后收起为摘要条显示 token 数。

2. **StickyModeHeader 为新组件，替换 `ExpertTeamView` + `BoardStatusView`**。从 `useTeamStore()` 和 `useChatStore().boardState` 读取状态，合并为单一 sticky 条（`position: sticky; top: 0; z-index: var(--z-sticky)`）。`PhaseIndicator` 独立保留在 sticky 头部下方，不合并。

3. **expert_result/team_synthesis 流式采用 `execute_stream()` + token 转发 + 前端累积式 updateMessage**。后端 `_phase_executor.py` 从 `agent.execute()` 切换为 `agent.execute_stream()`，在 `async for event in` 循环中转发 `token`/`final_answer` 事件到 WS。前端 `chatStream.ts` 的 `expert_result`/`team_synthesis` 分支从一次性 `appendMessage` 改为累积式 `updateMessage`（复用 `final_answer` 的 526-532 模式）。

4. **路由 tag 隐藏采用 `v-show` + opacity transition**（与 U5 Approach 对齐）。`AssistantText.vue` 的 `showRouting` computed 增加 hover 状态依赖，CSS `opacity: 0` → `1` + `transition: opacity 0.2s ease` 淡入淡出。`v-show` 保留 DOM 避免首次悬停的重挂载闪烁。

5. **UserBubble 回填采用 chatStore 共享状态**。新增 `chatStore.refillText: string` ref，`UserBubble` 的回填操作设置该值，`ChatInput` watch 该值回填到 `inputText`。删除操作新增 `chatStore.deleteMessage(convId, msgId)` 从 `currentMessages` 移除（仅前端隐藏，不删服务端副本）。

6. **日历 .fc-* 覆盖采用最小范围 CSS 覆盖**。仅覆盖字体、边框、表头背景、今日高亮四类样式，选择器限定为 `.fc-toolbar` / `.fc-col-header` / `.fc-day-today` 等稳定类名，接受 FC 升级时可能需要维护的成本。

---

## High-Level Technical Design

### 流式事件链路（R8-R11）

```
Backend: _phase_executor.py
  agent.execute_stream()  ← async generator yields ReActEvent
    ├─ event_type="token"     → broadcast "expert_result_chunk" { expert_id, content }
    ├─ event_type="thinking"  → broadcast "expert_step" { expert_id, thinking }
    ├─ event_type="final_answer" → broadcast "expert_result_chunk" { expert_id, content }
    └─ 循环结束 → broadcast "expert_result" { expert_id, content: full, status: "completed" }

Frontend: chatStream.ts
  case "expert_result_chunk":
    → updateMessage(convId, lastExpertMsg.id, { content: accumulated + chunk })
  case "expert_result":
    → updateMessage(convId, lastExpertMsg.id, { status: "completed" })  ← 仅标记完成，不再 append
```

### 组件依赖关系

```
U1 ThinkingBlock (独立)
U2 StickyModeHeader (独立，替换两个旧组件)
U3 后端流式切换 (独立，无前端依赖)
U4 前端流式消费 (依赖 U3 联调，可 mock 开发)
U5 路由 tag 隐藏 (独立)
U6 UserBubble hover (独立，需 chatStore 新方法)
U7 CalendarGrid 重设计 (独立)
```

---

## Implementation Units

### U1. ThinkingBlock 流式展示重设计

**Goal:** 让 agent 思考过程在执行时展开流式显示，完成后收起为摘要条，切换不丢失内容。

**Requirements:** R1, R2, R3

**Dependencies:** 无

**Files:**
- `src/agentkit/server/frontend/src/components/chat/ThinkingBlock.vue`（修改）
- `src/agentkit/server/frontend/tests/unit/components/ThinkingBlock.test.ts`（新建）

**Approach:**
- 默认展开（`expanded = ref(true)`），`isStreaming` 时显示流式光标（CSS `::after` 伪元素闪烁动画）
- watch `content.length` 截断展示：流式中仅显示已到达的字符，模拟 token-by-token 效果
- 完成后（`isStreaming` 由 true→false）自动收起为摘要条，显示思考开始时间与字符数
- 摘要条点击切换展开/收起，展开时定位到上一次滚动位置（记录 `scrollTop` 到 ref）
- 状态保持：收起时内容不丢失，`content` prop 始终持有完整文本

**Patterns to follow:**
- `ChatView.vue:47-76` 的 streamingSteps 渲染模式（字符数 counter）
- `final_answer` 在 `chatStream.ts:526-532` 的累积模式

**Test scenarios:**
- 流式中展开态显示光标 + 已到达字符，完成后自动收起为摘要条
- 摘要条点击展开，显示完整思考内容
- 展开→收起→再展开，滚动位置恢复到上次位置
- 空思考内容（`content=""`）时不显示 ThinkingBlock
- `isStreaming=true` 但 `content=""` 时显示加载占位

**Verification:** 思考流式中可见光标闪烁与字符累加，完成后收起为一行摘要，点击可重新展开查看完整内容。

---

### U2. StickyModeHeader + PhaseIndicator token 化

**Goal:** 替换静态 banner 为 sticky 持久头部条，显示模式 badge + 任务目标 + 专家头像组；PhaseIndicator 独立保留并 token 化。

**Requirements:** R4, R5, R6, R7

**Dependencies:** 无

**Files:**
- `src/agentkit/server/frontend/src/components/chat/StickyModeHeader.vue`（新建）
- `src/agentkit/server/frontend/src/components/chat/ExpertTeamView.vue`（废弃/删除引用）
- `src/agentkit/server/frontend/src/components/chat/BoardStatusView.vue`（废弃/删除引用）
- `src/agentkit/server/frontend/src/components/chat/PhaseIndicator.vue`（修改 — token 化）
- `src/agentkit/server/frontend/src/views/ChatView.vue`（修改 — 替换 banner 引用）
- `src/agentkit/server/frontend/tests/unit/components/StickyModeHeader.test.ts`（新建）

**Approach:**
- `StickyModeHeader` 从 `useTeamStore()` 读取 `isTeamMode` / `teamState`（含 `task_description` / `experts`），从 `useChatStore().boardState` 读取私董会状态。注：实际 store 暴露 `teamState` / `activeExperts` / `leadExpert`，无 `currentPlan` / `experts` 顶层 ref（FYI F8 提示）— 实施时通过 `teamState.value?.task_description` 与 `activeExperts` / `teamState.value?.experts` 访问
- 左侧：模式 badge（"专家团" / "私董会"），使用 `--accent-team` / `--accent-board`
- 中间：任务目标（@team 取 `teamState.value?.task_description`）或主题（@board 取 `boardState.topic`）
- 右侧：专家头像组（`v-for` 渲染 `activeExperts`，每个显示 `avatar` emoji + `color` 边框），溢出时显示 `+N`
- 头像点击弹出 `a-popover` 详情面板，显示 `name` / `description` / `persona`（现有字段，不假设 role/bio）。**Popover 关闭**：Esc 键 + 外击关闭；关闭时焦点回归触发头像（focus management）。**移动端**：viewport<768px 隐藏任务主题文本，仅显示 mode badge + avatar group；`+N` 溢出点击打开完整专家列表 popover
- `PhaseIndicator` 硬编码颜色（`#722ed1` / `#52c41a` / `#cf1322`）替换为 token（`--accent-board` / `--color-success` / `--color-error`）
- ChatView.vue 中 `<ExpertTeamView />` + `<BoardStatusView />` 替换为 `<StickyModeHeader />`，`<PhaseIndicator />` 保留在其下方

**Patterns to follow:**
- `MessageShell.vue:5-11` 的 custom-avatar 渲染逻辑
- `tokens.css` 的 `--z-sticky: 1020` / `--accent-team` / `--accent-board`

**Test scenarios:**
- @team 模式进入对话后渲染 sticky 条，显示"专家团" badge + 任务目标 + 专家头像
- @board 模式进入对话后渲染 sticky 条，显示"私董会" badge + 主题 + 专家头像
- 非 team/board 模式不渲染 sticky 条
- 专家头像点击弹出 popover 显示 name/description/persona
- Popover 通过 Esc 键关闭，关闭后焦点回归触发头像
- Popover 通过外击关闭
- 头像超过 5 个时显示 `+N` 溢出标识，`+N` 点击打开完整专家列表 popover
- viewport<768px 时隐藏任务主题文本，仅显示 mode badge + avatar group
- PhaseIndicator 颜色使用 token 变量而非硬编码

**Verification:** @team/@board 模式下顶部持久显示 sticky 头部条，专家头像可点击查看详情，PhaseIndicator 颜色与 tokens.css 一致。

---

### U3. 后端 _phase_executor.py 流式切换

**Goal:** 将专家阶段执行从同步 `agent.execute()` 切换为流式 `agent.execute_stream()`，转发 token/thinking/final_answer 事件到 WS。

**Requirements:** R8, R9（后端依赖）

**Dependencies:** ConfigDrivenAgent.execute_stream() 暴露 — 需先在 BaseAgent/ConfigDrivenAgent 暴露 stream dispatch（react/rewoo/plan_exec/reflexion 模式委派至底层引擎；direct/llm_generate/tool_call/custom 模式定义显式 fallback）

**Files:**
- `src/agentkit/core/config_driven.py`（修改 — 暴露 `execute_stream()` + `handle_task_stream()` dispatch，镜像 `execute()` → `handle_task()` 模式）
- `src/agentkit/core/base.py`（修改 — 在 BaseAgent 加 `execute_stream()` 抽象方法签名）
- `src/agentkit/experts/_phase_executor.py`（修改 — `_run_agent_steps` 方法）
- `src/agentkit/experts/_synthesizer.py`（修改 — `_synthesize_results` 注入 `broadcast_callback` 参数，保留 dict 返回类型，方法内每次 LLM chunk 调用 callback 广播 `team_synthesis_chunk`）
- `src/agentkit/experts/orchestrator.py`（修改 — `_synthesize_results` 调用处传入 `_broadcast_event` 的部分应用作为 callback；`final_result = await self._synthesize_results(...)` 与 `final_result.get("content")` 消费保持不变）
- `tests/unit/experts/test_phase_executor_streaming.py`（新建）

**Approach:**
- `_run_agent_steps`（line 222）从 `await agent.execute(task_msg)` 改为 `async for event in agent.execute_stream(task_msg, ...)` 循环
- 循环内按 `event.event_type` 转发（载荷在 `event.data: dict[str, object]`，无 `event.content` / `event.output` 属性 — 见 `react.py:131-137` ReActEvent 定义）：
  - `"token"` → `_broadcast_event("expert_result_chunk", { expert_id, content: event.data.get("content") })`
  - `"thinking"` → `_broadcast_event("expert_step", { expert_id, thinking: event.data.get("content") })`
  - `"final_answer"` → `_broadcast_event("expert_result_chunk", { expert_id, content: event.data.get("output") })`
  - `"tool_call"` / `"tool_result"` → 可选转发（保持现有 `expert_step` 语义）
- 循环结束后广播 `expert_result` 完整事件（content = 累积的完整结果，status: "completed"）
- `TeamOrchestrator._synthesize_results` 接口策略：注入 `broadcast_callback` 参数，保留 `dict[str, object]` 返回类型（对 `orchestrator.py:289` 消费者零破坏）。方法内在每次 LLM 输出 chunk 时调用 `broadcast_callback({"chunk": text})` 广播 `team_synthesis_chunk`，最终返回完整 dict；`orchestrator.py:294-301` 的最终 `team_synthesis` 广播路径不变
- **重试 + 流式合约**：`_run_agent_steps` 的 retry 循环（`_phase_executor.py:220-241`）与流式交互须遵循：(a) 重试前广播 `expert_result_chunk_reset` 事件，前端清空已累积内容；(b) 最终 `expert_result` 事件携带完整内容覆盖（前端在 `expert_result` 到达时替换而非累加）；(c) 重试不广播新 chunks 直到本次流式完成 — 避免失败 attempt 1 的部分 chunks 与 attempt 2 的 chunks 在前端累积成乱码
- 异步生成器安全：遵循 AGENTS.md 约定，`async def` 中第一个 `yield` 前禁止 `return`（用 `return; yield` 模式）
- 异常分类：遵循 `docs/solutions/conventions/any-and-except-exception-governance.md`，WS 广播用 `except (ConnectionError, RuntimeError, asyncio.TimeoutError)`

**Patterns to follow:**
- `react.py:1443` 的 `execute_stream` 签名与 ReActEvent 结构
- `chatStream.ts:526-532` 的 final_answer 累积模式（后端镜像）
- `docs/solutions/logic-errors/long-horizon-reliability-code-review-fixes.md` 的 `execute_stream` 入口 `self.reset()` 前置条件

**Test scenarios:**
- `execute_stream` 产出 token 事件时，`_broadcast_event` 被调用转发 `expert_result_chunk`
- `execute_stream` 产出 final_answer 事件时，`_broadcast_event` 被调用转发 `expert_result_chunk`
- 循环结束后广播完整 `expert_result` 事件
- `execute_stream` 抛出异常时，phase 状态标记为 failed 且不阻塞其他阶段
- `execute_stream` 中途抛出异常时，广播 `expert_result` 事件 status="error" 携带已累积内容，前端可识别错误态并保留部分内容
- 流式会话必须以 `expert_result(completed)` 或 `expert_result(error)` 终结，不允许静默挂起
- `execute_stream` 在 2 chunks 后抛异常 → retry loop 触发 → 广播 `expert_result_chunk_reset` → 重试成功 → 前端消息仅含 attempt 2 内容（无 attempt 1 残留）
- TeamOrchestrator 综合阶段流式广播 `team_synthesis_chunk`

**Verification:** 后端日志可见 `expert_result_chunk` 事件按 token 推送，最终 `expert_result` 完整事件作为结束标记。

---

### U4. 前端 expert_result/team_synthesis 流式消费 + 身份标识

**Goal:** 前端将 expert_result/team_synthesis 从一次性 append 改为累积式 update，流式过程中显示专家身份标识。

**Requirements:** R8, R9, R10, R11

**Dependencies:** U3（联调，可 mock 开发）

**Files:**
- `src/agentkit/server/frontend/src/stores/chatStream.ts`（修改 — expert_result/team_synthesis 分支）
- `src/agentkit/server/frontend/src/components/chat/messages/MessageShell.vue`（修改 — 渲染 expert_name/expert_color badge）
- `src/agentkit/server/frontend/tests/unit/stores/chatStream.test.ts`（修改 — 新增流式测试）

**Approach:**
- 新增 `expert_result_chunk` 事件分支：若当前无该专家的流式消息则 `appendMessage` 创建占位（`status: "streaming"`），否则 `updateMessage` 累加 content（复用 final_answer 526-532 模式）
- `expert_result` 事件分支改为：仅 `updateMessage` 标记 `status: "completed"`（不再 appendMessage）；`status="error"` 时标记为错误态并保留部分内容
- `expert_result_chunk_reset` 事件分支（U3 重试合约）：清空已累积 content，重置 streaming 状态
- `team_synthesis_chunk` 同理：首次 append 占位，后续 update 累加
- `team_synthesis` 事件改为：仅 update 标记完成
- **并发专家流式顺序**：每个 `expert_id` 维护独立 streaming message slot（keyed by `expert_id`），同 expert 的 chunks 累积到该 expert 的消息；并发专家渲染为独立并行 streaming 消息，按 first-chunk 到达时间排序
- 流式消息渲染时显示身份标识（`expert_name` + `expert_color` badge），流式结束后标识保留
- R10 身份标识为用户可见的专家 badge（区别于 R12 的内部路由 tag），通过 `message.expert_name` / `message.expert_color` 在 MessageShell 中渲染

**Patterns to follow:**
- `chatStream.ts:526-532` 的 `final_answer` 累积模式
- `dispatchWsEvent` 纯函数设计（入参 `ChatStreamState`，无模块级状态）

**Test scenarios:**
- `expert_result_chunk` 首次到达时创建 streaming 占位消息
- `expert_result_chunk` 后续到达时累加 content 到已有消息
- `expert_result` 到达时标记消息 status 为 completed
- `expert_result` 到达时 status="error" 时，标记 streaming 消息为错误态并保留部分内容
- `expert_result_chunk_reset` 到达时清空已累积 content 并重置 streaming 状态
- `team_synthesis_chunk` 首次到达时创建 streaming 占位
- `team_synthesis` 到达时标记完成
- 流式消息显示 expert_name + expert_color badge
- 流式会话异常终止时，UI 显示错误指示器不静默挂起
- 两个专家并发流式 → 两条独立 streaming 消息，各自累积自己的 chunks，按 first-chunk 到达时间排序

**Verification:** 专家结果按 token 流式累加显示，完成后标记为 completed，身份标识保留在最终消息上。

---

### U5. AssistantText 路由标签悬停显示

**Goal:** 路由元信息 tag 默认隐藏，悬停助手消息时淡入显示。

**Requirements:** R12, R13

**Dependencies:** 无

**Files:**
- `src/agentkit/server/frontend/src/components/chat/messages/AssistantText.vue`（修改）
- `src/agentkit/server/frontend/tests/unit/components/AssistantText.test.ts`（新建）

**Approach:**
- `showRouting` computed 增加 hover 状态依赖：`return props.message.role === 'assistant' && props.message.matched_skill && isHovered.value`
- 根容器加 `@mouseenter` / `@mouseleave` 事件设置 `isHovered = ref(false)`
- CSS `.assistant-text__routing` 加 `transition: opacity 0.2s ease`，默认 `opacity: 0`，hover 时 `opacity: 1`
- 采用 `v-show` + opacity transition（保留 DOM 避免首次悬停的重挂载闪烁，`opacity:0→1` + `transition:opacity 0.2s ease` 满足 R13 淡入要求）

**Patterns to follow:**
- Ant Design Vue 的 `<a-tag>` 组件用法
- `tokens.css` 的 `--transition-base` 变量（若存在）

**Test scenarios:**
- 默认状态路由 tag 不可见
- 鼠标悬停助手消息时 tag 淡入显示
- 鼠标移开时 tag 淡出
- 无 `matched_skill` 的消息悬停时也不显示 tag
- 淡入淡出过渡平滑无闪烁

**Verification:** 路由 tag 默认不可见，悬停时淡入，移开时淡出。

---

### U6. UserBubble 悬停操作（复制/删除/回填）

**Goal:** 用户消息气泡悬停时显示操作工具条，支持复制、删除（前端隐藏）、回填输入框重发。

**Requirements:** R14, R15, R16, R17

**Dependencies:** 无（chatStore 新方法独立于其他单元）

**Files:**
- `src/agentkit/server/frontend/src/components/chat/messages/UserBubble.vue`（修改）
- `src/agentkit/server/frontend/src/stores/chatStore.ts`（修改 — 新增 deleteMessage / refillText）
- `src/agentkit/server/frontend/src/components/chat/ChatInput.vue`（修改 — watch refillText）
- `src/agentkit/server/frontend/tests/unit/components/UserBubble.test.ts`（新建）

**Approach:**
- `UserBubble.vue` 根容器加 `@mouseenter` / `@mouseleave` 控制 `showActions = ref(false)`
- **focus-visible parity**：根容器加 `@focus` / `@blur`（或 `:focus-within`）使 Tab 聚焦时也显示工具条（键盘用户可达）；Touch 设备 tap on bubble 切换显示，tap elsewhere 隐藏
- 悬停时显示工具条（`v-if="showActions"`）：复制按钮（`CopyOutlined`）、删除按钮（`DeleteOutlined`）、回填按钮（`EditOutlined`）
- 复制：`navigator.clipboard.writeText(content)`，成功后按钮短暂变色反馈
- 删除：弹出 `a-popconfirm` 二次确认，确认后调 `chatStore.deleteMessage(convId, msgId)` 从 `currentMessages` 移除（仅前端隐藏，不删服务端副本）；**若有助手回复跟随**（thread order 中下一条为 assistant 消息），删除按钮 disabled 并显示 tooltip "该消息已有回复，无法删除"
- 回填：调 `chatStore.setRefillText(content)`，`ChatInput` watch `refillText` 回填到 `inputText`，不自动发送
- chatStore 新增：`refillText = ref('')`、`deleteMessage(convId, msgId)`、`setRefillText(text)`

**Patterns to follow:**
- `chatStore.resendLastUserMessage()`（chatStore.ts:449-462）的消息定位模式
- `chatStore.deleteConversation` 的 pending 状态清理模式（见 `calendar-capability-and-ui-fixes.md` 学习）

**Test scenarios:**
- 悬停时显示三个操作按钮
- Tab 聚焦用户消息时显示工具条；Enter 触发默认动作（复制）
- Touch 设备 tap on bubble 显示工具条，tap elsewhere 隐藏
- 复制按钮点击后内容写入剪贴板，按钮变色反馈
- 删除按钮点击后弹出二次确认，确认后消息从列表消失
- 删除取消后消息不消失
- 删除有助手回复跟随的用户消息 → 删除按钮 disabled + tooltip "该消息已有回复，无法删除"
- 回填按钮点击后 ChatInput 输入框显示消息文本，不自动发送
- 离开悬停后操作工具条消失

**Verification:** 悬停用户消息可见三个操作，复制/删除/回填功能正常，删除有二次确认。

---

### U7. CalendarGrid token 重设计

**Goal:** 日历模块统一消费 `tokens.css`，消除硬编码颜色，`.fc-*` 元素对齐 Notion 风格。

**Requirements:** R18, R19, R20, R21, R22

**Dependencies:** 无（tokens.css 已含全部变量；本期不修改 tokens.css）

**Files:**
- `src/agentkit/server/frontend/src/components/calendar/CalendarGrid.vue`（修改）
- `src/agentkit/server/frontend/src/styles/calendar-overrides.css`（新建 — .fc-* 覆盖）
- `src/agentkit/server/frontend/tests/unit/components/CalendarGrid.test.ts`（新建）

**Approach:**
- `CalendarGrid.vue:38` 的 `#1677ff` 替换为按 event_type 映射到 token（显式映射表）：
  - `event_type=team` → `var(--accent-team)`
  - `event_type=board` → `var(--accent-board)`
  - `event_type=task` → `var(--color-primary)`
  - `event_type=reminder` → `var(--color-warning)`
  - `event_type=system` → `var(--text-tertiary)`
  - 默认 → `var(--color-primary)`
  - 实施前需从 `CalendarGrid.vue` 的事件源枚举实际 `event_type` 值
- 事件卡片 `backgroundColor` / `borderColor` 改为从 token 映射函数返回
- 新建 `calendar-overrides.css` 覆盖 `.fc-toolbar`（按钮风格）、`.fc-col-header`（表头背景）、`.fc-day-today`（今日高亮）、`.fc-event`（事件卡片字体/边框）
- 覆盖范围最小化：仅覆盖 4 类稳定类名，接受 FC 升级时可能需要维护的成本
- 侧栏与头部工具栏组件使用 token 重绘

**Patterns to follow:**
- `tokens.css` 的 Notion 调色板（`--color-primary: #1a1a1a`、`--accent-team`、`--accent-board`）
- `CalendarGrid.vue` 现有的 `firstDay: 1` 配置（见 `jwt-secret-dev-mode-user-id-mismatch.md` 学习）

**Test scenarios:**
- 日历事件颜色按 event_type 映射到 token，无硬编码颜色值
- `.fc-toolbar` 按钮风格与主界面按钮一致
- `.fc-day-today` 今日高亮使用 token 色
- 暗色模式（`[data-theme="dark"]`）下日历正确显示
- FullCalendar 视图切换/日期导航/事件拖拽功能不受影响
- Calendar 零事件 → 空态 placeholder 文本使用 `--text-tertiary` token
- 事件获取失败 → 错误态带 retry CTA（使用 `--color-error` token）
- 事件加载中 → skeleton loader 匹配 token 间距（使用 `--color-border` / `--color-fill-quaternary`）

**Verification:** 日历视觉与主界面 Notion 风格一致，无硬编码颜色，FC 既有功能正常。

---

## Scope Boundaries

### Deferred for later

- 思考内容的 markdown 渲染：本期 ThinkingBlock 仍以纯文本呈现（origin 文档 Deferred）
- 日历事件的拖拽创建、resize 行为变更：仅重绘视觉外壳（origin 文档 Deferred）
- 专家头像组在线状态指示：仅展示静态头像（origin 文档 Deferred）
- 路由元信息 tag 的可配置显示规则：固定为"默认隐藏 + 悬停显示"（origin 文档 Deferred）
- 后端流式推送协议的具体方案（事件变体 vs token 分块）：U3 采用 `execute_stream()` + token 转发，具体事件命名（`expert_result_chunk` 等）可在实施时调整

### Outside this product's identity

- 不引入新的 UI 组件库或设计系统（继续基于 Ant Design Vue + token 覆盖）
- 不替换 FullCalendar 为自建日历组件（保留 FC 核心，仅覆盖外壳与 `.fc-*` 样式）
- 不改变 `PhaseIndicator` 的现有阶段进度语义与展示形式（仅独立保留 + token 化）

### Deferred to Follow-Up Work

- R16 服务端删除语义：本期仅前端隐藏，服务端删除留待后续迭代（origin OQ4）
- 交互状态全覆盖（错误态/空态/加载态规范）：origin OQ1，本期在相关单元测试中覆盖核心场景，全面规范留待后续
- 响应式与无障碍全面策略：origin OQ2，本期在组件中遵循基本 a11y，全面策略留待后续
- @team/@board 模式可发现性引导：origin OQ9（FYI 级别）

---

## Risks & Dependencies

| 风险 | 影响 | 缓解 |
|------|------|------|
| `_phase_executor.py` 流式切换可能影响阶段执行稳定性 | expert_result 流式中断或丢失；retry loop 与流式交互导致前端累积乱码 | U3 单独测试；遵循 `execute_stream` 入口 `self.reset()` 前置条件；retry+流式合约：重试前广播 `expert_result_chunk_reset` 让前端清空，最终 `expert_result` 覆盖累积（详见 U3 Approach） |
| FullCalendar `.fc-*` 类名升级时可能破坏 | 日历样式失效 | 覆盖范围最小化（4 类稳定类名）；在 `calendar-overrides.css` 顶部注释标注风险 |
| chatStore 新增 `deleteMessage` 可能影响会话状态一致性 | 消息索引错乱 | 参考 `deleteConversation` 的 pending 清理模式；单元测试覆盖删除后消息索引 |
| U3/U4 联调依赖 | 前端流式无法独立验证 | U4 可用 mock `expert_result_chunk` 事件开发；U3 完成后联调 |

---

## System-Wide Impact

- **前端**：ChatView 组件树结构调整（banner 替换为 sticky header）；chatStore 新增方法与状态；chatStream 事件分支改造
- **后端**：`_phase_executor.py` 执行模式变更（execute → execute_stream）；WS 事件新增 `expert_result_chunk` / `team_synthesis_chunk`
- **测试**：新增组件测试文件（ThinkingBlock / StickyModeHeader / AssistantText / UserBubble）；chatStream.test.ts 扩展流式测试
- **样式**：新增 `calendar-overrides.css`；PhaseIndicator token 化；CalendarGrid token 化

---

## Open Questions

来自 origin 文档的 9 个 Open Questions 处理状态：

| OQ | 状态 | 处理方式 |
|----|------|----------|
| OQ1 交互状态覆盖 | Deferred | 本期测试覆盖核心场景，全面规范留待后续 |
| OQ2 响应式与无障碍 | Deferred | 本期遵循基本 a11y，全面策略留待后续 |
| OQ3 流式结构 | Resolved | U3 采用 `execute_stream()` + token 转发 |
| OQ4 R16 删除作用域 | Resolved | 前端隐藏，服务端删除留待后续 |
| OQ5 模式切换用户流 | Addressed | U2 Approach 中覆盖 |
| OQ6 关键交互细节 | Addressed | U2/U6 Approach 中指定（Popover/Popconfirm） |
| OQ7 .fc-* 兼容性 | Noted as Risk | Risks 表中标注 |
| OQ8 Sticky 头部替代 | Resolved | brainstorm 已确认 sticky 方案 |
| OQ9 模式可发现性 | FYI | Deferred to Follow-Up |
| OQ10 (NEW) U3 后端协议 scope 声明 | Deferred | origin 行 88 显式排除后端流式协议（"不属本文档 scope"），但 U3 已纳入本计划作为实施单元。是否重新分类 U3 为 "Cross-cutting dependency unit"（与 UI/UE 单元分开）或显式标注为 scope 扩展？建议实施前确认。 |
| OQ11 (NEW) PhaseIndicator token 化 R-id 追溯 | Deferred | U2 中 PhaseIndicator 颜色 token 化（`#722ed1`/`#52c41a`/`#cf1322` → `--accent-board`/`--color-success`/`--color-error`）无对应 R-id 追溯（origin R7 仅要求"独立保留 + token 化"）。是否新增 R-id 显式覆盖此 token 化工作，或延后到独立 token 化 pass 处理其他组件同类硬编码？ |

---

## Sources & Research

- Origin: `docs/brainstorms/2026-07-01-ui-ue-enhancement-requirements.md`（ce-brainstorm 产出 + ce-doc-review 审查强化）
- `docs/solutions/logic-errors/long-horizon-reliability-code-review-fixes.md` — `execute_stream` 入口 reset 前置条件、team_synthesis 综合阶段完整内容读取
- `docs/solutions/ui-bugs/calendar-agent-create-no-refresh.md` — WS 事件链端到端追踪模式、notify_callback 注入
- `docs/solutions/logic-errors/calendar-capability-and-ui-fixes.md` — SystemMonitorPanel flex 方向、deleteConversation pending 清理
- `docs/solutions/conventions/any-and-except-exception-governance.md` — WebSocket 异常分类、CancelledError 守卫
- `docs/solutions/integration-issues/jwt-secret-dev-mode-user-id-mismatch.md` — CalendarGrid firstDay 配置
- 代码库研究：`react.py:1443`（execute_stream）、`_phase_executor.py:222`（流式缺口）、`chatStream.ts:526-532`（final_answer 累积模式）、`tokens.css`（Notion 调色板）、`configs/experts/*.yaml`（专家元数据字段）
