Your Name and Claude 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 Name and Claude 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 Name and Claude Opus 5
211d93a0e4
修复青少年模式独立密码校验报错显示英文原文的问题(第10-11章复测发现)
...
关闭/开启青少年模式都要先过 POST /settings/independent-password/verify 这一关,
PIN 输错、连续错误触发限流这两种失败,后端 reason 分别是 CODE_INVALID/
TOO_MANY_ATTEMPTS——REASON_MESSAGES 表里没收录这两个 key,直接透传英文原文
message("code invalid"/"too many attempts"),且限流的具体等待时长(details.
retry_after_seconds)完全没有展示给用户。
REASON_MESSAGES 原来只支持字符串值,限流提示需要按 retry_after_seconds 动态拼
文案,所以顺带把 imRequest 的取值逻辑改成支持函数值(传入 payload.details)。
已用真实账号端到端验证:对 https://api.118im.com 连续发送错误 PIN,前3次真实
返回 401 CODE_INVALID,UI 正确显示"独立密码不正确";第4次起后端真实触发限流
返回 429 TOO_MANY_ATTEMPTS(details.retry_after_seconds 从300开始递减),UI
正确显示"连续输错次数过多,请 240 秒后重试",秒数随后端返回值动态变化。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-09-04 14:46:08 +08:00
Your Name and Claude 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 Name and Claude 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 Name and Claude 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 Name and Claude 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