第五轮深度测试:管理员撤回、所有人@提醒、失效附件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
@@ -16,6 +16,8 @@ const DIALING_SOUND = new Audio('audio/dialing.m4a');
|
||||
|
||||
const VIDEO_MUTED_EVENT = 'video:muted';
|
||||
const VIDEO_UNMUTED_EVENT = 'video:unmuted';
|
||||
// See handleVideoOfferMsg's use of this below.
|
||||
const UNMOUNTED_DURING_OFFER = Symbol('unmounted-during-offer');
|
||||
|
||||
function formatElapsedTime(totalSeconds) {
|
||||
const h = Math.floor(totalSeconds / 3600);
|
||||
@@ -571,6 +573,19 @@ class CallPanel extends React.PureComponent {
|
||||
return navigator.mediaDevices.getUserMedia(this.localStreamConstraints);
|
||||
})
|
||||
.then(stream => {
|
||||
// Same permission-prompt-outlives-the-call race as start()/unmuteVideo()/
|
||||
// handleSwitchCameraClick() -- this is the callee-side counterpart (answering an offer),
|
||||
// a separate getUserMedia call this round's fix missed: start() takes an early-return
|
||||
// branch for an already-in-progress call and never reaches its own getUserMedia here.
|
||||
// A bare `return` wouldn't be enough on its own to bail out of the rest of *this* chain
|
||||
// (createAnswer/setLocalDescription/onSendAnswer below still run on the next tick with
|
||||
// `undefined`) -- rejecting with the sentinel skips straight past all of those (none of
|
||||
// them pass an onRejected handler to .then()) to the .catch() at the end, which checks for
|
||||
// it explicitly so this cancellation isn't reported as a real getUserMedia failure.
|
||||
if (this._unmounted) {
|
||||
stream.getTracks().forEach(t => t.stop());
|
||||
return Promise.reject(UNMOUNTED_DURING_OFFER);
|
||||
}
|
||||
let dummyVideo;
|
||||
if (!this.localStreamConstraints.video) {
|
||||
// Starting an audio-only call. Create an empty video track so
|
||||
@@ -602,7 +617,7 @@ class CallPanel extends React.PureComponent {
|
||||
this.props.onSendAnswer(this.props.topic, this.props.seq, pc.localDescription.toJSON());
|
||||
this.setState({ callInitialSetupComplete: true }, _ => this.drainRemoteIceCandidatesCache());
|
||||
})
|
||||
.catch(this.handleGetUserMediaError);
|
||||
.catch(err => { if (err !== UNMOUNTED_DURING_OFFER) { this.handleGetUserMediaError(err); } });
|
||||
}
|
||||
|
||||
// Call disconnected by remote.
|
||||
@@ -781,9 +796,15 @@ class CallPanel extends React.PureComponent {
|
||||
const match = nextOn ?
|
||||
outputs.find(d => /speaker/i.test(d.label)) :
|
||||
outputs.find(d => /earpiece|receiver/i.test(d.label));
|
||||
// Same unmount race as the gUM-based methods -- enumerateDevices can still be pending
|
||||
// after the call ends. Currently silent either way (the .catch below only logs), so this
|
||||
// just avoids a pointless TypeError in the console rather than fixing a visible symptom.
|
||||
if (this._unmounted || !this.remoteRef.current) {
|
||||
return null;
|
||||
}
|
||||
return this.remoteRef.current.setSinkId(match ? match.deviceId : '');
|
||||
})
|
||||
.then(_ => this.setState({speakerOn: nextOn}))
|
||||
.then(_ => { if (!this._unmounted) { this.setState({speakerOn: nextOn}); } })
|
||||
.catch(err => console.error('Failed to switch audio output device', err));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user