OverviewFilesCommitsIssuesDocsReleasesPeers

favela-android commits / 1f9b1a9

a failed request is not a failed app
onError/onHttpError fire for the MAIN FRAME, which includes the POST a compose submits. So a single 500 from one write tore down the entire rendered UI and replaced it with a full-screen "could not load the local UI", losing the user's place — a one-request failure presented as the app dying, and it discarded favela-web's own error message in the body while doing it. Only a failure before anything has ever rendered is fatal now; that is the case the screen exists for (the server never came up). After that the page is still there and reports its own errors. Observed on device: post fails once, whole UI replaced, retry works. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1f9b1a9d6710c6338e5daa7344893b7c2607ca33
by plankatron <fleeky@gmail.com> · 2026-08-11T22:18:34.000Z

Changed files (1) +13 -2

app/App.jsx +13 -2
……@@ -68,6 +68,8 @@ function Web ({ status }) {
6868 const ref = useRef(null)
6969 const [canBack, setCanBack] = useState(false)
7070 const [failed, setFailed] = useState(null)
71 // Has anything EVER loaded? Only a failure before the first successful page is fatal.
72 const loaded = useRef(false)
7173
7274 // THE SESSION, handed to the WebView's own cookie jar. The worklet signed in over loopback and holds
7375 // the token; a WebView cannot be told about that, so favela-web's /auth/handoff swaps the token for the
……@@ -109,8 +111,17 @@ function Web ({ status }) {
109111 // in a real browser is a later refinement, and refusing is the safe default in the meantime.
110112 onShouldStartLoadWithRequest={(r) => r.url.startsWith(status.favelaUrl)}
111113 onNavigationStateChange={(n) => setCanBack(n.canGoBack)}
112 onError={(e) => setFailed((e.nativeEvent && e.nativeEvent.description) || 'load failed')}
113 onHttpError={(e) => setFailed('HTTP ' + ((e.nativeEvent && e.nativeEvent.statusCode) || '?'))}
114 onLoadEnd={() => { loaded.current = true }}
115 // ⛔ A FAILED REQUEST IS NOT A FAILED APP. These fire for the MAIN FRAME, which includes the POST
116 // a compose submits — so a single 500 from one write tore down the entire rendered UI and replaced
117 // it with a full-screen "could not load the local UI", losing the user's place and making a
118 // one-request failure look like the app dying. (Observed on device 2026-08-11: post fails once,
119 // whole UI is replaced, retry works.)
120 //
121 // Only a failure BEFORE anything has ever rendered is fatal — that is the case the screen is for
122 // (server never came up). After that, the page is still there and owns its own error reporting.
123 onError={(e) => { if (!loaded.current) setFailed((e.nativeEvent && e.nativeEvent.description) || 'load failed') }}
124 onHttpError={(e) => { if (!loaded.current) setFailed('HTTP ' + ((e.nativeEvent && e.nativeEvent.statusCode) || '?')) }}
114125 // Loopback only, so no mixed-content or remote-code concern; the pages need JS for likes/compose.
115126 javaScriptEnabled
116127 domStorageEnabled