ReviewOS

also looking at this

craft-native/craft

fix(android): recover remote loads after connectivity returns

#265
Merged glennmichael123 wants to merge codex/android-load-failure-fallback into main
4 files +130 -11
packages/android/src/index.test.tsmodified+23-1
Changes to packages/android/src/index.test.ts
@@ -306,7 +306,29 @@ describe('Craft Android builder', () => {
306306 const config = JSON.parse(readFileSync(join(output, 'app/src/main/assets/craft.config.json'), 'utf8'))
307307 const activity = readFileSync(join(output, 'app/src/main/java/org/wildloop/app/MainActivity.kt'), 'utf8')
308308 expect(config.hasBundledFallback).toBe(true)
309 expect(activity).toContain('hasBundledFallback && !loadedBundledFallback')
309
310 // #254: only a main-frame connectivity failure may hide the remote page
311 // behind the bundled copy. TLS, authentication, and bad-response failures
312 // must remain visible instead of looking like a successful offline launch.
313 const failureHandler = activity.slice(
314 activity.indexOf('override fun onReceivedError('),
315 activity.indexOf('webView.webChromeClient = WebChromeClient()'),
316 )
317 expect(failureHandler).toContain('CraftLoadFailure.isUnreachable(error.errorCode)')
318 expect(failureHandler).not.toContain('hasBundledFallback && !loadedBundledFallback')
319
320 // Falling back is no longer one-way. Bound the assertion to the named
321 // recovery method so the field declaration cannot make this pass alone.
322 const recovery = activity.slice(
323 activity.indexOf('private fun returnFromBundledFallback('),
324 activity.indexOf('private fun loadContent()'),
325 )
326 expect(recovery).toContain('loadedBundledFallback = false')
327 expect(recovery).toContain('webView.loadUrl(remoteUrl)')
328
329 expect(activity).toContain('returnFromBundledFallback("the app returned to the foreground")')
330 expect(activity).toContain('val wasConnected = isConnected')
331 expect(activity).toContain('if (!wasConnected && isConnected) {')
310332 })
311333
312334 it('delivers shortcut activations and refuses impossible event streams', async () => {