Skip to content

fix(web): a deleted message never counts as unread#565

Open
gbasin wants to merge 1 commit into
masterfrom
fix/deleted-never-counts-unread
Open

fix(web): a deleted message never counts as unread#565
gbasin wants to merge 1 commit into
masterfrom
fix/deleted-never-counts-unread

Conversation

@gbasin

@gbasin gbasin commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Last of the #562 follow-ups. Closes the client/server disagreement about what counts as unread.

The divergence

#562 made the server exclude deleted messages from latest_event_id. The client's unreadCount only excluded messages that render nothing (deleted && replyCount === 0).

So a deleted message that still has replies counted as unread on the client but not the server: the pill says 1 new, the sidebar says nothing, and the "new" thing is the words "Message deleted".

The fix

countsAsUnread — deletion always removes the unread content. Replies under the tombstone announce themselves through their own broadcast events.

Renderability and unread are now separate questions, because they have different answers:

isRenderableMessage countsAsUnread
deleted, no replies ✗ paints nothing
deleted, has replies ✓ tombstone hosts the thread ✗ nothing to catch up on
live message

A deleted-with-replies message still paints a row, so it's still the newest thing a reader can scroll to — but it's never something to catch up on. Conflating those two is what stranded #test-channel unread forever.

countsAsUnread strictly subsumes isRenderableMessage's deleted case, so unreadCount needs only the one predicate and the #562 guarantee still holds.

firstUnreadId uses it too: if only a tombstone lies past the watermark it returns null and takes the existing bottom-landing path; if real unread follows, the divider skips the tombstone and targets that. No change to mark-read or the read cursor — reading is still only marked at the bottom.

Verification

The new test fails without the fix — the pill renders 1 new for a deleted-with-replies message.

Local: pnpm lint · typecheck (7/7) · web 776 · shared 452 · e2e 119/119, including the read-position and history-read-cursor specs that guard against swallowing unread.

🤖 Generated with Claude Code

The server stopped counting deleted messages toward a channel's unread
counter, but the timeline's "N new" pill still counted a deleted message that
kept its tombstone to host replies. The pill could say "1 new" while the
sidebar said nothing, and the new thing was the words "Message deleted".

Deletion takes the content away, so there is nothing left to have missed. Any
replies still under the tombstone announce themselves through their own
broadcast events.

Renderability and unread are now separate questions, because they have
different answers: a deleted message with replies still paints a row, so it is
still the newest thing a reader can scroll to, but it is never something to
catch up on. Conflating the two is what stranded a channel unread forever.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant