426 lines
51 KiB
Markdown
426 lines
51 KiB
Markdown
---
|
||
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-IDs(R1-R13)并新增 R14-R19(F4-A / D4-方案1 / U4 决策固化),共 19 条,分组如下:
|
||
|
||
**单会话私董会限制(R1-R4)**:
|
||
- R1:`ChatInput.vue` "私董会"按钮 `@click` 在打开 `BoardMeetingModal` 前检查 `chatStore.boardState`,`status === 'discussing' | 'concluding'` 时弹 a-modal,**不**打开 modal
|
||
- R2:a-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 chrome:bg + border + radius + shadow)、`team_plan`(`TeamPlanCard` → `.team-plan-card`,full chrome)、`debate_banner`(`DebateBannerCard` → `.debate-banner`,partial chrome:bg + 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` 有完整 chrome(bg + 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` 浅色背景,不应用深色气泡样式
|
||
- R18:dark 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`)有完整 chrome(bg + 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 chrome(2 种 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` token,light `#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 path(assistant):渲染 `<MessageShell role="assistant">` + 默认 slot 内容 → `.message-shell__content` 计算样式含 `background-color: rgb(255, 255, 255)`(light mode 下 `--bg-message-bubble: #ffffff`)
|
||
- Happy path(user):渲染 `<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 客户端,发起普通 chat,assistant 消息应有浅灰(实际为 `#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
|
||
|
||
- **风险 1:U3 气泡样式可能影响 `AssistantText` 内部代码块/表格的视觉对比度** — 缓解:测试场景明确要求代码块背景 `--code-bg` 不被覆盖;`AssistantText.vue:257-401` 的 `pre/hljs/code/table` 样式已用独立 token,理论上不受影响。如出现对比度问题,可在 `AssistantText.vue` 内微调 `--code-bg` 但**不**回滚 U3 的气泡。
|
||
- **风险 2:U1 的 `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 1(2026-07-02 demo 确认)** 已决策 F1/F3/F4/C2/D4,**Round 2(2026-07-02 复审)** 新增 7 个 findings(G1-G5 + D2-R2/D3-R2/D4-R2)全部 append 到本 section 留待实现期处理。其中 **G1 挑战 Round 1 D4-方案1 决策**(P0 load-bearing,3 reviewer 一致 confidence 100 认为不可实现),**G5 挑战 Round 1 F1-A 决策**(`#ffffff` 与 `#fbfbfa` 视觉不可区分)—— 实现期需优先处理这两个挑战。
|
||
|
||
### 已决策(Round 1 — 2026-07-02 demo 确认)
|
||
|
||
- **F1(feasibility,P1)— 已决策 F1-A**:U3 气泡背景与 inline code/table 背景冲突。**决策**:引入独立 token `--bg-message-bubble`(light `#ffffff` / dark `#1f1f1f`)区分气泡背景与 inline code/table 背景(保持 `--bg-secondary: #fbfbfa`)。已固化到 R9、U3 Approach、Key Technical Decisions。**⚠️ Round 2 G5 挑战**:`#ffffff` 与 `#fbfbfa` 色差 <1% 低于 JND,且 `#ffffff` 与 `--bg-primary` 相同导致气泡仅靠边框可见——见下方 G5。
|
||
- **F3(feasibility,P2)— 已决策 F3-A**:U2 删除 `BoardBannerCard` 的 `experts` prop 后 `useMessageRenderer.ts:145` 的 fallthrough 风险。**决策**:在 U2 Files 列表中加 `useMessageRenderer.ts`,删除 `board_banner` case 中 `experts` prop 传参。已固化到 U2 Files、U2 Approach、U2 Test scenarios。
|
||
- **F4(feasibility,P2)— 已决策 F4-A**:U3 气泡包裹 `BoardConclusionCard` 的嵌套冲突。**决策**:气泡选择器排除 `board_conclusion` 类型,`BoardConclusionCard` 保留自身 card chrome 不被气泡包裹。已固化到 R14、U3 Approach、U3 Test scenarios、Key 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。
|
||
- **C2(coherence,P2)— 已应用 safe_auto**:R7 表述已修订为"删除 `.board-banner-card` 的重样式(background/border/border-radius/box-shadow)及 `__bar / __chip` 等重样式类,保留 `.board-banner-card` 容器(仅 margin/padding)+ `__title / __meta` 最小样式"。
|
||
- **D4(design-lens,P2)— 已决策 D4-方案1**:U3 空内容气泡表现。**决策**:通过 `:empty` 选择器隐藏 `background / border / padding`,仅显示 thinking dots。已固化到 R15、U3 Approach、U3 Test scenarios、Key Technical Decisions。**⚠️ Round 2 G1 推翻**:`:empty` 永不匹配(AssistantText 总渲染根 div)——见下方 G1,实现期必须替换方案。
|
||
|
||
### 仍待实现期决定
|
||
|
||
#### Round 1 遗留
|
||
|
||
- **D1(design-lens,P1)**:U1 `selectConversation(newId, true)` 失败时无错误处理——modal 已关闭、currentConversationId 已变更、用户无反馈。待选方案:try/catch + a-message error toast + 回滚 currentConversationId。
|
||
- **D2(design-lens,P2)**:U1 modal 关闭与 selectConversation resolve 之间的过渡期无 loading 反馈。待选方案:modal 内"新建会话"按钮 loading spinner / 全局 loading overlay。
|
||
- **D3(design-lens,P2)**: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 决策,实现期优先处理):**
|
||
|
||
- **G1(feasibility + design-lens + adversarial 三方共识,P0,conf 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) 并更新 R15、U3 Approach 第二步 CSS、U3 Test scenarios、Key Technical Decisions。
|
||
|
||
- **G5(adversarial + feasibility residual,P2,conf 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) 并更新 R9、U3 Approach、AE6、tokens.css 值。
|
||
|
||
**P2 gated_auto(事实修正 + 决策落地):**
|
||
|
||
- **G2(adversarial + feasibility residual,P2,conf 75)— Tauri `:has()` 事实错误**:plan U3 Approach 第 202 行称"Tauri WebView 基于 Chromium 111+ 支持",实际 Tauri 2.x macOS 用 WKWebView(Safari 引擎),Linux 用 WebKitGTK,仅 Windows 用 WebView2(Chromium)。`: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 用 WKWebView(Safari 15.4+ 支持 `:has()`);Linux WebKitGTK 2.35+;Windows WebView2 Chromium 105+。若需支持更老 macOS,优先用 `messageType` prop 方案"。
|
||
|
||
- **G3(coherence + adversarial,P2,conf 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-R2(design-lens,P2,conf 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 bug,U4 加剧但不引入)。
|
||
|
||
- **D3-R2(design-lens,P2,conf 75)— U3 dark mode 代码块对比度不足**:dark mode 下 `--code-bg: #11111b`(tokens.css:255)vs `--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-R2(design-lens,P2,conf 75)— U2 简化 banner 缺乏视觉分隔**:删除所有 chrome 后,两行纯文本 banner(font-md semibold + font-xs tertiary)可能融入消息流,失去"section divider"作用。BankOutlined 图标删除后无视觉锚点。**待选方案**:(a) 保留 2px 左边框 `--accent-board`(匹配私董会身份,克制);(b) 加小色点前缀;(c) 纯排版足够("私董会 —" 文本前缀即锚点)。
|
||
|
||
#### Round 3 复审新增(2026-07-03)
|
||
|
||
**P1 manual(origin scope 边界):**
|
||
|
||
- **R3-A1(scope-guardian,P1,conf 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-F2(feasibility,P2,conf 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-C1(coherence,P1,conf 100,safe_auto applied)** — Lines 58 & 389 "board_conclusion 例外" 已更新为 "card-bearing 类型例外(F4-A Round 4 扩展)"
|
||
- **R4-F1(feasibility,P2,conf 75,gated_auto applied)** — 已验证 9 种 card root class 名并添加到 R14、Key Technical Decisions F4-A、U3 CSS 注释(注意 7 种 partial chrome 卡片 root class 名不带 `-card` 后缀)
|
||
- **R4-A1(adversarial,P1,conf 100,auto-resolve applied)** — F4-A 排除列表从 5 种扩展到 9 种(新增 `debate_resolved` / `collaboration_graph` / `review_result` / `risk_flagged`)。已更新 R14、Key Technical Decisions F4-A、U3 Goal、U3 CSS 注释、U3 Test scenarios、U3 Verification
|
||
- **R4-D1(design-lens,P2,conf 100,auto-resolve applied)** — F4-A card chrome claim 已修正:仅 `BoardConclusionCard` + `TeamPlanCard` 有完整 chrome(bg + border + radius + shadow),其余 7 种只有 partial chrome(bg + left-border + radius,无 shadow 无 full border)。已更新 R14、Key Technical Decisions F4-A、U3 CSS 注释
|
||
- **R4-D3(design-lens,P2,conf 75,auto-resolve applied)** — U1 Approach 新增焦点恢复说明:a-modal 关闭后焦点返回"私董会"按钮(WAI-ARIA modal 对话模式)
|
||
|
||
**P2 manual(待后续处理):**
|
||
|
||
- **R4-DA1(design-lens + adversarial cross-persona promote,P2,conf 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-A3(adversarial,P2,conf 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-A4(adversarial,P2,conf 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`),代码块/表格对比度保持可读。
|
||
- AE6(F1-A):assistant 消息气泡内含 inline code → inline code 背景 `--bg-secondary: #fbfbfa` 与气泡背景 `--bg-message-bubble: #ffffff` 视觉可区分。
|
||
- AE7(F4-A):私董会结论消息(board_conclusion)→ `BoardConclusionCard` 保留自身 card chrome(background/border/shadow),**不**被气泡包裹,无嵌套冲突。
|
||
- AE8(D4-方案1):assistant 消息在 thinking 阶段(空内容)→ 不显示空气泡矩形,仅显示三点动画;内容流入后自动出现气泡。
|
||
- AE9(U4):用户发送普通文本消息 → 右侧显示深色圆角气泡(`--color-primary` 背景 + 白字),max-width 70%
|
||
- AE10(U4):用户发送 @board 命令 → 右侧显示浅色结构化命令卡片(保持现有 `--bg-tertiary` 背景),不受深色气泡影响
|
||
- AE11(U4):切换 dark mode → 普通文本消息自动反转为浅色气泡 + 深色文字,@board 命令卡片样式不变
|