Commit Graph
2117 Commits
Author SHA1 Message Date
Your Name cd092108c2 重新构建dev产物(清理调试残留日志+本轮3处修复的编译产物) 2026-09-06 15:04:00 +08:00
Your Name 4715ea4497 修复未登录状态下深链/跳转到某个话题导致应用整体白屏
handleHashRoute 判断 path[1](话题名)是否合法只检查了 Tinode.topicType(topicName)
这一个语法层面的条件——没登录时把hash设成一个真实群/好友的"#/<topic>"地址(比如
退出登录后浏览器历史回退、或者一个带话题参数的通知/深链在未登录状态下被打开),
话题名本身语法上合法,于是mobilePanel被设成'topic-view'。但render()里
MessagesView真正渲染的前提除了mobilePanel还要求!chapterOneActive(即
this.tinode.isAuthenticated())——这个前提没登录时是假的。结果移动端下
SidepanelView的显示条件(mobilePanel=='sidepanel')和MessagesView的显示条件同时
为假,两边都不渲染,应用整体白屏(app-container是空的,没有任何报错),
仅靠换hash也无法恢复,只能整页刷新。

修复:判断话题合法性时把"当前是否已认证"也纳入,未登录时统一走已经验证过安全的
"话题名非法"分支(topicName置null,mobilePanel回退到sidepanel)。

已实测复现:退出登录后把location.hash设成一个真实群的话题地址,修复前白屏,
修复后正确回退。
2026-09-06 15:03:48 +08:00
Your Name ed9a92896c 修复系统消息列表原样显示机器码"group.announcement.updated"
NOTIFICATION_TEXT 查表漏收了这一种群事件码。real账号(asn494180)的真实系统消息
列表里实测出现过未翻译的原始事件码——server/db/mysql/tenant_groups.go 的
insertGroupEventTx 一共会写9种 group.* 事件(announcement.updated/created、
join_request.created、member.joined/left/updated、mute.updated、
owner.transferred、role.updated、settings.updated),这张表原来漏了公告更新
这一种,是9种里唯一没查表命中、直接把机器码露给用户看的。
2026-09-06 15:03:34 +08:00
Your Name a81a7b162d 修复群系统事件消息重新变回普通气泡+"未找到"作者名(并发编辑意外丢失CSS)
e6e51f62 提交的".meta .bubble.system"样式规则,被同一时间并发运行的另一个agent
提交(8087fd7e)在给css/base.css暂存自己的"通话小窗grab光标"改动时意外整个删掉了
(这个环境多个并发session同时改同一个文件、共享git暂存区,8087fd7e自己的commit信息
也提到了这个现象,但它删掉这条规则时没有同时补上——本轮测试期间反复出现的同一类问题)。

净效果:meta-message.jsx渲染系统事件消息用的".bubble.system"类此前已经没有对应CSS,
群里的成员加入/角色变更/公告发布这类系统提示消息会退回默认气泡样式,重新出现
"未找到"这种内部占位作者名——e6e51f62本来要修的问题又变相复现了。

重新补上这条规则,内容与e6e51f62最初提交的完全一致。
2026-09-06 15:03:25 +08:00
Your Name fa2288c497 修复群文件上传不传file_type,图片/视频/语音群文件全部显示成通用文件图标
根因:POST /groups/{topic}/files 的 file_type 是客户端登记时自己声明的字段(im-api.js的
addGroupFile早就留了这个参数),不是服务端拿mime_type反推的——实测:上传一张mime_type
正确记成"image/png"的图片,只要请求体不带file_type,服务端就把它落成字面量"file"(不是
留空)。GroupFiles的onFile上传流程一直没传这个字段,导致FILE_TYPE_META准备好的
image/video/audio/document四种彩色图标从未被真正用上,所有群文件不分类型清一色显示灰色
通用文件图标。

修复:新增fileTypeFromMime辅助函数(与content-library-view.jsx的同名函数用同一套mime前缀
优先、扩展名兜底文档类型的判断口径),上传时算出分类一起传给addGroupFile。

验证:真实浏览器上传一张PNG,接口返回file_type从"file"变成"image",列表图标从灰色通用
文件图标变成绿色图片图标;对照同一会话里upload-fix之前的旧文件,图标仍是灰色(存量数据
不受影响,符合预期,不需要也没有接口能批量回填旧记录)。
2026-09-06 13:47:19 +08:00
Your NameandClaude Sonnet 5 ddf23d40b2 修复新会话首次打开时消息历史被静默截断为1条、对方已读回执永不刷新
根因:tinode-web.jsx的prefetchLatestMessagePreviews()(会话列表"最后一条消息"预览)
会在会话列表一渲染就对每个有动态但本地无缓存摘要的话题调用
topic.subscribe(startMetaQuery().withLaterData(1)...),只为预览目的抓取"最新1条"
消息——这常常发生在用户真正点开这个会话之前。这次订阅会把tinode-sdk的
Topic._attached置为true,且把_maxSeq设成那一条消息的seq。

用户随后点开会话时,messages-view.jsx自己的subscribe()发现topic.isSubscribed()
已经是true就直接return,从未真正发出过完整的{sub}请求。即使去掉这个短路,
SDK的Topic.subscribe()本身在_attached为true时也会无条件短路成
Promise.resolve(this),完全不看传入的getParams——所以无论如何都不会把
withLaterSub()/withAux()等真正需要的查询发给服务端。

现场复现:全新注册的两个互为好友账号之间发4条真实消息,全新session下第一次点开
该会话,只渲染出最新1条,其余3条静默丢失;对方的已读回执(应变蓝色双勾)永远
不刷新——这两个现象都会在离开会话再重新打开后自愈(因为离开会清掉_attached),
但用户在此之前看到的是一个残缺、状态错误的会话。

修复分两处:
1. subscribe()内,若topic已经isSubscribed(),先topic.leave(false)(只清客户端和
   服务端的订阅态,不清本地已缓存消息),再执行真正的subscribe(),确保这次一定
   是货真价实的、服务端会认真处理getParams的订阅请求。
2. 把消息分页查询从withLaterData(MESSAGES_PAGE)("给我比本地_maxSeq更新的消息")
   改成withData(undefined, undefined, MESSAGES_PAGE)("给我最新的N条消息",不依赖
   本地缓存游标)。前者在_maxSeq被上述预览订阅污染后会查询"since:被污染的seq+1",
   服务端自然返回空;后者是绝对查询,对全新话题(_maxSeq为0,两者退化成同一个
   since:undefined查询)行为不变,对被污染的话题能正确要回完整历史。

第14+轮最小粒度测试 Ch3-4 发现(P2P单聊消息状态机场景)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:46:58 +08:00
Your Name 8087fd7e97 补上视频通话小窗拖动功能遗漏的CSS改动(.call-party.self 的 grab 光标提示)
上一个 commit(call-panel.jsx 里新增的"全屏连线中拖动本地小窗"功能,对应 8.9 双方
视频通话中.png 的"小窗应支持拖动"需求)只提交了 JSX/JS 那部分,配套的 CSS
(.call-party.self 增加 cursor:grab 提示可拖动,[disabled] 态改回 cursor:unset)
当时被同一文件里其他并发改动的暂存操作顶掉,没有一起进去——这个环境里多个并发
session 同时在改 css/base.css,共享 git index 导致 git add 经常把彼此的暂存内容
一起扫进对方的 commit,这是本轮测试期间反复确认到的现象,不是这次改动本身的问题。
JS 逻辑不受影响(拖动本身早就用真实指针事件验证过可用),这里只是补上缺的光标提示
样式,让用户在尝试拖动前就能看出这个小窗是可拖的。
2026-09-06 13:40:03 +08:00
Your NameandClaude Sonnet 5 e6e51f625e 群系统事件消息改为居中纯文本渲染,不再走普通气泡+作者名的展示逻辑
根因:群里的系统事件消息(成员加入、角色变更、公告发布等,x-im-type=system_action/
system_notice)服务端并不带真实的 `from`,但 messages-view.jsx 一直把它们和普通消息
一样交给 ChatMessage 渲染——chat-message.jsx 的 fullDisplay 逻辑会在气泡下方额外画一行
发送者名字,解析不到发送者时回退显示"未找到"。对照参考图 内容沟通对话3-开启进群验证与
拉人状态.png,这类系统事件应该是居中的纯灰字提示,不带头像、不带气泡、不带作者名——现状
既不符合参考图,也让"未找到"这种内部占位文案暴露给了用户。

怎么修:meta-message.jsx 新增 systemText 分支,复用日期分隔条同一套"居中提示"视觉语言
(.meta .bubble.system,无背景、纯居中灰字);messages-view.jsx 在消息渲染分支里识别
system_action/system_notice 且不带可操作按钮(group_invitation_created 那种带
"立即加入"按钮的除外,仍走完整气泡展示)的消息,改为渲染 MetaMessage 而不是 ChatMessage。

验证:用真实群(3 个真实账号)走了两遍"扫码申请入群→通过审批",会话里"有成员通过审批
加入了群聊。"正确显示为居中灰字,不再出现气泡、头像和"未找到"作者名;带按钮的
group_invitation_created 消息不受影响,仍是完整气泡+"立即加入"按钮。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:39:35 +08:00
Your Name e53af93409 修复收藏页多选模式下类型状态丢失、跨tab选择静默累加两个问题
根因1:'favorites-select' 是全部类型 tab 共用的同一个路由,本身不带类型信息,Favorites
组件原来进入多选模式时把 type 硬编码回退成 'file',不管进入前实际停在哪个 tab。实测在
"文本"tab 点"多选",列表和头部计数会静默换成"文件"tab 的数据,用户看不出发生了什么,
勾选/删除会作用在自己根本没看到的文件项上——测试过程中因此真的误删了一条文件收藏。
修复:进入多选前把当前 type 作为 hash 参数带上,多选页/分享页的返回箭头、"取消"、发送后
跳转都改用该参数换算的 tab,不再硬编码 'favorites-files'。

根因2:切换类型 tab 只更新 type,没有清空 selected——在"文件"tab 选中项目后切到"图片"tab,
头部仍显示旧的"已选择 N 项",实际操作的是已经不在当前列表里的旧类型收藏,删除/转发没有任何
可见提示。修复:切 tab 时一并清空 selected。

验证:真实浏览器逐步复现两个问题后确认修复生效——在"文本"tab进入多选保留文本列表、
切"图片"tab选择清零、"取消"正确回到多选前所在的图片tab;多选删除功能本身回归正常。
2026-09-06 13:38:25 +08:00
Your NameandClaude Sonnet 5 1228d6a534 补上组织架构部门蓝色高亮修复遗漏的CSS改动(.expanded重命名为.me)
上一个commit(1d47c586)只提交了JSX那部分(DeptRow/DeptNode改用containsMe),
对应的CSS改名(.organization-department.expanded → .me)当时被同一文件里
其他并发改动的暂存操作顶掉了,没有一起进去,导致上一commit单看JSX是完整的,
但CSS选择器名字还没改,实际不生效。这里补上,两个commit合起来才是完整修复。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:36:27 +08:00
Your NameandClaude Sonnet 5 1d47c586d3 修复组织架构页部门蓝色高亮跟"是否展开"挂钩,根节点被误判成"我的部门"
DeptRow原来的蓝色高亮(.organization-department.expanded)直接复用"展开/收起"
这个纯UI交互状态:depth 0的根节点默认就是展开的(DeptNode: useState(depth===0)),
导致根节点从打开页面那一刻起就被误染成"当前用户所在部门"的蓝色,与参考图
(docs/images/部门组织框架.png)不符——图里根节点"总裁办50人(一级)"明明是展开状态,
颜色却是黑色默认色,只有真正包含当前用户的"技术4人(二级)"是蓝色实心菱形。

根因是把"展开状态"和"是否是我的部门"这两个独立概念用同一个CSS类混在了一起。改为
新增containsMe(DeptNode根据自己已加载的members列表里有没有当前用户算出,与expanded
彻底解耦,expanded继续只管chevron方向),CSS类从.expanded改名成.me(与PersonRow已有的
.me保持同一套命名)。顺手把三处重复的"裸id/usr前缀id两种形式对比myUserId"的内联判断
提成共享的isMyUserId函数。

当前测试租户组织架构数据本身为空(GET /organization/departments、/members 都是真实
200+空数组,非账号权限问题——kcg051307交叉验证过同样为空),无法用真实多级树肉眼过一遍,
按测试要求改用桩数据验证:mock了总裁办→财务/销售/技术三个部门+技术下4个成员(含当前
用户)复现参考图的结构,展开技术后确认根节点保持黑色、技术正确变蓝,与参考截图逐像素
对照一致;修复前用同一份桩数据可复现根节点被误染蓝的问题。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:35:38 +08:00
Your NameandClaude Sonnet 5 a964d51ba5 修复回复/编辑消息触发风控弹窗时,"正在回复"预览条在弹窗结果出来前就被清空
现象:对一条历史消息点"回复",输入含链接的文本点发送,风控弹窗(安全提醒/警示级)
刚弹出的同时,composer 里"正在回复 XX"的预览条就已经消失了;如果这时候点"取消发送",
这条回复就被永久放弃——原来引用的消息内容和回复关系不再存在,也没有任何提示,唯一的
恢复办法是回到那条历史消息重新点一次"回复"。编辑消息(修改一条已发消息的内容后再次
包含链接)是同一根因的同款问题,只是编辑可以靠重新点"编辑"找回原文,没有回复那么彻底
不可逆。

根因:sendMessage() 原来在根据 this.state.reply / this.props.forwardMessage 组装好
msg/head 之后,同步、无条件地立刻调用了 this.handleCancelReply()(清空 reply/编辑
状态),这一步发生在下面的 URL 风控检查(POST /security/urls/check)之前——但风控
弹窗要等用户点"继续发送"或"取消发送"才会真正决定要不要走 doSend(),这中间的等待期里
UI 状态已经被提前清空了。

修法:把 handleCancelReply() 从三处提前调用点移到 doSend() 内部、真正调用
this.props.sendMessage(...) 之前。这样它只在真的要发送时才执行——不含链接的普通消息
里 doSend() 本来就是同步立即执行,时机与原来完全一样;命中风控的消息里,"继续发送"
时才会触发(此时清空回复状态是正确的,消息确实发出去了),"取消发送"时 doSend()
根本不会被调用,回复/编辑的预览条和引用内容都会完整保留,可以直接修改文本重试或
自己手动取消。转发场景不受影响:转发消息不会触发这条风控检查(originalText 恒为
null),移动调用时机对它是纯粹的时序等价替换。

验证:真实浏览器操作,对一条消息点"回复",输入含 https://example.com/a?token=abc123
的文本发送——警示弹窗弹出的同时预览条仍然可见;点"取消发送"后弹窗消失、预览条依旧
完整保留、composer 文本不变;再次点发送、这次点"我已知晓,继续发送",消息正确以
带引用卡片的回复形式发出,预览条在发送成功后才清空。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:32:59 +08:00
Your NameandClaude Sonnet 5 cdca509183 修复群邀请消息卡片丢弃"立即加入"按钮,被邀请人无法接受邀请
根因:POST /groups/{topic}/invitations 邀请成功后,后端会往邀请人与被邀请人的
单聊会话里发一条 x-im-type=system_action、template=group_invitation_created 的
消息,content.actions 里带着真实可用的 {type:"group_invitation_accept",
label:"立即加入", target_id:<invitation_id>}(对照参考图 6.3 加入群聊.png)。但
chat-message.jsx 原来对所有 system_action/system_notice 消息一律只取
content.text 渲染成纯文本,这个 action 数据被整体丢弃,消息只显示"邀请你加入群聊。"
没有任何按钮——im-api.js 里封装好的 acceptGroupInvitation() 因此从来没有调用点。

怎么修:chat-message.jsx 在原有的文本兜底之前,单独识别 content.actions 里
type=="group_invitation_accept" 的这一种模板,渲染一张带按钮的小卡片(其余
system_action/system_notice 模板的纯文本渲染保持不变,不受影响);
messages-view.jsx 新增 handleAcceptGroupInvitation,调用 acceptGroupInvitation()
并用已有的 showToast 反馈结果,逐层传给 ChatMessage 的新 onAcceptGroupInvitation
prop。按钮点击后按 busy/joined/pending_approval/error 四态展示,避免重复提交。

验证:用两个真实账号(A 建群,直接对 B 发起 POST /invitations)复现,重新构建后
B 的会话里正确出现"邀请你加入群聊。"+"立即加入"按钮;点击后正确调用
POST /group-invitations/{id}/accept 并把返回的失败原因("服务器繁忙,请稍后重试")
展示出来——这一步暴露了一个独立的后端 500 bug(accept 接口本身的实现有问题,
已记录在测试报告里,不在本次前端修复范围内),前端的请求发起、按钮状态机和错误展示
均已确认按预期工作。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:26:14 +08:00
Your NameandClaude Sonnet 5 a6014b84ee 修复桌面端拖动悬浮通话窗口后收窄浏览器至移动端断点,通话条卡在旧桌面坐标不跟随移动端布局
现象:视频通话最小化后处于桌面宽度(>640px)时,拖动悬浮小窗到任意位置(dragStart 会把
定位方式从 CSS 的 right/auto-left 切换成显式 left/top 内联样式,方便拖拽期间自由移动),
之后把浏览器窗口收窄到 640px 以下(真实场景:桌面用户把窗口拖窄到半屏和另一个应用并排),
悬浮条不会像预期那样贴顶变成通栏胶囊(base.css 里
#video-container.minimized:not(.audio-minimized) 的移动端规则:left:0/right:auto/
top:var(--caption-panel-height)/width:100%/height:11rem),而是继续停在拖拽结束时的旧
桌面坐标——因为内联 style 的优先级恒高于任何样式表选择器,不管后者的媒体查询是否匹配,
移动端规则里的 left/right/top 全部被内联值盖住,只有从未被内联样式接触过的 width/height
还能生效,导致宽/高不成比例、位置错位。

修复:CallPanel 新增 handleWindowResize,监听 window resize;每次触发时如果
matchMedia('(width<=640px)') 命中且当前处于 minimized,就清掉 containerRef 上手动写入的
left/right/top 内联样式,把定位权交还给样式表,让移动端规则完整生效。componentDidMount/
componentWillUnmount 里和已有的 pointerdown/move/up 监听一起注册/清理。从未拖拽过时这个
清理是空操作,不影响现有行为。

验证:真实 CallPanel 实例(React Fiber 直接获取),伪造 canvas/AudioContext 媒体流跑通整
个通话中状态,用 computer 工具做真实指针拖拽把悬浮窗从默认右上角拖到别处(确认
style.left/top 已写入、right:auto),再用 resize_window 把视口收窄到 375px 宽并确认应用
自身的 resize 监听正确触发(displayMobile 状态同步更新)——收窄后 style 只剩
"cursor: grab",left/right/top 均被清空,getComputedStyle 显示 left:0/top:53.33px(=
var(--caption-panel-height)按当前根字号换算)/width:375px(100%)/height:146.66px(11rem),
与移动端样式表规则完全一致;再放宽回桌面宽度确认悬浮窗正常回到默认右上角18rem方框位置,
未引入回归。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:22:06 +08:00
Your NameandClaude Sonnet 5 0748f30cc1 实名认证提交成功后补拉一次 GET,用服务端脱敏字段替换本地草稿回显(新增61)
现象(IM-后端问题清单-2026-09-04.md"新增61"):POST /security/realname/submissions
成功响应只有 {status, submission_id},从不带 masked_name/masked_id_number,前端此前
的兜底是回显用户本次会话里刚输入的原始姓名/证件号草稿——多数情况下看起来没问题,
但那是未脱敏的原始值,不是服务端认可的脱敏展示值,且只覆盖"这次会话内",换一个
从未在本会话提交过的场景(比如同一账号换设备/清缓存后第一次看提交结果)就没有任何
草稿可回退,只能空白。

之前的复核已经确认 GET /security/realname 从这个功能最初上线的 commit 起就一直带着
masked_name/masked_id_number 这两个字段,只是提交成功后从未再调用过它。

修法:submit() 里 POST 成功后追加一次 getRealnameStatus(),用响应里的
masked_name/masked_id_number 覆盖本地状态,展示服务端脱敏后的真实值;这次追加请求
失败时静默忽略(提交本身已经成功,不应该因为这次锦上添花的请求失败而回退),保留
原有的本会话草稿兜底作为这次请求解析完成前的过渡显示。

验证:注册全新测试账号(无历史提交),提交姓名"测试用户"+证件号
110105199003077856 后,抓包确认 POST 响应确实只有 {status,submission_id},紧接着
自动触发的 GET 响应带 masked_name:"测*"、masked_id_number:"110***********7856";
页面输入框读取到的实际 value 是这两个脱敏值,不是刚输入的原始文本,证实修复生效。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:21:05 +08:00
Your NameandClaude Sonnet 5 ded440078a 补上用户名注册路径缺失的"无法自助找回密码"提醒文案
需求文档 1.4 第5条硬性要求:用户名方式注册不发送短信,必须提示用户"未绑定手机号
无法自助找回密码,忘记密码后只能联系企业管理员重置"。这句话在注册表单里从未真正
写出来过——不是视觉上被样式盖住,是 JSX 里压根没有对应节点,选择用户名注册的人
完全不会被告知自己放弃了手机号找回密码的能力。

修法:在邀请码输入框下方补上这行提示,只在 usingPhone 为假(用户名注册路径)时
展示,不影响手机号注册路径(那条路径本来就能自助找回密码)。参考图"注册页面.png"
是两个字段都还没交互的初始态,不代表这条路径相关的提示不应该出现。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:20:29 +08:00
Your NameandClaude Sonnet 5 46a98782fd 修复"退出当前登录"仍用原生 window.confirm(),与其余三个确认弹层不一致
现象:清除缓存/清除聊天记录/注销账号三处早前已经统一改成组件内 ConfirmSheet 弹层
(本文件其它三处组件自己的注释也在讲这次统一),唯独"退出当前登录"还留着最原始的
window.confirm()。本轮测试发现这在当前浏览器自动化环境下会被判定为"环境不支持同步
弹窗"而直接返回 false(控制台可见"Page dialog suppressed"警告),退出登录点了完全
没反应,且没有任何错误提示——这不只是测试环境的偶然状况:不少内嵌 WebView/嵌入式
容器场景本身就会禁用 confirm()/alert() 这类阻塞式原生对话框,真实用户在这些环境下
点"退出登录"会遇到一模一样的静默无效。

修法:新增 LogoutConfirm 组件,复用同一个 ConfirmSheet(图标 logout,标题"退出登录"),
"退出当前登录"按钮改成 setSheet('logout') 打开这个弹层,与 CacheClear/
ClearHistoryConfirm/DeleteAccountConfirm 完全同构,不再依赖浏览器原生对话框。

验证:npm run build:dev 后在浏览器实测,点击"退出当前登录"正确弹出本地弹层(标题/
文案/确认取消按钮与设计一致,logout 图标正常渲染为图形而非文字),点"确认"后真实
退出登录、回到企业码入口页;点"取消"正确关闭弹层且不登出。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:19:45 +08:00
Your NameandClaude Sonnet 5 eaafabe94f 修复用户名+邀请码注册(无手机号)必现崩溃:Tinode.credential(null) 抛异常
现象:企业码注册流程走"仅用户名+邀请码,不填手机号"这条路径时,点"注册"必然抛出
原始 JS 报错"Cannot destructure property 'val' of 'meth' as it is null",直接
展示在错误提示条上,注册无法完成。

根因:auth-onboarding-view.jsx 的 Register 组件在没有手机号凭证、只有邀请码时,
把 cred_ 显式传成 null(没有可附加的凭证)。tinode-sdk 的 Tinode.credential(meth,...)
用 `typeof meth == 'object'` 判断参数是不是一个 {meth,val,resp,params} 对象,但
JS 里 `typeof null === 'object'` 也是 true,导致对 null 无条件解构,直接抛异常。
所有走"登录"分支的调用点传的从来都是"字段可能是 undefined 的对象",从未传过字面量
null,所以这个 bug 只在"用户名注册且没有手机号凭证"这一条路径上必现。

修法:cred_ 为空时不再调用 Tinode.credential(),直接把 cred 字段设为 undefined,
绕开 SDK 这个不处理 null 输入的缺陷,不改动 vendored 的 tinode-sdk 代码。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:19:26 +08:00
Your NameandClaude Sonnet 5 fa2c6c2f07 修复会议详情页响应会议后toast文案"已已接受"/"已已拒绝"重复"已"字
MeetingDetail.respond()提交回应成功后用 `已${RESPONSE_LABEL[response]}` 拼toast文案,
但RESPONSE_LABEL的accepted/declined两项本身已经是"已接受"/"已拒绝"(给参与人列表
徽标用的),再拼一次"已"前缀就变成"已已接受"/"已已拒绝";tentative没有"已"前缀,
拼出来是"已待定",语义也不通顺。同页面另一个响应入口MeetingCard.respond()
早就手写了三条正确文案(已接受会议邀请/已拒绝会议邀请/已标记为待定),
两处本应一致却各写一套。

改为复用MeetingCard已有的正确文案,不再从RESPONSE_LABEL派生。

已用kcg051307发起、asn494180作为参会人的3场真实会议在浏览器逐一实测三种回应,
确认toast分别正确显示"已接受会议邀请"/"已拒绝会议邀请"/"已标记为待定"。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:18:19 +08:00
Your NameandClaude Sonnet 5 db75aaa25b 修复会议详情页"参会确认"统计遗漏"待定"响应,三项数字之和对不上参会人数
MeetingDetail 的 counts 对象本就统计了 tentative({accepted,pending,declined,tentative}),
但渲染只画了已接受/待响应/已拒绝三项,待定响应被计算出来后从未显示。真实复现:
组织者建会议、参会人回应"待定"后查看会议详情,"参会确认"区域三个数字之和小于
participant_count,且没有任何说明缺的那一份去了哪。

补充第4个统计项(待定),保持原有三项顺序不变;base.css 的
.meeting-status > div 网格列数从3改4(.report-statistics 复用同一条规则,
拆开单独设置避免影响日报/月报团队统计的应提交/已提交/未提交三列布局)。

已用 asn494180(组织者)/kcg051307(参会人) 两个真实账号在浏览器实测:kcg回应"待定"后
version从1变为2,asn的会议详情页刷新后正确显示"1 已接受/0 待响应/0 已拒绝/1 待定",
截图确认4列布局无溢出/挤压。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:14:10 +08:00
Your Name 693265658f fix(导航): 关闭聊天/信息面板改为真实返回,不再新增历史记录
用户反馈:从会话列表打开聊天A,点页面自己的"返回"关掉聊天,再打开聊天B,这时候点
浏览器的返回键,会先回到列表,再点一次返回又跳回了聊天A——因为"关闭"这个动作和"打开"
一样都在往浏览器历史里push新记录,而不是弹出(pop)"打开"时push的那条,导致聊天A那条
记录一直遗留在历史栈里,多按一次返回就会被重新翻出来。

两处受影响、同一根因:
- tinode-web.jsx 的 handleHideMessagesView(移动端聊天页左上角"←",messages-view.jsx
  的#hide-message-view)——打开聊天(handleTopicSelected)是真实push,关闭时原来无条件
  再push一条"没有话题"的状态,现在改成先尝试HashNavigation.goBack(),找不到可回退的
  历史才退回原来的push方式。
- info-view.jsx 的 handleBackNavigate(旧版InfoView自己的返回/关闭按钮,目前只有"已
  保存的消息"这一个话题还在用它,好友/群的资料页已经在更早的章节改用真正修过返回逻辑
  的friend-settings/group-settings)——同样的问题,同样的修法。

已在浏览器验证:连续"开聊天A→点返回关闭→开聊天B"后,浏览器返回键能一次性回到聊天A
从未存在过一样,不会再被重新翻出来;InfoView的关闭按钮点击后hash是异步变化的(真实
history.back()的特征),不再是原来同步的push。
2026-09-06 12:29:56 +08:00
Your Name 4fc65b0f40 fix(日报): "提醒未提交成员"改用组织架构接口凑候选名单,不再必现400
后端问题清单"新增2"记录过:POST /reports/daily/reminders 要求调用方传入完整候选
user_ids 名单(服务端只做"从候选里筛出未交的人再提醒",不会自己算全公司名单),
但前端一直传空数组,必然命中服务端"user_ids为空"的400校验。

重新核实时发现这条其实不是后端缺口——GET /organization/members 不传 q/department_id
时就会返回调用者可见范围内的全部在职成员(分页),这个接口本来就在会议参会人选择器
里用着,只是"提醒未提交成员"这个按钮没有复用它。改为点击时先分页拉取全部组织成员
user_id,再传给提醒接口;组织架构为空时给出明确提示而不是让请求再次落入400。

已在浏览器验证请求格式(limit=100,与后端实际接受的参数一致)和空组织架构下的
兜底提示;当前测试租户组织架构数据本身为空,未能覆盖"真的有人被提醒"这条路径,
但接口契约已对照 im_be 源码逐字段核实。
2026-09-06 12:07:24 +08:00
Your Name 623f7dc40f fix(导航): 排查全项目后修复其余同类tab切换污染历史记录问题
上一提交(e61eb402)只修了"我的收藏"/"查找聊天记录"两处,用户要求排查其他页面是否
也有同样问题。全项目排查<nav>/tabbar/active态切换元素,逐一确认onClick是否直接调用
HashNavigation.navigateTo()(每次都push一条历史记录),找到5处同类实例并修复:

- message-center-view.jsx / workplace-collaboration-view.jsx:底部导航"工作台"<->"消息"
  两个tab——这是全应用唯一的根级tab bar,也是点击频率最高的一处,之前同样每次切换都
  push一条历史记录。
- workplace-collaboration-view.jsx:日报/月报列表页的"我的xx"<->"团队统计"切换(同一个
  组件、同一个页面,只是team boolean不同),同样的问题。
- new-topic-view.jsx:"开始新会话"页的搜索/新群组/通过id三个tab。
- wallpapers.jsx:"壁纸"设置页的图案/图片两个tab。

均改用上一提交新增的HashNavigation.navigateReplace()。已在浏览器逐一实测:连续切换
多次tab后,无论真实浏览器返回还是应用自己的返回按钮,都能一次性返回到进入该页面之前
的真实上一页。

以下经排查确认不受影响,未改动:group-management-view.jsx的群聊分类tab(我创建的/
管理的/加入的)和contact-management-view.jsx的字母索引锚点均只用本地state/scrollIntoView,
不涉及hash跳转;auth-onboarding-view.jsx的登录<->注册切换、new-topic-view.jsx提交后的
三处跳转是真实的"离开当前页"操作,保留push语义符合预期。
2026-09-06 11:16:01 +08:00
Your Name e61eb40250 fix(收藏/聊天记录检索): 切换页内tab不再污染浏览器历史记录
用户反馈"我的收藏"页面点击tabs切换(全部/图片/视频/文件/语音/文本/链接)会记录到浏览器
历史记录里,导致点击返回一直在这个页面内切换tab,而不是真正返回上一页。

根因:content-library-view.jsx的Favorites/HistoryNav每次切tab都调用navigate(),
即HashNavigation.navigateTo() -> location.hash = ...,每次赋值都会被浏览器记为一条
新的历史记录。切N次tab就push N条记录,无论是系统返回键还是应用自己的"返回"按钮
(HashNavigation.goBack()),都要先把这N条tab切换记录走完才能真正离开页面。

修复:新增HashNavigation.navigateReplace(),用history.replaceState替换当前历史记录
而不是新增一条(hash该更新还是更新,刷新/复制链接不受影响),并手动派发hashchange事件
供tinode-web.jsx现有的监听器捕获——replaceState本身不会自动触发该事件。Favorites的
TYPE_LABELS tabs和HistoryNav("查找聊天记录"页的聊天记录/图片/视频/文件/链接tabs)
两处tab切换都改用这个新方法,不再计入navigationCount。

已在浏览器实测两处:连续切3次tab后,无论是原生history.back()还是应用返回按钮,都能
一次性返回到进入这个页面之前的真实上一页,不再卡在tab切换历史里。
2026-09-06 10:45:24 +08:00
Your Name 01325ad009 fix(css): 补齐按钮内标题/内容间距异常的遗漏实例
用户反馈"我的收藏-文件"tab标题(strong)与内容(small)间距过大,根因是全局
`button{line-height:2.2rem}`重置(面向单行居中按钮文案)继承进了改造成
卡片/多行行容器的<button>内部未显式覆盖line-height的<strong>/<small>。

本次除修复用户直接指出的.library-file-card/.library-record-header外,
系统排查全项目同类模式(button内嵌套strong+small),额外修复3处此前遗漏:
- .library-message-results(查找聊天记录-聊天记录tab搜索结果)
- .personal-identity("我"页头像+昵称+企业名身份卡片)
- .report-cards > button(月报列表行,日报列表用article非button故不受影响)

均已在浏览器实测line-height从2.2rem降至1.3,视觉间距恢复正常。
2026-09-06 10:40:40 +08:00
Your NameandClaude Sonnet 5 150db2fc02 重新构建dev产物(消息首页会话预览修复)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 10:16:55 +08:00
Your NameandClaude Sonnet 5 2ee8bac6a0 修复消息首页会话预览恒为"暂无消息",即使会话有真实历史消息
message-center-view.jsx 的预览文字读 topic.latestMessage(),这个方法
只会从话题自己的本地消息缓存里取值——而这个缓存只有在话题真正被
subscribe 过一次数据拉取后才会填充。me.contacts()(resetContactList)
给出的是每个联系人真实的、已缓存的Topic实例,包括用户这次会话里
从未打开过的——这些话题的latestMessage()恒为undefined,即使同一个
对象自己的touched/seq字段能证明服务端真实存在历史消息。

新增prefetchLatestMessagePreviews():对chatList里seq>0但本地缓存为空
的话题,一次性拉取最新1条消息填充预览。用一个Set跟踪已尝试过的话题,
避免resetContactList()(几乎每个{meta}/{pres}事件都会触发一次)反复
重新拉取同一个话题。

调试过程中发现并修正了自己犯的两个错误:(1) resetContactList()在
websocket握手完成前就会被本地持久化缓存恢复触发一次,此时的subscribe
必然503失败,加了isConnected()判断跳过,避免过早失败把话题永久标记
为"已尝试";(2) Topic.subscribe(getParams, setParams)的参数顺序搞反,
把查询对象传成了第二个参数(setParams),导致subscribe成功但发出的
{sub}包完全没有get子句,从未真正请求过消息数据。

已用真实浏览器验证:好友kcg051307会话(真实71条历史消息)刷新页面
后预览从"暂无消息"变为真实的最新消息内容,未读角标数字不受影响
(确认没有误将未读消息标记为已读)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 10:16:48 +08:00
Your NameandClaude Sonnet 5 ca71667975 重新构建dev产物(收藏文本折叠/搜索高亮、跳转定位消息两处修复)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 05:31:00 +08:00
Your NameandClaude Sonnet 5 68044fa1bb 修复"查找聊天记录"跳转定位到原消息在全部入口100%失效
componentDidUpdate里的jumpToSeq检查只在这个值发生变化时触发,但
tinode-web.jsx从不会同时渲染MessagesView和第7章侧边栏(!chapterSevenActive
门禁),所以从History的搜索结果/图片宫格/视频宫格跳转,每次都是卸载
第7章视图、挂载一个"首次渲染就已经带着jumpToSeq"的全新MessagesView
实例——componentDidUpdate根本不会跑(它不处理首次挂载)。7.12/7.15/
7.16全部"定位到消息来源"入口100%失效:jumpseq参数永久留在URL里,
会话固定停在最底部。

修复过程中还发现一个真实的竞态:首次挂载意味着componentSetup()刚刚
才发起topic订阅(异步、fire-and-forget),jumpToMessage自己的
topic.getMeta()调用要求订阅已建立,立即调用会抢在订阅完成前——实测
这个抢跑的getMeta()调用真实收到了服务端 {"code":403,"text":"permission
denied"} 拒绝(不是本地兜底,是真实的服务端往返),而真正的订阅请求
当时还在路上。改为轮询已有的isSubscribed()标志(而不是猜一个固定延迟
或改造subscribe()暴露其内部promise),封顶5秒,真正卡住的订阅仍会
落到jumpToMessage自己已有的错误处理上。

第十二轮最小粒度测试 Ch7 发现,已用真实浏览器验证:从工作台等其它
页面跳转过来时,消息正确加载、jumpseq参数正确从URL清除。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 05:30:53 +08:00
Your NameandClaude Sonnet 5 6882103461 实现收藏文本长文本折叠/展开/复制、收藏搜索关键词高亮
需求7.4两处此前完全没接:文本收藏是不限高度的纯<p>,长文本不折叠、
没有展开/收起,也没有复制按钮;收藏搜索的query从未传到文本收藏这一
层,History那边已有的Highlight组件在Favorites里从未被引用(实测:
搜索一个真实命中的关键词,结果列表0个<mark>)。

新增FavoriteText组件:用scrollHeight/clientHeight判断是否真的溢出
(避免给短文本也显示无用的"展开"按钮),复制走和context-menu.jsx
的copyMessageText一致的navigator.clipboard.writeText。

-webkit-line-clamp在当前环境对这个<p>不生效(getComputedStyle能读到
设置值但视觉不裁切),改用line-height反推的max-height+overflow:hidden;
外层容器不能用display:flex,会被UA blockify成flow-root导致同样失效。

第十二轮最小粒度测试 Ch7 发现,已用真实浏览器验证展开/收起切换正常、
搜索关键词高亮正确命中。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 05:30:40 +08:00
Your NameandClaude Sonnet 5 cccd21aff1 修复通讯录字母索引条位置错误,与好友列表重叠
.people-index 用了固定 top:13rem,是"新朋友/群组/文件助手/系统消息"
四行快捷入口成为现在这个样式之前遗留的旧值——实测索引条渲染在约173px
处,而按字母分组的好友列表实际从约375px才开始,索引条整整偏高200px,
盖在快捷入口行上而不是贴着好友列表。按真实布局(header+搜索框+四行
入口+分组标题外边距)重新算出27.5rem。

第十二轮最小粒度测试 Ch5-6 发现。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 05:13:12 +08:00
Your NameandClaude Sonnet 5 5b836d50b2 修复通话发起后立即退出登录导致应用整体白屏崩溃
handleLogout()先断开WS连接、再重置界面状态,界面重置卸载
MessagesView时其componentWillUnmount会尝试发一条挂断信令,vendored
SDK的Connection.sendText()在已关闭的socket上同步抛错,componentWillUnmount
里没有error boundary兜底,导致整个应用被React卸载成白屏。

5处几乎相同的通话信令发送方法(ringing/hang-up/offer/ice-candidate/
answer)合并成一个sendCallSignal()辅助方法,发送前检查连接状态、
发送本身包一层try/catch。

第十二轮最小粒度测试 Ch8 发现(点击通话按钮后立即退出登录复现)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 05:09:23 +08:00
Your NameandClaude Sonnet 5 4a140e6c79 修复消息首页空状态误导文案、未登录完成时静默显示空列表
会话列表为空时统一显示"未找到相关会话"+搜索图标,即使用户根本没有
输入过搜索词——账号本身还没有任何会话时,也被说成"你的搜索没找到东
西"。现在区分:真正搜索无果才显示这句,否则显示"暂无会话"。

另外,myUserId尚未就绪(自动登录未完成/失败)但sidePanelSelected已
经是messages-home(比如收藏的旧链接、恢复的历史标签页)时,原来的
offline判断条件恒为false,页面会静默渲染一个和"账号确实是空的"没法
区分的空列表,用户以为账号没数据而不是还没登录成功。现在myUserId为
空时也按"未连接"处理,诚实展示"加载中"。

第十二轮最小粒度测试 Ch1-2 发现。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 04:59:35 +08:00
Your NameandClaude Sonnet 5 8575c83980 修复举报原因列表文案裁切、举报详情页原因标签换行错位
两处grid都按"预留图标位"设计成3栏(2.7-2.8rem首列+内容+尾部),但
ReportReasons/ReportDetails实际渲染的都只是纯文字span,没有图标元素
——真实文案被挤进过窄的图标列:举报原因列表里"诈骗欺诈"等文案裁切成
看不懂的碎片(部分文字还叠在蓝底图标框上导致白底白字不可见);举报
详情页"举报原因"标签被挤到换行。改成按真实子元素数量分栏(2栏)。

第十二轮最小粒度测试 Ch10-11 发现。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 04:58:36 +08:00
Your NameandClaude Sonnet 5 052cedb39b 重新构建dev产物(实名认证标题、会议响应按钮门禁两处修复)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 04:23:54 +08:00
Your NameandClaude Sonnet 5 22468edf6b 修复已取消/不可响应的会议列表卡片仍提供拒绝/接受按钮,点击后端403
MeetingCard对拒绝/接受按钮的渲染没有像MeetingDetail那样按
permissions.can_respond门禁,取消后的会议(my_response仍是
pending,属正常状态)会一直显示看似可用的按钮,点击后PUT
.../my-response正确返回403 PERMISSION_DENIED,用户点了却没反应。

第十二轮最小粒度测试 Ch9 发现,用真实取消会议(mtg_0w2TnfQiqaMwy8Bj)
复现(该agent进行中因会话限流中断,此项修复在中断前已完成,事后
确认无误后提交)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 04:23:48 +08:00
Your NameandClaude Sonnet 5 164a41270c 修复实名认证已认证状态显示臆造标题"认证信息"
参考图(实名认证.png / 实名认证-已实名.png)里两种状态标题文案完全
一致,都是"请您使用有效身份证信息认证"——原来按status分叉出的"认证
信息"这句是mock阶段从未核对过参考图的臆造文案。

第十二轮最小粒度测试 Ch1-2 发现(该agent进行中因会话限流中断,此项
修复在中断前已完成,事后确认无误后提交)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 04:23:44 +08:00
Your NameandClaude Sonnet 5 1e5e64ae2c 重新构建dev产物(撤回菜单误判、验证码文案、文本收藏撤回态三处修复)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 02:13:55 +08:00
Your NameandClaude Sonnet 5 b5531b0b7f 修复文本收藏/聊天记录检索不识别已撤回状态、P2P来源收藏名称解析404
问题13(收藏availability恒为available)后端修复后暴露的真实缺口:
text类型收藏和History的history-search结果都只判断item.type,从不看
availability,撤回的文本消息会原样一直显示旧内容(History里甚至会
显示后端messageText()对nil content兜底出的字面量"null")。

另外,P2P来源的收藏来源名称解析函数把复合p2p话题id当用户uid传给
/users/{id}/profile,必现404,永远显示原始话题字符串而非好友昵称;
改用已有的getConversationContext接口正确解析。

第十一轮最小粒度测试 Ch7 发现。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 02:13:49 +08:00
Your NameandClaude Sonnet 5 117bf43c25 修复身份验证输错短信验证码时显示"独立密码不正确"
CODE_INVALID 这个reason同时被独立密码校验和短信验证码校验两个不相关
接口复用,错误文案只写了前者的措辞,在身份验证页答非所问。改成对两
种场景都准确的通用措辞。

第十一轮最小粒度测试 Ch10-11 发现。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 02:13:40 +08:00
Your NameandClaude Sonnet 5 fc757cc043 修复消息发送失败/排队中时长按菜单仍提供"撤回"
判断只查 params.seq > 0,未排除 tinode-sdk 本地占位 seq(LOCAL_SEQID,
268435455,同样是正数)——发送失败/仍在排队的消息会被误判成"已持久化
的真实消息",点击撤回后端正确拒绝(无此消息),却给用户一个答非所问
的"请求内容有误"提示。

第十一轮最小粒度测试 Ch3-4 发现。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 02:13:34 +08:00
Your NameandClaude Sonnet 5 89fdd31cd4 通讯录/新朋友/黑名单列表区分"加载失败"与"真的是空"
三处都用 .catch(() => {}) 吞掉网络错误,loading 结束后空数组直接落到
"暂无好友"/"暂无新朋友申请"/"黑名单为空",加载失败和真的没数据在界面
上无法区分。改成 group-management-view.jsx 里 11 处同类组件已经在用
的 loadError 状态模式。

第十轮最小粒度测试 Ch5-6 发现。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 00:57:17 +08:00
Your NameandClaude Sonnet 5 4dbe76f2bd 重新构建dev产物(注册持久化登录、白屏崩溃、视频缩略图三处修复)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 00:49:13 +08:00
Your NameandClaude Sonnet 5 bab1f13e04 修复视频收藏/聊天记录缩略图恒为破损图片、时长换算错误
MediaThumb 把视频实体自身字节(.val/.ref,给 <video> 解码用)当图片
塞进 <img src>,浏览器无法解码,缩略图恒为损坏图标;改用后端已下发但
从未使用的 .preview/.premime 海报字段(lib/formatters.js 的
quoteImageOrVideo()/inlineVideoAttr() 早已用同一约定)。

时长换算:收藏页把毫秒当秒数传给 secondsToTime,2.4秒视频显示
"39:26";History 媒体宫格则完全没接上 duration 字段(mediaMessagePayload
是扁平结构,需要跟 history-files 一样重建 Drafty 实体才能取到)。

第十轮最小粒度测试 Ch7 发现(纠正第九轮"证据不足"的结论)。已用真实
收藏视频实测确认:缩略图正确解码(320x240/96x96),时长正确显示 0:02。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 00:49:04 +08:00
Your NameandClaude Sonnet 5 9d2db5a648 修复转发/切换话题时应用整体白屏崩溃
componentDidUpdate 的 initMessage 分支直接访问 this.messageEditArea
未判空,而输入框在 noInput(转发中)或 disabled(ACS未解析完)时会
被卸载——此时 initMessage(草稿/编辑内容)一变化就会解引用 null,
无 error boundary 兜底,整个应用白屏。

第十轮最小粒度测试 Ch3-4(实测复现2次)与 Ch8(同一处代码路径独立
命中)各自发现同一个bug。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 00:46:37 +08:00
Your NameandClaude Sonnet 5 35f659249e 修复注册流程不写入持久化登录态、注册失败500错误显示原始英文
Register组件缺少Login同款的keep-logged-in挂载副作用,导致注册产生的
新账号在下一次刷新时静默丢失会话(无报错、无跳转登录页)。
另外注册走原生WS {acc}协议而非REST,未接入CTRL_ERROR_TEXT_CN翻译表,
"internal error (500)"等错误会原样展示给用户。

第十轮最小粒度测试 Ch1-2 发现。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 00:46:27 +08:00
Your NameandClaude Sonnet 5 eb4c1cd8da 用登录页现有logo重新生成PWA图标/favicon/OG分享图;顺手发现并重建了严重过期的生产构建产物
图标部分:logo.svg/logo96.png/logo192.png/logo32x32.png/logo32x32a.png原来画的是开源Tinode
项目自己的蓝绿橙"t"speech-bubble标志。用登录页AppMark组件的真实CSS参数(白色圆角方卡 + 旋转
-18deg的蓝色squircle + 加粗白色首字母,色值#1677ff,圆角50% 50% 50% 18%)原样重新画成独立
的SVG/PNG资源,不是另起一套新设计——文字/图标/圆角比例/旋转角度均按登录页实测的computed style
数值1:1还原。favicon(32x32)的"未读"变体(右下角绿点)沿用原有的visual language。

og-logo.jpeg(社交分享预览图,og:image meta标签用)问题比图标本身更明显:原图整个背景是金门大桥
照片,前景写着"Tinode / Open Source Instant Messenger",这两行文字是本次engagement里最直接的
一处Tinode品牌泄漏(没有隐藏在代码里,链接分享出去别人就直接看到)。改成深色渐变背景+同一套
logo mark+"MeetingIM"+"即时通讯",不再引用任何第三方项目的名字或素材。

意外发现:umd/index.prod.js(生产构建产物,index.html的<script>直接引用)从很早之前就没有
重新构建过,仍然包含"TinodeWeb"/"web.tinode.co"/tinode.co的隐私政策与服务条款链接/
support@tinode.co邮箱等一整套本轮及更早各轮已经在源码里修掉的Tinode字样——如果这份产物被
原样部署,之前所有品牌清理都不会真正体现在生产环境。已用`npm run build:prod`重新构建(干净
通过,只有两条跟本次改动无关的资源体积警告),确认重建后的产物里"MeetingIM"正确替换了旧品牌名、
真实域名替换了tinode.co。这次只重新生成了产物文件,没有执行任何部署动作。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 23:30:11 +08:00
Your NameandClaude Sonnet 5 ee2cf6f965 修复收藏/聊天记录检索的请求竞态、下载页按钮占一半宽度、两个类名零CSS覆盖
- Favorites/History 组件的 load() 原来没有过期响应保护:切换类型/输入搜索词都会触发新请求,
  但如果一个更早发出的、范围更宽的请求(比如"全部")比后发的窄范围搜索请求更晚返回,会无条件
  覆盖掉已经正确显示的搜索结果——实测复现过"搜索框显示查询词,列表却是全量未过滤结果"。改成
  每次调用 load() 时认领一个自增计数器,响应到达时如果计数器已经被更新的调用推进过,直接丢弃
  这个过期响应。History组件(按topic/tab/query切换)是同一个模式,一并修复。
- `.library-download-actions`(下载页loading/done两态的按钮容器)是2列等宽grid,但两个状态
  永远只渲染1个按钮——按钮固定占左侧50%宽度,右侧约一半容器宽度是死空白。改成单列。
- `.library-empty-hint`(收藏/聊天记录/下载页的加载中提示文字)和`.library-load-more`
  ("加载更多"按钮)在base.css里完全没有对应规则,退化成浏览器默认段落间距/按钮外观——
  补了基础样式,跟第八轮修复的.library-link-text/.library-record-header是同一类遗漏。

均已重新构建并实测验证(下载按钮宽度、空状态文字样式、加载更多按钮样式均用真实DOM测量确认;
请求竞态修复通过真实收藏页搜索/清空往返验证无回归,竞态本身的精确时序难以在测试环境里稳定
复现,但修复采用的是标准、成熟的自增计数器丢弃过期响应模式)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 18:46:29 +08:00
Your NameandClaude Sonnet 5 38b43936ce 修复桌面端会话内错误提示完全不可见、收藏发送面板显示空白/裸类型名、delivery-marker图标className拼出undefined
- 桌面布局下打开一个会话时,sidepanel-view.jsx 走的是 messages-home 分支(渲染
  MessageCenterView),这个分支从来没有渲染过ErrorPanel;messages-view.jsx自己的ErrorPanel
  又只在displayMobile时才渲染——两边一凑,桌面端(最常见的使用状态)里任何onError(...)调用
  (上传失败、收藏转发失败、URL风控拦截等)全部没有可见的展示出口,用户只会觉得"点了但什么
  反应都没有"。已用真实handleError()调用实测确认:修复前DOM里根本没有.info-box节点,修复后
  正确渲染且visible。
- "我的收藏"发送面板:favoriteSummary()对图片/视频收藏显示空白(真实数据里这两类的
  snapshot.content.txt是占位用的单个空格,被直接当文本展示)、对文本类收藏显示裸露的内部
  类型字符串"text"(FAVORITE_TYPE_LABEL没有text这个key,且文本收藏的content本身就是纯字符串
  而非{txt:...}对象,原逻辑没处理这种形状)。改成图片/视频/语音三类固定用类型标签、文本类型
  优先判断content本身是不是纯字符串、并给FAVORITE_TYPE_LABEL补上text条目。
- received-marker.jsx的已发送/已送达图标(deliveryMarker对这两种状态返回的对象没有color字段)
  拼接className时把undefined直接字符串化拼了进去,实际渲染成"material-icons small undefined"。

均已重新构建验证;received-marker/favoriteSummary为纯函数级修复,逻辑经过仔细核对但未逐一
截图复核(不依赖运行时状态,风险低)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 18:42:08 +08:00
Your NameandClaude Sonnet 5 47148aa75d 修复登录页浏览器后退卡死、已认证态下登录/注册的凭据校验绕过、系统消息分页丢失等第1-2章问题
- 最主要的一处:登录成功后点浏览器"后退",会回到一个仍然认为"已登录"的底层连接、但重新挂载出来
  的登录表单——按钮永久卡在禁用的"登录中…"(loginDisabled只有失败分支会重置,成功分支从来没有,
  因为正常路径下这个页面早就导航走了,没人会看到);如果不管这个直接切换企业码后再次提交,
  doLogin()/handleNewAccountRequest()开头的"已认证就直接放行"逻辑会让登录悄悄跳过凭据校验,
  或者让注册请求收到后端原始英文错误"already authenticated (409)"。根治方式是在
  handleHashRoute加一道门:hash指向登录/注册等纯"未登录态"视图、但连接其实已认证时,直接重定向
  离开,不让这类表单有机会被重新渲染出来——已用真实浏览器验证4个入口(login/register/tenant/
  reset/cred)全部正确重定向,且确认不影响真正的登录后安全类页面(如identity-check)。
  handleLoginSuccessful也顺手补了loginDisabled:false兜底。
- 系统消息列表:后端`GET /system-notifications`明明返回`next_cursor`表示还有更多,前端从来没用过,
  未读50+条永远只能看到最近20条、永远没法真正清零。补了"加载更多"分页——已用真实账号(60+条历史
  通知)验证,点击后正确追加下一页、游标耗尽后正确不再显示按钮。
- 已处理(通过/拒绝)的好友申请通知,原来只要event_type以"friend.request"开头就显示"查看新朋友"
  按钮——不区分"待处理"和"已是历史结果",点了跳到待处理列表也找不到对应项。改成只在
  `friend.request.created`时显示,已用真实账号里同时存在created/accepted/rejected三种通知
  验证行为符合预期。
- 注册页四处不统一的占位品牌名(讯工捷迅/捷信密使/捷讯/即时通讯,没有一个是真实品牌名)与
  系统消息页脚"即时通讯安全团队",统一改用BASE_APP_NAME("MeetingIM")。
- config.js的KNOWN_HOSTS.hosted从web.tinode.co改为web.118im.com(这是WS连接失败时的最终
  兜底地址,指向无关第三方域名不只是品牌问题,是真实会连错服务器);host-name.js的
  detectServerAddress()原来只要hostname是localhost就无条件返回硬编码的localhost:6060,
  完全不管页面实际监听的端口——本项目自己的开发代理跑在8093,清缓存后首次连接必然先撞一次
  死连接,改成和非localhost分支一样用页面自己的真实端口。
- 实名认证提交成功后,姓名/证件号输入框原来会变成空白(后端成功响应不含masked_name/
  masked_id_number,组件切到只读态时又没有回退到用户自己刚输入的草稿值)——改成没有脱敏字段时
  回退显示用户本次会话里刚输入的原始值,不是留白。
- Firebase推送通知初始化失败原来复用登录/注册表单的错误提示通道,可能盖住一个同时发生的真实
  登录错误,或在用户还没操作登录表单时就先弹出一条无关提示——推送本身是锦上添花功能,改成只
  console记录+静默关闭开关,不再抢占登录页的错误展示位。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 18:36:03 +08:00