fischer-agentkit/docs/plans/2026-07-02-001-feat-private...

426 lines
51 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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-02
type: feat
title: 私董会单会话限制 + 方案B 气泡 + 简化开始卡片
origin: docs/brainstorms/2026-07-02-private-board-restrictions-and-scheme-b-bubbles-requirements.md
status: ready
---
## Summary
收尾私董会(@board模块的 UI 细节与单会话状态约束,四处协同改动:(1) 在 `ChatInput.vue` 的"私董会"按钮 click 处拦截"当前会话已存在进行中的私董会"场景,弹 a-modal 提示并提供快捷新建会话按钮;(2) 将 `BoardBannerCard.vue` 从带边框/紫条/进度条/专家 chip 的重样式简化为单行标题+副标题;(3) 给 `MessageShell.vue` 中所有 `role === 'assistant'` 的消息内容添加方案 B 风格的浅灰圆角矩形气泡F1-A 独立 token / F4-A 排除所有 card-bearing 类型 / D4-方案1 `:empty` 隐藏);(4) 将 `UserBubble.vue` 普通文本消息改为 demo 中的深色右对齐气泡(`--color-primary` + `--text-inverse`@board/@team 命令卡片保持现有浅色背景。改动范围仅限前端 Vue 组件与少量 store/type/token 文件,不动后端、不动方案 B 调色板、不动 StickyModeHeader 顶部条。
## Problem Frame
私董会功能上线后存在三处 UI 粗糙点(详见 origin: docs/brainstorms/2026-07-02-private-board-restrictions-and-scheme-b-bubbles-requirements.md
1. **单会话多私董会无约束**`ChatInput.vue:75` 的"私董会"按钮 `@click` 直接 `showBoardModal = true`,对当前会话是否已存在私董会无任何判断。连续两次 `SendMessage("@board:...")` 会创建第二个私董会,叠加在第一个未结束的私董会之上,`boardState.experts` 被覆盖、轮次错乱、`StickyModeHeader` 头像数与实际不符。
2. **BoardBannerCard 样式过重**`BoardBannerCard.vue` 使用了 card+border+shadow+4px 紫条+进度条+专家 chip pill 等装饰,与方案 B 整体"克制、不重样式"取向冲突。方案 B 截图中的"开始"区域是单行文本,不带装饰。
3. **方案 B 气泡未落地**:方案 B 截图中专家发言区域**有**浅灰圆角矩形气泡包裹内容,与 ChatGPT / Notion AI 风格一致。当前 `MessageShell.vue:178-184``.message-shell__content` 没有背景/边框/圆角,气泡效果完全缺失。
## Requirements
完整继承 origin 文档的 13 条 R-IDsR1-R13并新增 R14-R19F4-A / D4-方案1 / U4 决策固化),共 19 条,分组如下:
**单会话私董会限制R1-R4**
- R1`ChatInput.vue` "私董会"按钮 `@click` 在打开 `BoardMeetingModal` 前检查 `chatStore.boardState``status === 'discussing' | 'concluding'` 时弹 a-modal**不**打开 modal
- R2a-modal 标题"当前会话已存在私董会",副文"请新建会话来创建新的私董会",按钮"我知道了"+ "新建会话"(主操作);"新建会话"流程:关 modal → `chatStore.createConversation()``chatStore.selectConversation(newId, true)` → 不自动打开 modal
- R3`boardState === null` 或 `status === 'completed' | 'dissolved'` 时保持当前行为
- R4以前端 `chatStore.boardState` 为权威源,不依赖后端 / `is_board` 标记
**BoardBannerCard 简化R5-R8**
- R5重构为单行标题+副标题,保留 `topic / maxRounds / currentRound` props 向后兼容,删除 `experts` prop
- R6模板输出两行`私董会 — {topic}` + `轮次:第 {currentRound} / {maxRounds} 轮`
- R7删除 `.board-banner-card` 的重样式background/border/border-radius/box-shadow`__bar / __chip` 等重样式类,保留 `.board-banner-card` 容器(仅 margin/padding+ `__title / __meta` 最小样式
- R8`useMessageRenderer.ts` 中 `board_started` 渲染路径不变
**AssistantText 浅灰气泡R9-R15**
- R9`MessageShell.vue` 的 `.message-shell__content``background: var(--bg-message-bubble)` + `border-radius: var(--radius-md)` + `padding: var(--space-3) var(--space-4)` + `border: 1px solid var(--border-color)` + `color: var(--text-primary)`,仅 `role === 'assistant'` 时生效。**F1-A 决策**:引入独立 token `--bg-message-bubble`light `#ffffff` / dark `#1f1f1f`),与 `--bg-secondary`inline code/table 背景)解耦,避免气泡背景与代码/表格背景视觉冲突
- R10不使用 `!important`,不覆盖代码块/表格/路由 tag 样式
- R11不影响 `BoardRoundCard``AssistantText` 渲染
- R12私董会专家发言、普通 chat 通过 `MessageShell + AssistantText` 统一获得气泡;@team 阶段使用 `TeamPlanCard`、Debate 使用 `DebateArgumentCard`/`DebateSummaryCard`/`DebateBannerCard` 等自带 card chrome 的组件(通过 `MessageShell` 渲染但不走 `AssistantText`),由 R14 排除气泡
- R13气泡宽度继承现有 `width: 100%; max-width: 100%`,不强制固定最大宽度
- R14**F4-A 决策Round 4 扩展)**:气泡选择器排除所有自带 card chrome 的 assistant 消息类型(共 9 种):`board_conclusion``BoardConclusionCard` → `.board-conclusion-card`full chromebg + border + radius + shadow、`team_plan``TeamPlanCard` → `.team-plan-card`full chrome、`debate_banner``DebateBannerCard` → `.debate-banner`partial chromebg + left-border + radius、`debate_argument``DebateArgumentCard` → `.debate-argument`partial、`debate_summary``DebateSummaryCard` → `.debate-summary`partial、`debate_resolved``DebateConclusionCard` → `.debate-conclusion`partial + 4 变体 bg、`collaboration_graph``CollaborationGraphCard` → `.collab-graph`partial、`review_result``ReviewResultCard` → `.review-card`partial + 3 变体 bg、`risk_flagged``RiskFlagCard` → `.risk-card`partial。**chrome 区分Round 4 修正)**:仅 `BoardConclusionCard` + `TeamPlanCard` 有完整 chromebg + border + radius + shadow其余 7 种只有 bg + left-border + radius无 shadow、无 full border。**已验证根 class 名Round 4 R4-F1**:注意 7 种 partial chrome 卡片 root class 名与组件名不匹配,不带 `-card` 后缀(`.debate-banner` / `.debate-argument` / `.debate-summary` / `.debate-conclusion` / `.collab-graph` / `.review-card` / `.risk-card`)。实现方式:在 `MessageShell.vue` 通过 `messageType` prop 或 `:has()` 选择器排除(具体机制依 G3 决策)。**第三种架构替代方案Round 4 R4-A3**:在 `MessageRenderSpec` 接口(`useMessageRenderer.ts:44-48`)新增 `bubble: boolean` 字段,在 renderer 层集中决策是否包裹气泡,避免 messageType prop 污染或 `:has()` 选择器脆弱性——见 Open Questions Round 4。**未来防护Round 4 R4-A4**:新增 card-bearing 类型时需手动加入 F4-A 排除列表,或采用上述 `bubble` 字段方案由 renderer 集中管理——见 Open Questions Round 4
- R15**D4-方案1 决策**:空 slot 内容pre-stream thinking / tool-call-only时气泡不渲染背景——通过 `:empty` 选择器隐藏 `background / border / padding`,仅显示 thinking dots。有内容流入后自动恢复气泡样式
**UserBubble 普通文本深色气泡R16-R19**
- R16`UserBubble.vue` 的普通文本消息(`<span class="user-bubble__text">`)样式改为 demo 中的深色右对齐气泡:`background: var(--color-primary)` + `color: var(--text-inverse)` + `padding: var(--space-3) var(--space-4)` + `max-width: 70%`
- R17@board/@team 命令卡片(`.user-bubble__command`)和文件附件保持现有 `--bg-tertiary` 浅色背景,不应用深色气泡样式
- R18dark mode 自动反转——`--color-primary` 在 dark mode 下为 `#fbfbfa`(浅),`--text-inverse` 为 `#1a1a1a`user 气泡自动变为浅色背景 + 深色文字
- R19通过 `isPlainText` computed`!fileAttachment && !commandBubble`+ `.user-bubble--text` modifier class 区分普通文本与命令卡片,不修改 `.user-bubble` 默认样式
## Key Technical Decisions
- **拦截点选择 ChatInput 按钮 click不放在 BoardMeetingModal 内部** — 最自然的 UX 拦截点,避免用户填表后才发现不能发起。判断依据 `chatStore.boardState` 实时状态,不依赖后端 / `is_board` 标记,避免 reload 后误判(见 origin R4
- **私董会状态判定取 `discussing | concluding`** — `BoardState.status` 类型为 `"discussing" | "concluding" | "completed" | "dissolved"`chatStream.ts:65 确认)。`completed | dissolved` 的旧私董会不阻塞新私董会发起,符合 origin R3 要求。
- **"新建会话"按钮流程:`createConversation()` → `selectConversation(newId, true)`** — `createConversation()` 是同步函数chatStore.ts:333 确认),自动设置 `currentConversationId` 但不加载服务端会话;后续 `selectConversation(newId, true)` 强制 reload 确保状态干净。不自动打开 `BoardMeetingModal`,由用户在新会话中再次点击"私董会"按钮继续(见 origin R2
- **BoardBannerCard 简化为单行标题+副标题,不删除组件本身** — 保留 `BoardBannerCard.vue` 文件,仅重构 template + style。`useMessageRenderer.ts` 的 `board_started` 渲染路径不变origin R8保持 streaming 期间与 reload 后视觉一致。
- **气泡样式挂在 `MessageShell.vue``.message-shell__content` 上,不挂在 `AssistantText.vue`** — `MessageShell` 是所有 assistant 消息的统一外壳,挂在这里可一次性覆盖 board_speech / board_summary / 普通聊天 / @team / Debate 等场景origin R12且不影响 `UserBubble.vue` 的 user 消息样式。**card-bearing 类型例外F4-A Round 4 扩展,共 9 种)**`board_conclusion`/`team_plan`/`debate_banner`/`debate_argument`/`debate_summary`/`debate_resolved`/`collaboration_graph`/`review_result`/`risk_flagged` 渲染的 card 组件自带 card chrome详见 R14不被气泡包裹。
- **气泡使用 CSS 变量,禁止硬编码** — 新增 `--bg-message-bubble: #ffffff` (light) / `#1f1f1f` (dark) 到 `tokens.css`,与 `--bg-secondary`inline code/table 背景 `#fbfbfa`解耦F1-A 决策。dark mode 自动切换。禁止硬编码 `#f3f4f6` / `#fbfbfa` / `#ffffff` 等值project rules 硬约束)。
- **`role === 'assistant'` 时启用气泡,`role === 'user'` 不启用** — 通过 `.message-shell--assistant .message-shell__content` 选择器限定,避免污染 user 消息的 `UserBubble` 样式。
- **F4-A 排除所有 card-bearing 类型Round 4 扩展,共 9 种)** — 排除 9 种 card-bearing assistant 消息类型:`board_conclusion`/`team_plan`/`debate_banner`/`debate_argument`/`debate_summary`/`debate_resolved`/`collaboration_graph`/`review_result`/`risk_flagged`。**chrome 区分Round 4 修正)**:仅 `BoardConclusionCard``.board-conclusion-card`+ `TeamPlanCard``.team-plan-card`)有完整 chromebg + border + radius + shadow其余 7 种(`DebateBannerCard`→`.debate-banner`、`DebateArgumentCard`→`.debate-argument`、`DebateSummaryCard`→`.debate-summary`、`DebateConclusionCard`→`.debate-conclusion`、`CollaborationGraphCard`→`.collab-graph`、`ReviewResultCard`→`.review-card`、`RiskFlagCard`→`.risk-card`)只有 bg + left-border + radius无 shadow、无 full border。气泡选择器需排除这些类型避免"card 嵌套在气泡里"的视觉冲突。实现方式:在 `MessageShell.vue` 增加 `messageType` prop`:has()` 选择器card-bearing 类型不加气泡样式。具体机制依 G3 决策。**第三种替代方案**:在 `MessageRenderSpec` 新增 `bubble: boolean` 字段由 renderer 集中决策(见 Open Questions Round 4 R4-A3。**未来防护**:新增 card 类型需手动加入排除列表或采用 `bubble` 字段方案(见 Open Questions Round 4 R4-A4
- **D4-方案1 `:empty` 隐藏空气泡** — 空 slot 内容pre-stream thinking / tool-call-only时通过 `:empty` 选择器隐藏 `background / border / padding`,仅显示 thinking dots 动画。有内容流入后自动恢复气泡样式,无需 JS 判断。
- **U4 仅普通文本消息深色气泡,命令卡片保持浅色** — `UserBubble.vue` 同时渲染三种内容(普通文本 / @board|@team 命令卡片 / 文件附件命令卡片含结构化信息header + experts list深色背景会降低可读性。通过 `isPlainText` computed + `.user-bubble--text` modifier 精确限定深色样式仅作用于普通文本,命令卡片和文件附件继承 `.user-bubble` 默认 `--bg-tertiary` 浅色背景。
- **U4 用 `--color-primary` + `--text-inverse` 自动适配 dark mode** — `--color-primary` 在 light mode 为 `#1a1a1a`dark mode 为 `#fbfbfa`(浅);`--text-inverse` 反之。user 气泡在 dark mode 下自动反转为浅色背景 + 深色文字,无需额外 dark mode 覆盖。
## Implementation Units
### U1. ChatInput 拦截 + a-modal 弹窗 + 快捷新建会话
**Goal:**`ChatInput.vue` 的"私董会"按钮 click 处增加拦截逻辑:当前会话已存在进行中的私董会时弹 a-modal 提示,提供"我知道了"和"新建会话"两个按钮;"新建会话"按钮调用 `chatStore.createConversation()` + `selectConversation(newId, true)` 跳转到新空会话。
**Requirements:** R1, R2, R3, R4
**Dependencies:**
**Files:**
- src/agentkit/server/frontend/src/components/chat/ChatInput.vue修改
- src/agentkit/server/frontend/src/components/chat/\_\_tests\_\_/ChatInput.test.ts新建
**Approach:**
-`<template>` 中新增一个 `<a-modal v-model:open="showBoardBlockModal">`,标题"当前会话已存在私董会",内容"请新建会话来创建新的私董会"footer 两个按钮:`<a-button @click="showBoardBlockModal = false">我知道了</a-button>` + `<a-button type="primary" @click="handleCreateNewConversationForBoard">新建会话</a-button>`
- 新增 `showBoardBlockModal = ref(false)` state
- 新增 `handleBoardClick()` 函数替代 inline `@click="showBoardModal = true"`
- 检查 `chatStore.boardState?.status`:若为 `'discussing' | 'concluding'``showBoardBlockModal.value = true`return
- 否则 `showBoardModal.value = true`
- 新增 `handleCreateNewConversationForBoard()` 函数:
- `showBoardBlockModal.value = false`
- `chatStore.createConversation()`
- `await chatStore.selectConversation(chatStore.currentConversationId!, true)`
- 不设置 `showBoardModal.value = true`(让用户在新会话中再次点击)
- "私董会"按钮 `@click` 改为 `handleBoardClick`
- **焦点恢复Round 4 R4-D3**a-modal 关闭时(无论"我知道了"还是"新建会话")需将焦点返回到触发按钮("私董会"按钮),符合 WAI-ARIA modal 对话模式。Ant Design Vue 的 `<a-modal>` 通过 `@cancel` / `@ok` 事件触发关闭,关闭后焦点默认落在 modal 本身;需在 `handleCreateNewConversationForBoard` 和"我知道了"按钮 click 后显式 `nextTick(() => boardButtonRef.value?.focus())`(或使用 autofocus 属性)。新建会话跳转后焦点自然落于新会话输入框,无需额外处理
**Patterns to follow:**
- `ChatInput.vue:63-71``showTeamModal` 模式v-model:open + ref + handleXxxSubmit
- `chatStore.ts:333-345``createConversation` 同步签名
- `chatStore.ts:219``selectConversation(id, force = false)` 异步签名
**Test scenarios:**
- Happy path`chatStore.boardState === null` → 点击"私董会"按钮 → `showBoardModal === true``showBoardBlockModal === false`
- Happy path`chatStore.boardState?.status === 'completed'` → 点击"私董会"按钮 → `showBoardModal === true`(已完成私董会不阻塞)
- Happy path`chatStore.boardState?.status === 'dissolved'` → 同上
- 拦截 path`chatStore.boardState?.status === 'discussing'` → 点击"私董会"按钮 → `showBoardModal === false``showBoardBlockModal === true`
- 拦截 path`chatStore.boardState?.status === 'concluding'` → 同上
- "新建会话"按钮:点击后 `showBoardBlockModal === false``chatStore.currentConversationId` 已变化,`chatStore.boardState === null`(新会话清空 boardState
- "我知道了"按钮:点击后 `showBoardBlockModal === false``chatStore.currentConversationId` 不变,`showBoardModal === false`
> **注**Pinia setup store 的 ref 自动解包,`chatStore.boardState` 已是 `BoardState | null`,不能写 `chatStore.boardState.value`。如需 ref 访问用 `storeToRefs(chatStore).boardState.value`。
**Verification:**
- 启动 Tauri 客户端,在已有私董会的会话中点击"私董会"按钮,应弹出提示而非 modal
- 点击"新建会话"后自动跳转到新会话boardState 清空
- 在新会话中点击"私董会"按钮可正常打开 BoardMeetingModal
---
### U2. BoardBannerCard 简化为单行标题+副标题
**Goal:**`BoardBannerCard.vue` 从带边框/紫条/进度条/专家 chip 的重样式简化为纯文本两行:`私董会 — {topic}` + `轮次:第 N/M 轮`,删除 BankOutlined 图标、4px 紫条、专家 chip、卡片背景/边框/圆角/阴影。
**Requirements:** R5, R6, R7, R8
**Dependencies:** 无(与 U1/U3 互不依赖,可并行)
**Files:**
- src/agentkit/server/frontend/src/components/chat/messages/BoardBannerCard.vue修改
- src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts修改**F3-A 决策**:删除 board_banner case 中 experts prop 传参,避免 Vue 3 attribute fallthrough 把 `[object Object]` 渲染到根 div
- src/agentkit/server/frontend/src/components/preview/scenes/Scene4BoardDiscussion.vue修改适配新 props
- src/agentkit/server/frontend/src/components/chat/messages/\_\_tests\_\_/BoardBannerCard.test.ts新建
**Approach:**
- `<template>` 简化为:
```vue
<div class="board-banner-card">
<div class="board-banner-card__title">私董会 — {{ topic }}</div>
<div class="board-banner-card__meta">轮次:第 {{ currentRound }} / {{ maxRounds }} 轮</div>
</div>
```
- 删除 `<script setup>` 中的 `BankOutlined` import 和 `progressPercent` computed
- `interface Props` 删除 `experts` 字段,保留 `topic / maxRounds / currentRound`
- `<style scoped>` 删除 `.board-banner-card__bar / __body / __icon / __experts / __chip / __chip--moderator / __chip-avatar / __progress / __progress-fill` 共 9 个类,仅保留:
- `.board-banner-card` 容器(`margin-bottom: var(--space-3)` + `padding: var(--space-2) 0`,无 background/border/shadow
- `.board-banner-card__title``font-size: var(--font-md); font-weight: var(--font-weight-semibold); color: var(--text-primary); margin-bottom: var(--space-1)`
- `.board-banner-card__meta``font-size: var(--font-xs); color: var(--text-tertiary)`
- `Scene4BoardDiscussion.vue` 中调用 `<BoardBannerCard>` 的地方删除 `:experts` prop 传参
- `useMessageRenderer.ts``board_banner` case 删除 `experts` prop 传参(**F3-A 决策**
**Patterns to follow:**
- `MessageShell.vue:150-163``.message-shell__name / __meta` 简洁文本样式
**Test scenarios:**
- Happy path传入 `topic="测试主题" maxRounds=5 currentRound=2` → 渲染两行文本,第一行"私董会 — 测试主题",第二行"轮次:第 2 / 5 轮"
- 边界:`currentRound=0` → 第二行"轮次:第 0 / 5 轮"(不报错)
- 边界:`maxRounds=0` → 第二行"轮次:第 1 / 0 轮"(不报错,不做除零校验)
- 渲染契约:组件根 div **不**含 `background / border / border-radius / box-shadow` 任一 CSS 属性
- 渲染契约:组件**不**渲染 `<BankOutlined />` / `.board-banner-card__bar` / `.board-banner-card__chip` 任一元素
- Props 契约:传入 `experts` 数组时组件不报错向后兼容prop 已删除会被 Vue 忽略为 fallthrough attr但需确认不会作为 attribute 渲染到根 div
- **F3-A fallthrough 契约**`useMessageRenderer.ts` 的 `board_banner` case 不再传 `experts` prop已删除根 div **不**含 `experts="[object Object]"` 属性
**Verification:**
- 启动 Tauri 客户端,发起私董会,第一条消息应显示为纯文本两行,无卡片背景/图标/进度条
- 预览场景 `Scene4BoardDiscussion.vue` 仍然能正常渲染
---
### U3. MessageShell 浅灰气泡方案B
**Goal:**`MessageShell.vue``.message-shell__content` 上增加方案 B 风格的浅灰圆角矩形气泡样式,仅 `role === 'assistant'` 时生效,使用 CSS 变量绑定dark mode 自动切换。**F1-A 决策**:气泡用独立 token `--bg-message-bubble`(与 inline code/table 的 `--bg-secondary` 解耦);**F4-A 决策Round 4 扩展,共 9 种)**:排除所有 card-bearing assistant 类型(`board_conclusion` / `team_plan` / `debate_banner` / `debate_argument` / `debate_summary` / `debate_resolved` / `collaboration_graph` / `review_result` / `risk_flagged`),这些类型由各自 card 组件自带 card chrome2 种 full chrome + 7 种 partial chrome详见 R14**D4-方案1 决策**:空内容通过 `:empty` 隐藏气泡。
**Requirements:** R9, R10, R11, R12, R13, R14, R15
**Dependencies:** 无(与 U1/U2 互不依赖,可并行)
**Files:**
- src/agentkit/server/frontend/src/styles/tokens.css修改**F1-A 决策**:新增 `--bg-message-bubble` tokenlight `#ffffff` / dark `#1f1f1f`
- src/agentkit/server/frontend/src/components/chat/messages/MessageShell.vue修改
- src/agentkit/server/frontend/src/components/chat/messages/\_\_tests\_\_/MessageShell.test.ts新建或扩展
**Approach:**
- **第一步F1-A**:在 `tokens.css``:root`light`[data-theme="dark"]`(或等效 dark 选择器)下新增 `--bg-message-bubble: #ffffff`light/ `#1f1f1f`dark。位置紧邻 `--bg-secondary` 定义,注释说明"消息气泡背景,与 inline code/table 背景解耦"
- **第二步**:在 `MessageShell.vue``<style scoped>` 中新增规则:
```css
.message-shell--assistant .message-shell__content {
background: var(--bg-message-bubble);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: var(--space-3) var(--space-4);
color: var(--text-primary);
}
/* F4-A: 所有 card-bearing assistant 类型不加气泡(共 9 种Round 4 扩展)。
已验证根 class 名:`.board-conclusion-card` / `.team-plan-card`full chrome含 shadow /
`.debate-banner` / `.debate-argument` / `.debate-summary` / `.debate-conclusion` / `.collab-graph` / `.review-card` / `.risk-card`
7 种 partial chrome仅 bg + left-border + radius无 shadow 无 full border注意 root class 名与组件名不匹配,不带 -card 后缀)。
实现期依 G3 决策扩展选择器或 messageType prop 排除列表 */
.message-shell--assistant.message-shell--conclusion .message-shell__content,
.message-shell--assistant:has(.board-conclusion-card) .message-shell__content {
background: transparent;
border: none;
border-radius: 0;
padding: 0;
}
/* D4-方案1: 空 slot 内容隐藏气泡背景,仅显示 thinking dots */
.message-shell--assistant .message-shell__content:empty {
background: transparent;
border: none;
padding: 0;
}
```
- **第三步F4-A 配套)**`MessageShell.vue` 的 `interface Props` 新增 `messageType?: string` prop可选template 根 div 根据 `messageType === 'board_conclusion'` 添加 `.message-shell--conclusion` class。或使用 `:has(.board-conclusion-card)` 选择器(更简洁但需确认浏览器支持——现代浏览器已支持 `:has()`Tauri WebView 基于 Chromium 111+ 支持)
- 选择器限定 `.message-shell--assistant` 前缀,确保 `role === 'user'``UserBubble` 不受影响
- 不使用 `!important`,让 `AssistantText.vue:257-401` 内部的 `pre / hljs / code / table` 样式自然继承(这些样式已用 `--code-bg` 等 token浅灰气泡背景下自动适配
- 不修改 `.message-shell__content``display: flex / flex-direction: column / gap: var(--space-2) / width: 100% / max-width: 100%`R13 要求)
- 不修改 `.message-shell--user .message-shell__content``align-items: flex-end`user 消息保持右对齐,不加气泡)
**Patterns to follow:**
- `tokens.css:65,223``--bg-secondary` 定义light `#fbfbfa`dark `#1f1f1f`)—— `--bg-message-bubble` 紧邻此定义
- `tokens.css:79,237``--border-color` 定义
- `tokens.css:97``--radius-md: 6px`
- `LoginView.vue:184-196` 的"显式绑定 token禁硬编码"模式
**Test scenarios:**
- Happy pathassistant渲染 `<MessageShell role="assistant">` + 默认 slot 内容 → `.message-shell__content` 计算样式含 `background-color: rgb(255, 255, 255)`light mode 下 `--bg-message-bubble: #ffffff`
- Happy pathuser渲染 `<MessageShell role="user">` + 默认 slot 内容 → `.message-shell__content` 计算样式 `background-color` 为空或 `rgba(0,0,0,0)`(不加气泡)
- **F1-A 区分性**slot 内含 `<code>async</code>` inline code → inline code 背景为 `--bg-secondary` (`#fbfbfa`),气泡背景为 `--bg-message-bubble` (`#ffffff`),二者视觉可区分
- 气泡内代码块slot 内含 `<pre><code>` → 代码块背景 `--code-bg` 不被气泡背景覆盖(视觉可区分)
- 气泡宽度:`.message-shell__content` 计算样式 `width: 100%``max-width: 100%`(跟随消息列宽)
- Dark mode切换到 dark mode → `.message-shell__content` 计算样式 `background-color: rgb(31, 31, 31)``--bg-message-bubble: #1f1f1f`
- **F4-A 排除 card-bearing 类型Round 4 扩展,共 9 种)**:分别渲染 `<MessageShell role="assistant" messageType="board_conclusion|team_plan|debate_banner|debate_argument|debate_summary|debate_resolved|collaboration_graph|review_result|risk_flagged">` + 对应 card slot`BoardConclusionCard` / `TeamPlanCard` / `DebateBannerCard` / `DebateArgumentCard` / `DebateSummaryCard` / `DebateConclusionCard` / `CollaborationGraphCard` / `ReviewResultCard` / `RiskFlagCard`)→ 每种类型 `.message-shell__content` 计算样式 `background-color: transparent``border: none`不加气泡card 自身 chrome 保留2 种 full chrome + 7 种 partial chrome 详见 R14
- **D4-方案1 空内容隐藏**:渲染 `<MessageShell role="assistant">` + 空 slot → `.message-shell__content` 计算样式 `background-color: transparent``border: none``padding: 0``:empty` 选择器生效)
- **D4-方案1 有内容恢复**:渲染 `<MessageShell role="assistant">` + 非空 slot → `:empty` 不匹配,气泡样式正常应用
- 渲染契约:样式**不**使用 `!important`
- 渲染契约:样式**不**硬编码 `#f3f4f6 / #fbfbfa / #ededec / #ffffff` 等值(必须用 CSS 变量)
**Verification:**
- 启动 Tauri 客户端,发起普通 chatassistant 消息应有浅灰(实际为 `#ffffff` 纯白,与 `#fbfbfa` 的 inline code 区分)圆角矩形气泡
- 发起私董会专家发言board_speech应有同款气泡
- 发起私董会结论消息board_conclusion应保留 `BoardConclusionCard` 自身 card 样式,**不**被气泡包裹
- 同理验证 `team_plan` / `debate_banner` / `debate_argument` / `debate_summary` / `debate_resolved` / `collaboration_graph` / `review_result` / `risk_flagged` 消息保留各自 card chrome`TeamPlanCard` / `DebateBannerCard` / `DebateArgumentCard` / `DebateSummaryCard` / `DebateConclusionCard` / `CollaborationGraphCard` / `ReviewResultCard` / `RiskFlagCard`**不**被气泡包裹F4-A Round 4 扩展,共 9 种)
- 切换 dark mode气泡背景应自动变为深色`#1f1f1f`
- user 消息(右侧)应显示为 demo 中的深色气泡(`--color-primary` 背景 + `--text-inverse` 白字),@board/@team 命令卡片保持现有浅色背景(见 U4
- assistant 消息在 thinking 阶段(空内容)不应显示空气泡矩形,仅显示三点动画
---
### U4. UserBubble 普通文本深色气泡(参考 demo
**Goal:**`UserBubble.vue` 的普通文本消息(`<span class="user-bubble__text">`)样式改为 demo 中的深色右对齐气泡:`--color-primary` 背景 + `--text-inverse` 白字 + `padding: var(--space-3) var(--space-4)` + `max-width: 70%`。@board/@team 命令卡片和文件附件保持现有 `--bg-tertiary` 浅色背景,不变。
**Requirements:** R16, R17, R18, R19
**Dependencies:** 无(与 U1/U2/U3 互不依赖,可并行)
**Files:**
- src/agentkit/server/frontend/src/components/chat/messages/UserBubble.vue修改
- src/agentkit/server/frontend/src/components/chat/messages/\_\_tests\_\_/UserBubble.test.ts新建或扩展
**Approach:**
- `<script setup>` 新增 `isPlainText` computed`!fileAttachment.value && !commandBubble.value`
- `<template>` 根 div 的 `:class` 新增 `'user-bubble--text': isPlainText` modifier
```vue
<div
class="user-bubble"
:class="{
'user-bubble--focusable': msgId,
'user-bubble--text': isPlainText,
}"
...
>
```
- `<style scoped>` 新增 `.user-bubble--text` 规则仅普通文本消息时覆盖 `.user-bubble` 默认背景
```css
/* U4: 普通文本消息参考 demo 深色气泡command card / file attachment 保持浅色 */
.user-bubble--text {
background: var(--color-primary);
color: var(--text-inverse);
padding: var(--space-3) var(--space-4);
max-width: 70%;
}
```
- 不修改 `.user-bubble` 默认样式`--bg-tertiary` 背景 + `--text-primary` 文字 command card file attachment 继承现有浅色背景
- 不修改 `.user-bubble__command` / `.user-bubble__command-*` 内部样式保持现有结构化卡片可读性
- 不修改 `.user-bubble__actions` 操作按钮工具栏样式当前 `--bg-secondary` 背景 + `--border-color` 边框在深色气泡旁可见性需验证
- `--color-primary` `--text-inverse` light/dark mode 下自动反转tokens.css:18,176 + 75,233 确认dark mode user 气泡自动变为浅色背景 + 深色文字
**Patterns to follow:**
- demo `tmp_scheme_b_options_demo.html:230-240` `.user-msg__bubble` 样式`--color-primary` + `--text-inverse` + `--space-3 var(--space-4)` + `--radius-lg` + `max-width: 70%`
- `tokens.css:18,176` `--color-primary` 定义light `#1a1a1a` / dark `#fbfbfa`
- `tokens.css:75,233` `--text-inverse` 定义light `#ffffff` / dark `#1a1a1a`
**Test scenarios:**
- Happy path普通文本渲染 `<UserBubble content="Hello">` div `.user-bubble--text` class计算样式 `background-color: rgb(26, 26, 26)`light mode `--color-primary: #1a1a1a``color: rgb(255, 255, 255)``--text-inverse: #ffffff`
- Happy path@board 命令渲染 `<UserBubble content="@board 测试主题">` div **** `.user-bubble--text` class背景保持 `--bg-tertiary`浅色command card 内部样式不变
- Happy path@team 命令渲染 `<UserBubble content="@team 测试">` 同上保持浅色背景
- Happy path文件附件渲染 `<UserBubble content="[文件][x.pdf](url)">` div **** `.user-bubble--text` class背景保持 `--bg-tertiary`
- Dark mode切换到 dark mode 普通文本消息 `.user-bubble--text` 计算样式 `background-color: rgb(251, 251, 250)``--color-primary: #fbfbfa``color: rgb(26, 26, 26)``--text-inverse: #1a1a1a`自动反转
- 操作按钮工具栏普通文本消息 hover `.user-bubble__actions` 可见在深色气泡左侧`right: calc(100% + var(--space-2))`显示`--bg-secondary` 背景与深色气泡有视觉区分
- max-width 契约普通文本消息 `max-width: 70%`command card 保持 `max-width: 100%``.user-bubble__command` 自身设置
- 渲染契约样式****使用 `!important`
- 渲染契约样式****硬编码 `#1a1a1a / #ffffff` 等值必须用 CSS 变量
**Verification:**
- 启动 Tauri 客户端发送普通文本消息 右侧显示深色圆角气泡白字
- 发送 @board 命令 右侧显示浅色结构化命令卡片保持现有样式
- 发送 @team 命令 同上保持浅色
- 切换 dark mode 普通文本消息自动反转为浅色气泡 + 深色文字command card 不变
- 普通文本消息 hover 操作按钮工具栏在气泡左侧可见
---
## Scope Boundaries
### Deferred to Follow-Up Work
- **后端按会话去重 `board_started`**本次只做前端拦截不改后端如果未来需要在多端/多浏览器同步场景下做强一致再考虑后端校验origin 文档已声明 deferred)。
- **StickyModeHeader 顶部条的视觉细化**徽章大小专家头像间距紫色边框粗细不在本次范围
- **BoardConclusionCard / DebateBannerCard / TeamPlanCard 等其他 board 模式组件的样式统一**本次只改 `BoardBannerCard`其他卡片样式留待后续迭代
### Outside this product's identity
- 不调整方案 B 调色板`expertIdentity.ts` 12 PALETTE和专家头像首字符规则
- 不调整私董会后端流程`BoardOrchestrator` / `chatStream` 事件顺序)。
- 不调整 AssistantText markdown 渲染逻辑代码高亮表格样式
## Risks & Dependencies
- **风险 1U3 气泡样式可能影响 `AssistantText` 内部代码块/表格的视觉对比度** 缓解测试场景明确要求代码块背景 `--code-bg` 不被覆盖`AssistantText.vue:257-401` `pre/hljs/code/table` 样式已用独立 token理论上不受影响如出现对比度问题可在 `AssistantText.vue` 内微调 `--code-bg` ****回滚 U3 的气泡
- **风险 2U1 `selectConversation(newId, true)` 强制 reload 可能导致用户感知"卡顿"** 缓解`force=true` 仅在用户主动点击"新建会话"时触发是用户预期行为如确实卡顿可考虑后续优化不在本次范围)。
- **依赖 1`chatStore.createConversation()` 是同步函数无返回值但内部已自动设置 `currentConversationId`** 实现时需注意 `createConversation()` 再读 `chatStore.currentConversationId` 拿到新 id不能直接 `const newId = await chatStore.createConversation()`
- **依赖 2`BoardState.status` 当前联合类型为 4 个值未来若新增 `forming / executing / synthesizing` 等中间状态需扩展"已存在"判断** `handleBoardClick` 中用 `if (status === 'discussing' || status === 'concluding')` 显式判断未来扩展时只需补 `||`
## Open Questions
以下问题在 ce-doc-review 中由 reviewer 提出。**Round 12026-07-02 demo 确认** 已决策 F1/F3/F4/C2/D4**Round 22026-07-02 复审** 新增 7 findingsG1-G5 + D2-R2/D3-R2/D4-R2全部 append 到本 section 留待实现期处理其中 **G1 挑战 Round 1 D4-方案1 决策**P0 load-bearing3 reviewer 一致 confidence 100 认为不可实现**G5 挑战 Round 1 F1-A 决策**`#ffffff` `#fbfbfa` 视觉不可区分)—— 实现期需优先处理这两个挑战
### 已决策Round 1 — 2026-07-02 demo 确认)
- **F1feasibilityP1)— 已决策 F1-A**U3 气泡背景与 inline code/table 背景冲突。**决策**引入独立 token `--bg-message-bubble`light `#ffffff` / dark `#1f1f1f`区分气泡背景与 inline code/table 背景保持 `--bg-secondary: #fbfbfa`)。已固化到 R9U3 ApproachKey Technical Decisions。**⚠ Round 2 G5 挑战**`#ffffff` `#fbfbfa` 色差 <1% 低于 JND `#ffffff` `--bg-primary` 相同导致气泡仅靠边框可见——见下方 G5
- **F3feasibilityP2)— 已决策 F3-A**U2 删除 `BoardBannerCard` `experts` prop `useMessageRenderer.ts:145` fallthrough 风险。**决策** U2 Files 列表中加 `useMessageRenderer.ts`删除 `board_banner` case `experts` prop 传参已固化到 U2 FilesU2 ApproachU2 Test scenarios
- **F4feasibilityP2)— 已决策 F4-A**U3 气泡包裹 `BoardConclusionCard` 的嵌套冲突。**决策**气泡选择器排除 `board_conclusion` 类型`BoardConclusionCard` 保留自身 card chrome 不被气泡包裹已固化到 R14U3 ApproachU3 Test scenariosKey Technical Decisions。**⚠ Round 2 G2/G3 挑战**`:has()` 事实错误 + `messageType` prop 选项需 ChatMessage.vue不在 Files)——见下方 G2/G3。**⚠ Round 3 扩展**F4-A 排除列表从 1 种扩展到 5 新增 `team_plan`/`debate_banner`/`debate_argument`/`debate_summary`)。**⚠ Round 4 扩展R4-A1**进一步扩展到 9 新增 `debate_resolved`/`collaboration_graph`/`review_result`/`risk_flagged`并修正 chrome 描述2 full chrome + 7 partial chrome)。详见 R14 Open Questions Round 4
- **C2coherenceP2)— 已应用 safe_auto**R7 表述已修订为"删除 `.board-banner-card` 的重样式background/border/border-radius/box-shadow `__bar / __chip` 等重样式类保留 `.board-banner-card` 容器 margin/padding+ `__title / __meta` 最小样式"。
- **D4design-lensP2)— 已决策 D4-方案1**U3 空内容气泡表现。**决策**通过 `:empty` 选择器隐藏 `background / border / padding`仅显示 thinking dots已固化到 R15U3 ApproachU3 Test scenariosKey Technical Decisions。**⚠ Round 2 G1 推翻**`:empty` 永不匹配AssistantText 总渲染根 div)——见下方 G1实现期必须替换方案
### 仍待实现期决定
#### Round 1 遗留
- **D1design-lensP1**U1 `selectConversation(newId, true)` 失败时无错误处理——modal 已关闭currentConversationId 已变更用户无反馈待选方案try/catch + a-message error toast + 回滚 currentConversationId
- **D2design-lensP2**U1 modal 关闭与 selectConversation resolve 之间的过渡期无 loading 反馈待选方案modal "新建会话"按钮 loading spinner / 全局 loading overlay
- **D3design-lensP2**U2 topic 文本无 overflow/wrap/truncation 策略待选方案`text-overflow: ellipsis` + `white-space: nowrap` / `word-break: break-word` / 多行 line-clamp
#### Round 2 复审新增2026-07-02
**P0/P1 load-bearing挑战 Round 1 决策,实现期优先处理):**
- **G1feasibility + design-lens + adversarial 三方共识P0conf 100×3)— 推翻 Round 1 D4-方案1**`:empty` 选择器永不匹配。`AssistantText.vue:1-6` 总是渲染根 `<div class="assistant-text">` loading spinner / tool cards / routing div`useMessageRenderer.ts:361-362` 总是将 AssistantText 作为 slot 子组件挂载到 `.message-shell__content`所以 `.message-shell__content` 在生产环境永远非空D4-方案1 `:empty` CSS 规则不会生效thinking 阶段会显示空气泡矩形正是 D4-方案1 要防止的 UX 缺陷)。plan 的测试场景" slot `:empty` 生效"用真空 slot 不反映生产。**待选方案**
- (a) MessageShell `isEmpty` computed`!message.content && !message.thinking && toolCalls.length === 0`+ `.message-shell--empty` class 覆盖气泡样式 —— 推荐最简单
- (b) `:has(.assistant-text__loading:only-child)` 检测纯 loading 状态 —— 脆弱不推荐
- (c) AssistantText 真空时不渲染根 div —— 高风险可能破坏 ThinkingBlock / ToolCallCard
- 实现期需选择 (a)/(b)/(c) 并更新 R15U3 Approach 第二步 CSSU3 Test scenariosKey Technical Decisions
- **G5adversarial + feasibility residualP2conf 75)— 挑战 Round 1 F1-A**`#ffffff`纯白vs `#fbfbfa`off-white色差 <1% 低于人眼 JND~2.3 `#ffffff` `--bg-primary: #ffffff`页面背景相同导致气泡仅靠 1px `--border-color: #ededec` 边框可见AE6"视觉可区分"断言可能不成立origin 文档描述为"浅灰圆角矩形气泡" `#ffffff` 实际是白色。**待选方案**
- (a) `--bg-message-bubble` light 改为 `#f7f7f5`实际浅灰 `--bg-tertiary` 一致)—— 视觉符合 origin 描述
- (b) 保持 `#ffffff` 但修订 AE6 + U3 Test scenarios "视觉可区分" "token 级解耦未来兼容视觉差异 sub-JND"
- (c) 改用 `#f3f4f6` 等更明显灰值
- 实现期需选择 (a)/(b)/(c) 并更新 R9U3 ApproachAE6tokens.css
**P2 gated_auto事实修正 + 决策落地):**
- **G2adversarial + feasibility residualP2conf 75)— Tauri `:has()` 事实错误**plan U3 Approach 202 行称"Tauri WebView 基于 Chromium 111+ 支持"实际 Tauri 2.x macOS WKWebViewSafari 引擎Linux WebKitGTK Windows WebView2Chromium)。`:has()` Safari 15.4+2022-03/ WebKitGTK 2.35+ / Chromium 105+ 支持实际可用但 plan 推理错误。**Fix**修正 U3 Approach 202 + Key Technical Decisions F4-A 段为"Tauri 2.x macOS WKWebViewSafari 15.4+ 支持 `:has()`Linux WebKitGTK 2.35+Windows WebView2 Chromium 105+。若需支持更老 macOS优先用 `messageType` prop 方案"。
- **G3coherence + adversarialP2conf 75)— F4-A `:has()` vs `messageType` prop 决策悬而未决 + ChatMessage.vue 缺失**plan F4-A 结论排除提供两个选项`:has(.board-conclusion-card)` 选择器 OR `messageType` prop + `.message-shell--conclusion` class但不决策且三处描述不一致Key Technical Decisions `:not(:has())`U3 Approach ""U3 CSS 用两者 comma-joined)。若选 `messageType` prop需修改 ChatMessage.vue 传递 `:message-type="spec.type"` ChatMessage.vue 不在 U3 Files 列表。**Fix**实现期决策用 `messageType` prop更稳健 CSS 兼容风险添加 `src/agentkit/server/frontend/src/components/chat/ChatMessage.vue` U3 Files更新 Key Technical Decisions + U3 Approach + U3 CSS 三处统一为 `messageType` prop 方案
**P2 manual设计决策**
- **D2-R2design-lensP2conf 75)— U4 focus ring 在深色气泡上不可见**`UserBubble.vue:304` `var(--accent-primary, #1a1a1a)` `--accent-primary` tokens.css 未定义fallback `#1a1a1a` `--color-primary` 相同U4 深色气泡`--color-primary: #1a1a1a` 背景)上 focus ring 不可见WCAG 1.4.11 违规。**待选方案**(a) 定义 `--accent-primary` token `#3b82f6` light / `#60a5fa` dark(b) 改用 `--color-info` / `--accent-team`(c) 不在本次处理pre-existing bugU4 加剧但不引入)。
- **D3-R2design-lensP2conf 75)— U3 dark mode 代码块对比度不足**dark mode `--code-bg: #11111b`tokens.css:255vs `--bg-message-bubble: #1f1f1f` 仅差 ~14 亮度级代码块可能融入气泡light mode 对比度足够`#1e1e2e` on `#ffffff`)。**待选方案**(a) dark mode 专属测试场景验证对比度(b) 调整 `--bg-message-bubble` dark 值为 `#252525`接近 `--bg-elevated`(c) `pre` `border: 1px solid var(--border-color)`
- **D4-R2design-lensP2conf 75)— U2 简化 banner 缺乏视觉分隔**删除所有 chrome 两行纯文本 bannerfont-md semibold + font-xs tertiary可能融入消息流失去"section divider"作用BankOutlined 图标删除后无视觉锚点。**待选方案**(a) 保留 2px 左边框 `--accent-board`匹配私董会身份克制(b) 加小色点前缀(c) 纯排版足够"私董会 —" 文本前缀即锚点)。
#### Round 3 复审新增2026-07-03
**P1 manualorigin scope 边界):**
- **R3-A1scope-guardianP1conf 100)— U4 UserBubble 深色气泡超出 origin 范围**origin `docs/brainstorms/2026-07-02-private-board-restrictions-and-scheme-b-bubbles-requirements.md` Key Decisions 明确声明"role === 'user' 的用户消息气泡保留现有 UserBubble.vue 的右对齐独立样式不加 AssistantText 风格的浅灰块"。但本 plan U4 改动 `UserBubble.vue`将普通文本消息改为 `--color-primary` 深色背景 + `--text-inverse` 白字的深色气泡R16-R19属于 origin 之外的 scope creep。**待选方案**(a) Drop U4 R16-R19保持 origin 不变U3 仍完成 assistant 气泡统一(b) 保留 U4 但正式 amend origin requirements doc补充"用户普通文本消息改为深色右对齐气泡"决策建议 (b)因为 U4 视觉冲击显著demo 已确认方向但需走 origin amend 流程避免 plan/origin 漂移
**P2 gated_auto事实修正**
- **R3-F2feasibilityP2conf 50)— selectConversation `force=true` local 会话不生效**plan U1 Approach 中关于"切换会话时通过 `selectConversation(force=true)` 强制重新加载消息"的描述有事实偏差。`chatStore.ts:233` 的守卫为 `!conv?.is_local && (force || ...)` `is_local=true` 的会话含新建会话会跳过 reload 调用——状态干净实际由 `createConversation` 保证新建即清空 boardState/selectedExpert )。**建议 fix** U1 Approach / Key Technical Decisions 条目中明确归因——"切换会话时 boardState 清空由 `createConversation` 在新建会话时执行对非 local 会话调用 `selectConversation` `force=true` 仍生效"。无需修改代码仅修正 plan 文档归因
#### Round 4 复审新增2026-07-03
**已应用auto-resolve 决策汇总):**
- **R4-C1coherenceP1conf 100safe_auto applied** Lines 58 & 389 "board_conclusion 例外" 已更新为 "card-bearing 类型例外F4-A Round 4 扩展"
- **R4-F1feasibilityP2conf 75gated_auto applied** 已验证 9 card root class 名并添加到 R14Key Technical Decisions F4-AU3 CSS 注释注意 7 partial chrome 卡片 root class 名不带 `-card` 后缀
- **R4-A1adversarialP1conf 100auto-resolve applied** F4-A 排除列表从 5 种扩展到 9 新增 `debate_resolved` / `collaboration_graph` / `review_result` / `risk_flagged`)。已更新 R14Key Technical Decisions F4-AU3 GoalU3 CSS 注释U3 Test scenariosU3 Verification
- **R4-D1design-lensP2conf 100auto-resolve applied** F4-A card chrome claim 已修正 `BoardConclusionCard` + `TeamPlanCard` 有完整 chromebg + border + radius + shadow其余 7 种只有 partial chromebg + left-border + radius shadow full border)。已更新 R14Key Technical Decisions F4-AU3 CSS 注释
- **R4-D3design-lensP2conf 75auto-resolve applied** U1 Approach 新增焦点恢复说明a-modal 关闭后焦点返回"私董会"按钮WAI-ARIA modal 对话模式
**P2 manual待后续处理**
- **R4-DA1design-lens + adversarial cross-persona promoteP2conf 100)— 7 partial chrome 卡片硬编码浅色dark mode 下与 F4-A 排除气泡后视觉冲突加剧**`DebateBannerCard``#f9f0ff`)、`DebateArgumentCard``#fafafa`)、`DebateSummaryCard``#fff7e6`)、`DebateConclusionCard`4 变体 `#fff7e6`/`#f6ffed`/`#fffbe6`/`#f5f5f5`/`#fff2f0`)、`CollaborationGraphCard``#f0f8ff`)、`ReviewResultCard`3 变体 `#fafafa`/`#f6ffed`/`#fffbe6`/`#fff2f0`)、`RiskFlagCard``#fff7e6`均使用硬编码浅色背景U3 气泡用 `--bg-message-bubble` token 自动适配 dark mode但这些 card F4-A 排除后dark mode 下仍显示硬编码浅色背景对比度刺眼。**这是 pre-existing 问题超出 U3 scope**U3 仅负责气泡不负责修复 card 颜色)。**建议**新建独立 unit U5: Card Dark Mode Adaptation将这 7 card 的硬编码颜色迁移到 CSS 变量 U3 并行但不阻塞或暂记为 follow-up tech debt
- **R4-A3adversarialP2conf 75)— F4-A 遗漏第三种架构替代方案**当前 G3 决策在 `messageType` prop `:has()` 选择器间二选一存在第三种方案 `MessageRenderSpec` 接口`useMessageRenderer.ts:44-48`新增 `bubble: boolean` 字段 renderer `ChatMessage.vue`集中决策是否包裹气泡card-bearing 类型在 spec 定义时设 `bubble: false`。**优势**新增 card 类型时无需改 `MessageShell.vue` 排除列表避免 R4-A4 的未来防护问题spec 集中管理单一数据源。**劣势**需修改 `MessageRenderSpec` 接口和所有 spec 定义点12+ 类型改动面比 prop/selector 方案大。**建议**实现期若 G3 二选一难以决断可考虑此方案作为 tie-breaker否则记为 future refactor 候选
- **R4-A4adversarialP2conf 75)— F4-A 排除列表对未来新增 card 类型无防护**当前 F4-A 排除列表硬编码 9 种类型新增 card-bearing 类型时需手动加入 `MessageShell.vue` 排除列表prop selector 方案否则新 card 会被气泡错误包裹。** R4-A3 关联**采用 `MessageRenderSpec.bubble` 字段方案可从根本上解决renderer 集中决策但需更大重构。**建议**若采用 G3 prop/selector 方案需在 `useMessageRenderer.ts` 新增 card 类型时强制走 code review checklist 验证 F4-A 排除列表是否同步更新或采用 R4-A3 `bubble` 字段方案一劳永逸
## System-Wide Impact
- **前端用户**所有 Tauri 客户端和 Web GUI 用户将看到 (a) 私董会按钮在已有私董会的会话中弹提示而非 modal(b) 私董会开始消息显示为简洁两行文本(c) 所有 assistant 消息有浅灰圆角气泡`--bg-message-bubble: #ffffff`card-bearing 类型例外保留自身 card(d) thinking 阶段不显示空气泡视觉变化明显但符合方案 B 整体取向
- **后端/运维**无影响不动后端不动部署不动配置)。
- **其他团队**无影响仅前端 Vue 组件)。
## Acceptance Examples
- AE1用户在已存在 `status='discussing'` 私董会的会话中点击"私董会"按钮 弹出 a-modal 提示"当前会话已存在私董会"点击"新建会话"后自动跳转到新空会话boardState 清空
- AE2用户在新会话中发起私董会 第一条消息显示为两行纯文本"私董会 {topic}" + "轮次 1/5 "无卡片背景/图标/进度条
- AE3用户发起私董会专家依次发言 每条专家发言board_speech显示为彩色圆形头像 + 粗体名字 + " N · 专家" + 浅灰圆角矩形气泡包裹内容
- AE4用户发起普通 chat不涉及私董会)→ assistant 消息有浅灰圆角气泡user 普通文本消息有深色右对齐气泡U4)。
- AE5用户切换到 dark mode 浅灰气泡自动变为深色`--bg-message-bubble: #1f1f1f`代码块/表格对比度保持可读
- AE6F1-Aassistant 消息气泡内含 inline code inline code 背景 `--bg-secondary: #fbfbfa` 与气泡背景 `--bg-message-bubble: #ffffff` 视觉可区分
- AE7F4-A私董会结论消息board_conclusion)→ `BoardConclusionCard` 保留自身 card chromebackground/border/shadow****被气泡包裹无嵌套冲突
- AE8D4-方案1assistant 消息在 thinking 阶段空内容)→ 不显示空气泡矩形仅显示三点动画内容流入后自动出现气泡
- AE9U4用户发送普通文本消息 右侧显示深色圆角气泡`--color-primary` 背景 + 白字max-width 70%
- AE10U4用户发送 @board 命令 右侧显示浅色结构化命令卡片保持现有 `--bg-tertiary` 背景不受深色气泡影响
- AE11U4切换 dark mode 普通文本消息自动反转为浅色气泡 + 深色文字@board 命令卡片样式不变