Skip to content

fix(mobile): live-update open threads instead of freezing on the history fetch - #4707

Open
CryptoJones wants to merge 1 commit into
block:mainfrom
CryptoJones:fix/mobile-thread-live-replies
Open

fix(mobile): live-update open threads instead of freezing on the history fetch#4707
CryptoJones wants to merge 1 commit into
block:mainfrom
CryptoJones:fix/mobile-thread-live-replies

Conversation

@CryptoJones

Copy link
Copy Markdown

An open thread never updated. threadRepliesProvider was a one-shot paginated query that resolved once and cached, so replies arriving afterwards — including the viewer's own, and agent replies — were never in that list. The activity feed looked instant while the thread it linked to sat frozen.

What changed

  • ThreadLiveRepliesNotifier — a limit: 0 subscription on the same filter (live only; history stays the paginated fetch's job), mirroring ChannelsNotifier._subscribeLive.
  • Subscribe before snapshotting history. An event published after /query returns but before the REQ registers is in neither result and is lost permanently — the same class of bug this provider exists to fix. Gated on a Completer that always completes, success or failure, so a thread that cannot subscribe still loads its backlog rather than hanging.
  • Handle relay CLOSED. RelaySessionNotifier drops the subscription from _liveSubscriptions, so it is not replayed on reconnect — but the notifier would still hold a non-null _unsubscribe and report healthy while silently receiving nothing. A generation counter stops a stale callback tearing down a newer subscription.
  • Ownership of a pending local reply is released only by the authoritative paginated fetch, never by a live echo. The subscription can deliver an event the relay will not return on a later query; confirming on the echo hands the row back, and if the refetch then fails nothing is left holding the message on screen — the viewer's own reply disappears. Costs one redundant entry that _mergeReplies de-duplicates by id.

Auto-dispose is load-bearing on both new providers and is commented as such: a keep-alive family would leak one live subscription per thread opened for the whole app session, and the relay caps subscriptions.

Tests

Two new cases — a live reply arriving after the history fetch, and ownership held across a failed refetch.

Full mobile suite 906 pass. The single remaining failure (ChannelDetailPage keeps follow mode off while a tall newest message stays visible) reproduces on a clean tree and is unrelated to this change. flutter analyze clean, dart format clean.

Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/

…ory fetch

An open thread never updated. `threadRepliesProvider` was a one-shot paginated
query that resolved once and cached, so replies arriving afterwards - including
the viewer's own, and agent replies - were never in that list. The activity feed
looked instant while the thread it linked to sat frozen.

- Add `ThreadLiveRepliesNotifier`, a `limit: 0` subscription on the same filter
  (live only; history stays the paginated fetch's job), mirroring
  `ChannelsNotifier._subscribeLive`.
- Establish the subscription BEFORE snapshotting history. An event published
  after `/query` returns but before the REQ registers is in neither result and
  is lost permanently - the same class of bug this provider exists to fix.
  Gated on a `Completer` that always completes, success or failure, so a thread
  that cannot subscribe still loads its backlog rather than hanging.
- Handle relay CLOSED. `RelaySessionNotifier` drops the subscription from
  `_liveSubscriptions`, so it is NOT replayed on reconnect, but the notifier
  would still hold a non-null `_unsubscribe` and report healthy while silently
  receiving nothing. A generation counter stops a stale callback from tearing
  down a newer subscription.
- Ownership of a pending local reply is released ONLY by the authoritative
  paginated fetch, never by a live echo. The subscription can deliver an event
  the relay will not return on a later query; confirming on the echo hands the
  row back, and if the refetch then fails nothing is left holding the message on
  screen - the viewer's own reply disappears. Costs one redundant entry that
  `_mergeReplies` de-duplicates by id.

Auto-dispose is load-bearing on both new providers and is commented as such: a
keep-alive family would leak one live subscription per thread opened for the
whole app session, and the relay caps subscriptions.

Tests: two new cases - a live reply arriving after the history fetch, and
ownership held across a failed refetch. Full mobile suite 906 pass; the single
remaining failure (`ChannelDetailPage keeps follow mode off while a tall newest
message stays visible`) reproduces on a clean tree and is unrelated.
`flutter analyze` clean, `dart format` clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xHDhHGPwDTtBCGb8Sjn6Z
Signed-off-by: Aaron K. Clark (CryptoJones) <cryptojones@owasp.org>
@CryptoJones
CryptoJones requested a review from a team as a code owner August 4, 2026 11:27
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