Skip to content

refactor(channel): 重构团队功能模块#770

Merged
leeguooooo merged 7 commits into
mainfrom
codex/team-functional-refactor
Jul 26, 2026
Merged

refactor(channel): 重构团队功能模块#770
leeguooooo merged 7 commits into
mainfrom
codex/team-functional-refactor

Conversation

@leeguooooo

@leeguooooo leeguooooo commented Jul 26, 2026

Copy link
Copy Markdown
Owner

改动说明

这次重构解决频道内功能模块边界与交互闭环,不是样式换皮。

  • Team 收口为「成员 / 工作」两个模块;Host 关注项拆回 Focus,完成件与成员筛选拆回 Messages,catch-up 回到频道级。
  • 工作项、投递、成员历史支持下钻到真实任务或消息,不再停留在只读摘要。
  • Team 与成员列表统一正式分工、待确认自报、未分工和在线/离线统计口径,包含 participant-only 与在线人类成员。
  • 离线状态优先于陈旧 working/busy;保留最后上报状态作为事实说明。
  • 角色编辑补齐保存失败、取消恢复、切换草稿、焦点恢复、保存中防关闭和 rejected Promise 边界兜底。
  • 移除打开/刷新分工面板时自动写公告的副作用;只有显式点击「同步到公告」才写入。
  • 消除 Web 测试的 Bun 模块 mock/全局状态污染,并移除一个与行为断言重复的 CLI 墙钟时序 flake。

合并前检查(review-ack 强制闸——不留结论合不了)

  • 我已读 pr-agent / CodeRabbit 的 review,真问题已处理、误报已判明
  • 本地门禁通过(tsc / 测试 / build)
  • 涉及安全/鉴权/数据的改动已做对抗性验证(变异测试或等价手段)

审查说明:最新实质 CodeRabbit review 在 129a40c 提出两项有效问题,均已于 5c69a97 修复;current head 的自动增量复审因额度限流未执行。current head 的 pr_agent check 成功,但 synchronize 事件被跳过且未产生新 review,新增 diff 已人工复核,并以 rejected save、participant-only roster、元数据优先级和显式公告同步测试覆盖。

验证

  • remote full check:SUCCESS(current head 5c69a97
  • remote pr_agent:SUCCESS
  • scripts:215 passed
  • CLI:1362 passed,TypeScript passed
  • shared:TypeScript passed
  • Web:1207 passed,TypeScript passed,Vite production build passed
  • Worker:758 passed,TypeScript passed
  • git diff --check passed

官方测试站基线证据

https://agentparty.pwtk-dev.work/c/agentpartyv0.2.152 / fd7954d 上用 chrome-use 复现:

  • 仅打开 Team 就把公告从版本 20 写到 21,并产生 charter updated to rev 21
  • Team 顶部显示 14 未认领,成员区显示 13,口径不一致。
  • Work 只提供只读状态,没有任务/消息下钻。
  • 协调页混入离开摘要、Host 原始命令和成员筛选,模块边界失焦。

该测试站只在 v* tag 的 xdream Worker 部署后更新,因此合并后会发布 v0.2.153,等待 deploy (xdream)verify-dual 成功,再在同一站做发布后回归。

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

本次变更扩展团队成员详情、任务与消息跳转、角色编辑、在线状态统计和 Host 关注展示;TeamTabs 收敛为两个页签,并更新弹层、完成项、文案、样式及测试隔离逻辑,同时移除 CLI 测试中的墙钟耗时断言。

Changes

团队拓扑与任务工作流

Layer / File(s) Summary
成员详情任务过滤与跳转
web/src/components/AgentDetailModal.tsx, web/src/components/AgentDetailModal.test.tsx, web/src/pages/Channel.tsx
成员详情新增未完成任务过滤、状态排序、离线状态文案,以及任务和消息打开回调。
团队运行面板与成员统计
web/src/pages/Channel.tsx, web/src/pages/AgentBoardPanel.test.tsx
团队运行区接入任务/消息打开交互,并统一成员在线统计及状态分栏。
角色名册与异步编辑
web/src/pages/Channel.tsx, web/src/pages/DivisionBoard.test.tsx, web/src/lib/divisionCharter.ts
角色面板统一成员投影,支持异步保存、失败恢复、焦点控制和手动同步公告。

页签、焦点与支持改动

Layer / File(s) Summary
双页签与弹层控制
web/src/components/TeamTabs.tsx, web/src/components/ChannelFocusBar.tsx, web/src/components/ChannelFocusPanel.tsx, web/src/pages/Channel.tsx
团队页签从三个调整为两个,新增 pending claims、Host 补充关注项、关闭锁定和完成项显示控制。
复制测试隔离
web/src/components/AgentJoin.test.tsx, web/src/components/AgentTokens.copypack.test.tsx
复制测试改用真实 vault 和 Clipboard 路径,并恢复测试期间修改的全局属性。
文案与视觉样式
web/src/i18n/strings/*, web/src/styles/app.css
更新成员详情、团队、时间线和 Host 汇总的中英文文案及对应样式。
CLI 测试时序
cli/test/watch.test.ts
移除 Watch 测试中的墙钟退出时长断言,保留匹配消息和游标断言。
组织树交互
web/src/components/OrgTreePreview.tsx
忙碌状态存在时禁用所有组织汇报关系下拉框。

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChannelPage
  participant AgentBoardPanel
  participant AgentDetailPanel
  participant TaskOrMessageView
  ChannelPage->>AgentBoardPanel: 渲染成员状态、任务和消息入口
  AgentBoardPanel->>AgentDetailPanel: 打开成员详情
  AgentDetailPanel->>ChannelPage: 请求打开任务或消息
  ChannelPage->>TaskOrMessageView: 打开任务 ID 或消息 seq
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed 标题明确指向频道团队功能重构,且与本次主要改动一致。
Description check ✅ Passed 描述包含改动说明、review-ack 勾选项、验证结果和补充审查说明,结构完整且信息充分。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/team-functional-refactor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Potential Memory Leak

The addition of multiple references to the same state and ref variables within the DivisionBoard component may lead to memory leaks. This is because each reference keeps a separate copy of the state and ref, which can cause the component to hold onto memory longer than necessary. This is particularly relevant when dealing with large datasets or complex state management.

const [selfHintOpen, setSelfHintOpen] = useState(false);
const [selfHintCopied, setSelfHintCopied] = useState(false);
const [editingRoleName, setEditingRoleName] = useState<string | null>(null);
const editingRoleNameRef = useRef<string | null>(null);
const editableRolesRef = useRef<Map<string, ChannelRoleInfo>>(new Map());
const onRoleDraftRef = useRef(onRoleDraft);
const editButtonRefs = useRef(new Map<string, HTMLButtonElement>());
onRoleDraftRef.current = onRoleDraft;
const [unassignedOpen, setUnassignedOpen] = useState(false);
Potential Infinite Loop

The useEffect hook in the DivisionBoard component that resets the editing role draft may cause an infinite loop if the reset function is called recursively. This is because the useEffect hook is triggered by the state update, which in turn calls the reset function, leading to an infinite loop.

useEffect(() => () => {
  const name = editingRoleNameRef.current;
  if (name !== null) resetRoleDraft(name);
}, []);

@github-actions

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
处理空角色列表

确保 teamRoleBuckets 返回的 selfReported 角色列表在 selfRoles 中正确处理。

web/src/pages/Channel.tsx [466]

 const roleBuckets = teamRoleBuckets(roles, presence, identities, t);
-const selfRoles = roleBuckets.selfReported;
+const selfRoles = roleBuckets.selfReported || [];
Suggestion importance[1-10]: 7

__

Why: 提供了一个合理的默认值,以防 teamRoleBuckets 返回空列表。这有助于避免潜在的错误,但考虑到代码的上下文,它可能不是必需的,因为 roleBuckets 应该总是返回一个列表。

Medium
处理空在线成员

确保 onlineNamesteamMemberOnlineNames 返回空值时有默认值。

web/src/pages/Channel.tsx [466]

-const onlineNames = teamMemberOnlineNames(Object.values(state.presence), state.participants);
+const onlineNames = teamMemberOnlineNames(Object.values(state.presence), state.participants) || new Set();
Suggestion importance[1-10]: 7

__

Why: 提供了一个合理的默认值,以防 teamMemberOnlineNames 返回空集合。这有助于避免潜在的错误,但考虑到代码的上下文,它可能不是必需的,因为 onlineNames 应该总是返回一个集合。

Medium
处理空成员状态

确保 memberPresenceSummaryteamMemberPresenceSummary 返回空值时有默认值。

web/src/pages/Channel.tsx [466]

 const memberPresenceSummary = useMemo(
-  () => teamMemberPresenceSummary(authoritativeMemberNames, memberOnlineNames),
+  () => teamMemberPresenceSummary(authoritativeMemberNames, memberOnlineNames) || { online: 0, offline: 0 },
   [authoritativeMemberNames, memberOnlineNames],
 );
Suggestion importance[1-10]: 7

__

Why: 提供了一个合理的默认值,以防 teamMemberPresenceSummary 返回空对象。这有助于避免潜在的错误,但考虑到代码的上下文,它可能不是必需的,因为 memberPresenceSummary 应该总是返回一个对象。

Medium
确保任务状态字段的默认值和覆盖验证。

task 函数中,确保 state 字段的默认值为 "assigned",并且在 overrides 中覆盖时进行验证。

web/src/components/AgentDetailModal.test.tsx [42-64]

 const task(overrides: Partial<TaskRecord> & { id: number; title: string }): TaskRecord {
+  const { state = "assigned", ...restOverrides } = overrides;
   return {
     type: "task",
     channel: "alpha",
     desc: null,
-    state: "assigned",
+    state,
     assignee: { name: "worker-a", kind: "agent" },
     created_by: "leo",
     created_by_kind: "human",
     priority: 1,
     labels: [],
     parent_id: null,
     anchor_seqs: [],
     scope: [],
     blocked_reason: null,
     external_ref: null,
     completion_artifact: null,
     workflow_id: null,
     created_at: 1,
     updated_at: 1,
     completed_at: null,
-    ...overrides,
+    ...restOverrides,
   };
 }
Suggestion importance[1-10]: 7

__

Why: 建议确保 state 字段的默认值为 "assigned",并在 overrides 中覆盖时进行验证。这有助于提高代码的健壮性和可维护性。

Medium
General
显示补充概览内容。

ChannelFocusBar 组件中,确保 supplementalOverview 存在时显示补充概览内容。

web/src/components/ChannelFocusBar.tsx [117-121]

 if (
   focus.empty
   && focus.focus === null
   && !showDecisionLoadState
-  && supplementalOverview === undefined
+  && !supplementalOverview
 ) return null;
Suggestion importance[1-10]: 5

__

Why: 建议在 supplementalOverview 存在时显示补充概览内容。这可以提高用户体验,但改动较小。

Low
显示空状态信息。

ChannelFocusPanel 组件中,确保当 focus.items 为空且 children 不存在时,显示空状态信息。

web/src/components/ChannelFocusPanel.tsx [95-108]

+{children}
+{focus.items.length === 0 && children == null && (
+  decisionState === undefined || decisionState.lastSuccessfulData !== null
+) ? (
+  <p className="d-empty">{t("ChannelFocusBar.overviewEmpty")}</p>
+) : focus.items.length > 0 ? (
+  <ol className="focus-overview-list">
+    {focus.items.map((item) => {
+      const action = actionFor(item);
+      return (
+        <li key={item.id} role="listitem" className="focus-item">
+          <div className="focus-item-content">
+            <span className="focus-item-label">{action?.label ?? t("ChannelFocusBar.itemDefaultLabel")}</span>
+            <span className="focus-item-action" onClick={action?.run}>{action?.label ?? t("ChannelFocusBar.itemDefaultAction")}</span>
+          </div>
+        </li>
+      );
+    })}
+  </ol>
+) : null}
 
-
Suggestion importance[1-10]: 3

__

Why: 建议当 focus.items 为空且 children 不存在时,显示空状态信息。这个建议与现有代码一致,没有实际改进。

Low

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/components/ChannelFocusPanel.tsx`:
- Around line 63-68: 不要用 ChannelFocusPanel 中的 children == null
判断补充内容是否实际存在;改为由父组件传入并使用明确的 hasSupplementalContent 状态,或在没有 Host 内容时传入
null,同时保持现有空状态条件。同步更新 web/src/components/ChannelFocusPanel.test.tsx
第99-114行附近的测试,加入返回 null 的 supplemental 子组件并断言 focus.items 为空时仍显示空状态。

In `@web/src/i18n/strings/Channel.ts`:
- Line 545: Update the Chinese translation for Channel.tools.timeline from “消息”
to “时间线”, keeping the corresponding English Timeline meaning synchronized.

In `@web/src/i18n/strings/ChannelFocusBar.ts`:
- Line 24: Update the ChannelFocusBar.hostSummary translation to handle
attention counts correctly, displaying singular “alert” when attention is 1 and
plural “alerts” otherwise, using the project’s established i18n pluralization
pattern if available.

In `@web/src/pages/Channel.tsx`:
- Around line 2066-2080: 更新 Channel.tsx 中工作项和任务项的 aria-label,移除直接拼接的编号与标题,改为复用
AgentDetailModal 使用的 i18n 文案键 AgentDetailModal.openMessage 和
AgentDetailModal.openTask,并传入对应的 work.seq/work.summary 或 task.id/task.title
参数;同步确认英文和中文翻译键均已覆盖。
- Around line 946-950: Replace the aria-label translations in the role-live-dot
markup with member-specific keys, using Channel.team.member.online and
Channel.team.member.offline without a count parameter. Add both keys to the
English and Chinese Channel i18n strings in web/src/i18n/strings/Channel.ts,
preserving the existing team badge keys for aggregate counts.

In `@web/src/pages/CompletionPanel.test.tsx`:
- Around line 51-56: 在 CompletionPanel 测试的 afterEach 中补充清理被覆盖的全局
localStorage,使用与 AgentBoardPanel.test.tsx 一致的 Reflect.deleteProperty 方式,并保留现有
renderer 卸载逻辑,确保每个测试后全局属性均被还原。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b6808f30-2511-427c-b7d5-1a1f6113f632

📥 Commits

Reviewing files that changed from the base of the PR and between fd7954d and 63b0c71.

📒 Files selected for processing (23)
  • cli/test/watch.test.ts
  • web/src/components/AgentDetailModal.test.tsx
  • web/src/components/AgentDetailModal.tsx
  • web/src/components/AgentJoin.test.tsx
  • web/src/components/AgentTokens.copypack.test.tsx
  • web/src/components/ChannelFocusBar.test.tsx
  • web/src/components/ChannelFocusBar.tsx
  • web/src/components/ChannelFocusPanel.test.tsx
  • web/src/components/ChannelFocusPanel.tsx
  • web/src/components/OrgTreePreview.tsx
  • web/src/components/TeamTabs.test.tsx
  • web/src/components/TeamTabs.tsx
  • web/src/i18n/strings/AgentDetailModal.ts
  • web/src/i18n/strings/Channel.i18n.source.test.ts
  • web/src/i18n/strings/Channel.ts
  • web/src/i18n/strings/ChannelFocusBar.ts
  • web/src/lib/divisionCharter.ts
  • web/src/pages/AgentBoardPanel.test.tsx
  • web/src/pages/Channel.tsx
  • web/src/pages/ChannelTeamRoute.test.tsx
  • web/src/pages/CompletionPanel.test.tsx
  • web/src/pages/DivisionBoard.test.tsx
  • web/src/styles/app.css

Comment thread web/src/components/ChannelFocusPanel.tsx
Comment thread web/src/i18n/strings/Channel.ts
Comment thread web/src/i18n/strings/ChannelFocusBar.ts Outdated
Comment thread web/src/pages/Channel.tsx
Comment thread web/src/pages/Channel.tsx
Comment thread web/src/pages/CompletionPanel.test.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web/src/pages/DivisionBoard.test.tsx (1)

91-101: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

统一恢复测试期间修改的 IS_REACT_ACT_ENVIRONMENT

三个测试文件都在 beforeEach 覆盖同一个全局属性,但只清理了 localStorage;Bun 复用进程时会把该状态泄漏到后续测试。

  • web/src/pages/DivisionBoard.test.tsx#L91-L101:保存并恢复该属性的原始 descriptor。
  • web/src/pages/AgentBoardPanel.test.tsx#L71-L79:保存并恢复该属性的原始 descriptor。
  • web/src/pages/CompletionPanel.test.tsx#L46-L57:保存并恢复该属性的原始 descriptor。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/pages/DivisionBoard.test.tsx` around lines 91 - 101, 保存并恢复测试期间修改的
IS_REACT_ACT_ENVIRONMENT 原始 property descriptor。在
web/src/pages/DivisionBoard.test.tsx 的
91-101、web/src/pages/AgentBoardPanel.test.tsx 的 71-79,以及
web/src/pages/CompletionPanel.test.tsx 的 46-57 中,于 beforeEach 覆盖前记录
descriptor,并在对应 afterEach 中恢复它,同时保留现有 localStorage 清理和 renderer 卸载逻辑。
web/src/pages/Channel.tsx (1)

469-483: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

teamRoleBuckets 与 Team 统计共享完整 roster。

ChannelPageauthoritativeMemberNames/在线统计包含 state.participants,但这里的投影只接收 presenceidentities。因此,仅存在于当前连接列表、尚未产生 presence/identity 记录的成员会被 Team 头部计数,却不会出现在 DivisionBoard 或未分工数量中。请把 participants(或已计算的 authoritative member set)纳入该投影,并补充 participant-only 回归测试;AgentBoardPanel.test.tsx Line 224-230 已证明该状态可达。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/pages/Channel.tsx` around lines 469 - 483, 扩展 teamRoleBuckets
及其调用链,接收并使用 state.participants 或已计算的 authoritative member set,确保仅存在于
participants、尚无 presence/identity 记录的成员也进入 selfReported/unassigned 投影及 Team
统计。保持现有 presence/identity 处理不变,并补充 participant-only 回归测试覆盖该成员出现在 DivisionBoard
或未分工数量中的行为。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/pages/Channel.tsx`:
- Around line 947-950: 在 Channel 组件中更新表示成员在线状态的 role-live-dot span,为其添加支持
aria-label 的可访问语义角色(优先使用 role="img"),同时保留现有 className 和在线/离线 aria-label 逻辑。

---

Outside diff comments:
In `@web/src/pages/Channel.tsx`:
- Around line 469-483: 扩展 teamRoleBuckets 及其调用链,接收并使用 state.participants 或已计算的
authoritative member set,确保仅存在于 participants、尚无 presence/identity 记录的成员也进入
selfReported/unassigned 投影及 Team 统计。保持现有 presence/identity 处理不变,并补充
participant-only 回归测试覆盖该成员出现在 DivisionBoard 或未分工数量中的行为。

In `@web/src/pages/DivisionBoard.test.tsx`:
- Around line 91-101: 保存并恢复测试期间修改的 IS_REACT_ACT_ENVIRONMENT 原始 property
descriptor。在 web/src/pages/DivisionBoard.test.tsx 的
91-101、web/src/pages/AgentBoardPanel.test.tsx 的 71-79,以及
web/src/pages/CompletionPanel.test.tsx 的 46-57 中,于 beforeEach 覆盖前记录
descriptor,并在对应 afterEach 中恢复它,同时保留现有 localStorage 清理和 renderer 卸载逻辑。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f1786aaa-b6d5-453b-a2ae-829564e7e2e6

📥 Commits

Reviewing files that changed from the base of the PR and between 63b0c71 and c19f30a.

📒 Files selected for processing (6)
  • web/src/i18n/strings/Channel.ts
  • web/src/i18n/strings/ChannelFocusBar.ts
  • web/src/pages/AgentBoardPanel.test.tsx
  • web/src/pages/Channel.tsx
  • web/src/pages/CompletionPanel.test.tsx
  • web/src/pages/DivisionBoard.test.tsx

Comment thread web/src/pages/Channel.tsx
@leeguooooo

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web/src/pages/Channel.tsx (2)

4995-5016: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

teamRoleSummary 计算未做 memo,与同批新增的兄弟统计不一致。

同一段落里新增的 memberOnlineNames/memberPresenceSummary 都用 useMemo 包了,但紧邻的 teamRoleBuckets(channelRoles, state.presence, channelIdentities, state.participants, t)(现在还多处理了 participants)仍是每次渲染直接执行。ChannelPage 在每个 websocket 帧到达时都会重渲染(dispatch({ type: "frame", frame })),而这里的结果 pendingRoleClaimCount/unclaimedTeamCount 只在 Team 面板展示的徽标里用到,值得和旁边两个统计一样做 memo,避免活跃频道里每条消息都重复做一遍 Map/Set 构建。

♻️ 建议补 useMemo
-  const teamRoleSummary = teamRoleBuckets(
-    channelRoles,
-    state.presence,
-    channelIdentities,
-    state.participants,
-    t,
-  );
+  const teamRoleSummary = useMemo(
+    () => teamRoleBuckets(channelRoles, state.presence, channelIdentities, state.participants, t),
+    [channelRoles, state.presence, channelIdentities, state.participants, t],
+  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/pages/Channel.tsx` around lines 4995 - 5016, Wrap the teamRoleBuckets
calculation in ChannelPage with useMemo, using channelRoles, state.presence,
channelIdentities, state.participants, and t as dependencies. Keep
pendingRoleClaimCount and unclaimedTeamCount derived from the memoized
teamRoleSummary unchanged.

1033-1042: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

onSaveRole 的 Promise 链未处理失败分支。

DivisionBoard 里两处保存入口都是 void onSaveRole(...).then((saved) => {...}),没有 .catchDivisionBoardProps.onSaveRole 的类型是 Promise<boolean>,并未在类型层面承诺“绝不 reject”;一旦某个调用方(现有的 ChannelPage.saveRole 内部已用 try/catch 兜底,但这是隐式契约,非类型强制)在未来实现中抛出异常,这里会产生未处理的 Promise rejection,且由于重置 roleSaving 的逻辑在调用方的 finally 块里,调用方来不及执行 finally 就已经抛出的话,roleSaving 会永久卡在非 null,导致整块角色编辑 UI 一直被禁用。补一个 .catch 兜底可以避免这种放大故障。

🛡️ 建议补充兜底
                             <button
                               className="d-btn"
                               type="button"
                               disabled={roleSaving !== null}
                               onClick={() => {
-                                void onSaveRole(role.name, draftForRole).then((saved) => {
-                                  if (saved) finishRoleEdit(role.name);
-                                });
+                                void onSaveRole(role.name, draftForRole)
+                                  .then((saved) => {
+                                    if (saved) finishRoleEdit(role.name);
+                                  })
+                                  .catch(() => undefined);
                               }}
                             >
-            <button className="d-btn d-btn--primary" type="button" disabled={roleSaving !== null} onClick={() => { void onSaveRole(roleName, roleDraft); }}>
+            <button
+              className="d-btn d-btn--primary"
+              type="button"
+              disabled={roleSaving !== null}
+              onClick={() => { void onSaveRole(roleName, roleDraft).catch(() => undefined); }}
+            >

Also applies to: 1165-1171

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/pages/Channel.tsx` around lines 1033 - 1042, 在 DivisionBoard
的两个角色保存入口中,更新围绕 onSaveRole 的 Promise 链,为 .then(...) 增加 .catch(...)
兜底处理未捕获的保存异常,避免产生未处理 rejection 并确保角色编辑状态不会永久保持禁用;保留现有成功分支
finishRoleEdit(role.name) 的行为。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@web/src/pages/Channel.tsx`:
- Around line 4995-5016: Wrap the teamRoleBuckets calculation in ChannelPage
with useMemo, using channelRoles, state.presence, channelIdentities,
state.participants, and t as dependencies. Keep pendingRoleClaimCount and
unclaimedTeamCount derived from the memoized teamRoleSummary unchanged.
- Around line 1033-1042: 在 DivisionBoard 的两个角色保存入口中,更新围绕 onSaveRole 的 Promise
链,为 .then(...) 增加 .catch(...) 兜底处理未捕获的保存异常,避免产生未处理 rejection
并确保角色编辑状态不会永久保持禁用;保留现有成功分支 finishRoleEdit(role.name) 的行为。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 18d4541b-25b5-4e1d-ab65-13f3758f5788

📥 Commits

Reviewing files that changed from the base of the PR and between a1c24b1 and 129a40c.

📒 Files selected for processing (4)
  • web/src/pages/AgentBoardPanel.test.tsx
  • web/src/pages/Channel.tsx
  • web/src/pages/CompletionPanel.test.tsx
  • web/src/pages/DivisionBoard.test.tsx

@leeguooooo

Copy link
Copy Markdown
Owner Author

review-ack: 已读最新实质 CodeRabbit(129a40c,两项有效 finding)与 PR Agent review;两项已在 5c69a97 修复,current head full check、Web 1207/1207、TypeScript 与生产构建均通过。current head CodeRabbit 因额度限流未执行,pr_agent synchronize 事件也被跳过;新增两文件 diff 已人工复核,无未解决阻断项。

@leeguooooo
leeguooooo merged commit 5678a8a into main Jul 26, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant