Skip to content

Fix SSE live updates for open conversations (#14) - #36

Open
Melloss wants to merge 1 commit into
mainfrom
fix/app_refresh
Open

Fix SSE live updates for open conversations (#14)#36
Melloss wants to merge 1 commit into
mainfrom
fix/app_refresh

Conversation

@Melloss

@Melloss Melloss commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #14 — "the app isn't refreshing / i have to get out of the
conversation and enter again to see it."

Root cause: leaving and re-entering a chat screen "fixed" it only
because it constructs a new ChatViewModel, which does a one-shot REST
refetch. The actual SSE live-update pipeline was never being repaired —
it stayed broken, so the conversation went stale again shortly after.

Three independent, compounding defects in the SSE/session layer:

  1. Unguarded exception silently kills live updates.
    SessionRepositoryProvider.collectWorkspaceEvents called
    repository.acceptEvent(event) inside a collect {} with no
    try/catch. Since the collector runs under a SupervisorJob, one
    throwing event permanently and silently terminated only that
    workspace's
    event job — no log, no crash, no UI signal, and it was
    never recreated by chat-screen navigation (only by closing the tab).

  2. Zombie SSE sockets go undetected. OpenCodeEventSource used
    readTimeout(0), so a socket that died silently (NAT/proxy idle-kill,
    Doze-related network changes) never fired onError/onClosed.
    connectionState stayed Connected indefinitely with zero events
    flowing, and nothing checked for it.

  3. Reconnect self-heal was dead code, and incomplete even when live.
    The synthetic OpenCodeEvent.Connected never reached the
    per-workspace fan-out (it has no directory, so it never entered
    ConnectionManager.scopedEvents), meaning
    SessionRepositoryImpl.acceptEvent's reconnect → re-hydrate path
    (hydrateAfterReconnect()) never fired in production. Even when it
    did fire it only rebuilt the session/project snapshot — it never
    refreshed a conversation's messages.

Changes

  • SessionRepositoryProvider.kt — wrap acceptEvent in try/catch
    (rethrows CancellationException, logs everything else) so one bad
    event can't kill the collector. Also broadcasts a synthetic
    Connected event to every live workspace repository (filtered by
    connection generation) on each non-Connected → Connected transition,
    since the original per-directory routing could never reach it.
  • OpenCodeEventSource.kt — liveness watchdog: tracks the last received
    frame (message or heartbeat) and forces reconnect() if the socket
    claims Connected but has been silent > 60s. Also fixes a pre-existing
    leak where shutdown() didn't cancel the event-pump scope.
  • SessionRepositoryImpl.kt — on every Connected event, re-fetches
    messages (via the existing loadMessages REST path) for every
    actively-observed session, independent of the one-shot snapshot-hydrate
    guard (inFlight), so it runs on every reconnect, not just the first.
  • docs/design-locks/B-sse-hydrate-race.md — extends item 11 with a new
    item 12 + worked examples documenting that reconnect self-heal now
    covers message state, not just the session/project snapshot.

Test plan

  • ./gradlew :app:testDebugUnitTest — full suite passes, including
    new regression tests:
    • collector survives a throwing event and keeps processing subsequent
      events (SessionRepositoryProviderTest)
    • messages refresh on every reconnect, not just the first
      (SessionRepositoryProviderTest — this one initially caught a bug
      in my own first attempt, where the refresh was accidentally gated
      behind the one-shot hydrate guard)
    • watchdog staleness decision logic (OpenCodeEventSourceTest)
  • ./gradlew :app:detekt — clean
  • ./scripts/check_theme_violations.sh — clean
  • Manual, on-device: stopped the OpenCode server while a conversation
    was open, sent a message from the desktop TUI, restarted the server —
    the open conversation on the phone caught up without navigating away
    and back.

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.

the app isn't refreshing

1 participant