fix: PR #779 review polish — 全量清理 P2/P3 finding#784
Conversation
清理 PR #779 三轮 review 残留 P2/P3 finding: - MembersTab: admin disable 自己 confirm (P3-3 自锁) + busyUserId 全局 disable (P3-5 并发) + flash setTimeout cleanup (P3-8) + invitation is_expired fallback (P3-5 时钟漂移) - SettingsModal: activeTab 回落 general (P3-2 空状态) + AnimatePresence exit 动画 (R1 P3-3) - GeneralTab/AIConfigTab: ApiError 替代 (err as any) (P3-4) + success setTimeout cleanup (P3-8) - ChatContainer: dropdown active 项显示 work_dir (P3-4 三轮提 UX 回归) + currentUser 401 区分红点提示 (P3-7) - errors.ts: ApiError 类 (P3-4 类型安全) - workspaces.ts: updateWorkspace 抛 ApiError - auth.ts: Invitation is_expired 接口 + admin* cookie 通道注释 (P3-5) Refs #772
P2: ChatContainer getMe catch 把所有失败等同 session 过期。改为仅 401/403 setAuthError(true),网络/5xx 静默(re-login 解决不了)。 getMe 改用 ApiError.fromResponse 提供带 status 的类型错误。 P3: ApiError 构造补 info.raw 回退(非 JSON body 不再退化为 "API error N")。 ApiError.fromResponse 接入 getMe(消除死代码)。 Refs #772
hotplex-ai
left a comment
There was a problem hiding this comment.
Code Review — hotplex-ai
Verdict: Request Changes | P0:0 P1:1 P2:1 P3:0
本 PR 作为 PR #779/#783 review 的 polish,整体质量高:is_expired 服务端计算、ApiError 类型化、abort signal 传播、setTimeout 清理、Hooks 顺序修复均正确。仅 1 个 P1(本 PR 新增路径引入的行为不一致)需处理。
🔴 P1 — 自禁用账号后未登出,confirm 文案与实际行为不符(矛盾 UI 状态)
webchat/app/components/chat/settings-modal/members-tab.tsx:77-92
handleToggleUser 的 self-lock guard 对用户承诺:"You will be logged out immediately and unable to sign back in"(line 78),但成功路径(line 86-87)只做了 flash('ok') + load(),既无 logout() 也无 window.location.replace('/login')。
实际因果链(已对照后端 internal/admin/user_handlers.go:52-56 核实):
adminUpdateUserStatus(self, 'disabled')成功 →flash('ok', "→ disabled")- 紧接
load()→adminListUsers→GET /api/admin/users requireAdmin此时当前用户status已是disabled→ 返回 403USER_DISABLED(user_handlers.go:53-54)load()catch →setError(...)→ MembersTab 整体渲染为红色 error div(line 118-120),取代整个列表
用户实际看到的是:绿色"成功"flash 紧跟红色"user disabled"错误页,卡死,而非文案承诺的"立即登出"。这是本 PR 新增的 self-lock 路径引入的 UX/行为一致性缺陷。
修复建议(成功分支针对自禁用单独处理):
await adminUpdateUserStatus(user.id, next);
if (currentUser?.id === user.id && next === 'disabled') {
// 兑现 confirm 文案承诺的 "logged out immediately"
try { await logout(); } catch {}
window.location.replace('/login');
return;
}
flash('ok', `${user.username} → ${next}`);
load();🟡 P2 — ChatContainer 的 getMe 未传 AbortSignal(与 PR 主题不一致)
webchat/app/components/chat/ChatContainer.assistant-ui.tsx:129
本 PR 正在系统性补 abort signal(MembersTab load()、各 tab 的 setTimeout 清理),但 ChatContainer 此处的 getMe() 调用未传 signal。getMe(signal?) 已支持(auth.ts:59)。mounted 闭包守卫已规避 unmount 后 setState 的实际危害,故降为 P2(非 bug,主题一致性改进)。
✅ 已核验通过(无 finding)
- Go
invitationView嵌入*session.Invitation+IsExpired:无 nil 解引用、无 JSON tag 冲突(SQLite/PG 两种 store 的scanInvitation失败即 return err,不追加 nil) - 前端
inv.is_expired ?? (expires_at*1000 < Date.now())fallback:与expires_at秒级 Unix 时间戳单位一致,向后兼容旧后端 ApiError extends Error类型化:所有现存调用点(instanceof Error/空 catch)向前兼容- settings-modal
AnimatePresence+ 无条件useEffect:修复了原if(!open) return null的 Hooks 顺序隐患 - MembersTab
busyUserId !== null全局禁用:正确串行化并发 PATCH,避免陈旧列表快照写入 - spec 勘误(Gap-C1 守卫条件):已对照
bridge_forward.go:913-916核实行号与MaxTokens||TotalTokens||Model短路逻辑准确
…tSignal P1: handleToggleUser self-disable 成功后只 flash + load(),load 会 403 USER_DISABLED 致红色错误页卡死,与 confirm 承诺的"立即登出"矛盾。 改为 self-disable 成功后 logout + redirect /login(兑现文案)。 P2: ChatContainer getMe 未传 AbortSignal(本 PR abort 主题一致性)。 改用 AbortController + ctrl.signal.aborted 替代 mounted 闭包守卫。 Refs #772
P1/P2 已在 fc86c22 修复(self-disable logout 兑现文案 + getMe AbortSignal),dismiss 旧 review 等 cron re-review。
hotplex-ai
left a comment
There was a problem hiding this comment.
等效 re-review APPROVE:上轮 CHANGES_REQUESTED 的 P1(self-disable 后 logout 兑现 confirm 文案)+ P2(getMe AbortSignal)已在 fc86c22 修复。cron gateway 收到 webhook(202)但未生成自动 review,手动提交通过结论。
同步 #779/#784 引入的后端契约(时序在 #781 文档巡逻之前,未被覆盖): - PATCH /api/workspaces/{id} 补 body 字段语义:worker_preference 指针三态 (省略=不更新 / 空串=清除回默认 / 非空=设置),与 name、agent_config_overrides 的"空串=不更新"显式区分;work_dir 不可变整合说明 - GET /api/admin/invitations 补响应字段 is_expired(服务器计算:未被使用且已过期, 规避客户端时钟漂移) Refs #779 #784 Co-authored-by: Sisyphus 🏔️ <sisyphus@mountain.forever> Co-authored-by: Claude <noreply@anthropic.com>
spec ⑦ Phase 3 PR #779 review polish — 清理三轮 review 的非阻塞 P2/P3 finding(13 项去重:11 项修,1 项开 #782,1 项接受)。
修复清单
MembersTab
SettingsModal
GeneralTab / AIConfigTab
(err as any)反模式ChatContainer
API 层
errors.ts新增ApiError类(status/code/info 类型安全)+ raw 回退workspaces.tsupdateWorkspace 抛 ApiErrorauth.tsInvitation is_expired 接口 + admin* cookie 通道注释ApiError.fromResponse接入 getMe(消除死代码)后端
ListInvitations响应加is_expired(服务器计算,避免客户端时钟漂移)spec
后续 issue
接受(历史已定)
验证
go vet ./internal/admin/...+gofmt+go test ./internal/admin/...✓pnpm exec tsc --noEmit✓Refs #772