Read-fence SMS/AI mark-reads, fold sessionType into sameSessions

Fixkamo-internal
Shipped
August 26, 2026 at 2:23 AM UTC
Author
kamo
Commit
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).

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing