Live thread-summary push: badge counts update on reply ingest#1521
Merged
Conversation
#1490 and #1487 each landed a few lines in ChannelScreen.tsx and pushed it to 1006, tripping the 1000-line file-size check and turning main's Desktop CI red. Recover the headroom by tightening prose comments (no semantic content dropped) and compacting one ternary the formatter accepts on a single line. No code behavior changes; 999 lines by the checker's count. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Thread badge counts in the channel timeline came only from relay-signed kind:39005 overlays attached to channel-window page responses — a snapshot frozen at fetch time. Live replies route to the thread-replies cache and never become timeline rows, so an active thread's count never moved until something refetched the head window; rows that reached the screen outside a page (live overlay, refetch reconciliation) had no badge at all. Relay: after a reply insert commits (and after threaded deletes decrement counters), sign a fresh 39005 from thread_metadata — same tags/content contract as the page overlay in api/bridge.rs — and fan it out via the existing Redis + local paths. Never stored: pages recompute summaries on every fetch, so a persisted copy would only add staleness. Counts are re-read post-commit, not incremented, so pushes are exact under concurrency and deletes count down. Desktop: the channel live subscription adds kind 39005; the window store gains a liveSummaries overlay (newest created_at wins per root, cleared by the authoritative head refetch) merged over page summaries in channelWindowThreadSummaries. The existing local reply merge in useLiveChannelUpdates is kept unchanged: without it, a new client against an old relay (no 39005 push) would freeze counts entirely, and badge rendering already resolves local-vs-relay via max() in mergeThreadSummaries. Validation: window-store + response-parser unit tests; ignored e2e (test_reply_ingest_pushes_live_thread_summary) verified against a local relay — reply pushes reply_count=1, deleting it pushes 0; cargo test -p buzz-relay (463 passed); desktop typecheck/check/test all green; smoke suites covering thread badges (messaging:561, thread-unread, thread-reply-anchor-roleplay) pass against the built bundle. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
c335ea4 to
98d5204
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Two badge symptoms reported on staging (thread: buzz-relay-optimization), one root cause:
Mechanism (full trace in
RESEARCH/THREAD_COUNT_BADGE_MECHANISM_2026_07_05.mdon my workspace, summarized in the PR thread): badge counts come exclusively from relay-signedkind:39005overlays attached to channel-window page responses — a snapshot frozen at fetch time.thread_metadatais already updated transactionally on every reply insert, but the relay never told anyone. Live replies deliberately skip the main timeline store, so the client can't compensate locally.Fix: the relay pushes what it already knows
Relay (
side_effects.rs, call sites iningest.rs+ delete handlers):39005fromthread_metadataand broadcast it through the existing Redis + local fan-out paths (same access gating as every other event).api/bridge.rs: one contract, two delivery doors.thread_metadataon every fetch; a persisted copy would only add staleness. 39005 is relay-signed, so clients can't spoof counts.Desktop:
subscribeToChannelLive) adds kind 39005 — scoped to this one subscription, notCHANNEL_EVENT_KINDS, so unread tracking and timeline caches never see summary overlays.liveSummariesoverlay: newestcreated_atwins per root; cleared by the authoritative head refetch; survives scrollback pages (a racing push can be fresher than a just-fetched page summary). Merged over page summaries inchannelWindowThreadSummaries— which also fixes the missing-badge symptom for rows that never got a page summary.useLiveChannelUpdatesis intentionally kept: badge rendering resolves local-vs-relay counts viamax()inmergeThreadSummaries, and without the local path a new client against an old relay (no 39005 push) would freeze counts entirely. (An earlier revision of this branch removed it and promptly broke the thread-badge smoke suites — CI caught it; reverted.)Backward compatible both directions: old clients ignore the pushed 39005 (parameterized-replaceable, never stored); new clients against an old relay degrade to today's fetch-time snapshot behavior.
Mobile (post-#1518 channel windows) has the same gap — follow-up, not this PR.
Validation
test_reply_ingest_pushes_live_thread_summary, run against a real local relay: subscribe with the desktop-shaped filter, reply → pushed summary withreply_count: 1, delete the reply → pushed summary withreply_count: 0. Both received live, no window refetch.cargo test -p buzz-relay: 463 passed.cargo clippy --tests: clean.typecheck/check/test(1623): all green; thread-badge smoke suites (messaging:561,thread-unread,thread-reply-anchor-roleplay,human-edit-agent-content,spoiler:145) pass locally against the built bundle.Also cherry-picks the ChannelScreen size-gate trim from #1520 (main's Desktop CI is currently red without it; drops out on rebase if #1520 merges first).