- Ya
- 26 Agosti 2026, 02:23 UTC
- Mwandishi
- kamo
- Ahadi ya
- 486a456
Three review findings against Task 21, all plan-mandated: - markSmsRead/markAiRead had no read fence: a refresh or an inbound push resolving between the optimistic local clear and the server recording the read could carry a still-pre-read count and resurrect a just-cleared badge. VOIPService's unread broadcast sends a full server-computed snapshot on every change, so this was reachable, not theoretical. Fixed by extracting the fencing rule into a pure, tested applyFlatUnreadSnapshot (mirroring unreadState.ts's own pendingReads fence for a flat id -> count map), applied in refreshSms, refreshAi, and the SMS WS handler, guarded by pendingSmsReadsRef / pendingAiReadsRef held exactly the way beginMarkRead/endMarkRead hold markAsRead's fence. - sameSessions compared only unreadCount and senderId, so a session whose sessionType changed between polls (an old MediaService pod's frame, then a new one's) with count and sender unchanged in between was treated as unchanged — the stale chat classification would outlive the deploy that introduced sessionType, surviving until that session's count next moved. Now compares sessionType too. - Added regression coverage for both: a raw payload driven through applyUnreadPayload to prove sessionType actually survives parsing (the original gap let this ship past a clean typecheck and a green suite once already), and a **************** case that fails without the sameSessions fix (verified RED before restoring the fix).