Repository navigation
fix(unread): read timeline mentions when catching up at the bottom - #1679
Conversation
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 One unread-state regression needs correction.
baxen
left a comment
There was a problem hiding this comment.
🤖 LGTM. Catch-up only reads mentions and broadcasts once the channel type is known and isn't a DM, and per-message mention marks are kept for older clients. Known tradeoff accepted: a mention that arrives late, with a timestamp at or before the catch-up mark, is read silently.
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.
Not ready to merge at 5ca9888d. Recommended blocker, detailed in Kalvin's thread at unread.ts:583: the timestamp-based activity: mark now silently reads mentions and broadcasts that arrive after catch-up with older timestamps. That covers late delivery, ±900 s clock skew, and the reply-inflated cut. Their notifications are dropped. It is also why CI required is red (WebKit notifications.spec.mjs:357).
Validation gaps
- No journey covers the reported symptom end to end: tall mention, bottom dwell, durable read, reload. The changed tests call
catchUpdirectly. - The whole-row visibility rule (
use-reading.ts:103-113) is unchanged, so a tall mention above the bottom still can't be read individually. That was already true before this PR; it just means the fix is a policy change, not a visibility fix.
Merge criteria
- Mentions and broadcasts delivered after catch-up stay unread and still notify, with a regression test.
CI requiredgreen. - PR text: support or drop the Slack/Discord parity claim. Note the notify-while-viewing exception to notification cancellation (
notifications/messages.ts:167). Also note that broadcast replies in the viewer's own threads are now read by channel catch-up and leave thread activity, which contradicts "Thread replies still need to be read in their thread."
Product call for maintainers (not a blocker): one bottom dwell now clears every mention in the channel, including history never loaded, and removes them from Inbox Unread.
Reaching the live bottom of a channel or Solo chat now also reads the retained top-level mentions and broadcast replies through the bottom row, as Slack and Discord do. Before, each mention had to sit fully on screen for a dwell, so a mention taller than the viewport could never be read and kept its chat bold. Catch-up writes a `msg:` mark for each of them rather than extending the `activity:` timestamp, which would also read attention delivered later with an older timestamp (a retried send, a fast clock). Messages marked unread keep their intent, and mobile and older desktop clients honor the marks. Thread replies and DMs are unchanged. Co-authored-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: morgmart <[email protected]>
5ca9888 to
4ad39ea
Compare
Mark at most the newest 128 retained mentions and broadcasts per catch-up, as one observation does, so a large backlog cannot crowd older marks out of the synced read-state budget. A later catch-up marks the rest, even when `activity:` already covers its cut. Co-authored-by: Carl <c217fe6b9d958f41c3a5e030dccc7f626775a923089cb6491305eade75ea1f1b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: morgmart <[email protected]>
## Problem Bug report (mobile 0.28.0 (46)): opening a mention from the Inbox in an archived Solo chat leaves the Inbox row unread. The video shows the cause. Each unread row is a long agent answer. On a phone, the answer is taller than the screen, or it lands partly cut off after the deep-link jump. Mobile automatic reading writes a `msg:` mark only for a row that is fully visible for the 300 ms dwell. A mention row is high priority, so `activity:<channel>` does not read it. The mention can stay unread until the reader marks it read by hand. The archived state is not the cause. A widget test shows that an archived Solo chat writes read marks for short messages. Archived chats make the problem easy to see, because there is no composer and the reader only reads. ## Fix A message counts as read only when it actually showed, for the 300 ms dwell. A row counts when any of these holds: - **It fits:** the whole row is inside the reading area (unchanged). - **It fills the screen:** the row is taller than the reading area and covers all of it. This applies to the channel timeline and the thread view. - **A link opened it:** the message a deep link or Inbox tap opened counts once any part of it shows, because the jump places it at 35% and a long one lands cut off. - **It is the newest message at the live bottom:** its end is showing, in a channel, a Solo chat (replies inline) or a thread. This covers a long final answer that only ever shows its end. Reaching the bottom does **not** read mentions or broadcasts above the newest message that never came on screen. Desktop does that sweep (block/buzz-app#1679). Mobile deliberately does not. Ordinary `activity:` / `thread-activity:` catch-up is unchanged, and messages the reader marked unread stay unread. ## Tests - `reading_marks_test.dart`: the bottom does not read mentions above it; the newest message is read at the live bottom (and respects manual unread); a flat Solo timeline reads only its newest inline reply; a tall thread tail reply is read at its end; DM catch-up does not mark mentions; `isReadableRow` cases, including linked rows. - `channel_detail_page_test.dart`: an archived Solo chat and a Solo inline reply each read a tall newest mention at the bottom; a tall row away from the bottom is read once it fills the screen; tall linked messages (channel and thread) are read when the jump leaves them cut off; in a thread, the tall last reply is read at the end and an earlier tall reply is read once it fills the screen. These fail on `main`. - `just mobile-check` and the full mobile suite pass. ## Human test steps 1. Get a long agent reply that mentions you in a Solo chat, then archive the chat. 2. Open the row from the Inbox and hold still for about one second. 3. Go back. The row should now show as read, with no dot. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Blake McAnally <[email protected]> Signed-off-by: morgmart <[email protected]> Co-authored-by: BlakeBot <85bbd70443139fd5c5762e34c2e214240939eb243c37f98380f69fed3b3bb4a6@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: morgmart <[email protected]> Co-authored-by: Morgarita <6e967af659416160ab4f2fd56d74c8c6f273791ee7bfc9468a1be37c387947be@buzz.block.builderlab.xyz>
Problem
A Solo chat could stay bold even though you'd read everything in it. Reaching the bottom of a chat cleared normal messages, but a message that mentioned you only counted as read once the whole message had been fully on screen for 300 ms. A mention taller than the viewport, like a message with a portrait video plus text, could never meet that, so the chat stayed bold for good. We confirmed this from a real read-state journal: in the affected chat, every message was read except one tall mention.
Change
When you reach the live bottom of a channel or Solo chat, catch-up now also reads the mentions and "also send to channel" broadcast replies that the timeline holds through the bottom row. This matches Slack and Discord, where viewing the bottom of a channel clears its mentions.
It does this by writing a per-message
msg:mark for each one. Theactivity:timestamp mark stays ordinary-only. Extending that timestamp to mentions would also have read attention delivered later with an older timestamp, such as a retried send or a fast clock, so you'd never get notified about it.msg:marks, so a mention read this way is read there too.Side effects
Under the Slack model, these are all intended. After you catch up on a chat, its mentions stop counting as unread:
While you stay focused at the live bottom, an older-stamped mention that lands above the bottom row is read on the next 300 ms dwell, the same as ordinary rows. Once you leave, nothing reads it.
Tests
activity:stays ordinary-only. An older-timestamp mention delivered after catch-up stays unread and still counts as attention.activity:already covers the cut, marks the rest.main'sunread.tsand pass with this change.src/features/relaypasses locally.Screenshots
Not included. The only visible change is that the chat title stops being bold after you reach the bottom, which is a timing and state behavior, not a layout change, so a still image can't show it.
🤖 Generated with Claude Code