Commit Graph
11 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 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 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 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 4337c45674 修复第9章工作台三处问题:日报列表错误吞没/月报重新编辑未接线/信箱附件超限(复测发现)
1. DailyReportList 的 load() 把请求失败(网络错误、403等)整个 catch 吞掉,只留一个
   空/旧列表,跟"今天真的没有日报"是同一种"暂无日报"空态展示——用户没法分辨到底是
   没数据还是加载失败了。补上 loadError + onToast,参照 DailyReportDetail/
   MonthlyReportList 已有的同款处理方式。

2. reopenMonthlyReport 早就在 im-api.js 里写好但零调用点:MonthlyReportDetail 一直
   没有"重新编辑"入口,已提交的月报只能干看着改不了。核对 im_be 源码发现原函数还多
   传了一个 reason 字段——serveIMMonthlyReportReopen 用 DisallowUnknownFields 只认
   {version},真调用起来会先 400。同时确认这是纯自助操作(SQL 的 UPDATE 条件里
   author_id=调用者自己),不存在"管理员帮别人退回"这回事,去掉多余字段后在
   MonthlyReportDetail 给本人的已提交月报加上这个入口。

   接上之后在真实账号上跑通整条链路时,又炸出一个新竞态:reopen 成功后跳转编辑器,
   编辑器用同一个月份的模块级缓存做"再次进入不清空"的初始渲染,但缓存里存的是
   reopen 之前的旧 version——首次自动保存草稿用这个旧 version 发出去,服务端真实
   返回 409,界面弹出"请检查网络后重新编辑"(其实跟网络毫不相干)。加了个 hydrated
   门槛:只有当组件自己这次挂载真正拿到新鲜数据确认过之后,才允许自动保存的
   effect 生效;没有旧缓存要甄别的场景(绝大多数情况)行为完全不变。修复前后各跑
   了一次完整的"提交→查看详情→重新编辑",前者复现了 409,后者干净无报错。

3. 总经理信箱的附件上传一直没有数量上限——真实 config.attachment_policy.max_files
   (联调确认默认租户是5,不是后端Go默认结构体里那个从未在这个环境出现过的
   max_count)从来没被读取过,界面上能一直点"上传文件"加到任意多个。补上跟
   意见反馈页一致的"数量/上限"展示与到达上限后禁用。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:25:05 +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 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 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 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