Commit Graph
9 Commits
Author SHA1 Message Date
Your NameandClaude Sonnet 5 7cf8e4f4e0 fix(groups): correct rename-sync event name and unreliable getMeta path
The previous fix listened for a documented-but-wrong event name
(group.profile.updated); live instrumentation of a real rename showed
the actual wire event is group.settings.updated. Fixing only the name
still didn't work: the refresh mechanism it triggered
(getMeta(startMetaQuery().withDesc().build())) was itself unreliable
against this backend -- live-confirmed to either time out (504) on a
brand-new topic or return stale pre-rename data on an established one,
which also explains the "second rename flips back to the original
name" symptom (a late, stale response from an earlier call clobbering
the correct value).

Replace it with the already-reliable REST getGroup() response applied
locally via the SDK's own _processMetaDesc (same internal method the
SDK calls when a real meta response arrives; confirmed to reach
me.contacts(), which the sidebar/header read).

Also fixes a second, independently-discovered bug found while
verifying the above: notifyTopic's requestAnimationFrame-based
notification batching could get permanently stuck if the callback
was scheduled while the page wasn't compositing frames, silently
freezing all future topic-list updates (not just renames) until a
full reload. Falls back to setTimeout when the document is hidden,
since that is never dropped the way the rAF was.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 18:07:41 +08:00
Your NameandClaude Sonnet 5 5748f1290d fix(groups): sync group rename in real time for owner and members
PATCH .../groups/{topic} pushes a custom {info what:"group",
event:"group.profile.updated"} that nothing recognized, so the
sidebar/header name (native SDK contact.public.fn) never refreshed
until an unrelated focus-triggered resync happened to touch it.
Add the missing branch to onInfo (mirrors the existing
group.dissolved/message.recalled handling) to re-pull public/desc
via topic.getMeta(...withDesc()), and trigger the same refresh from
the renamer's own save success since they won't receive their own
push.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 17:36:11 +08:00
Your Name 7fff1a7b3a fix: call media-connect state, blocklist banner staleness, group rename, avatar URL
- stores/calls.ts, CallRoot.tsx: distinguish signaling-connected from
  actual media-joined (mediaJoined flag), so a call whose mic never
  attaches no longer shows a live timer/"网络良好"; propagate a
  disconnected reason on post-accept media failure so both sides agree
  on the outcome instead of one side seeing a normal "已结束".
- DetailColumn.tsx: invalidate conversation-context alongside blocks on
  block/unblock, so the composer's blocked-notice banner updates
  immediately instead of only after a window-focus refetch.
- GroupDetailColumn.tsx: add an owner-only group rename control
  (updateGroup was already implemented but never wired to any UI).
- Avatar.tsx: stop re-wrapping an already-absolute avatar_file_id (seen
  on default-avatar placeholders) into a malformed
  /v0/file/s/https://... request.
2026-09-18 15:01:08 +08:00
Your Name 804618ad63 修复群聊解散通知覆盖不全:主动退出/解散群聊会误报"该群聊已解散"且留下幽灵会话
前情:上一版修复只覆盖了"被动看着群被别人解散"这一种场景(后端走自定义
{info what:"group" event:"group.dissolved"}事件而非原生{pres what:"gone"},
需要单独识别)。这次用户实测发现同一个"话题彻底消失后清理不干净"的问题在另外
两条路径上都复现了:自己点"退出群聊"、自己(群主)点"解散群聊"。

根因:这两个操作各自的mutation.onSuccess只是navigate('/messages'),没有做任何
本地缓存清理;而服务端在REST调用成功后,通常还会异步地给这条已经不再属于你的
订阅补发一个原生/自定义的"gone"信号——这个信号触发的是topics.ts里那条本该只用
于"被动围观"场景的goneListeners通知链,于是ConversationView的通用"该群聊已
解散"提示被复用到了并不贴切的"我自己退出/解散"场景上,且同一个SDK缓存stub没
被清理的老毛病(getTopic()被再次调用就会重建一个空壳)也照样在这两条路径上
重演,表现为消息列表里多出一个显示原始群ID、"1970/1/1"的幽灵会话。

修复:
- topics.ts的markTopicGoneAndNotify新增goneTopicNames去重判断——只要
  forgetTopic已经先标记过这个话题,稍后到达的原生/自定义gone信号就直接跳过
  (包括不再触发goneListeners),避免重复通知。
- GroupDetailColumn.tsx的leaveMutation/disbandMutation改成onSuccess里先调用
  forgetTopic(topicId)(清缓存+加入排除名单+让上面的守卫生效)再navigate,
  不再空等异步信号来做清理。

实机验证:systest04建群拉systest02入群 -> systest02主动退出,返回消息列表后
侧栏不再残留幽灵会话;systest04(群主)解散同一个群,同样返回消息列表且侧栏
干净。顺带在GroupDetailColumn.tsx里把群头像/成员格子头像接上了真实上传头像
(见下一条commit的头像修复,这个文件因为改动集中放在了一起)。
2026-09-16 23:36:35 +08:00
Your NameandClaude Sonnet 5 66dad284c7 修复邀请入群交互:点击邀请要直接进入邀请,而不是先看成员列表再点第二次
用户实测反馈的真实bug:GroupDetailColumn里那个专门的"+邀请"图标(虚线边框
方块,只有canInvite为真才显示——这个权限门控本身一直是对的)点击后走的是
setOpenModal('members'),和"查看全部成员"是完全同一个入口——弹出的是成员
列表,标题栏里那个真正的"邀请"按钮还要再点一次才能看到可邀请的联系人列表。

GroupMembersModal新增initialView属性,"invite"直接以邀请视图打开(跳过
成员列表这一步),"查看全部"/"查看全部成员"两处不受影响仍然打开成员列表。

同时补全了邀请结果的反馈——原来邀请成功后只做了查询失效,无论是"立即加入"
还是需要对方/群设置审批的"待确认",界面上都没有任何区别于点击前的视觉反馈,
容易让人怀疑是不是点了没反应从而重复点击。读了im_be的serveIMGroupInvitations
源码(不是协议文档的单一示例)确认真实的单人邀请结果有joined/already_member/
pending/pending_approval/failed五种可能,现在每种都有对应的行内文案,
"failed"不计入"已处理"状态,允许原地重试。

实机验证:全新建群后点"+邀请"直接进入邀请视图(标题"邀请成员",不经过成员
列表);邀请一个全新好友后确认成功加入、群成员数正确从2变3、界面正确显示
结果反馈。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 22:26:12 +08:00
Your Name 8013c1675f 修复禅道Bug清单中的前端问题:好友申请source校验、群解散二次验证缺失、
群解散事件未处理、错误码翻译缺失、恢复登录页误导性提示

- 好友申请首次必失败30000(P0):AddFriendModal把手机号搜索单独标记为
  source:"phone"上报,但后端serveIMFriendRequestCreate的source校验只认
  search/qr/group,命中default分支返回"invalid friend request source"。
  改为手机号搜索也统一上报source:"search"(后端本就不区分具体搜索渠道),
  用两个全新、此前无任何关系的测试账号验证首次申请200成功
- 解散群聊需要短信二次验证但完整流程从未实现(P0):新增
  SecurityChallengeModal通用二次验证弹层(POST /security/challenges→
  验证码→verify→action_token)+ requestActionToken()(stores/
  securityChallenge.ts,与requestConfirm同一模式),接入解散群聊和转让
  群主(后者此前也因缺X-Action-Token必然404/400)。同时发现并修复
  imDelete对无body请求不显式传空对象体,导致携带Content-Type:
  application/json但请求体真正为空时后端JSON解码失败返回400——实测确认
  两个问题都解决后DELETE返回200
- 群聊被解散但当前正查看该群的用户界面不会跳转(P1):新增
  topics.ts的subscribeTopicGone()监听SDK自身的onDeleteTopic回调,
  ConversationView据此在当前会话被解散时弹出提示并跳转回消息列表
- P2P会话发起方标题栏显示原始topic id、对方明明在线却显示离线:确认
  为上一轮已修复的isCommType()根因问题的同一症状,本轮实测(新建从未
  聊过天的账号对)已不再复现;群内消息发送者显示原始uid的关联描述同样
  实测未复现
- 好友关系冲突(已是好友/申请正在处理中)报错英文原文透出:翻译表补充
  FRIEND_REQUEST_CONFLICT
- 深链/刷新页面时的"改为重新登录"链接此前在任何恢复登录过程中都会
  立即显示(不管是否超时),造成短暂白屏时的误导性提示:改为仅在真正
  超时后才显示该链接,与其上方的提示文案保持一致的显示条件
2026-09-16 15:42:41 +08:00
Your NameandClaude Sonnet 5 8f4b0de636 fix(web): 第9轮审查批次2——群聊域一批问题
- 建群留空群名字直接被后端拒绝:placeholder写的"未填写时默认为成员
  昵称组合"从未真正实现,现在留空时用已选成员昵称拼接生成默认名,
  按MAX_TITLE_LENGTH截断
- 群文件上传按钮不受"群文件上传权限"治理开关约束:新增canUpload
  (和canInvite同一个算法),仅管理员时非管理员看不到上传入口
- 群管理弹层5个治理开关(进群验证/全员禁言/文件上传/私聊权限/邀请
  权限)都没有读取后端返回的对应_editable字段,现在全部接上
- 禁言成员列表只拉第一页:新增getAllGroupMutes复用fetchAllPages,
  和getAllGroupMembers同一个模式,人数多的群不会再静默截断
- 添加管理员没有前端数量上限校验:达到上限时按钮禁用并提示"已达
  管理员人数上限",不再让用户选完人才被后端拒绝
- 清空聊天记录没有错误处理,失败时静默无提示,现在补上

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 12:37:46 +08:00
Your NameandClaude Sonnet 5 37d08e8989 feat(web): 用符合系统风格的组件替换全部 window.alert/confirm
全项目搜出15处 window.confirm、9处 window.alert(其中3处是各文件
各自复制的 notImplemented() 辅助函数,文案还都一样)用在退出登录、
删除消息/收藏/好友、清空聊天记录、解散群聊、转让群主、危险链接
二次确认等场景——全是浏览器原生弹窗,样式和整个应用的设计语言完全
脱节。

新增两个全局UI基础设施(Zustand store + 挂载在App.tsx根节点的
渲染组件,参照stores/calls.ts/messageJump.ts已有的"任何组件都能调用"
惯例,不是每个弹窗各自管理一份本地状态):

- stores/confirmDialog.ts + app/ConfirmDialogRoot.tsx:替换
  window.confirm。requestConfirm()返回Promise<boolean>,和
  window.confirm的返回值语义一致,所以原来"if (!window.confirm(...))
  return"这一行改成"if (!(await requestConfirm({message:...})))
  return",外层函数标成async,不用大改周围逻辑。危险/不可恢复操作
  (删除、解散群聊、注销账号等)传danger:true,确认按钮变红色,
  弹窗视觉沿用转发弹窗已有的"描边取消+实心确认"按钮搭配。
- stores/globalToast.ts + app/GlobalToastRoot.tsx:替换window.alert,
  复用应用里本来就有、每个页面各自本地状态管理的Toast组件。顺手把
  3个文件里各自重复定义、文案完全一样的notImplemented()合并成
  一个共享导出。

实机验证了非危险确认(清空缓存,蓝色确认按钮)、危险确认(删除
消息,红色确认按钮)、取消按钮、Escape键取消、全局提示(点"投诉
举报")——均正常工作,且原有页面各自的Toast错误提示不受影响。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 00:08:15 +08:00
Your NameandClaude Sonnet 5 5c3f3b7653 feat(web): complete groups/contacts/favorites/settings/calls/system domains + extensive review-round fixes
Rounds out the full rewrite with the remaining feature domains: group
management (admin panel, mute, files, announcements, QR-based invite/join),
contacts (friend profile, add-friend, blocklist, new-friends), favorites
(real media rendering across text/image/video/voice/file/link), settings
(profile/security/privacy/general/about), calls (Agora-backed audio/video
signaling + call history), and system messages/file assistant.

Also folds in a large batch of live-verified fixes surfaced by repeated
fresh code-review passes and hands-on E2E testing against the real
backend, including: WS reconnect not resubscribing any topic after a
disconnect, missing x-im-client-mid on every send path (protocol
compliance for retry dedup), group mute/member-settings/self-profile
endpoint corrections, relationship-state type fixes across search/QR/
friend-profile, tenant-switch logout gap, password-policy now read from
the real per-tenant config instead of guessed, NavRail highlight/
aria-current desync, several call-state-machine races, and responsive
layout fixes across all four breakpoints (mobile/tablet/laptop/desktop)
including a full mobile rebuild of favorites, call UI, and the settings
home/sub-list split.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 20:16:33 +08:00