fix(frontend): keep a local generation streaming while the machine is offline - #258
Merged
Merged
Conversation
… offline Turning off Wi-Fi could cost the rest of a running generation. `waitForReconnect` treated `navigator.onLine === false` as "cannot reach the backend": it set "Connection paused while offline. Waiting for network..." and returned a promise that settled only on an `online` event. No timer, no retry. On a machine that stays offline -- a plane, a dead router, an adapter switched off -- the stream never reconnected, and the answer being generated one process away was lost to the UI. `navigator.onLine` reports whether the machine has network connectivity. The Cortex backend is not on the network: `normalizeApiBaseUrl` refuses anything but a same-origin path or a loopback host in production, and throws otherwise. So the flag says nothing about whether the backend is reachable, and offline is the ordinary operating condition for a local-first desktop app rather than a fault to wait out. The branch is gone. A dropped stream now always retries on the existing backoff, which is what a loopback connection needs, and the status text says what is actually happening. The test that covered the old behaviour asserted the pause and the resume, so it was pinning the defect; it now asserts the opposite -- with the machine offline for the whole test and no `online` event ever dispatched, the retry still has to happen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
The problem
Turning off Wi-Fi can cost the rest of a running generation.
If an SSE stream drops while
navigator.onLineis false, the UI parks on:with no timer and no retry. On a machine that stays offline — a plane, a dead router, an adapter switched off — it never reconnects, and the answer being generated one process away is lost to the UI.
Root cause
The promise settles on an
onlineevent or an abort. Nothing else.navigator.onLinereports whether the machine has network connectivity. The Cortex backend is not on the network —normalizeApiBaseUrlenforces that in production:So the flag says nothing whatsoever about whether the backend is reachable. Worse, for a Windows-first, local-first desktop assistant running against local models, offline is the ordinary operating condition, not a fault to wait out. This is the one app where the check is guaranteed wrong.
The fix
The branch is gone. A dropped stream always retries on the existing backoff, which is what a loopback connection needs, and the status text says what is actually happening (
Connection interrupted. Retrying in Ns...).The abort path, the backoff schedule and every other behaviour are untouched — this only removes a special case that could never be correct here.
The old test was pinning the defect
"pauses reconnects while offline and resumes when the browser comes online"asserted both halves of the broken behaviour: that the stream did not retry while offline, and that it only resumed onceonlinefired. It could not survive a correct implementation, so it is replaced rather than kept.The new test keeps the machine offline for its whole duration and never dispatches an
onlineevent, then requires the retry to happen anyway:Against the unfixed code it fails, timing out after ~1 s with the retry never attempted.
Verification
npm test -- --runnpm run typechecknpm run lintpython -m pytest -qCompatibility and rollback
Frontend only, confined to one function. No API contract, stored data, or migration. Reverting the commit restores the previous behaviour exactly.
Limits
A genuinely unreachable backend (the process died) now retries on the backoff until the user stops it, rather than waiting on a network event that would never have been the right signal anyway. That is the pre-existing behaviour for every other disconnect reason, and it is bounded —
reconnectDelaycaps at 30 s per attempt.This does not add a "backend is gone" state distinct from "connection interrupted". Detecting that properly means probing the health endpoint rather than reading a browser flag, which is a larger change than removing an incorrect one.
🤖 Generated with Claude Code