fix(llc): handle per-channel notification.mark_read events again - #2890
Conversation
`ChannelClientState._listenReadEvents` subscribed to both `message.read` and `notification.mark_read` until the delivery-receipts refactor (#2429) split them and dropped the latter. `message.read` only reaches watchers, so from 9.20.0 on `markRead()` on a non-watched channel left `unreadCount` permanently stale — the unread pill and divider could not be cleared by any user action. Re-adds the event type to the existing listener, which since 9.19.0 also guards against thread-scoped reads and preserves delivery fields. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe channel read-event listener now handles ChangesNotification mark-read handling
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to This is a localized fix that restores per-channel read notifications and is covered by targeted tests; no actionable merge-blocking risk remains after normal checks and review. Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…rk-read-unread-count
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2890 +/- ##
=======================================
Coverage 73.96% 73.96%
=======================================
Files 435 435
Lines 28149 28149
=======================================
Hits 20821 20821
Misses 7328 7328 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Linear: FLU-693
Github Issue: #2886
CLA
Description of the pull request
ChannelClientState._listenReadEventssubscribed to bothmessage.readandnotification.mark_readuntil the delivery-receipts refactor (#2429,3b5e410bb) split them into separate listeners and did not carrynotification.mark_readover. The changelog documents the rest of that refactor but not this removal, so it looks unintentional.That distinction is load-bearing:
message.readis a channel event — delivered to watchers only.notification.mark_readis delivered on the reading user's own connection — so it reached the channel whether watched or not.Channel.markRead()writes no local state itself, and channels are easily non-watched (watchis silently downgraded when there is no connection id). So from 9.20.0 on, marking a non-watched channel as read succeeded server-side whileunreadCountstayed stale forever — the unread pill and divider survived the dismiss X,markReadWhenAtTheBottom, and every reopen, with no user action able to clear them.The fix re-adds the event type to the existing listener:
Not a literal revert. The handler has improved since 9.19.0 — it now skips thread-scoped reads and preserves
lastDeliveredAt/lastDeliveredMessageId— and the merged listener keeps both. Nothing from #2429 is undone.Verification against the backend event definition
Rather than infer the payload, I checked
lib/chat/event/notification_mark_read.go. The two events are the same shape for every field the handler reads:message.readnotification.mark_readevent.threadthread,omitemptythread,omitemptyevent.userevent.createdAtevent.lastReadMessageIdWhich settles three things:
unreadMessages: 0is correct, not a compromise.unread_messagesexists on exactly one event in the backend —notification.mark_unread. Onnotification.mark_read,unread_count/total_unread_countare account-wide totals, andunread_countis deprecated. Neither event can report this channel's count, so 0 is the only available value.last_read_at, solastRead: event.createdAtis right.NewNotificationMarkThreadReadEventsetsThread, so thread mark-reads do arrive on this type and would otherwise clobber the channel-levelRead.Also confirmed:
read_state.goreturns early when!channel.Config.ReadEvents, so no event is emitted for read-events-disabled channel types — channels using local unread counts are unaffected. AndmarkAllRead()posts{}, which routes toMarkAllRead→ an event with nocid, exactly what the client-level handler'sif (event.cid != null) return;expects. The channel-level / client-level split is correct.Testing
Four tests in
channel_test.dart. I verified they actually catch the regression by reverting the one-line fix and re-running — three go red:Expected: <0> Actual: <10>Expected: <0> Actual: <10>No matching callsFull
stream_chatsuite: 1624 passing.dart analyze --fatal-infosanddart formatclean.Out of scope
totalUnreadCount/unreadChannelsgoing stale too, and the reporter shipped a client-level listener for it. That part isn't needed —ClientState.subscribeToEventsapplies both from any event carrying them, andset currentUserpumps them through_computeUnreadCounts.sync()replay re-incrementingunreadCountvia the non-idempotentaddNewMessage) is real and related — through 9.19.0 this reset washed that drift away on the next mark-read — but it's a separate bug.channel.darthave zero test references anywhere in the suite, so any could be silently dropped the same way with CI staying green —channel.truncated,notification.channel_truncated,channel.updated,member.added,member.removed,user.banned,user.unbanned,reaction.updated,poll.closed,poll.updated. Tracked in FLU-693's notes.Screenshots / Videos
Not applicable — no visual change beyond the unread pill and divider now clearing, which is the bug itself.
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests