[1.x] fix: Handle network connection loss in the frontend - #4843
Merged
Conversation
Classify network-level request failures (status 0) in Application#requestErrorCatch and show a dedicated translated alert instead of the generic 'Oops! Something went wrong' message. While the browser reports being offline, a more specific offline message is used; failed cross-origin requests keep the existing cross-origin message. Only one connection alert is shown at a time, even when several parallel requests fail at once. Aborted requests cannot trigger the alert, as Mithril leaves their promises unsettled. Additionally, listen to the window online/offline events: going offline shows a persistent dismissible alert, and once connectivity is restored it is replaced by a brief confirmation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When a GET request fails at the network level while the browser reports being offline, hold its promise open instead of rejecting it and queue the request. Once the online event fires, deferred requests are re-issued and their original promises settled with the retry's result — so content that failed to load while offline (discussion posts, lists, blog articles) appears automatically on reconnection, without a page reload or any changes to calling code. Only GETs qualify: they are safe to repeat. Writes keep failing fast to avoid duplicate submissions. A request that fails again while still offline is re-deferred; a network-level failure while the browser reports being online still rejects as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Key deferred requests by method, URL and params. Identical requests queued while offline — e.g. a polling widget firing every few seconds — are retried with a single request once connectivity is restored, and every caller's promise is settled with that request's result. This prevents a burst of duplicate requests on reconnection (fof/horizon's admin stats widget polls a GET every 5 seconds by default). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8 tasks
imorland
pushed a commit
that referenced
this pull request
Jul 29, 2026
* feat: handle network connection loss in the frontend Port of the 1.x network-loss handling (#4843) to 2.x: - Classify network-level request failures (status 0) in requestErrorCatch and show a dedicated translated alert (offline-specific when navigator.onLine is false; failed cross-origin requests keep their existing message). Parallel failures produce a single alert. Aborted requests cannot trigger this, as Mithril leaves their promises unsettled. - Listen to the window online/offline events: going offline shows a persistent dismissible alert; reconnecting clears connection alerts and briefly confirms. - Defer GET requests that fail while the browser is offline: their promises are held open and settled with the result of a retry once connectivity is restored, so content that failed to load appears automatically without a page reload. Writes keep failing fast. Identical deferred requests (e.g. from a polling widget) are keyed by method, URL and params, and retried with a single request. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: retry lazy chunk loads that failed while offline Code-split chunks load via script tags, not XHR, so the offline handling in Application#request never sees them. A chunk that failed to load while offline previously rejected (or was silently swallowed by its caller): the reply/discussion composer would not open, and DiscussionPage's PostStream import left the page stuck on its loading skeleton with no retry — even after connectivity returned. Since every chunk load funnels through ExportRegistry#loadChunk (webpack's script loader is overridden with it), hold back failures that occur while the browser reports being offline and retry once the online event fires. The import() promise stays pending in the meantime, so every caller — composer bodies, lazy routes, modals, the post stream — recovers on its own, without changes to call sites. Failures while the browser is online are reported unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style: format Application.tsx with the repo's pinned prettier * test: silence expected chunk-url warning in ExportRegistry tests The tests never register chunks, so the registry legitimately warns before falling back to the URL passed in. Spy it away, as the admin Application test does for console.group/error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: assert the unregistered-chunk warning and URL fallback Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
imorland
approved these changes
Jul 29, 2026
imorland
pushed a commit
to flarum/flarum-core
that referenced
this pull request
Jul 30, 2026
* feat: handle network connection loss in the frontend Port of the 1.x network-loss handling (flarum/framework#4843) to 2.x: - Classify network-level request failures (status 0) in requestErrorCatch and show a dedicated translated alert (offline-specific when navigator.onLine is false; failed cross-origin requests keep their existing message). Parallel failures produce a single alert. Aborted requests cannot trigger this, as Mithril leaves their promises unsettled. - Listen to the window online/offline events: going offline shows a persistent dismissible alert; reconnecting clears connection alerts and briefly confirms. - Defer GET requests that fail while the browser is offline: their promises are held open and settled with the result of a retry once connectivity is restored, so content that failed to load appears automatically without a page reload. Writes keep failing fast. Identical deferred requests (e.g. from a polling widget) are keyed by method, URL and params, and retried with a single request. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: retry lazy chunk loads that failed while offline Code-split chunks load via script tags, not XHR, so the offline handling in Application#request never sees them. A chunk that failed to load while offline previously rejected (or was silently swallowed by its caller): the reply/discussion composer would not open, and DiscussionPage's PostStream import left the page stuck on its loading skeleton with no retry — even after connectivity returned. Since every chunk load funnels through ExportRegistry#loadChunk (webpack's script loader is overridden with it), hold back failures that occur while the browser reports being offline and retry once the online event fires. The import() promise stays pending in the meantime, so every caller — composer bodies, lazy routes, modals, the post stream — recovers on its own, without changes to call sites. Failures while the browser is online are reported unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style: format Application.tsx with the repo's pinned prettier * test: silence expected chunk-url warning in ExportRegistry tests The tests never register chunks, so the registry legitimately warns before falling back to the URL passed in. Spy it away, as the admin Application test does for console.group/error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: assert the unregistered-chunk warning and URL fallback Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 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.
Problem
All frontend XHR goes through
Application#request(). When a request fails at the network level — the user is offline, DNS fails, the origin is unreachable — the rejection carriesstatus === 0, whichrequestErrorCatch()has no branch for. Users get the generic "Oops! Something went wrong. Please reload the page and try again." alert, which wrongly suggests a server-side bug and prompts bug reports for what is a client connectivity issue.Core also makes no use of the browser's
online/offlineevents, so users get no proactive indication that they've gone offline, and content that failed to load while offline stays missing until a full page reload.This was discussed with @imorland and is contributed as three self-contained commits.
What this PR does
1. Classify network-level failures (
status === 0) —requestErrorCatchnow shows a dedicated translated alert: an offline-specific message whennavigator.onLine === false, otherwise a generic connection-problem message. The existing cross-origin special case is preserved inside the new branch. Parallel failures produce a single alert instead of one per request. Aborted requests cannot trigger this: Mithril (2.0.4) wrapsxhr.abortand leaves aborted promises unsettled, so they never reach the error handler by construction.2.
online/offlineevents + automatic retry — going offline shows a persistent, dismissible alert; reconnecting clears all connection alerts and shows a brief confirmation. Additionally, a GET request that fails while the browser reports being offline is not rejected: its promise is held open, and the request is re-issued when theonlineevent fires, settling the original promise with the retry's result. Content that failed to load while offline (post stream pages, discussion lists) appears automatically on reconnection with no changes to calling code. Only GETs qualify — writes keep failing fast, so nothing can be submitted twice. A network failure while the browser reports being online still rejects exactly as before, so no promise can wait for anonlineevent that will never come.3. Deduplicate deferred requests — deferred requests are keyed by method + URL + params; identical requests queued while offline are retried with a single request whose result settles every caller's promise. This matters in practice: e.g. FoF Horizon's admin stats widget polls a GET every 5 seconds, which would otherwise replay dozens of identical requests on reconnection.
New locale keys:
core.lib.error.network_message,core.lib.error.offline_message,core.lib.connection_restored_message(English only).Behavior notes
customErrorHandlerand rejection propagation are unchanged for every status except the offline-GET deferral case; regression tests cover 422/500/cross-origin.errorHandler/.catchlogic would be adversely affected by deferral — GET failure handling in the wild is loading-flag resets, which now correctly keep spinners visible until the content self-heals.Testing
tests/unit/common/Application.test.ts): status-0 classification (online/offline/cross-origin), alert dedup, custom-error-handler contract, 422/500 regression guards, connectivity events, deferral + resolution on reconnect, re-deferral while still offline, retry dedup.1.x.Screenshot
Necessity
Confirmed
composer test).Required changes: