Skip to content

fix(app): refresh open session messages on resume/reconnect#1418

Open
chphch wants to merge 1 commit into
slopus:mainfrom
chphch:fix/session-resume-message-refresh
Open

fix(app): refresh open session messages on resume/reconnect#1418
chphch wants to merge 1 commit into
slopus:mainfrom
chphch:fix/session-resume-message-refresh

Conversation

@chphch

@chphch chphch commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Messages an agent produces while the Happy app is backgrounded don't appear when you return to the app — the open chat stays stale until you leave and re-enter the session. The cause is a misdirected refresh trigger: SessionView's effect that re-fetches an open session's messages depends on realtimeStatus, but realtimeStatus tracks the LiveKit voice session (storage.ts useRealtimeStatus, written only by RealtimeVoiceSession*/RealtimeSession) — not the data socket, whose status lives in the separate socketStatus/useSocketStatus. So on app resume (or any socket reconnect), that effect never re-runs, and sync.ts's onReconnected handler deliberately skipped refreshing the open session's messages, with a comment that relied on exactly this dead trigger (onSessionVisible "called by SessionView when realtimeStatus changes"). Re-entering the session works only because a fresh mount re-fetches. This fix refreshes the currently-viewing session's messages on both AppState → 'active' and the socket onReconnected path via onSessionVisible(currentViewingSessionId) (a bounded forward sync). It also covers a foreground network blip that reconnects with recovered=false (server connection-state recovery off), which had the same gap.

Proof

Verified end-to-end on a throwaway standalone happy-server (PGlite) + Expo web + a paired happy-cli daemon, driven with Playwright. Two browser contexts on one account: context A opens a session then goes offline (simulating the app being backgrounded); context B (online) sends a marked message to that session; context A comes back online. The missed message now renders on A on reconnect without a remount (onSessionVisiblefetchForwardSince). Screenshots/GIF in the PR comment below — A shows "No messages yet" while offline, then the full conversation (including the message it missed) after reconnect. pnpm typecheck clean.

The SessionView effect that re-fetches an open session's messages depends on
realtimeStatus, but realtimeStatus tracks the LiveKit voice session — not the
data socket (that is the separate socketStatus / useSocketStatus). So when the
app is backgrounded and resumed, or the socket simply drops and reconnects, the
effect never re-runs and the open chat stays stale until a fresh SessionView
mount. Messages an agent produced while the app was in the background never
appear until you leave and re-enter the session. The onReconnected handler
explicitly skipped refreshing messages, relying on that dead trigger.

Refresh the currently-viewing session's messages on both AppState -> 'active'
and socket onReconnected via onSessionVisible(currentViewingSessionId), which
runs a bounded forward sync. Also covers a foreground network blip that
reconnects with recovered=false.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
@chphch

chphch commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

Proof — before / after

Captured on a standalone happy-server (PGlite) + Expo web + a paired happy-cli daemon, driven with Playwright. Methodology that isolates this fix specifically:

  1. Context A opens a session and primes it with a message so it is already-loaded (sessionLastSeq set) — the user's real condition. (The custom session-prefetch only fetches never-loaded sessions, so an empty session would be refreshed by prefetch and mask the fix.)
  2. The agent is then stopped, so nothing answers later messages (no post-reconnect update to trigger handleUpdate's gap-refresh).
  3. A goes offline for >45s (past the server pingTimeout) so the socket is genuinely disconnected — a shorter pause keeps the socket alive and the broadcast is merely buffered, which masks the bug.
  4. Context B sends a marked message to the same session while A is disconnected, so A truly misses it.
  5. A comes back online (resume).

before/after

Without the fix (top): after reconnect A is stale — the message that arrived while A was away is missing; it only appears after leaving and re-entering the session. With the fix (bottom): the missed message renders automatically on reconnect, no remount. The only change between the two runs is sync.ts (verified: the served bundle contained the fix in the "after" run and not in the "before" run). pnpm typecheck clean.

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