第五轮深度测试:管理员撤回、所有人@提醒、失效附件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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
4a30cfa6b3
commit
d42591284f
@@ -31,6 +31,7 @@ CALL_ENDED_SOUND.loop = true;
|
||||
const DIALING_SOUND = new Audio('audio/dialing.m4a');
|
||||
const VIDEO_MUTED_EVENT = 'video:muted';
|
||||
const VIDEO_UNMUTED_EVENT = 'video:unmuted';
|
||||
const UNMOUNTED_DURING_OFFER = Symbol('unmounted-during-offer');
|
||||
function formatElapsedTime(totalSeconds) {
|
||||
const h = Math.floor(totalSeconds / 3600);
|
||||
const m = Math.floor(totalSeconds % 3600 / 60);
|
||||
@@ -498,6 +499,10 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
||||
pc.setRemoteDescription(desc).then(_ => {
|
||||
return navigator.mediaDevices.getUserMedia(this.localStreamConstraints);
|
||||
}).then(stream => {
|
||||
if (this._unmounted) {
|
||||
stream.getTracks().forEach(t => t.stop());
|
||||
return Promise.reject(UNMOUNTED_DURING_OFFER);
|
||||
}
|
||||
let dummyVideo;
|
||||
if (!this.localStreamConstraints.video) {
|
||||
dummyVideo = this.emptyVideoTrack();
|
||||
@@ -525,7 +530,11 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
||||
this.setState({
|
||||
callInitialSetupComplete: true
|
||||
}, _ => this.drainRemoteIceCandidatesCache());
|
||||
}).catch(this.handleGetUserMediaError);
|
||||
}).catch(err => {
|
||||
if (err !== UNMOUNTED_DURING_OFFER) {
|
||||
this.handleGetUserMediaError(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
handleRemoteHangup() {
|
||||
if (!this.state.waitingForPeer) {
|
||||
@@ -685,10 +694,17 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
||||
navigator.mediaDevices.enumerateDevices().then(devices => {
|
||||
const outputs = devices.filter(d => d.kind == 'audiooutput');
|
||||
const match = nextOn ? outputs.find(d => /speaker/i.test(d.label)) : outputs.find(d => /earpiece|receiver/i.test(d.label));
|
||||
if (this._unmounted || !this.remoteRef.current) {
|
||||
return null;
|
||||
}
|
||||
return this.remoteRef.current.setSinkId(match ? match.deviceId : '');
|
||||
}).then(_ => this.setState({
|
||||
speakerOn: nextOn
|
||||
})).catch(err => console.error('Failed to switch audio output device', err));
|
||||
}).then(_ => {
|
||||
if (!this._unmounted) {
|
||||
this.setState({
|
||||
speakerOn: nextOn
|
||||
});
|
||||
}
|
||||
}).catch(err => console.error('Failed to switch audio output device', err));
|
||||
}
|
||||
render() {
|
||||
const audioTracks = this.state.localStream && this.state.localStream.getAudioTracks();
|
||||
|
||||
Reference in New Issue
Block a user