Skip to content

Heal the inbox after a missed broadcast (chats--refresh-inbox)#2

Merged
rameerez merged 2 commits into
mainfrom
feat/inbox-missed-broadcast-recovery
Jul 9, 2026
Merged

Heal the inbox after a missed broadcast (chats--refresh-inbox)#2
rameerez merged 2 commits into
mainfrom
feat/inbox-missed-broadcast-recovery

Conversation

@rameerez

@rameerez rameerez commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Gap

The thread got stale-catch-up in 0.1.1 — the reliability pattern docs/campfire_review.md calls "the single biggest ... in [Campfire's] codebase," because mobile WebViews reap sockets constantly. The inbox never got the same treatment.

The inbox updates via Turbo 8 page-refresh broadcasts (Chats::Broadcasts.refresh_inbox_ofbroadcast_refresh_later_to). Action Cable has no replay: a refresh broadcast sent while this client's socket was down (backgrounded tab/app, network blip, laptop asleep) is lost, and the inbox silently sits at its last render until the user happens to navigate. Same failure mode the thread already guards against — just on the other list.

(Found while auditing a production host app whose inbox is a native tab root with pull-to-refresh disabled, so there was no user-reachable recovery at all.)

Fix

A tiny chats--refresh-inbox controller that re-runs the same page refresh on the two moments we might have missed one:

  • cable reconnect — reusing the thread's channel-free detection (observe the connected attribute turbo-rails toggles on <turbo-cable-stream-source>; no new Action Cable channel), and
  • return-to-visible after the socket-reap threshold.

The recovery action is Turbo.session.refresh() (not the thread's ?since= HTTP delta) because inbox broadcasts already are whole-page refreshes — idempotent, morphing, scroll-preserving. It skips refreshing while the search box is focused so it never yanks the list out from under someone typing.

Auto-registered via the engine importmap pin, so hosts need zero changes; a host can still override it by pinning the same key.

Tests

  • engine_test: the controller is pinned under controllers/chats/ for stimulus auto-registration.
  • conversations_flow_test: the inbox stream source is wrapped by the reconciler.

Full suite green (the one unrelated red in my local run is a pre-existing asset-path assertion that hard-codes the checkout dir name chats; it passes in CI / a normally-named checkout).

Docs

CHANGELOG (Unreleased), README (both surfaces self-heal now), and the campfire_review.md ledger updated to note inbox parity.

rameerez added 2 commits July 7, 2026 04:50
The inbox already receives Turbo 8 page refreshes (broadcast_refresh_later_to),
but Action Cable has no replay: a refresh sent while the client's socket was
down (backgrounded tab/app, network blip, laptop asleep) is lost and the inbox
sits stale until the user navigates. The thread got stale-catch-up in 0.1.1
(the single biggest reliability pattern from the Campfire review); the inbox
was the remaining gap.

Add a tiny chats--refresh-inbox controller that re-runs the same page refresh
on the two moments we might have missed one — cable reconnect and
return-to-visible — reusing the thread's channel-free reconnect detection
(observe the <turbo-cable-stream-source> `connected` attribute; no new Action
Cable channel). The recovery action is Turbo.session.refresh() rather than the
thread's `?since=` delta, because inbox broadcasts already ARE page refreshes.

Auto-registered via the engine importmap pin, so hosts need zero changes. It
guards against morphing the list out from under someone typing in search.

- engine_test: assert the new controller is pinned for auto-registration
- conversations_flow_test: assert the inbox stream source is wrapped by the
  reconciler
@rameerez
rameerez merged commit faeaf76 into main Jul 9, 2026
14 checks passed
@rameerez
rameerez deleted the feat/inbox-missed-broadcast-recovery branch July 9, 2026 04:59
@rameerez

rameerez commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Added a second commit (adb1c4f) that closes the other inbox-staleness case, distinct from the missed-broadcast one this PR started with.

chats--refresh-inbox heals a missed live broadcast on a page that stays open. But there's a second way the inbox goes stale that the controller can't touch: a Turbo restoration visit (browser back / Hotwire Native stack pop) serves the inbox's cached snapshot with no GET. Open a chat from a profile, send a message, tap back → the snapshot cached before the chat existed comes back and the new conversation is missing until you navigate away and return. (Reported in a downstream host app; reproduced end-to-end there.)

Fix: turbo-cache-control: no-cache on the inbox → Turbo never stores the snapshot → a restore visit re-fetches fresh. Verified it reaches Hotwire Native too: the native back-pop issues a Turbo .restore visit, and bundled turbo.js marks a no-cache page isCacheable == false, so restore falls through to a network fetch.

The two mechanisms are complementary (missed live broadcast vs. stale restored snapshot), so this PR now covers inbox freshness on both axes.

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