---
date: 2026-07-02
topic: private-board-restrictions-and-scheme-b-bubbles
---

## Summary

收尾私董会（@board）模块的 UI 细节与单会话状态约束。三处改动：(1) 限制一个对话只能存在一个私董会，新建私董会必须从新会话发起；(2) 将 `BoardBannerCard`（私董会开始卡片）从带边框 / 紫条 / 进度条 / 专家 chip 的重样式简化为单行标题 + 副标题；(3) 给所有 `AssistantText` 渲染添加方案 B 风格的浅灰圆角矩形气泡，与方案 B 截图保持一致。

## Problem Frame

私董会功能自 2026-06 上线以来已能正确触发多轮讨论，但 UI 上仍残留三处粗糙点：

1. **单会话多私董会无约束**。`ChatInput.vue:75` 的"私董会"按钮 `@click` 直接 `showBoardModal = true`，对当前会话是否已经存在私董会无任何判断。后端在 `board_started` 事件中没有按会话做去重，连续两次 `SendMessage("@board:...")` 会在同一会话里创建第二个私董会，叠加在第一个未结束的私董会之上，`boardState.experts` 被覆盖、轮次错乱、`StickyModeHeader` 头像数与实际不符。
2. **`BoardBannerCard` 样式过重**。`BoardBannerCard.vue:55-137` 使用了 `background / border / border-radius / box-shadow` 四件套 + 4px 紫条 + 进度条 + 专家 chip pill，与方案 B 整体"克制、不重样式"的取向冲突。方案 B 截图（参考 `docs/.../2026-06-18-chat-area-vi-redesign-requirements.md` 中的方案 B 示意）的"开始"标题区域是单行文本，不带装饰。
3. **方案 B 气泡未落地**。方案 B 截图中的"专家发言"区域是**有**浅灰圆角矩形气泡包裹内容（不是无气泡），与 ChatGPT / Notion AI 风格一致。当前 `AssistantText.vue:1-30` 的内容区 `.assistant-text` 没有背景 / 边框 / 圆角，气泡效果完全缺失。

## Key Decisions

- **私董会限制的"已存在"判断以 `boardState.status` 为准**：`status === 'discussing' | 'concluding'` 时禁止在当前会话再次发起；`status === 'completed' | 'dissolved' | null` 时允许（已完成 / 已解散的旧私董会不阻塞新私董会）。判断点放在 `ChatInput.vue` "私董会"按钮 `@click` 处（最自然的 UX 拦截点），不放在 `BoardMeetingModal` 内部（避免用户填表后才发现不能发起）。
- **私董会限制的反馈方式是 a-modal 弹窗 + 快捷新建按钮**。点击"私董会"按钮时若检测到已有私董会，弹出 `a-modal`，标题"当前会话已存在私董会"，副文"请新建会话来创建新的私董会"，按钮"我知道了" + "新建会话"。"新建会话"按钮直接调用 `chatStore.createConversation()` 并 `chatStore.selectConversation(newId)`，让用户立即在新会话里继续操作。
- **BoardBannerCard 简化为单行标题 + 副标题**。完全去掉 `BankOutlined` 图标、专家 chip 列表、4px 紫条、进度条、卡片背景 / 边框 / 圆角 / 阴影。最终输出形如：

  ```
  私董会 — 利用 agent 实现私董会的功能，应该用什么功能来打动客户
  轮次：第 1 / 5 轮
  ```

  标题字号 = `var(--font-base)` 加粗 + 主题文本；副标题字号 = `var(--font-xs)` + `var(--text-tertiary)`。`StickyModeHeader` 顶部的紫色"私董会"徽章 + 主题 + 专家头像组保持不变，承担需要"重样式"的展示职责。
- **方案 B 浅灰气泡应用到所有 assistant 消息**。具体范围：`role === 'assistant'` 的所有 `MessageShell` 内的内容（普通 chat、@team 阶段、@board 发言、Debate、Plan exec、Tool result 等）都加同款浅灰圆角矩形气泡；`role === 'user'` 的用户消息气泡保留现有 `UserBubble.vue` 的右对齐独立样式，不加 AssistantText 风格的浅灰块。气泡使用 token 颜色（`var(--bg-secondary)` 或 `var(--bg-elevated)` 系）+ 圆角 `var(--radius-md)` + 内边距 `var(--space-3) var(--space-4)`，确保与方案 B 截图视觉一致；颜色与边框用 CSS 变量绑定，**禁止硬编码** `#f3f4f6` / `#fbfbfa` / `#ededec` 等值。
- **气泡内的代码块、表格、行内代码样式保持不变**。`AssistantText.vue:257-401` 的 `pre / hljs / code / table` 样式已经在 dark-on-light 配色上做了适配（`--code-bg` / `--code-fg` / `--code-keyword` 等 token），气泡背景换浅灰后这些 token 自动适配，不需要单独再改。
- **不触碰 StickyModeHeader 顶部条**。顶部条的紫色边框、徽章、4 个专家头像等不属于本次改动范围。

## Requirements

### 单会话私董会限制

- R1. `ChatInput.vue` 的"私董会"按钮 `@click` 处理函数在打开 `BoardMeetingModal` 前，先检查 `chatStore.boardState.value`：若非 null 且 `status === 'discussing' | 'concluding'`，触发 a-modal 弹窗，**不**打开 `BoardMeetingModal`。
- R2. a-modal 弹窗内容：标题"当前会话已存在私董会"，副文案"请新建会话来创建新的私董会"，按钮"我知道了"（关闭弹窗）+ "新建会话"（主操作）。"新建会话"按钮点击后：(a) 关闭弹窗；(b) 调用 `chatStore.createConversation()`；(c) `await chatStore.selectConversation(newId)`；(d) 不自动打开 `BoardMeetingModal`，由用户在新会话中再次点击"私董会"按钮继续。
- R3. 若 `boardState.value === null` 或 `status === 'completed' | 'dissolved'`，保持当前行为：`showBoardModal = true` 直接打开 `BoardMeetingModal`，不弹提示。
- R4. 判定不依赖后端 / `is_board` 标记 / `conv.is_board`——以前端 `chatStore.boardState.value` 实时状态为权威源，避免 reload 后误判。

### 私董会开始卡片简化

- R5. `BoardBannerCard.vue` 重构为单行标题 + 副标题：保留 `topic / maxRounds / currentRound` props（向后兼容调用方），不再使用 `experts` props（删除 prop）。
- R6. 模板输出仅两行：第一行 `私董会 — {topic}`（无 BankOutlined、无边框、无背景、无圆角、无 4px 紫条）；第二行 `轮次：第 {currentRound} / {maxRounds} 轮`（小灰字）。
- R7. `<style scoped>` 中删除 `.board-banner-card` / `.board-banner-card__bar` / `.board-banner-card__chip` 等所有重样式相关类；保留 `.board-banner-card__title` / `.board-banner-card__meta` 两条最小样式。
- R8. `useMessageRenderer.ts` 中 `board_started` 的渲染路径不变（仍然渲染 `BoardBannerCard` 组件），确保 streaming 期间与 reload 后的视觉一致。

### AssistantText 浅灰气泡

- R9. `MessageShell.vue` 的 `.message-shell__content` 增加浅灰气泡样式：仅当 `role === 'assistant'` 时生效（`user` 角色不触发）；`background: var(--bg-secondary)` + `border-radius: var(--radius-md)` + `padding: var(--space-3) var(--space-4)` + `border: 1px solid var(--border-color)` + `color: var(--text-primary)`。
- R10. 气泡样式**不**使用 `!important`，不覆盖组件内已有的代码块 / 表格 / 路由 tag 样式（这些样式已经在 R-9 之外独立维护）。
- R11. 浅灰气泡不影响 `BoardRoundCard` 内 `AssistantText` 的渲染（已统一走 `MessageShell` 槽位）。
- R12. 私董会专家发言（`board_speech` / `board_summary`）、普通 chat assistant 消息、@team 阶段消息、Debate 消息都通过 `MessageShell` + `AssistantText` 渲染，因此统一获得 R-9 浅灰气泡，无需逐个组件加样式。
- R13. 气泡宽度继承 `.message-shell__content` 的现有 `width: 100%; max-width: 100%`——气泡跟随消息列宽自适应，不强制固定最大宽度。

## Scope Boundaries

### Deferred for later

- **后端按会话去重 `board_started`**：本次只做前端拦截，**不**改后端逻辑。后端允许同一会话收到多次 `board_started` 是当前事实，本次不做接口变更；前端的"未在新会话发起"拦截在功能上等价于"阻止重复发起"。如果未来需要在多端 / 多浏览器同步场景下做强一致，再考虑后端校验。
- **StickyModeHeader 顶部条的视觉细化**（徽章大小、专家头像间距、紫色边框粗细）不在本次范围。
- **BoardConclusionCard / DebateBannerCard / TeamPlanCard 等其他 board 模式组件的样式统一**：本次只改 `BoardBannerCard`，其他卡片样式留待后续迭代。

### Outside this product's identity

- 不调整方案 B 调色板（`expertIdentity.ts` 的 12 色 PALETTE）和专家头像首字符规则。
- 不调整私董会后端流程（`BoardOrchestrator` / `chatStream` 事件顺序）。
- 不调整 AssistantText 的 markdown 渲染逻辑、代码高亮、表格样式。

## Dependencies / Assumptions

- 假设：`chatStore.createConversation()` 存在并返回新会话 id（`chatStore.ts:310` 已确认）。
- 假设：`chatStore.selectConversation(id)` 可异步调用（`chatStore.ts:218` 已确认）。
- 假设：`BoardState.status` 类型为 `"discussing" | "concluding" | "completed" | "dissolved"`（`chatStream.ts:65` 已确认），"已存在私董会"判断取 `discussing | concluding`。
- 假设：`--bg-secondary` / `--radius-md` / `--space-3` / `--space-4` / `--border-color` / `--text-primary` 已在 `tokens.css` 中定义并被前端使用（`styles/tokens.css` 已确认存在）。
- 不确定性：私董会可能存在的中间状态（`forming` / `executing` / `synthesizing` 等）目前不在 `BoardState.status` 联合中；如果未来增加新 status，本次"已存在"判断需扩展。
