fischer-agentkit/docs/plans/2026-07-01-001-feat-ui-ue-e...

463 lines
30 KiB
Markdown
Raw Permalink 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.

---
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` 顶层 refFYI 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 dispatchreact/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 slotkeyed 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:01` + `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 OQ9FYI 级别
---
## 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 headerchatStore 新增方法与状态chatStream 事件分支改造
- **后端**`_phase_executor.py` 执行模式变更execute execute_streamWS 事件新增 `expert_result_chunk` / `team_synthesis_chunk`
- **测试**新增组件测试文件ThinkingBlock / StickyModeHeader / AssistantText / UserBubblechatStream.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`专家元数据字段