fix(mobile): refetch thread replies when the relay session reconnects - #4831
Open
danielz1z wants to merge 1 commit into
Open
fix(mobile): refetch thread replies when the relay session reconnects#4831danielz1z wants to merge 1 commit into
danielz1z wants to merge 1 commit into
Conversation
The thread replies query ran exactly once per thread and watched only the session notifier, which never changes identity. A reply arriving while the connection was down was therefore unreachable forever: the replayed live subscription starts at the reconnect time, the channel window backfill carries thread summaries rather than reply bodies, and the cached one-shot query never re-ran. The thread stayed stale — and an Activity tap targeting the missed reply landed on the wrong message — until a full app restart. Listen for the disconnected-to-connected edge and invalidate, the same idiom the read-state provider uses: exactly one silent refresh per successful reconnect, nothing on the disconnect edge, and the previous replies stay rendered while the refresh runs. Both query providers are now auto-disposed so a reopened thread always starts from a fresh authoritative query; the local optimistic-reply overlay stays alive across route changes until relay confirmation. Verified against a live device capture: session reconnected and the channel window refetched, while a reply posted 14s into a 350s background window never appeared in the open thread. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: daniel <daniel@blobs.id> Signed-off-by: daniel <daniel@blobs.id>
wesbillman
added a commit
that referenced
this pull request
Aug 24, 2026
## Summary - refetch mounted mobile thread replies after relay reconnect, preserving the previous reply list during recovery - auto-dispose route-scoped relay reply caches so reopening a thread queries current relay state - invalidate live replies through both the channel-window and legacy websocket-history paths - preserve optimistic-reply confirmation when the route closes before its deferred cleanup - stabilize rapid same-second messages using desktop's existing split contract: channel timelines render `(created_at ASC, id DESC)` while threads render `(created_at ASC, id ASC)` - retain late live rows after a channel window is exhausted instead of dropping same-second tail messages Closes #4404. Closes #4830. Closes #6204. ## Context The broad all-channel/all-DM stale-session defect reported in #4402 is already addressed on current `main` by #4372 and #3053. Two distinct mobile gaps remained: 1. `threadRepliesProvider` was a process-lifetime one-shot query, so replies missed while the socket was stale remained absent after reconnect or after closing and reopening the thread. 2. Mobile had inconsistent timestamp-only and event-id ordering across channel producers. Rapid messages routinely share Nostr's one-second timestamp, so later hydration/live reconciliation could reshuffle them. Desktop deliberately has two render contracts: channel windows reverse the relay's composite order to `(created_at ASC, id DESC)`, while thread replies use `(created_at ASC, id ASC)`. This consolidates the current-main portions of #4831 and #3243 rather than reviving stale overlapping branches. ## Validation Exact pushed head: `be92d9542c6cd1342733bdc5e8359664b511ce02` - focused channel-provider/window/thread suites: 48/48 passed - incident regression: a mounted thread misses a reply while disconnected, reconnects, and renders the recovered reply - route regression: closing and reopening a thread performs a fresh authoritative query - websocket fallback regression: live reply invalidates the mounted thread even without the channel-window path - disposal regression: optimistic confirmation survives provider disposal between rebuild and deferred cleanup - ordering regressions: channel window/live, websocket fallback, optimistic sends, deep links, both pagination paths, and thread merges preserve their desktop-compatible same-second order - boundary regression: exhausted windows admit late same-second live rows without weakening open-page cursor boundaries - independent adversarial review: no production blocker; source contract verified across all producers and relay cursor semantics unchanged - pre-push Mobile lane passed at exact head, including analysis, file-size/branch checks, and full Flutter suite: 1,675/1,675 passed - `git diff --check` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
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.
On mobile a thread's replies are fetched once when the thread is opened and never again — the query watches only the session notifier, which never changes identity, so it never re-runs for the lifetime of the app. A reply that arrives while the phone is backgrounded never shows up in that thread; only a full restart brings it in. On a device capture, a reply posted 14 seconds into a 350-second background window never appeared until restart.
The fix: refetch thread replies once on the disconnected-to-connected edge — the same idiom the read-state provider already uses — keeping the previously rendered replies on screen while the refresh runs. The thread-reply providers also become autoDispose so a reopened thread starts from a fresh query instead of a stale one-shot result; without that, every thread ever opened would stay resident and refetch on every reconnect. The optimistic just-sent-reply overlay stays alive until the relay confirms it.
About 30 production lines in one provider file; the rest is regression tests: refresh fires exactly once on the reconnect edge, none on the disconnect edge, replies stay rendered during the refresh, reopened threads query fresh, and the optimistic overlay survives until confirmation.
Fixes #4830.