From ac157345fae038b0e1e918eea6b4ed282712d796 Mon Sep 17 00:00:00 2001 From: Jason Tudisco Date: Sun, 27 Sep 2026 10:42:47 -0600 Subject: [PATCH] fix(kez-chat): don't count messages in the open thread as unread Opening a thread marked it read, but messages arriving while it stayed open still bumped its unread badge in the conversation list. Now an inbound message marks the open thread read if the page is visible; a message that lands while backgrounded stays unread until the user returns to the tab. Co-Authored-By: Claude Opus 5.5 --- kez-chat/web/src/routes/Messages.svelte | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/kez-chat/web/src/routes/Messages.svelte b/kez-chat/web/src/routes/Messages.svelte index 70c8086..2f5316e 100644 --- a/kez-chat/web/src/routes/Messages.svelte +++ b/kez-chat/web/src/routes/Messages.svelte @@ -572,7 +572,7 @@ // new message lands. The service is already running (it started on // session unlock in store.svelte.ts) regardless of which route the // user was on. - unsubscribe = inboxService.onMessage(() => void refresh()); + unsubscribe = inboxService.onMessage(() => void onInboxMessage()); // Landing here = the user has seen new messages; reset the badge. inboxService.markAllRead(); // Relay state poll. @@ -580,6 +580,7 @@ relayStatuses = getRelayStatuses(); }, 2_000); document.addEventListener("click", onDocumentClick); + document.addEventListener("visibilitychange", onVisibilityChange); void evaluatePushNudge(); }); @@ -587,8 +588,27 @@ unsubscribe?.(); if (relayPollTimer) clearInterval(relayPollTimer); document.removeEventListener("click", onDocumentClick); + document.removeEventListener("visibilitychange", onVisibilityChange); }); + /** A message landing in the thread the user is looking at is read on + * arrival — don't badge it. Only while the page is visible: a + * message that arrives while the app is backgrounded stays unread + * until they come back (onVisibilityChange). */ + async function onInboxMessage() { + const pk = activePrimary; + if (pk && document.visibilityState === "visible") { + await markConversationRead(pk); + } + await refresh(); + } + + function onVisibilityChange() { + const pk = activePrimary; + if (document.visibilityState !== "visible" || !pk) return; + void markConversationRead(pk).then(refresh); + } + async function refresh() { conversations = await listConversations(); // Kick off peer-profile fetches for any conversation whose peer