# Thread / Channel 参与者与通知合并 Tab 设计方案

> 草案 v1 — 待 @agentteam-reviewer2 评审后再提交给 @龙恒 决策。
> 工作树：`.worktrees/thread-merge-tab`（分支 `thread-merge-tab`，基于 `origin/main` `53f7dbeec`）。

## 1. 背景与现状

用户的核心观察：
> 协作者本质上也是"他发的消息是否通知给我的其他人"；通知现在也是"一些消息是否通知到其他人"。两者是同一件事的两面。

### 1.1 Thread Reply Panel 现状

`web/src/agentteam/workspace/conversation/components/reply-thread-panel/ReplyThreadPanelHeader.tsx:136-208` 在 header 上摆了**三个互斥按钮**：

| 按钮 | 入口 | UI 形态 | 触发 | 控制语义 |
|---|---|---|---|---|
| `Users` | header 直接点击 | `Dialog` 浮窗（`ReplyThreadPanel.tsx:1510-1553`） | `openCollaboratorSharing` | **WHO** 能参与（人/应用，含过期、移除） |
| `Bookmark` | header 直接点击 | 即时 toggle | `toggleThreadSubscription` | 当前用户**自己**是否订阅 |
| `Bell` | `Ellipsis` 下拉菜单里 | 浮层 panel（fixed/overlay，`ReplyThreadPanel.tsx:722-770`） | `toggleThreadNotifications` | **HOW** 通知路由（self: active/quiet/mute；agents: auto-wake/no-wake） |

`ReplyThreadPanel.tsx:959-991` 显示三者**互斥**：打开任何一个都会关闭其他两个 + 关闭 activity layer。原作者已经把它们看成竞争关系，但视觉上仍分散。

### 1.2 Thread Tab 系统现状

`web/src/agentteam/workspace/conversation/components/reply-thread-panel/ThreadActivityLayerControls.tsx:59-85`：

```
chat | tasks | agents | review
```

类型：`ThreadActivityLayerTab = "chat" | "agents" | "tasks" | "review"`（`threadActivityLayerStorage.ts:9-10`）。状态：`threadActivityLayerOpen` + `threadActivityLayerView`，均为本地 React state（`ReplyThreadPanel.tsx:431-433`）。

### 1.3 Channel Settings 现状（右侧 sidebar）

`web/src/agentteam/workspace/conversation/components/ChannelSettingsPane.tsx`：
- 在 `ChannelRoute.tsx:969-987` 作为 `sidePanel` 渲染，本地 state 控制（`channelSettingsOpen`，`ChannelRoute.tsx:273`）。
- 4 个区块（`ChannelSettingsPane.tsx`）：
  1. **Basic info** 名称 + 描述（`:272-336`）
  2. **Collaboration** —— 含 `PeopleBoundariesManager`（`:338-372`，`scope.type === "channel"`）
  3. **Preferences** 折叠区块，含 `SurfaceNotificationPanel variant="embedded"`（`:374-427`）
  4. **Danger zone** 删除/离开（`:429-450`）

### 1.4 Channel Tab 系统现状

`web/src/agentteam/workspace/conversation/routes/ChannelRoute.tsx:1116-1136`：

```
chat | tasks | pins
```

类型：`ChannelRouteTab = "chat" | "tasks" | "pins"`（`web/src/agentteam/workspace/conversation/channel/channelRouteTabs.ts:3`）。Tab 持久化：URL query param（`writeRouteTabSearchParams` / `readRouteTabSearchParam`，`ChannelRoute.tsx:262-272`）。`knowledge` tab 的 i18n key 已存在但未接入。

### 1.5 Agent Settings Tab 参考

`web/src/agentteam/workspace/AgentRoute.tsx:639-648`：`tab === "settings"` 渲染 `AgentSettingsPanel`。组合模式（`agent-settings/AgentSettingsPanel.tsx`）：
- **单层 stacked sections**，不是子 Tab
- 每个区块是 `RepoSettingsSection` 包装
- 区块：Role/Profile、Model & Permissions、Integrations、Behavior、Danger zone
- i18n 命名空间：`agent.settings_section_*`

这是用户希望 Channel Settings Tab 模仿的模式。

### 1.6 关键利好

**`PeopleBoundariesManager` 与 `SurfaceNotificationPanel` 都已经支持 `embedded` 变体**：
- `PeopleBoundariesManager.tsx:231-248` 接收 `embedded` / `compact` / `title` / `description` 等 prop，已经是为内嵌设计
- `SurfaceNotificationPanel.tsx:38-67` 接收 `variant: "panel" | "embedded"`、`headerVariant: "primary" | "secondary"`

合并 Tab 在 UI 层基本无需重写底层组件，主要是重新组织外层容器与入口。

---

## 2. 设计目标

1. **统一心智**：把 WHO（参与）+ HOW（通知）合并到单一 Tab，用一致的心智模型表达。
2. **跨表面复用**：同一套组件同时服务 Thread 与 Channel。
3. **顺带把 Channel Settings 升级为 Tab**，参考 Agent Settings 模式，腾出右侧 sidebar。
4. **不破坏现有 API / 数据结构**，本次只重排 UI 入口与容器。
5. **可分阶段落地**：先 Thread，再 Channel Settings，最后 Channel Tab，每步都能独立 ship。

---

## 3. 命名建议（需要决策）

用户问到 Tab 该叫什么。候选：

| 候选 | 优点 | 缺点 |
|---|---|---|
| **Members** | 与 system i18n `system.json:24` 一致；slack/discord 常见；含人类+agent+app 自然 | 在 Channel 上下文里和"channel members"易混 |
| **People** | 与现有 `PeopleBoundariesManager` / `people_boundaries.*` i18n 对齐；最简洁 | 在英文里偏人类，agent 是否算 people 略勉强 |
| **Participants** | 语义最准（参与者），明确含 agent | 比 Members 长；现有 i18n 没用这个词 |
| **Notify / Notifications** | 突出通知维度 | 失去 access control 维度，反而更窄 |
| **Access & Alerts** | 显式双维度 | 太长，对齐成本高 |

**我的推荐：`Members`（中文：成员 / 日文：メンバー）**，理由：
- 在 Thread 上下文里"成员 = 这个 thread 的参与者"，不会和 channel members 混淆（channel 已是上一级）
- 包含 agent/app 在产品里是默认假设（agent 也用 avatar 展示）
- `Members` icon (`Users`) 与现有 header 按钮一致，迁移用户心智成本低
- i18n key `system.members` 已存在（`system.json:24`）

**次选**：`People`，与 `PeopleBoundariesManager` 命名链对齐。

**最终需要 @龙恒 决策**（见 §8 决策点 D1）。

---

## 4. 整体架构

```
┌──────────────────────────────────────────────────────────────┐
│ SharedLayer (新)                                              │
│   - 接收 scope: { type: "thread" | "channel", id, owner }    │
│   - 接收 scope_kind: "thread" | "channel"                    │
│   - 内部组合 PeopleBoundariesManager + SurfaceNotificationPanel │
│   - 暴露 variant: "tab" | "section"                          │
│     - "tab"：用于独立 Tab 场景（有自己滚动容器）              │
│     - "section"：嵌入到更大 Settings Tab 的某个 section      │
└──────────────────────────────────────────────────────────────┘
```

新组件 `web/src/agentteam/workspace/conversation/components/ParticipantsTab.tsx`（或 `MembersTab.tsx`，按命名决策定），它：
1. 拿到 `scope`，决定加载哪个 API（thread 还是 channel）
2. 渲染 3 个 sub-section，**单层 stacked**（参考 Agent Settings）：

```
┌────────────────────────────────────────────────┐
│ Members Tab                                    │
├────────────────────────────────────────────────┤
│ § You                                          │
│   - Avatar + 订阅 toggle (Bookmark)            │
│   - Self notification state (active/quiet/mute)│
├────────────────────────────────────────────────┤
│ § Other people                                 │
│   - PeopleBoundariesManager embedded           │
│   - 含 add/remove/leave、expiry                │
├────────────────────────────────────────────────┤
│ § Agents                                       │
│   - 每个 agent 一行 + wake state 下拉          │
│   - 按 public/private 分组                     │
└────────────────────────────────────────────────┘
```

为什么 stack 而不是子 Tab？因为：
- 当前 `SurfaceNotificationPanel` 内部已经把 self/agents 分了 section，把 people 也加进去成第三个 section 是最自然的扩展
- Agent Settings Tab 的成功模式（stacked sections）已经验证过
- 用户操作场景通常是"扫一眼谁在了 / 谁能加 / 我的通知怎么设"——一次性浏览，不需要切子 Tab

### 4.1 Section 1: You（你的设置）

这是当前**散落**的"自我"设置的归集：
- **订阅 toggle**（现 `Bookmark`）—— `thread.subscribe` / `thread.unsubscribe`，调用现有 subscribe/unsubscribe API
- **Self notification state**（现 `Bell` 内 self section）—— active/quiet/mute，调用现有 `setThreadNotificationSelfState`
- 头像 + 显示名展示

### 4.2 Section 2: Other people（协作者管理）

直接复用 `<PeopleBoundariesManager embedded compact />`，传入：
- `scope={{ type, id, executionOwnerMemberId }}`
- `availableMembers`、`currentMemberId`、`preferredMemberIds` 等不变
- `showEffective={scope.type === "thread" && parentChannelType === "channel"}`（保留 effective grants 视图）

现 header 上的 `Users` 按钮 → 进入 Tab 即可，Dialog 移除。

### 4.3 Section 3: Agents（agent 唤醒设置）

从 `SurfaceNotificationPanel` 拆出 agents section（public/private 分组 + per-agent wake state 下拉），复用相同的 mutation API。

### 4.4 边角：archived / read-only

`SurfaceNotificationPanel.tsx:222-234` 已有 `readOnly` / `readOnlyReason=archived` 处理。Tab 顶部带一个统一的 read-only badge，所有 section 同步禁用。

---

## 5. 落地方案分阶段

### Phase 1 — Thread 端（独立可 ship）

**改动范围**：

| 文件 | 改动 |
|---|---|
| `threadActivityLayerStorage.ts:9-10` | `ThreadActivityLayerView` 加 `"members"`；`ThreadActivityLayerTab` 随之扩展 |
| `ThreadActivityLayerControls.tsx:59-85` | `items` 数组加一项 `{ id: "members", icon: <Users />, label: t("thread.tab_members"), badge?: <MembersBadge /> }`，位置建议放 `chat` 之后、`tasks` 之前（最常用） |
| `ThreadAgentActivityFloatingLayer.tsx` 或新增 `ThreadMembersLayerView.tsx` | 接收 `view === "members"` 时渲染 `<ParticipantsTab scope="thread" />` |
| `ReplyThreadPanelHeader.tsx:136-208` | 移除 `Users` icon button（148-160）与 `Bell` dropdown item（196-206）；保留 `Bookmark` 作为快捷订阅 toggle（或也移除，统一进 Tab） |
| `ReplyThreadPanel.tsx:959-991` | 简化互斥逻辑：只剩 `threadShareDialogOpen` + `threadActivityLayerView` 互斥 |
| `ReplyThreadPanel.tsx:722-770, 1510-1553` | 移除 `renderMountedThreadNotificationPanel`、collaborator `Dialog` |
| i18n：`web/src/agentteam/i18n/locales/{en,zh-CN,ja}/workspace/thread.json` | 新增 `thread.tab_members` |

**决策点**：`Bookmark` 是否保留在 header？见 §8 D3。

### Phase 2 — Channel Settings 升级为 Tab（独立可 ship）

把现 sidebar 改成 Channel 的第 N 个 Tab：

| 文件 | 改动 |
|---|---|
| `channelRouteTabs.ts:3` | `ChannelRouteTab` 加 `"settings"` |
| `ChannelRoute.tsx:1116-1136` | `DetailTabs` items 加 `{ id: "settings", icon: <Settings />, label: t("channel.tab_settings") }` |
| `ChannelRoute.tsx:1154-1178` | 渲染分支：`tab === "settings"` → `<ChannelSettingsPanel />`（从 `ChannelSettingsPane` 改造） |
| `ChannelSettingsPane.tsx` → `ChannelSettingsPanel.tsx` | 改容器：从 sidebar panel 改成全屏 stacked-sections 面板（参考 `AgentSettingsPanel`），保留 4 个区块：Basic info / Collaboration / Preferences / Danger zone |
| `ChannelRoute.tsx:273, 699-705, 711-721, 959-963, 969-987` | 移除 `channelSettingsOpen` state，改用 tab 路由 |
| `SidebarThreadContextMenu` / `ConversationsPanel` 等所有 "open settings" 调用方 | 改为 `setTab("settings")` 而非 `setChannelSettingsOpen(true)` |
| i18n：`channel.json` | 新增 `channel.tab_settings` |

Channel Settings 内的 Collaboration section 仍**直接内嵌** `PeopleBoundariesManager`，不是 Tab 中 Tab。

### Phase 3 — Channel 端独立 Members Tab（依赖 Phase 1）

把 Phase 2 中的 Collaboration + Preferences(Notifications) 拆出来做独立 Channel Tab：

| 文件 | 改动 |
|---|---|
| `channelRouteTabs.ts:3` | `ChannelRouteTab` 加 `"members"` |
| `ChannelRoute.tsx:1116-1136` | items 加 `{ id: "members", icon: <Users />, label: t("channel.tab_members") }` |
| `ChannelRoute.tsx:1154-1178` | 渲染分支：`tab === "members"` → `<ParticipantsTab scope="channel" />` |
| `ChannelSettingsPanel.tsx`（Phase 2 产物） | 移除 Collaboration section + Preferences 内的 `SurfaceNotificationPanel`；保留 Basic info / Danger zone |
| i18n：`channel.json` | 新增 `channel.tab_members` |

Channel 端的 `surfaceNotificationQueryKeys.channel(...)` / `getChannelNotificationPanel` API 已经存在（`ChannelSettingsPane.tsx:393-425`），无需新增 backend。

---

## 6. 数据流与状态

### 6.1 现有 API 全部复用

| 用途 | Thread | Channel |
|---|---|---|
| 列协作者 | `listCollaboratorPeople(scopeType="thread")` | `listCollaboratorPeople(scopeType="channel")` |
| 列有效 grants | `listEffectiveCollaboratorGrants` | 同上 |
| 加/删/leave | `addCollaboratorPerson` 等 | 同上 |
| 拉 notify panel | `getThreadNotificationPanel` | `getChannelNotificationPanel` |
| 设 self state | `setThreadNotificationSelfState` | `setChannelNotificationSelfState` |
| 设 agent wake | `setThreadNotificationAgentWakeState` | `setChannelNotificationAgentWakeState` |
| 订阅 toggle | subscribe/unsubscribe thread API | （channel 端已有） |

**无需 backend 改动**，纯 UI 重组。

### 6.2 Tab 状态

- Thread：复用现有 `threadActivityLayerView` local state（非持久化），增加 `"members"` 值
- Channel：复用 URL query param（`writeRouteTabSearchParams`），增加 `"settings"` 和 `"members"` 值

### 6.3 列表 / 头像缓存

`agentAvatarById`、`collaboratorAvailableMembers` 等数据获取逻辑直接从 `ReplyThreadPanel.tsx` 搬到 `ParticipantsTab.tsx`（或通过 props 传入）。

---

## 7. 扩展功能（可选，按时间裁剪）

用户提到"扩展我其他的功能，能够更好地符合我现在的实际情况"。下面是值得考虑的扩展，按价值排序：

### 7.1 必做（本次）

- **统一 empty state**：现在 collaborators 为空时显示 `AgentCollaboratorEducation`（`PeopleBoundariesManager.tsx:735-739`），Tab 顶部也要有"只有你"的友好说明。
- **Count badge**：Tab label 上显示当前成员数（如 `Members · 5`），与现有 `tasks` tab 的 `ThreadTaskTabBadge` 一致（`ThreadActivityLayerControls.tsx:18-37`）。
- **未读提示**：当有新协作者加入或 agent 触发 wake 时，Tab 显示小圆点（参考 `ThreadTaskTabBadge` 的 `manualUnread`）。

### 7.2 强建议（本次或下次）

- **Per-person 通知开关**：当前只能控制 self state + agent wake，但其他人类协作者的通知状态只能在 `people_boundaries.notification_sent/pending`（`PeopleBoundariesManager.tsx:192-196`）查看，**不能改**。建议加上 per-person mute（需要后端支持，可能不在本期）。
- **批量操作**：mute all agents、wake all agents、批量移除。
- **Leave thread 入口**：当前 `PeopleBoundariesManager.tsx:795-812` 有 leave 按钮，但藏在 row 里；Tab 顶部给个 prominent 的 "Leave thread" 二级按钮。
- **Pending invites 视图**：`collaboratorNotificationTranslationKey` 已经有 `sent`/`pending`，但 pending 邀请没有单独视图。

### 7.3 长期（不在本期）

- 每个参与者的 **last active time** / 最近一条消息时间。
- **Role display**（owner/collaborator/viewer）——`executionOwnerMemberId` 已经有，可以 surface。
- **通知预览**：每个 wake state 切换时显示"你将在以下情况被叫醒"的说明。

---

## 8. 需要 @龙恒 决策的点

### D1：Tab 命名（必决）

推荐 **`Members`**，候选见 §3。需要选定一个，i18n key 才能定。

### D2：Thread header 的 `Bookmark`（订阅）按钮去留

- **方案 A**：保留在 header（一键 toggle 最方便）
- **方案 B**：移除，只留在 Tab 的 "You" section（更纯粹，header 更干净）

推荐 **方案 A 保留**——订阅是高频操作，进 Tab 才能订阅太重；header 一颗星icon 占位极小。

### D3：Phase 顺序

推荐 **Phase 1 → Phase 2 → Phase 3**：
- Phase 1 独立 ship 价值最大，先证明 ParticipantsTab 可行
- Phase 2 把 Channel Settings 升级成 Tab 是用户独立诉求，可单独 ship
- Phase 3 把 Channel 协作者也搬进 ParticipantsTab，达成最终一致性

也可并行（Phase 1 与 Phase 2 不冲突）。请确认或调整顺序。

### D4：Channel Settings 变 Tab 后，原 sidebar 入口怎么处理

现 sidebar 入口散在 `SidebarThreadContextMenu`、`ConversationsPanel` 等。变 Tab 后：
- **方案 A**：所有"打开设置"改为 `setTab("settings")`，原 sidebar 完全删除
- **方案 B**：保留 sidebar 入口（向下兼容一段时间），同时提供 Tab 入口

推荐 **方案 A**（一刀切），避免双入口混淆。

### D5：是否本期就拆 Channel Settings Tab 里的 Collaboration 出来（Phase 3）

Phase 3 不是用户硬需求，但有它才完整。如果时间紧，可以先 ship Phase 1+2，Phase 3 单独排期。

### D6：扩展功能 §7.2 的取舍

请挑出本期必做的（默认全做 §7.1，§7.2 选 1-2 项，§7.3 全部推迟）。

---

## 9. 待 @agentteam-reviewer2 评审的重点

1. **命名推荐 `Members` 是否合理**？有没有更好的（特别是与现有 i18n `system.members`、`channel.settings_collaboration` 的关系）？
2. **stacked sections vs 子 Tab 二级**？我选了 stacked（参考 Agent Settings），但 Thread 上下文里空间更窄（docked 时只有 ~480px），是否需要更紧凑布局？
3. **Phase 1 是否过度激进**（同时移除 Users + Bell + Dialog + NotificationPanel）？是否应保留 header `Users` 作为快捷入口直达 Tab？
4. **Tab 切换时数据预热**：进入 Members Tab 才拉数据 vs 全程预拉？建议 lazy。
5. **Channel Settings Phase 2 与 Phase 3 是否应合并**？两者一起做的话只有一次大改 ChannelRoute 的 cost，但 PR 变大。
6. **`ThreadMembersLayerView` 是否应该复用 `ThreadAgentActivityFloatingLayer`** 还是新开一个 component？现有那个FloatingLayer 跟 `agents/review/tasks` view 绑定很深，复用可能污染。
7. **路由参数 vs local state for Thread tab**：现 thread tab 是 local state（非持久），新加 `members` 是否需要升级成 URL（深链直达）？这会顺带改其他 4 个 tab 的语义。

---

## 10. 风险与回退

- **风险**：移除 header 按钮后老用户找不到了——mitigation：在 Tab 入口加 tooltip "Members & Notifications" 一段时间；或保留 `Users` 按钮作为直达 Tab 的快捷方式（D3 方案 B）。
- **风险**：Phase 2 改动 `ChannelRoute` 涉及多处调用方，PR 大——mitigation：单独一个 PR，先 ship Phase 1。
- **回退**：每个 Phase 独立 PR，任一回退不影响其他。

---

## 11. 工作树与下一步

- 工作树：`/Users/lonng/devel/pingcap/agentteam/.worktrees/thread-merge-tab`
- 分支：`thread-merge-tab`（基于 `origin/main`）
- 本文档：`docs/design/participants-tab.md`
- 下一步：等 @agentteam-reviewer2 反馈 + @龙恒 决策 D1-D6 后，写实施 plan 并切具体实现 PR。
