fix(correctness): call-join reset on embed swap + per-path notify dedupe

COR-2 (useCallEmbed): useCallJoined only reset `joined` when the embed became
undefined. Answering a 2nd call swaps the embed A->B directly (embed stays
truthy), so `joined` stayed true and call B rendered as already-joined,
skipping the loading/watchdog UI. Re-seed from `embed?.joined ?? false` on
every embed identity change.

COR-4 (ClientNonUIFeatures): the notify-dedupe used one Map<roomId,eventId>
slot shared by the main-timeline and per-thread paths, so a thread reply
overwrote the room's slot and a re-fired main message (decrypt/edit re-emit,
common in E2EE) then mismatched and double-notified. Key the slot by
`${roomId}|${threadId ?? 'main'}` so each path dedupes independently.

Both verified correct by two review passes (no missed-notification or
missed-join regressions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 17:00:52 -04:00
co-authored by Claude Opus 4.8
parent 726cefb5ab
commit 1f80d1d129
2 changed files with 19 additions and 11 deletions
+5 -3
View File
@@ -149,9 +149,11 @@ export const useCallJoined = (embed?: CallEmbed): boolean => {
);
useEffect(() => {
if (!embed) {
setJoined(false);
}
// Re-seed from the (new) embed whenever it changes identity, not only when
// it becomes undefined. Answering a 2nd call swaps embed A→B directly (embed
// stays truthy), so a plain `if (!embed)` reset left `joined` stuck true and
// B rendered as already-joined, skipping the loading/watchdog UI.
setJoined(embed?.joined ?? false);
}, [embed]);
return joined;