mirror of
https://github.com/block/buzz.git
synced 2026-10-30 05:51:46 +00:00
### Context Every app resume and relay reconnect tore down `ReadStateManager` and built a new one. `ReadStateNotifier.build()` watched the whole `relaySessionProvider` state, so each status change (disconnected, connecting, connected) rebuilt the notifier. On an iPhone 12 mini each resume re-ran the full read-state fetch and decrypt (about 19 to 24 decrypts, 60 to 95 ms). The rebuild also cleared `_forcedUnreadContexts`, so a message or channel the user marked unread lost that flag on any connection change. The watch dates from #422, when the manager was built with `remoteEnabled` tied to the connection status. #802 made `remoteEnabled` constant and added the `ref.listen` that calls `reinitializeRemote()` on connect, but left the watch behind. ### Change - Watch `relaySessionProvider.notifier` instead of its state. Config, nsec, community, pubkey, crypto and a replaced session still rebuild. Status changes don't. - Reconnects re-sync through the existing listener (`reinitializeRemote()`: re-fetch, then re-subscribe, preserving the history and live overlap). - Re-syncs are serialized. One runs at a time after any pending `initialize()`, and reconnects that arrive while one waits share it. Overlapping passes could otherwise each install a live subscription and leak one. - A connect that lands while the first `initialize()` is running (the cold-start connect) skips the re-sync when that initialize fetched and subscribed successfully. Without this, cold start did the read-state work twice. ### Validation - Provider journey: disconnected, connecting and reconnecting cause no refetch, no re-subscribe and no readiness drop, and the forced-unread flag and read mark survive. Connected runs exactly one refetch and one live-subscription swap. A second reconnect behaves the same. Account and community switches still rebuild and clear forced flags. - Manager tests: a connect during a successful `initialize()` does not sync again; one during a failed `initialize()` does, and leaves one live subscription; three reconnects during a running re-sync produce one more shared pass and one live subscription. - Falsified: restoring the old watch fails the reconnect journey (`isReady` false, flag lost). The previous version without serialized re-syncs fails the overlap test (5 passes instead of 3). - `just mobile-check` and `just mobile-test` pass. iPhone 12 mini (Profile builds of base and this branch, real community over the production relay, instrumented manager lifecycle and live-subscription counts): | | base | this branch | | --- | --- | --- | | Resume after 3 s, 10 s (x3), 60 s background | 3 new managers and 3 initializes per resume | no new manager, 1 re-sync | | Live read-state subscriptions after resume | 1 | 1 | | Channel marked unread, then 10 s / 60 s background | flag lost after the first resume | still unread after both | | Read-state work per resume (median) | ~24 decrypts, ~176 ms main isolate | ~18 decrypts, ~125 ms | | Unread badges | same bold set | same bold set | The first device run also found the doubled cold-start sync, fixed above. A re-check on this head: | Cold launch | base | first version | this head | | --- | --- | --- | --- | | Managers built | 3 | 1 | 1 | | Read-state syncs | 2 initializes | 1 initialize + 1 re-sync | 1 initialize | | Decrypts | 19 | 30 | 15 | | Main-isolate ms in read-state code | 124 | 185 to 197 | 94 to 98 | Four resumes on this head: no new manager, 1 re-sync and 1 live subscription each, 17 to 20 decrypts. A foreground socket drop was not exercised on the device; every resume there was a full disconnect and reconnect. buzz-review-completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>