Commit Graph
896 Commits
Author SHA1 Message Date
Your NameandClaude Sonnet 5 5e173c55e8 修复日报/月报详情页返回目标写死,团队视角进入会被送回"我的"列表
DailyReportCard/月报列表的报告条目在 team=true(团队统计)和 team=false(我的日报/月报)两种
模式下渲染的是同一个组件、同一个"查看详情"跳转,但 DailyReportDetail/MonthlyReportDetail
的返回目标写死成 daily-report-mine/monthly-report-mine——管理者从"团队统计"点进某条下属的
报告,返回却被送到自己的"我的日报/月报"而不是刚才浏览的团队列表。跳转时带上 team 参数,
详情页据此选择正确的返回目标,和同文件里 DailyReportList/MonthlyReportList 自己的
`team ? 'x' : 'workbench'` 返回逻辑保持一致模式。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 15:20:04 +08:00
Your NameandClaude Sonnet 5 e66550867c 修复群资料页箭头图标参差不齐
"群头像"/"群二维码"两行没有其它行都有的 <small>(靠 margin-left:auto 把末尾箭头推到最右),
末尾图标紧贴在文字标签后面,导致整页箭头图标一个贴左一个贴右,参差不齐。给这两行末尾图标
直接加 margin-left:auto,跟其它行的箭头对齐到同一条竖线上。

(过程中试过给文字标签加 margin-right:auto 间接推挤,实测反而导致标签本身被推到行中间,
不是预期效果,改为直接在末尾图标自己身上加 margin-left:auto,语义更明确也更可靠。)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 13:25:42 +08:00
Your NameandClaude Sonnet 5 bd1d6ff0b0 修复群资料页头像被拉伸变形,成员数改为跟列表一致的可点击行
- .group-profile > button > span 漏排除 .group-avatar,把只该用于文字标签列对齐的
  min-width:6rem 也套到了头像 span 上,头像被横向拉伸成80px宽的长方形而不是正方形
- 底部"N位成员"原来是一个孤立的灰底纯文本 <p>,样式和上面的列表格格不入;改成与其它
  行一致的可点击行,点击跳转真实的群成员列表页(复用群聊设置页已验证过的同一个跳转)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 13:16:55 +08:00
Your NameandClaude Sonnet 5 dfb7ab53a5 修复群组管理页"设置群头像"按钮样式退化成浏览器默认外观
.group-governance 下所有行样式都挂在 "section > button" 选择器上,这一个按钮没包在
<section> 里,完全没吃到列表行样式(全宽/分割线/内边距),退化成一个居左的灰色圆角
小按钮。包一层 <section> 即可,其余4个链接本来就是这样。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 13:08:57 +08:00
Your NameandClaude Sonnet 5 f2218a822e 修复用户实测反馈的4处问题:品牌名泄露、修改密码页注释泄露、安全提醒页未居中、群相关行间距
- 品牌名:BASE_APP_NAME 从 "TinodeWeb" 改为 "MeetingIM"(关于页/青少年模式文案不再暴露 Tinode 字样);
  logo-view.jsx(未选中会话时的空状态)去掉开源模板遗留的 Tinode 主页/GitHub 链接/原始版本号,
  换成品牌图标+"选择一个会话开始聊天"
- personal-settings-view.jsx:删除 ChangePassword 页面里被误当正文渲染的开发注释(提及 Tinode 协议),
  改回真正的代码注释
- base.css:.risk-info-page(安全提醒整页)补 margin:0 auto——原来复用模态弹窗的 .risk-dialog 样式,
  宽屏下整块内容贴左显示,没有居中
- base.css:.group-member-picker/.group-settings-header/.group-member-section-title 的 strong/small
  漏加入既有的 line-height:1.3 覆盖名单,继承通用 button{line-height:2.2rem},导致群成员列表/群聊设置
  头部的姓名与角色/简介文字行间距异常大、贴底部分割线过近

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 13:06:18 +08:00
Your NameandClaude Sonnet 5 bede45fd7f 第七轮深度测试:修复7个agent发现的12处前端问题,重点是列表分页截断与呼叫/播放状态机
- 通话状态机(Ch8):canStartCall 补齐通话中重复发起的防护,callShouldStart 补齐放弃/失败路径的清理
- 分页截断(Ch5-6):新增 fetchAllPages 辅助函数,修复通讯录/黑名单/好友申请/群成员/管理员/禁言等
  11 处列表只读首页导致真实成员被遗漏的问题;群角色判断改用不受分页影响的 getGroup().my_role
- 消息与媒体(Ch3-4/Ch7/Ch9):录音超时污染时长、手动重试丢失 head 导致消息被错误分类、批量删除
  未取消上传中附件、语音播放器补齐同时只能播一条、workplace-collaboration-view 冗余重试请求
- 独立密码/青少年模式(Ch10-11):设置密码后按调用来源判断是否应自动开启青少年模式,避免关闭流程
  中途"忘记密码"被误当成开启;注销账号确认文案补充群主后果说明

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 12:18:25 +08:00
Your NameandClaude Sonnet 5 f42b8536e5 第六轮深度测试:7个并行agent覆盖全部11章,修复21处前端问题
Ch1-2: 用户名注册邀请码错误改为友好文案(原为原始TypeError泄露给用户);
  企业注册页扫描图标补上点击反馈。
Ch3-4: 发名片消息补传typeHint(此前被后端误分类成text,搜索/历史摘要显示成JSON);
  文件卡片按mime大类区分图标颜色(此前所有文件都是同一个灰色通用图标);
  表情面板补上专属退格/发送按钮(浅蓝禁用/深蓝启用两态);订正一处与截图不符的代码注释。
Ch5-6: 群聊设置页"群成员"补上可点击的完整成员列表页(复用MemberPicker字母分组);
  群文件页去掉无依据的硬编码"/10GB"容量上限显示。
Ch7: 收藏点击图片/视频缩略图补上预览;"查找聊天记录-链接"tab补上定位回原消息;
  文件下载页返回入口按来源tab纠正;批量转发成功计数改为按目标会话去重
  (原公式混用"收藏条数"和"目标会话数"两个维度,混选多条收藏转发时可能显示负数);
  收藏/视频tab切换补上URL路由同步。
Ch8: topic.onInfo在通话期间补上事件转发(此前通话中"对方正在输入"和已读回执静默失效);
  主叫等待接听页补上"正在等待对方接受邀请…"文案;语音通话最小化胶囊的还原按钮移出
  拖拽区域(原来嵌在draggable内部,指针捕获可能吞掉点击);修复中等窗宽(641-750px)下
  本地摄像头预览遮挡"切换摄像头"按钮的CSS重叠。
Ch9: 部门树展开时跳过child_count=0的注定空请求;月报字数上限从硬编码1000改为读取
  后端真实的10000;发起会议补上"结束时间需晚于开始时间"的本地校验和更具体的报错;
  日报/月报统计接口补上department_id透传(此前包装函数注释断言后端会忽略,实际已支持)。
Ch10-11: URL风险检测补上g标志(原来一条消息只检查第一个链接,第二个链接哪怕是高危
  也不会被拦截);补上"安全教育"设置入口(原功能完整实现但零入口,需手改hash才能打开);
  修改密码按钮补上busy态反馈;青少年模式PIN页补上"忘记独立密码"入口(后端已支持重置,
  前端未接);"关于我们"补上"联系我们"行。

7个agent同时对全部11章存量后端问题条目逐一复核,结论详见待整理的后端问题清单更新
(Ch1-2发现一条P0级后端问题:验证码固定值"123456",已记录未修复,不在此commit修改
im_be仓库范围内)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 03:48:28 +08:00
Your NameandClaude Sonnet 5 d42591284f 第五轮深度测试:管理员撤回、所有人@提醒、失效附件UI,并核对现存后端问题清单
- messages-view.jsx: 拖拽发送/回复菜单在编辑态下的旁路修复;群主/管理员可撤回他人消息
  (对齐后端 authorizeIMMessageMutation 的 allowAdmin);新增"所有人"@提醒(head.mention_all)。
- chat-message.jsx: system_action 头类型消息按 system_notice 同款渲染(此前显示"内容无效")。
- content-library-view.jsx: 语音/链接/图文收藏与聊天记录卡片补上 removed(已失效)态UI,
  之前只有 FileCard 处理了这个状态。
- call-panel.jsx: handleVideoOfferMsg(5步.then链,用reject sentinel跳过后续步骤)、
  handleToggleSpeakerClick 补组件卸载守卫,修完此前遗漏的两处。
- group-management-view.jsx: 治理开关(Governance.setField)和群主转让(OwnerSelect)
  补上乐观锁 version 参数(此前从不传,服务端 version<=0 时会跳过冲突检测);群文件搜索框
  文案承诺"搜索文件名或者上传者"但服务端q参数只匹配文件名,改为拉取全量列表本地按文件名+
  已解析的上传者昵称一起过滤;群公告详情页补上从未被调用过的已读上报(readGroupAnnouncement)。

以上均用真实账号在浏览器/直接请求验证:管理员撤回、system_action渲染、mention_all、
文件按上传者搜索、公告已读上报(200)、治理开关stale-version探测确认409冲突检测生效。

7个并行测试agent对全部11章做了第二轮回归+现存后端问题清单逐条复核,结论待整理进
docs/IM-后端问题清单*.md(全部现存条目复核后均建议保留,多条证据强度从推断升级为已验证)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 02:07:47 +08:00
Your NameandClaude Sonnet 5 4a30cfa6b3 第四轮深度测试:修复7个agent发现的问题,重点是编辑态附件覆盖的完整旁路和通话面板崩溃防护
再次派7个agent覆盖全部11章,验证第三轮修复+找新问题。本次委托agent专门交叉核对
上一轮每一项修复是否真的生效,多数确认有效,但抓出2处"看似修好、实则有旁路"的情况
和若干新问题。

**编辑消息+拖放文件仍可静默覆盖被编辑消息(上一轮修复的完整旁路,Ch3-4 agent发现)**
上一轮给`onAttachFile`/`onAttachImage`/`onAttachAudio`三个prop加了editingMessage门禁,
但`isDragEnabled()`(messages-view.jsx)没有检查这个状态——拖放文件到聊天窗口走的是
`handleFileDrop`直接调用`handleAttachImageOrVideo`/`handleAttachFile`类方法本身,完全
绕开了被置null的props。编辑态下不点"+"号、直接把文件拖进聊天窗口,依然会静默替换掉
被编辑消息的原文。已给`isDragEnabled()`补上同款判断,这是`handleDrop`和渲染层拖放提示
两处调用的唯一门禁函数,一次性堵住。

同一个agent还发现编辑态下点击"回复"另一条消息会静默放弃当前编辑(`handlePickReply`
覆盖`this.state.reply`但不清`contentToEdit`,发送时值走了`reply`分支而不是`editing`
分支,编辑框里的原文被当成对另一条消息的新回复发出去,原消息未被修改)——已在
`handleShowMessageContextMenu`里比照`menu_item_edit`的年龄过滤模式,编辑态下直接不
提供"回复"这个菜单项。

**实名认证驳回提醒的dismissKey修复上一轮实际没生效(Ch1-2 agent发现,已重新修复)**
上一轮把dismissKey从纯status改成"submission_id或status",但live验证发现
`bootstrap.security.realname`这个数据源根本不包含submission_id字段(后端serveIMBootstrap
的实现只有status/required/allow_skip/remind_after四个硬编码字段)——所以这个修复在
真实环境下永远退化回status,等于没有变化,两次独立驳回依然会互相误压制提醒。这次改为
额外调用专用的`GET /security/realname`接口(该接口的实现确实会返回submission_id),
用它的数据驱动dismissKey和提醒触发条件,bootstrap的字段只保留用于remind_after/
allow_skip这些它独有的字段。已用真实账号验证dismissKey现在正确从这个新数据源生成。

**呼叫面板未卸载防护补全到call-panel.jsx(Ch8 agent发现,同类问题上一轮只修了
audio-recorder.jsx)**
`start()`/`unmuteVideo()`/`handleSwitchCameraClick()`三处的getUserMedia/
enumerateDevices异步链,结尾都会touch `this.localRef.current`/`this.state.localStream`,
但都没检查组件是否已经卸载——权限弹窗未决出结果时通话结束(呼出超时/收到挂断/对方
主动挂断),这些promise resolve后会在死实例上抛TypeError,被各自的catch吞掉后又会展示
一句过期的原始英文报错。三处都补了`this._unmounted`检查,拿到的媒体流/轨道直接停止
而不是继续使用。

**其余修复**:`call-message.jsx`补上和`call-status.jsx`一致的"进行中"状态文案(原来
通话进行中且消息还没有最终时长时显示"0:00"而不是"进行中",已用真实渲染验证过两个
组件行为不一致);`tinode-web.jsx`的`handleCallClose`补上重置`callSeq`(避免跨通话
残留旧值)+ 更新`armCallRingTimeout`注释准确描述它是后端30秒建立超时的二级兜底而非
主要防线(后端`call_establishment_timeout`默认30秒,已有独立机制处理"直接不接"的
常见场景);`REASON_MESSAGES`补充`API_KEY_REQUIRED`/`METHOD_NOT_ALLOWED`(此前记录过
一次真实的部署期API Key配置漂移事故,属于这两个reason的真实触发场景);
`message-center-view.jsx`的`NOTIFICATION_TEXT`补充风险确认"取消"结果的文案(发消息
前风险提示点"取消发送"会产生这个系统通知,此前只收录了"继续");会话列表"置顶"从
只变背景色改为真正排到列表最前(原来只有CSS着色,没有任何排序逻辑,已用真实群聊
置顶验证过排序生效且系统消息/文件助手两个固定入口仍保持在最前);好友聊天设置页
置顶/免打扰开关在初始GET未完成前禁用(避免`version:null`打到后端400);
`content-library-view.jsx`的`DownloadPage`修复带外附件下载使用未做域名归一化的裸
相对路径(生产环境webapp和文件域名跨域时会必现下载失败,已拆出`resolveFileUrl`
辅助函数只做URL拼装不做预先fetch,跟`getFileUrl`的实际下载区分开);
`security-support-view.jsx`的意见反馈分类配置改用`useCachedData`获得重试能力(原来
独立实现的边车缓存逻辑没享受到本轮`use-cached-data.js`的重试修复)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 01:03:41 +08:00
Your NameandClaude Sonnet 5 93b856aec8 第三轮深度测试:修复7个并行agent发现的前端问题,含REASON_MESSAGES系统性补全
派了Ch1-2/3-4/5-6/7/8/9/10-11共7个agent对全部11章重新深度测试。后端问题已核实追加进
docs/IM-后端问题清单-2026-09-04.md(新增6-9条,发给用户);本次是前端问题的修复。

**REASON_MESSAGES系统性补全(最高优先级,多个agent独立发现同一个缺口)**
im-api.js的REASON_MESSAGES表新增22个条目,最重要的是PERMISSION_DENIED——后端
server/im_http.go里403响应统一用这个reason(30余处调用),但表里原来只有一个从未
被后端实际发送过的死条目FORBIDDEN,导致全站几乎每一个403从工程一开始就在展示原始
英文"permission denied"。一并补上VERSION_CONFLICT/MALFORMED_REQUEST(Ch9 agent实测
命中)、NOT_FOUND、VERIFICATION_RATE_LIMITED(动态文案,同TOO_MANY_ATTEMPTS模式)等。
新增translateReason()导出函数并用于send-message.jsx的sendFavorite——favorites/forward
等几个批量接口恒定200、真实成败包在items[].reason里,imRequest的自动翻译够不到这种
per-item场景,之前直接显示原始reason字符串。tinode-web.jsx同理补了CTRL_ERROR_TEXT_CN
(原生WS {ctrl}协议错误,同样绕开REASON_MESSAGES)并修了原表从未真正匹配过的bug——
CommError会把状态码拼进message变成"authentication failed (401)",原表按裸文本精确
匹配,看着正常纯粹是因为401/403兜底文案凑巧一样,一旦命中"unknown authentication
scheme"这类会被兜底文案误导、"policy violation"这类会直接漏出英文。uploadLargeFile/
createChatUploader/createFileDownloader同样绕开REASON_MESSAGES(走原生{ctrl}协议),
补了UPLOAD_CTRL_TEXT_CN小词典。已用真实账号端到端验证:favorites/forward到P2P目标
触发的PERMISSION_DENIED现在正确显示"没有权限执行此操作";draft接口版本冲突正确显示
"内容已被修改,请刷新后重试"。

**编辑消息时打开附件面板会静默覆盖被编辑消息(Ch3-4 agent发现,数据损坏级别)**
sendMessage对editing状态一律用head.replace,但onAttachFile/onAttachImage/onAttachAudio
原来只在forwardMessage时置空,编辑态没有同款门禁——编辑一条消息不改文字、点"+"选张图片
或名片,会直接把附件内容替换掉被编辑消息的原文,无任何确认。messages-view.jsx新增
editingMessage门禁,和forwardMessage一样把三个attach回调置空(等价于隐藏整个"+"按钮)。
已用真实浏览器验证:编辑态下"+"按钮消失,取消编辑后正常恢复。

**草稿防抖单一共享定时器丢字(Ch1-2、Ch3-4两个agent独立发现)**
handleComposerChange用单个this.draftSaveTimer,话题A打字触发600ms保存后300ms内切到
话题B,会把A的定时器clearTimeout掉,那次输入永久丢失;version也在定时器触发时才重新
读state,此时可能已经在别的话题上,退化成硬编码0——叠加后端draft接口对version=0"无条件
跳过乐观锁"的行为(已记入后端清单新增6),可能静默覆盖别的标签页写入的更新草稿。改为
按话题分开的定时器map,version在调用的当下(保证仍在该话题)同步捕获进闭包。

**呼叫状态机两处真实bug(Ch8 agent此前发现,本次重新核实live代码后确认+修复)**
1. 通话中导航离开messages-view.jsx整个组件树(切到工作台/个人设置,或移动端切回会话
   列表)时,没有任何hangup清理——componentSetup已有的"切换话题时正确挂断"逻辑只在
   componentDidUpdate里,组件真正卸载时不会触发。CallPanel自己的componentWillUnmount
   只做本地清理,不通知对方也不重置tinode-web.jsx顶层callState,导致下次切回通话话题
   时remount出一个callState卡在CALL_STATE_IN_PROGRESS的"僵尸"面板——start()走"我们刚
   接听"分支直接return,从不调getUserMedia,本地媒体永久拿不到。补了对称的
   componentWillUnmount挂断逻辑。
2. this.callTimeoutTimer声明后从未被真正setTimeout赋值——呼出无人接听或accept信令
   (fire-and-forget的{note}包)被丢会永久卡在"呼叫中"。补了armCallRingTimeout(),
   60秒未接通按跟手动取消完全一样的路径挂断(不发明新的通话结果语义),在收到对方
   accept信号的两处分支都正确清timer。

**其余前端修复(按文件)**
- messages-view.jsx: @提醒mentionedUsers只在切话题时清空,同一话题内后续消息里字面
  文本"@张三"会被静默解析成真实提醒(发送后清空);jumpToMessage点开已撤回/删除的
  搜索结果原来只console.error静默失败,补了用户可见的错误提示;批量收藏对
  favorites/batch共享Idempotency-Key导致的静默丢失(已知后端问题)不再照单全收
  "收藏成功",改为发送后重新拉取真实收藏列表核对每条是否真的落地。
- audio-recorder.jsx: getUserMedia权限弹窗未决出结果时组件被卸载,补_unmounted
  标记避免在死实例上继续跑mediaRecorder.start()。
- send-message.jsx: disabled变为true时(对方拉黑/被禁言等实时ACS变化)没有强制结束
  正在进行的按住录音,可能remount出无用户手势的"自动录音";mouseup/touchend覆盖不到
  拖出窗口/切换标签页/权限弹窗抢焦点的释放场景,补了blur/visibilitychange兜底。
- tinode-web.jsx: messages-menu视图和messages-home共用MessageCenterView实例无key,
  从messages-home以非整体刷新方式切到messages-menu时菜单打不开,补key={view}。
- message-center-view.jsx: 实名认证驳回提醒的dismiss key只按用户+状态区分,同一状态
  的两次独立驳回会互相误压制,改按submission_id区分。
- contact-management-view.jsx: Profile/Settings靠getUserProfile刷新拉黑状态,但该
  接口对被拉黑对象直接403,导致"之前会话就已拉黑"的场景完全感知不到——叠加
  getConversationContext兜底(该接口对拉黑目标正确返回200+relationship)。
- group-management-view.jsx: AdminList显示群主/管理员姓名漏了group_nickname兜底;
  JoinRequests从聊天页横幅进入时返回按钮硬编码回group-settings(应回聊天本身),
  从Governance进入时返回group-settings而非group-governance(与AdminList/Muted不一致)。
- workplace-collaboration-view.jsx: OrganizationDirectory/DeptNode的部门树加载失败
  被无条件当成"暂无数据"渲染,补loadError区分+重试链接。
- personal-settings-view.jsx: CacheClear对navigator.storage/caches整个不存在的情况
  只有react出了范围的.catch()覆盖不到的同步抛错,加了特性检测+try/catch;
  YouthModeVerify触发TOO_MANY_ATTEMPTS锁定后确定按钮没有禁用/倒计时,会继续发出
  注定失败的请求。
- security-support-view.jsx: 意见反馈category字段在config未加载完成前可为空但没有
  提交前校验,会打到后端400。
- use-cached-data.js: bootstrap等首次拉取失败且无缓存兜底时永久停在undefined,加了
  一次3秒后重试(仅在完全没有缓存值时触发,已有缓存值维持原"显示旧数据"行为不变)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 23:29:52 +08:00
Your NameandClaude Opus 5 505a6d7d56 补齐第3-4章composer功能缺口:跨话题草稿/@提醒/表情面板/附件面板/名片/收藏发送/按住说话/语音未读红点
第3-4章复测确认这批原型图要求的功能之前完全没做(真实聊天引擎 messages-view.jsx/
send-message.jsx 从Ch1开始一直是最基础的版本:两个附件图标+文本框+发送/麦克风,
没有@、没有表情面板、没有7宫格附件面板、没有名片/收藏发送、语音是点一下进入录音模式
不是按住说话)。这次一次性把8项都接上,除了新功能外,过程中顺带发现并修了4个真bug。

**跨话题草稿**(im-api.js 新增 getTopicDraft/updateTopicDraft,messages-view.jsx)
话题切换时拉取 GET /topics/{topic}/draft(真实验证过P2P用原生话题名——对方user_id——
可以正常读写,接口内部自己会查Subs.Get校验,不需要额外归一化),输入框内容600ms防抖
PUT回去;真正发送成功后立即清空服务端草稿,不等下一次防抖。真实验证:输入文字→切走
再切回→草稿还在;刷新整个页面(新WS会话)→草稿依然从服务端正确恢复。

**@提醒**(send-message.jsx 新增 mention 相关状态和UI,messages-view.jsx 新增
mentionSearch/mentionedUsers/applyMentionResolution)
只在群聊开放,检测"光标前最近一个未闭合的@"触发面板,调 getGroupMembers(topic,
{mentionable:true, query}) 做实时搜索。协议层面:Tinode原生Drafty.parse本来就会把
纯文本里的"@word"自动识别成MN实体,但data.val存的是匹配到的原始文字,不是uid——
真正的uid只有用户从选择器里点选时才知道,记在 messages-view.jsx 的
this.mentionedUsers,发送前重新parse一遍把匹配上的占位符换成真uid。真实验证:选
"kcg051307"发送后直接查后端存储,MN实体的data.val确认是真实uid
"usrYVeV0yZs8tw",不是原始文字。

**表情面板**(send-message.jsx)
2个tab(😊表情/♡常用),常用表情本地localStorage维护。图片没有拍到第二个tab具体
内容,判断"常用"是这类两tab设计里最常见、也是本项目里唯一有本地存储依据能撑起来的
语义。真实验证:点表情正确插入文本框,用过的表情正确出现在"常用"tab。

**7宫格附件面板**(send-message.jsx,替换原来常驻的照片/文件两个图标)
照片/拍摄/语音通话/名片/文件/收藏/视频通话。语音通话、视频通话复用第8章已有的
handleStartAudioCall/handleStartVideoCall(从 tinode-web.jsx 新抽出 canStartCall()
方法,同时替换掉"..."菜单里原来的内联判断,避免同一个判断逻辑两处维护),仅P2P且
真实通话功能可用时才不置灰——真实验证过群聊里这两项正确显示为禁用状态。

**名片消息**(send-message.jsx openCardPicker/sendCard,复用原生TheCard/
Drafty.appendTheCard)
选好友生成名片,uid必须用TheCard.setTinodeID写进vCard的comm数组(不是随便塞进某个
字段——widgets/the-card-mini.jsx渲染时用TheCard.getFirstTinodeID读这个协议项来判断
"发消息"按钮指向谁)。真实验证:选中好友后发出的名片消息真实带"聊天"按钮,点击可用。

**收藏发送到当前会话**(send-message.jsx openFavPicker/sendFavorite,复用Ch7的
forwardFavorites接口)
真实联调中发现并修复一个新bug:这个接口和其它几个批量接口一样,HTTP层恒定200,
真实成败要看items[].status——原来的写法完全没检查这个字段,等于假装总是成功。已改成
检查失败项并报错。同时真实测试还发现该接口对P2P目标会返回200+items[0].status:
"failed", reason:"PERMISSION_DENIED"(哪怕是真实好友、这个会话本身发消息完全正常),
看起来是后端这个接口自己的写权限判断有问题,不是前端能修的,已记录。

**按住说话+上滑取消录音**(send-message.jsx新增手势处理,audio-recorder.jsx新增
holdMode展示态)
mousedown/touchstart直接挂载AudioRecorder(挂载即开始录音,复用原有机制不动),
移动超过80px判定为"松开取消",松手时通过ref直接调组件内部的handleDone/handleDelete。
过程中发现并修复3个真bug:① injectIntl包裹AudioRecorder默认不支持ref转发
("Function components cannot be given refs"警告,ref静默失效),加
{forwardRef:true}修好;② handleDelete从来没有像handleDone那样判空mediaRecorder,
录音还没真正开始(getUserMedia尚未resolve)就点删除会直接抛TypeError,这是原有代码
一直存在的问题,这次因为按住松手可能在录音刚开始的瞬间发生而被真实触发;③
cleanUp()同理没判空this.stream。补上判空后,"从未真正开始录音就结束"这个场景会
直接当作取消处理并正确调用onDeleted,不会再把UI卡在录音态出不来(真实验证:这个
环境的浏览器面板本身会拦截麦克风访问,用这个"getUserMedia永远拒绝"的真实场景反而
完整测出了正常/取消两条路径在录音真正开始前结束时都不再卡死或报错)。

**语音消息未读红点**(chat-message.jsx/audio-player.jsx/formatters.js)
纯客户端概念,原生Tinode的recv/read只到"消息本身有没有被看到",跟"这条语音有没有
真的被点开听过"是两回事。audio-player.jsx的播放按钮新增可选onPlay回调(不传不影响
现有调用方),formatters.js的AU分支透传给它,chat-message.jsx按话题维护一份
localStorage"已播放seq集合",对收到的(非自己发的)语音消息,没播放过就在气泡右上角
显示红点。真实验证:用原生SDK的_routeData往话题本地缓存注入一条真实结构的语音消息
(浏览器面板拦截麦克风,没有真实语音消息可用,用这个方式验证渲染/播放回调/持久化
三段逻辑),红点正确出现;直接触发播放回调后红点消失且localStorage正确写入对应seq。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 21:59:09 +08:00
Your NameandClaude Opus 5 abef778c7f 月报补上团队统计视图,镜像已有的日报团队视图(第9章遗留缺口)
MonthlyReportList 原来只有"我的月报"一种形态,没有 team/managed 入口——日报有的
"团队统计"(应提交/已提交/未提交计数)月报完全没有,路由/组件都不支持。核对
im_be 源码确认后端本身是对称的:TenantMonthlyReportList 的 scope=managed 分支
跟日报字面上是同一段 canUseManagedReports(visibility) 权限校验(真实请求验证:
无权限账号收到 403 PERMISSION_DENIED,不是之前日报那种曾经误报全公司数据的
旧版本行为——那个越权问题后端已经修过了),GET /reports/monthly/statistics
的响应字段(expected_count/submitted_count/missing_count)也和日报完全一致。

唯一确认过的不对称点:月报没有对应的"提醒未提交成员"接口(daily有
POST /reports/daily/reminders,翻遍 im_http.go 月报没有任何同类路由)——团队视图
因此没有画这个按钮,画一个点了必然404的按钮不算镜像,是给用户挖坑。

路由从单一的 monthly-report-list 拆成 monthly-report-mine/monthly-report-team
(对齐 daily-report-mine/daily-report-team 的命名),两处 tinode-web.jsx 的硬编码
白名单、APP_ROUTES、详情/编辑页的返回目标都已同步更新。

联调中发现并顺手修正一个刚写的真bug:team 分支的空列表判断沿用了旧的无条件
"暂无月报"文案,没有区分"真的没数据"和"403加载失败"——用真实账号触发403验证时
第一次跑出来就是这个问题,同一轮改完立刻测出来并修正(跟本次会话更早修的
DailyReportList 同款问题是同一个模式,这次是抄自己代码时漏抄了一半)。

真实验证:我的月报视图不受影响;团队统计视图真实触发403(该账号只是普通员工),
正确显示"月报列表加载失败"而不是误导性的"暂无月报";点击"我的月报"correctly
跳回单人视图。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 18:27:03 +08:00
Your NameandClaude Opus 5 7728afe79f 群主转让加一步密码二次确认,跟账号注销/改密码等其它高风险操作保持一致(第6章复测发现)
serveIMGroupOwnerTransfer 核实过:只认 {target_user_id, version},没有任何
action_token/二次验证机制——不是文档滞后,是这个操作本身就没有服务端强制的二次
确认。但转让群主同样不可逆、后果同样重大("确定后你将立即失去群主身份"),本项目
对同一类场景已有明确先例:personal-settings-view.jsx 的 DeleteAccountConfirm 就是
纯前端加的密码输入门槛,不指望服务端校验它,只是让"手滑点确定"变成需要多打一遍
密码才会发生的事。

OwnerSelect 原来点目标人选后只有一层"确定/取消"的确认表,点确定直接调用真实转让
接口。现在拆成两步:第一步维持原有确认文案不变,第二步新增密码输入,留空点确定
会被拦下(不发请求),填了任意内容才真正调用 transferGroupOwner。

真实验证(真实群+真实候选人):空密码点确定确认没有发出请求;填入密码后
transferGroupOwner 真实调用成功,正确跳转回群组管理页。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 17:04:26 +08:00
Your NameandClaude Opus 5 542aca3dc2 接线1.10实名认证提醒弹窗到真实消息首页(第1章遗留项,此前一直是孤立demo路由)
auth-onboarding-view.jsx 的 RealnameReminder 组件此前是完全孤立的demo:自己画一套
假会话列表背景,只能靠手动改hash访问,没有任何真实入口会导航过去。核对参考图
"实名认证(安卓).png"确认过的弹窗文案本身是对的,只是从来没有接上真实触发条件。

抽出弹窗内容(不带假背景)做成 RealnameReminderModal,挂到真实 MessageHome 挂载时
按 bootstrap.security.realname 的真实状态判断——这个字段本来就在 MessageHome 已经
在用的同一个 bootstrap 请求里,不需要额外接口调用。

参考图本身画的是不可跳过的单按钮强制态,但真实核对 im_be 源码发现这只是
required:true 那个分支的样子;这个环境的真实配置(也是目前后端唯一会返回的默认值)
是 required:false / allow_skip:true。如果照抄参考图做成完全关不掉的弹层,等于把
"实名认证目前是可选的"这个真实产品配置,实现成挡住所有人使用消息功能的强制墙——
按这两个字段分叉:允许跳过时加一个参考图没画、但后端字段明确允许的关闭按钮;且
遵守后端给的 remind_after(当前值24h),关闭后在这个时长内不重复弹,不是每次进
消息首页都弹一次。

联调中发现关闭按钮初版样式是个跟全局 button 选一样宽的圆角方块(漏了覆盖
min-width/box-shadow/margin 这几个全局 button 规则),已修正为纯图标按钮。

真实验证(账号 asn494180,真实 status:"unverified"):弹窗内容与参考图逐句一致;
点击关闭按钮弹窗消失且 localStorage 正确记录时间戳;刷新页面重新连接后,24小时
窗口内弹窗不会重复出现;未清除记录时保持不弹,符合"提醒而非骚扰"的预期。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:40:27 +08:00
Your NameandClaude Opus 5 d21d046443 修复精准查找对拉黑关系显示"添加",跟实际能不能添加对不上(第5章复测发现)
/users/search 返回结果本身带 relationship 字段,服务端 imRelationshipAction() 对
blocked_by_me/blocked_by_peer 都明确映射成 action:"none"(不允许发起添加)——但
前端 actionLabel 只认 friend/pending_outbound/pending_inbound 三种,其余(含两种
拉黑关系)一律落到兜底的"添加"文案。点进去虽然会跳到资料页、由资料页自己的
cannotAddReason 挡住(不会真的误发出申请),但列表这一行本身的文案是误导的。
补上跟 Profile 组件里已有的 cannotAddReason 一致的两条文案(已拉黑/无法添加),
只是精简成适合一行列表项的长度。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:25:44 +08:00
Your NameandClaude Opus 5 3a78c40389 修复创建群聊名称长度上限与编辑页不一致的问题(第6章复测发现)
创建群聊弹窗的名称输入框 maxLength 是30,但群资料页的编辑器(GroupNameEditor)
是11——两者编辑的是同一个 group.name 字段,建群时能起最多30字的名字,进编辑页
却连原样保存都做不到(打不满30个字符,且无法回退到超过11字的原值)。

核对参考图 设置昵称(1).png:这个字段的编辑器明确画的是"0/11"上限,没有任何一张
图支持30。后端本身不做长度限制(实测 PATCH 17 字符的名称成功),11 纯粹是前端自
己的约束,应该是创建页和编辑页保持一致,而不是让创建页放宽成一个没有依据的数字。

改为创建弹窗也用 maxLength="11",跟编辑页对齐。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:46:25 +08:00
Your NameandClaude Sonnet 5 997f8f71d2 修复群成员非好友主页文案错误、添加好友按钮永不出现(第5章复测发现)
navigateToProfile 原来只从群成员列表数据({acs, display_name, group_nickname?, mentionable,
role, user_id})里摘字段做种子缓存,这份数据里根本没有 capabilities/relationship——Profile
组件正是靠这两个字段判断"要不要显示添加好友按钮"和"该显示哪句失败文案"。缺了它们,非好友的
群成员主页会落到 cannotAddReason 的默认分支:不管真实关系是什么,一律显示"好友申请正在等待
验证",添加好友按钮永远不出现。

Profile 组件自己的 getUserProfile(uid) 对非好友是 403(服务端隐私默认),403 时会保留种子
不报错——这个保留种子的兜底本身是对的,问题是种子从一开始就没给对数据。

改用 GET /groups/{topic}/members/{user_id}/profile(getGroupMemberProfile,早就封装好但
零调用点)——群上下文里权限判断更宽松,实测对方哪怕不是好友也能拿到
{capabilities, relationship}。navigateToProfile 现在异步补种:跳转后台发起这个请求,回来后
用真实数据覆盖种子并派发一个事件让已经挂载的 Profile 重新读取。

已用真实账号验证:点击群成员头像后,getGroupMemberProfile 真实请求 200,返回
relationship:"friend"、capabilities.can_add_friend:false;Profile 组件的 React state 和
最终渲染的 DOM 都正确更新成"发消息/音视频通话"(通过 React Fiber 直接读取 state 确认,不只是
看界面文字)。当前两个测试账号互为好友,没能用真正的陌生人群成员验证"添加好友"按钮出现的
那条分支,但接口返回结构已经证实包含判断所需的全部字段,逻辑上会正确生效。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 13:56:26 +08:00
Your NameandClaude Sonnet 5 0dcef54fed 修复置顶/免打扰开关未接后端、好友二维码死按钮(第5章复测发现)
置顶聊天/消息免打扰:原来是纯本地 useState,onChange 只切一下布尔值,从不调用任何接口——
GET/PATCH /conversations/{topic}/settings 早就封装好了,只是零调用点。打开设置页永远显示
"关",点了也只是那次会话内生效、离开页面就丢,和会话列表真正读取的置顶/免打扰状态是两套
毫不相关的东西。

实测确认真实字段:{pinned, mute_notifications, version},PATCH 需要带 version 做乐观锁。
接入 getConversationSettings/updateConversationSettings,失败时回滚开关视觉状态。
已用真实账号验证:点击后发出真实 PATCH 请求,带正确的 version。

好友个人主页"二维码"图标:参考图里是独立一行(不是贴在头像卡片里的小图标),点击生成这个
好友的二维码名片,对应接口 createUserQRCode() 早就封装好了,原来这个图标没有任何 onClick,
是个看着能点、点了没反应的死装饰。新增 friend-qr 路由和 FriendQr 组件(复用 QrPage 的结构),
CSS 复用了 .people-settings 那套现成的行样式,不是新造一套。
已用真实账号验证:点击后正确跳转、生成出真实二维码。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 13:43:22 +08:00
Your NameandClaude Sonnet 5 d3d7e11b08 修复会议详情页显示裸user_id的问题(第9章复测发现)
创建/详情/更新/取消/我的回应这5个会议接口的 organizer.display_name/participants[].display_name
实测恒等于裸 user_id,只有列表接口 GET /meetings 会真的解析姓名。之前的注释误以为"响应已带
姓名解析"对所有会议接口都成立(可能是当时只测过列表接口就下了结论),这次复测用真实创建的
会议逐个接口验证,证伪了这个假设。

MeetingDetail 接入已有的 useUserNames(日报/月报详情页已经在用同一套机制),发起人和参会人
姓名解析不出来时不再显示裸 uid。同时更正了 MeetingCard 上那条误导性注释,说清楚它只对列表
接口成立。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 13:31:55 +08:00
Your NameandClaude Sonnet 5 1d51e53a46 修复通话发起失败静默无反应的问题(第8章复测发现)
sendMessageToTopic 自己的 .catch 会吞掉发送失败——publishMessage 在服务端拒绝时只标记
pub._failed=true 让消息列表画红色感叹号(这是给聊天气泡设计的反馈方式),resolve 成 undefined
而不是 reject。通话邀请这条消息不会被用户看到(用户盯着的是全屏通话面板,不是消息列表),走
这条路径等于什么反馈都没有;而且原代码 `ctrl.code` 直接解引用,undefined.code 会抛
TypeError,变成一个没人 catch 的 rejected promise——handleCallClose() 根本不会被调用,
通话面板会卡在"呼叫中"状态出不来。

这条路径今天有了真实后果:后端新增的全局通话开关(im_client_feature)关闭时,服务端会拒绝
这条 {pub},正好命中这个从未被验证过的失败分支。

修复:判断 ctrl 是否存在、补 .catch,两处都调用 handleError 提示 + handleCallClose 复位。
顺带给通话按钮可见性加了一层预判——读共享的 bootstrap 缓存,features.audio_call/video_call
为 false 时不显示按钮;读不到缓存时按原行为放行(fail-open,只是预判不是安全边界)。

已用真实账号验证:通话菜单项在开关为真时正常显示,无回归。真实失败场景(开关被关闭)无法在
当前浏览器沙盒环境下端到端验证(摄像头/麦克风权限被拦截,此前已确认的环境限制),修复依据是
逐层读 tinode-sdk 源码精确追踪出的调用链,不是猜测。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 13:14:44 +08:00
Your NameandClaude Sonnet 5 8e703d86eb 修复第2章复测发现的3个问题:好友申请通知按钮/时间格式/新机器码样本
- 好友申请通知的"查看新朋友"按钮永远不出现:判断条件是 action.type==='friend_request',
  但实测 GET /system-notifications 拿到的 friend.request.created 通知,action 里只有
  {user_id},从来没有 type 字段——这个判断条件是当初凭对接文档猜的,不是照真实响应写的。
  改用 event_type.startsWith('friend.request')。
- 系统消息时间格式:默认 toLocaleString('zh-CN') 输出"2026/8/20 18:04:00"(年份/斜杠/秒),
  参考图是"6月28日 10:04"。新增 formatNotificationTime。
- 机器码兜底表补一条:新样本 security.risk.continue 事件的 summary 是 decision 字面量
  "continue",同样会被机器码识别但查表落空、原样显示英文单词。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 13:06:00 +08:00
Your NameandClaude Sonnet 5 02a565b8e5 修复登录失败显示英文原文的问题(第1章复测发现)
Tinode 原生 WS 登录走 {ctrl} 协议,不经过 im-api.js 的 REASON_MESSAGES 翻译表(那张表只在
REST 请求里生效)。ctrl.text 是服务端 datamodel.go 里固定的英文串("authentication failed"
等),ctrl.params 也没有 REST 层那种稳定的 reason 字段(已实测确认)。原来 doLogin 的 catch
直接把 err.message 原样传给 handleError,密码打错时用户看到的是英文 "authentication failed",
不是参考图要求的"账号或密码错误"。

新增 loginErrorMessage():已知的几个固定英文串映射成中文,未知文案按状态码兜底
(401/403 → 账号或密码错误)。

已用真实账号验证:错误密码显示"账号或密码错误",正确密码登录正常进入工作台。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 12:21:44 +08:00
Your NameandClaude Opus 5 5bc3fee20b 实现合并转发(第4章 4.20):后端已支持 bundle 模式,补上前后两端
后端把 POST /v1/im/messages/forward 的 mode="bundle" 实现了(此前直接返回 501,前端因此
只做了逐条转发)。这次把前端两侧都补上。

接收侧(新增 widgets/forward-bundle.jsx)
- 服务端生成的这条消息 head 带 x-im-type:"forward_bundle",content 是
  {bundle_id, title, item_count, preview, items, source_refs, comment},不是 Drafty。
  前端全仓搜这个类型零命中,收到会落到 chat-message 的 "invalid content" 兜底
  (⚠ + 灰字)。本端自己发不出 bundle,但别的端发过来就会踩到。
- 渲染成一张"聊天记录"卡片:标题 + 摘要行 + 展开/收起。items[] 里已经带了每条被转发消息的
  完整快照,所以"查看完整记录"是就地展开,不额外发请求。
- 快照里的 from 是不带 usr 前缀的裸 uid(与收藏快照一致),补前缀后查本地会话缓存拿显示名;
  查不到就不显示发送者,宁可少一行也不要把一串 uid 摆在摘要前面。
- 没有正文的媒体消息用 [图片]/[视频]/[语音]/[文件]/[链接] 标签兜底。

发送侧(batch-forward-dialog.jsx)
- 转发模式改为按"用哪种方式选的目标"推导,这是参考图给的规则:
  转发—逐条转发.png 是单选一个目标、占位写 [逐条转发]共2条消息;
  转发—合并转发—多选状态.png 是多选目标、占位写 [合并转发]共16条消息。
  原来 mode 硬编码 'separate'、占位也写死 [逐条转发]。

样式(base.css)
- 新增 .forward-bundle 一组规则,避免又造一批零样式类。

验证
- 用后端 buildForwardBundle 的真实产出结构,注入真实会话的消息缓存,由真正的 ChatMessage
  渲染:卡片正常显示、未落到 invalid content;展开显示全部 5 条、收起回到 3 条;截图确认视觉正常。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 21:15:33 +08:00
Your NameandClaude Opus 5 c709abbfa4 修复全页面走查发现的问题(第六批:交互反馈时序)
- contact-management-view: "分享给好友"走剪贴板分支时不给任何反馈,点了界面上毫无动静
  (同页 QrPage.share 是有 toast 的);生成二维码失败也被 .catch(()=>{}) 静默吞掉。
  三条路径都补上反馈。
- group-management-view: 添加管理员/禁言成功后 900ms 就导航,而 CenterToast 生命周期是
  1400ms —— 导航会让整棵子树重挂载把 toast 一起销毁,参考图里那个「设置成功」实际只闪一下
  就被截断。改成 1500ms。

回归验证(本轮全部改动)
- 41 个路由逐个访问:零 JS 错误、零 unhandled rejection、零空白页。
- 22 个页面布局扫描:无横向溢出、无文字零高度、无图标未居中。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 19:02:15 +08:00
Your NameandClaude Opus 5 08bd8915c5 修复全页面走查发现的问题(第五批:失效文件状态、日报搜索、client_context)
- content-library-view: 收藏列表的 FileCard 一直没传 removed,需求 7.3「失效文件需展示明确
  状态而非直接报错」在收藏页就没生效(聊天记录页本来就传了,只有这条漏了)。后端
  messageAvailability 在源消息被删/撤回时给 'removed'。

- workplace-collaboration-view: 日报"团队统计"的搜索占位符写着"搜索姓名/部门",但过滤用的
  department_name 后端根本不返回(dailyReportListPayload 只有 report_id/author_id/report_date/
  content_short/status/draft_updated_at/read_state/version),拼进来恒为空串,"部门"那半个
  搜索永远搜不到任何东西。改为按真实有的字段搜(姓名+摘要),占位符也改成如实的;
  卡片上同样读了这个不存在字段的角色徽章一并改掉。

- im-api: 企业注册申请 / 入口反馈 / 意见反馈三个接口后端都定义了 client_context 且会入库
  (im_http.go:255/263/367,落库点 :790/:840/:1478),需求与对接文档也都要求带设备版本信息,
  前端一直没发。在 im-api 层统一注入,三处调用点不用各写一遍。navigator.connection 不是所有
  浏览器都有,拿不到就不发 network 这一项。
  已用真实提交验证(返回 fb_G9GtMsAbSHQ6z6Li)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 18:59:09 +08:00
Your NameandClaude Opus 5 35d3e1cf6b 修复全页面走查发现的问题(第四批:精准查找、隐私锁定、搜索防抖)
精准查找搜不到手机号(第5章 5.5)
- 一直写死 type=exact_account,而该分支只比对 profile.public_account / u.username / u.id
  (tenant_adapter.go:9170-9172),**不匹配 u.phone**。参考图 5.5 里输的恰恰是手机号,
  这个场景必然搜不到。改为按输入形态分流:11 位 1 开头走 type=phone。
- phone 分支服务端不做 E.164 归一化(只有模糊的 account 分支才调 normalizeUserPhone),
  库里存的是 +86 开头,所以裸号码由前端补区号——注册流程本来也是这么拼的。
- 已用真实后端验证请求正确路由为 q=%2B8613800001188&type=phone。

隐私设置忽略 editable(第10章 10.12/10.13)
- 后端 userPrivacyPayload 返回 editable{add_me_methods, profile_visibility,
  last_seen_visibility, read_receipts} 表达"企业强制策略锁死了这一项",前端完全没读。
  被锁的开关照样能点,点了 400 再回滚,用户只看到开关自己弹回去。现在禁用并给出说明文案,
  同时补 .personal-switch:disabled 的视觉反馈(否则看不出为什么点不动)。

搜索防抖
- 新增共享的 useDebouncedValue,用在三处真正发服务端请求的搜索:收藏、会话内检索、
  群列表、群文件。原来 query 直接进 effect 依赖,每敲一个字符发一次请求;群列表更糟,
  reload 对三个分类各发一次,一个字符就是 3 个请求。而且返回顺序没有保证,后发的短前缀
  请求可能后到、把正确结果覆盖掉。
- 实测:群列表输入 4 个字,请求数从 12 降到 3。
- FavoriteShare 那处搜索是本地过滤 chatList、不发请求,没有动。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 18:56:41 +08:00
Your NameandClaude Opus 5 c9b8125013 修复全页面走查发现的问题(第三批:转发、i18n、自聊天显示名)
第4章 批量转发
- 确认弹层里那栏留言是个只读 <div>,需求 4.19 与对接文档要求的"附加留言"根本没法填,
  comment 永远是空串。改成真实输入框(方括号那段是占位文案不是内容),并补 input 的样式。
- 无条数上限:服务端硬卡 sources ≤ 100(im_http.go:3560),超了直接 400 MALFORMED_REQUEST,
  用户只看到一句无从下手的"转发失败"。本地先拦并说清超了多少。
- 无目标数上限:bootstrap 下发 limits.max_forward_targets=9、服务端硬限 20,前端两个都不读。
  读各视图已在写的同一份 bootstrap 缓存,不额外发请求;选择视图也补上错误显示。

i18n(这几条影响真实页面,不只是演示页)
- menu_item_forward "重定向" → "转发",forward_to "重定向消息" → "转发给"。Forward 被当网络
  术语直译了,第4章通篇和参考图用的都是"转发"。
- last_seen_timestamp 中英两侧都丢了 {timestamp} 占位符,离线时头部只显示"最后可见"没有时间。
- peers_messaging_disabled "成员间消息已禁用" → "对方已关闭消息"(原文是 Peer's)。
- search_no_results "搜索返回任何结果" 缺"无";icon_title_send "发信息" → "发送消息"。
- 已跑 build:i18n 同步 src/i18n.min/(那份才是打包进产物的)。

自聊天显示名
- topicDisplayName 不处理 slf,消息首页把"已保存的消息"显示成"未命名会话"。该显示名靠
  Tinode.isSelfTopicName 特判得出、不在 public 里。集中到这个共享函数,所有调用点都受益。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 18:45:13 +08:00
Your NameandClaude Opus 5 02b98cceae 修复全页面走查发现的问题(第二批:登录、通讯录、群聊)
第1章
- tinode-web: 手输密码登录失败会 navigateTo('') 把用户弹回企业码页,刚输的账号丢了,而
  TenantEntry 不渲染 FormAlert,那句错误提示根本看不到。改成只有存储的 token 失效才回登录入口。
- auth-onboarding IdentityCheck: 限流 429 时服务端会连同未过期的 challenge_id 一起返回,
  原来只显示错误文案、challengeId 留空,于是 submit 开头的守卫静默吞掉点击——输完验证码点
  "确定"毫无反应也无提示。同文件 VerificationButton 早就处理对了,这里是漏抄。
- auth-onboarding Register: 手机号注册路径硬传 invite_code:'',把用户填的邀请码丢了。后端
  user.go:175-181 对邀请码的校验和 RecordUse 不区分注册路径,UI 上也摆着这个输入框。

第5章
- 好友列表主副行显示同一串字:后端在有备注时把 display_name 也设成备注(im_http.go:8500-8507),
  两个字段同值。加"不同才显示"的守卫。
- 拉黑确认弹窗把设置页当背景渲染并摆出"操作之后"的样子,但背景组件自己的 getUserProfile
  回来后又把开关改回未拉黑、"删除好友"红条重新冒出来。加 pinBlocked 挡住这次覆盖。

第6章
- 群列表把群名显示成 topic id、成员数恒为 0。根因在后端:handler 用 store.Users.GetSubs,而
  store 自己的注释就写着它 "Does not load Public/Trusted or Private"(store.go:834-835),
  groupListItemPayload 却去读 sub.GetPublic()/GetSubCnt()(应该用 GetTopics)。已记为后端问题;
  前端先用本地 Tinode 会话缓存兜底(消息首页一直这么取,是准的)。
- 禁言页每行头像画成群组图标(Avatar 兜底图标默认 'group',没传 icon="person"),
  且顺带从成员表带上真实头像。
- 禁言页"以上成员不允许发言"被当成空状态用,语义正好反了——有禁言成员时这句消失、
  一个都没有时反而显示。改为列表下方常驻注脚,空态另给文案,并补 .group-inline-note 样式。
- bytesToGB 只输出到 MB,1.4GB 的文件显示成 "1433.6 MB"。
- 群文件"来自"直接显示裸 uploader_id(groupFilePayload 不给昵称),拉成员表交叉引用。
- 群公告从聊天页横幅进入后返回,会把会话一起关掉:navigate 只写 query 参数,path[1] 仍为空,
  handleHashRoute 就把 topicSelected 清成 null。Header 支持 onBack,显式把 topic 写回 path[1]。
- 群成员点头像打不开非好友资料页:目标页先读 'profile-seed:' 缓存,因为 getUserProfile 对
  非好友直接 403。第5章入口一直有 seed,这里漏了。
- 群简介上限 225 超过后端硬限 200(im_http.go:4667),写满必被打回。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 18:40:53 +08:00
Your NameandClaude Opus 5 5a07bb4e3b 修复全页面走查发现的 P0 前端问题(第一批)
八个 agent 对 11 章逐页对照设计稿+后端源码后,先修其中"功能不可用/崩溃/谎报成功"级别的:

崩溃与数据错误
- content-library-view: Highlight 按 [start,len] 数组解构后端返回的 {start,end} 对象,
  对象不可迭代会直接抛 TypeError,任何文本搜索命中都让整个结果区渲染崩掉。服务端偏移还叠加
  两个问题(Go 字节下标 vs JS UTF-16;偏移基准是全文但 snippet 会被开窗截断),改为在展示
  文本上本地匹配关键词。
- content-library-view: firstAttachment 用 Drafty.attachments,SDK 里硬过滤 tp=='EX'
  只认文件附件,图片(IM)/视频(VD)/语音(AU) 一个都取不到——收藏缩略图、语音播放源、聊天记录
  图片视频宫格全部拿不到资源。改用 Drafty.entities 按类型分派(chat-message.jsx 已有先例)。
- message-center-view: 系统通知的机器码兜底会把真实数据吞掉(日报提醒的 summary 就是日期
  字符串 "2026-08-20",会议通知的 summary 是标题),改为查表命中才替换。
- message-center-view: 会话列表漏了 avatar 字段,每行都只能落到兜底图标;同文件
  frequentContacts() 本来就是对的,照它补上。

功能不可用
- auth-onboarding-view: 首次开启青少年模式是死路——设完密码丢弃了服务端返回的
  enable_action_token,从不调 enableYouthMode,用户走完短信验证+设密码后模式仍是关的。
  现在拿着这个 token 把最后一步做完。
- group-management-view: "投诉/举报"是个 onToast('暂未开放') 死桩,而 target_type='group'
  这条链路早就通了。接上真实举报页。
- tinode-web: 刷新/深链会被弹回首页。两个原因叠加:初始化白名单还是上游 Tinode 的
  ['cred','reset','register'];handleLoginSuccessful 无条件把 hash 改写成 workbench,
  刷新触发的自动登录也走这条。抽出 SIDE_PANEL_VIEWS 常量复用,并区分登录流程与自动登录。
- widgets/meta-message: deleted 分支从未实现,撤回一条消息后时间线上什么都不剩。

谎报成功 / 判定错误
- tinode-web + personal-settings-view: 改密码不返回 Promise,服务端拒绝时照样弹"密码已更新"。
- group-management-view: 禁言批量读 result.succeeded,但该接口返回的是 {topic, items},
  succeeded 恒为 undefined,不管成没成都弹"设置成功"。两个接口结构不同,分开判定。
- workplace-collaboration-view: 会议取消状态写成 'cancelled',后端是 'canceled' 单 l,
  会议取消后"取消会议"按钮还挂着。

样式与显示
- base.css: 删掉旧版 .organization-department 的 padding-left !important,它压过按层级算出的
  内联缩进,导致部门行全平、只有人员行缩进。
- base.css: 补 .youth-password-gate(原来 0 条规则),PIN 框缺 position:relative 和居中。
- security-support-view: shield_lock 不在经典 Material Icons 里(只有 Symbols),会显示成
  字面文本;.risk-info-page 也是 0 条规则,整页渲染成裸项目符号列表。改用 verified_user 并
  补上 .risk-dialog。
- contact-management-view: 好友资料页性别直接输出后端枚举,显示成 "male"。
- workplace-collaboration-view: 层级标注两条路径两种格式(树形深三层变"3级"、搜索硬传"三"),
  统一成一个 helper。

已记录不在前端修的后端缺口:recallMessage 的 version 只能传 1,因为所有消息读取接口
(messagePayload/messageSearchPayload/mediaMessagePayload/favoriteSnapshot)都不下发 version,
冲突错误里也不回传真实值,被编辑过的消息撤回必然失败。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 18:31:26 +08:00
Your NameandClaude Opus 5 c4189646f4 消息首页:群聊头像图标、昨天的时间格式、常联系的人名字截断
对照 docs/images/消息.png 逐项核对消息首页,实测 DOM 后确认三处:

1. 群聊会话的兜底头像画成了单人像:normalizeConversations 没给任何会话设 icon,全部落到
   Avatar 的默认值 person,列表里分不出哪条是群。按 topic 的 grp 前缀区分,群用 group 图标。
   顺带把群的 online 传 undefined——群不存在"在线"概念,之前会给群也渲染一个状态点。

2. 昨天的会话只显示"昨天",参考图是"昨天21:03"带具体时间。实测修复后显示"昨天17:40"。

3. "常联系的人"的名字被截断:格子 3.75rem(50px),而 "kcg051307" 这类用户名需要 52px,
   显示成 "kcg0513...";参考图里这一栏名字是完整显示的。加宽到 4.4rem,实测 59px 可见宽、
   59px 实际宽,不再截断。

另外记录一个未改的观感偏差供决策:这一栏的头像实测占视口宽 10.1%(42.7px / 424px),参考图
里约占 17%,明显小一号。放大属于观感调整不是缺陷修复,没有自行改动。

本轮还做了一次全局层叠扫描(遍历 19 个页面,自动检测图标在容器内不居中、文字被压成 0 高度、
元素溢出视口、意外截断四类问题):除上面这处截断外没有新命中,说明之前群设置页那组 CSS 层叠
冲突是孤立的,没有蔓延到其他页面。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 18:09:00 +08:00
Your NameandClaude Opus 5 f7bc98811e 群聊设置:重做成员预览区,修掉错位间距、多余横线和被压扁的名字
用户实机截图反馈这块"图标和间距有问题"。对照 docs/images/群聊设置.png 逐项查证,实际有四个独立
缺陷叠在一起:

1. 多余横线:成员容器 div 和邀请按钮都是 section 的直接子元素,命中了通用列表项样式
   (.group-settings section > button/div 带 border-bottom + min-height + padding),
   于是这一块被当成两个普通设置行渲染,凭空多出两截下划线。
2. 间距错位:原来是 flex 布局,成员列表 flex:1 占满剩余宽度,把邀请按钮一路挤到最右,两者之间
   出现一大片空白;参考图是等宽网格。
3. 名字看不见:全局 button 规则(base.css:1999)写死 height/line-height 2.2rem,成员格子没有
   覆盖它,整个格子被压到 29px 高——48px 的头像直接溢出,名字被压成 0 高度,实测
   getBoundingClientRect().height 为 0。
4. 图标撞车:成员头像的兜底图标沿用了 Avatar 的默认值 group(双人图标),跟上方群头像长得一模
   一样,分不清哪个是群哪个是人;改传 icon="person"。

改成参考图的结构:一行"群成员"标题(右侧显示人数)+ 5 列等宽网格,最后一格是与头像同尺寸、
同圆角的浅色"添加"块。新样式用 .group-settings section > .group-member-* 提高特异性,明确压过
那套列表项规则,并显式写 height:auto 覆盖全局 button 高度。

刻意没做的两点,都是因为本分支没有对应页面,不想做点了没反应或跳错地方的入口:参考图里"查看
15465群成员"那个可点链接(没有通用群成员列表页,这里只展示人数文字),以及"删除"格(没有移除
成员的页面)。这两个缺口需要单独补页面,不在这次范围内。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 17:43:22 +08:00
Your NameandClaude Opus 5 8225848493 系统消息:按通知自身内容决定入口和文案,不再一律显示"前往安全设置"
用户实机测试反馈:系统消息列表里每条通知底部都是同一个"前往安全设置"按钮,点进去跟通知内容毫不
相关;正文还直接显示 "group.settings.updated" 这种原始事件码。查证后两个问题性质不同:

入口按钮(前端问题,已修):原来无条件对所有通知渲染同一个按钮、固定跳 security 页。改为按通知
自己的 action 字段决定——action.topic 是 grp 开头的进对应群聊设置页,好友申请类(action.type
为 friend_request)进"新朋友"页,带 device/ip 的安全类才进安全设置页,都不匹配就不显示按钮。
注意 action.topic 不能直接当群用:好友申请通知的 action 里带的是 p2p 会话 topic
(db/mysql/tenant_adapter.go:8643),所以判断加了 grp 前缀检查。跳转目标都核对过在
tinode-web.jsx 的路由白名单里(friend-requests / group-settings),没有凭空造路由名。

正文文案(后端问题,前端只做兜底):后端多处把机器码直接当 summary 写进通知——群事件统一写
summary=event.EventType(im_group_events.go:42-44,7 种群事件还共用同一个 title"群聊动态"),
好友申请处理结果写 summary=status(im_http.go:5956 等,值就是 "accepted"/"rejected")。前端加
了一层兜底:summary 命中"纯小写字母数字点下划线"这种机器码特征时,按已知码翻译成中文,翻译不了
就退回通用文案;后端以后补上真正的文案会自动优先使用,不会被兜底覆盖。这条已记入后端问题清单。

实机验证:两条群通知正文由 "group.settings.updated" 变为"群设置有更新"、按钮变为"查看群聊"且
点击后正确进入 grpXDkK5cbXoK0 的群聊设置页;两条好友通知正文由 "accepted" 变为"对方已通过你的
好友申请",不再显示无关的安全设置按钮。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 17:32:36 +08:00
Your NameandClaude Opus 5 3bdb0bf40f 群昵称改用专门的接口读取,修好读不回已保存值的问题
上一个commit修好了"群资料"页把群名称当成个人群昵称显示的问题,但实机验证发现改完仍然显示"未设置"。
抓包确认根因:GET /groups/{topic}/members 列表接口的每一项里压根没有 group_nickname 字段(只有
acs/display_name/mentionable/role/user_id),所以任何从成员列表里读昵称的写法,不管代码逻辑对不对,
都永远拿不到值——这个文件里原有的 MyNicknameEditor 也是同样的写法,同样存在这个问题(打开编辑器时
不会回填已保存的昵称)。

真正能读到昵称的是保存用的那个路径上对应的 GET:GET /groups/{topic}/members/me/profile,返回
{nickname, version, updated_at}(server/im_http.go:4776-4782)。im-api.js 里之前只封装了 PUT,
补上了对应的 getMyGroupProfile,两处读取(群资料页预览行、昵称编辑器回填)都改成用它。

实机验证:保存"测试昵称A"后重新打开群资料页,"群昵称"正确显示"测试昵称A","群名称"独立显示
"扫码测试群"。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 17:20:36 +08:00
Your NameandClaude Sonnet 5 251c9de87c Convert im_webapp from static mock prototypes to real backend integration
Implements all 11 product chapters (auth, messaging, contacts, groups,
files/favorites, calls, workplace/reports, settings, security) against the
real /v1/im REST API and native Tinode WS protocol, replacing the hardcoded
mock data the ba8f48ee prototype commit shipped with. Adds src/lib/im-api.js
as the REST client, plus supporting fixes across messages-view.jsx and the
attachment/upload/download pipeline (wrong SDK auth headers, group topic
naming, seq/timezone edge cases) found via live end-to-end testing against
the real backend.

Backup checkpoint before reconciling with origin/dev's parallel rewrite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 11:40:53 +08:00
richjohnrambo ba8f48ee83 完成 IM Web Android 端产品页面开发 2026-08-25 18:30:40 +08:00
or-else 14e1e6b549 bug fix: typo 2026-07-04 16:01:47 +03:00
or-else 70a9ab8996 firebase updated, two bugs related to fb fixed 2026-07-04 15:42:55 +03:00
or-else 7bf3fa71d6 partially dependencies updated 2026-07-04 15:19:03 +03:00
or-else 3bfabc90f3 add missing vi translations, rearrange language imports in alpha order 2026-03-02 14:06:40 +03:00
or-else e6dfde28f6 restore topic subscription after a reconnect 2026-03-01 18:31:24 +03:00
or-else 2aa1b731ee fix for #140, fix for some UI breakage, cosmetic changes 2026-03-01 13:59:59 +03:00
or-else e097f134b7 dependencies updated 2026-01-20 13:02:19 +03:00
or-else d296bebe1c bump version to 0.25.2 2026-01-20 13:00:39 +03:00
or-else 234fb48b93 cleanup 2026-01-20 12:58:58 +03:00
or-else a54ae70746 split out a few modules, handle esc key 2026-01-19 15:41:33 +03:00
or-else 5b7565c8f8 debug logging removed 2026-01-17 12:58:36 +03:00
or-else dea781b423 adding TheCard preview 2026-01-17 12:55:45 +03:00
or-else 930f4f0656 TheCard handling in messages 2026-01-16 20:51:57 +03:00
or-else 16489432cf fix wallpaper defaults 2026-01-16 11:51:42 +03:00
or-else b2bae67ed2 version bumped, dependencies updated 2025-12-12 09:13:31 +03:00