Skip to content
WebViewGold Vibe Coding to App Store
English
Get WebViewGold

ChatGPT & Codex

Offline mode and error states that survive App Review: a Codex workflow for WebViewGold apps

Some reviewers switch on airplane mode, and every reviewer eventually hits a slow request. A wrapped web app has two offline layers: WebViewGold catches the moment the page cannot load at all, and your web app has to handle every request that fails after it loaded. Codex is a good fit for the second layer, because it can list every data call in the codebase and prove each error state with tests that cut the network.

From $109 one-time per platform, or let our team do it all from $698.

  • Approval guarantee – or money back within 14 days
  • No coding needed
  • One-time payment, free updates
  • 24/7 human support
  • Made in Germany since 2014 · ISO 27001

Goal and reviewer angle

Guideline 2.1 (App Completeness) is where offline problems end up: a spinner that never stops, a white screen after a failed request, a raw "TypeError: Failed to fetch" on a card, a form that loses everything you typed (the 2.1 rejection explained). Google Play files the same thing under broken functionality. None of this needs airplane mode to happen: a free hosting tier that sleeps after inactivity, a cold serverless function or a weak hotel network does it too.

The goal: the app never shows a blank or frozen screen. At launch without a network, a native offline screen with a retry button appears. Mid-session, every screen shows a loading state that ends, an error state with a Try again button, or cached data with a note that it may be out of date.

The offline mode hub compares the native options, and offline mode for ChatGPT and Codex apps covers the setup for Codex projects.

Two layers you need, two you can add

Native offline screen (WebViewGold)

Shown when the app cannot load your URL. On iOS, the dialog and screen texts come from offlinetitle, offlinemsg, screen1, screen2 and offlinebuttontext, and offlinescreenautoretry retries every second without a tap. On Android, OFFLINE_SCREEN_BACKGROUND_COLOR matches it to your brand. No web code involved.

Error states in the web app (Codex)

Every request gets a timeout, an error state with retry and a readable message. This is the layer reviewers hit most often, and the one this guide automates.

Bundled fallback page (optional)

With offlinelocalhtmlswitch (iOS) or FALLBACK_USE_LOCAL_HTML_FOLDER_IF_OFFLINE (Android), the app shows a page from the local-html folder of the project when offline, and a button that navigates to reconnect:// reloads the last online URL (Reconnect API). On iOS, zipfiledownloadfromserver and zipfileremoteurl can refresh that fallback from your server.

Cached data (optional, Firebase)

Firestore's persistent local cache serves the last known documents offline, so lists still render with a "last updated" hint, and writes are queued until the connection returns.

The error-state contract for AGENTS.md

Write the rules once, as invariants, in the AGENTS.md of the web app. Every later task ("add a reports page") then comes with error states for free, and the Review guidelines section lets Codex code review catch regressions on pull requests.

markdown
## Network and error states (every screen)
- All HTTP calls go through src/lib/request.ts: request(path, { timeoutMs = 10000 }).
  It uses AbortSignal.timeout, retries idempotent GETs once with backoff, and throws
  typed errors (OfflineError, TimeoutError, HttpError with status) that carry a
  user-facing message. Components never call fetch directly.
- Every data view renders exactly one of: loading (skeleton, ends after 10 s at the
  latest), content, empty (helpful text and a next action), error (message and a
  "Try again" button).
- No raw error text in the UI: no "TypeError", no stack traces, no JSON.
- Offline banner: listen to the online and offline events and render
  <div role="status">You are offline. Changes will sync when you are back.</div>
  Do not rely on navigator.onLine alone; a failed request also counts as offline.
- Forms keep their input when a submit fails. Submit buttons show progress and
  re-enable on error. Never send the same mutation twice without an idempotency key.
- Firestore: initializeFirestore with persistentLocalCache. Show "Last updated"
  when snapshot.metadata.fromCache is true.
- Express: error middleware returns JSON { error, message } with the right status,
  never an HTML error page. GET /healthz answers 200 without touching the database.
- No service worker for offline inside the native app. WebViewGold's offline screen
  and the bundled fallback page handle "no network at launch".

## Review guidelines
- P1: fetch() or axios outside src/lib/request.ts, or any request without a timeout.
- P1: a catch block that swallows an error without showing an error state.
- P1: a loading state with no exit (no timeout, no error branch).

With a good optimized version of your web app you can have a stunning mobile app within a couple of minutes. Additionally to that the customer service is very nice and very helpful! I can just recommend WebViewGold :-)

Julian Koch · Co-Founder of DuelDonkey · CodeCanyon

Inventory and fix: the Codex task

This one works well as a cloud task, because the result is a large but mechanical diff that you want to read as a pull request. In the cloud environment, add npx playwright install --with-deps chromium to the setup script so the browser is ready before the agent starts; in the CLI, Codex asks for approval when it needs the network to install it.

Paste into Codex (cloud task, CLI or IDE extension)
Read AGENTS.md, especially "Network and error states". This web app runs inside native iOS and Android apps built with WebViewGold, and App Review tests it on bad networks.

1. Inventory: find every network call (fetch, axios, Firestore getDoc, getDocs and onSnapshot, server-sent events or WebSockets, file uploads). Write docs/network-inventory.md with one row per call: file, screen, call, current loading, empty and error handling.
2. Create src/lib/request.ts as specified in AGENTS.md and move every HTTP call to it.
3. For each screen in the inventory, implement the four states: loading that ends after 10 seconds at the latest, content, empty, and error with Try again. Reuse one ErrorState and one EmptyState component.
4. Add the offline banner with role="status", driven by the online and offline events and by OfflineError from request().
5. Forms: keep the input on failure, show the error next to the submit button, re-enable the button.
6. Firestore: switch to persistentLocalCache and show "Last updated" when snapshot.metadata.fromCache is true.
7. Express: a JSON error middleware, a JSON 404 handler for /api, and GET /healthz.
8. Create native-fallback/index.html: one self-contained file (inline CSS, the logo as inline SVG or a data URI, no external requests) with the app name, the text "You are offline" and a Try again button that navigates to reconnect://. I will copy it into the local-html folder of the native projects.
9. Playwright tests: API requests aborted, API requests that never answer, and the browser context switched offline and back. Each must end in an error or offline state within 12 seconds and show no raw error text. Install Playwright if it is missing (ask first).
Finish with the inventory, the list of changed screens, and anything you could not cover.

The tests that prove it

Three network failures cover most of what a reviewer can run into. They run in the Codex sandbox and in CI, because they only need the local dev server.

javascript
// tests/offline.spec.js (Playwright)
import { test, expect } from "@playwright/test";

test("failed API shows a retry, not a white screen", async ({ page }) => {
  await page.route("**/api/**", (route) => route.abort("internetdisconnected"));
  await page.goto("/dashboard");
  await expect(page.getByRole("button", { name: /try again/i })).toBeVisible({ timeout: 12000 });
  await expect(page.getByText(/TypeError|Failed to fetch/)).toHaveCount(0);
});

test("hanging API never leaves an endless spinner", async ({ page }) => {
  await page.route("**/api/**", () => { /* never answer: a server that hangs */ });
  await page.goto("/dashboard");
  await expect(page.getByRole("button", { name: /try again/i })).toBeVisible({ timeout: 12000 });
});

test("offline banner appears and clears", async ({ page, context }) => {
  await page.goto("/dashboard");
  await context.setOffline(true);
  await expect(page.getByRole("status")).toContainText(/offline/i);
  await context.setOffline(false);
  await expect(page.getByRole("status")).toHaveCount(0);
});

WebViewGold offline settings at a glance

GoaliOS (Config.swift)Android (Config.java)
Offline texts and button labelofflinetitle, offlinemsg, screen1, screen2, offlinebuttontext (translatable via alternatelanguage1 and alternatelanguage2)No text keys documented in Config.java; background color via OFFLINE_SCREEN_BACKGROUND_COLOR
Retry without a tapofflinescreenautoretryNot a documented option; use a fallback page with reconnect://
Bundled fallback pageofflinelocalhtmlswitch true, page in the local-html folderFALLBACK_USE_LOCAL_HTML_FOLDER_IF_OFFLINE true, page in the local-html folder
Try again from the fallbackreconnect://reconnect://
Update the fallback remotelyzipfiledownloadfromserver and zipfileremoteurlShip it with the build
Refresh when the app returns from the backgroundautoRefreshEnabledAUTO_REFRESH_ENABLED

Ship it

  1. Add the contract to AGENTS.md

    Paste the section above and adjust paths and timeouts to your project. Commit it before the task runs, so a cloud task sees it.

  2. Run the inventory-and-fix task

    As a cloud task you get a diff and a pull request; in the CLI, use codex --sandbox workspace-write --ask-for-approval on-request. Either way the Playwright tests must pass.

  3. Review against the inventory

    Every row in docs/network-inventory.md should map to a screen with four states. Comment @codex review on the pull request to apply the review guidelines a second time.

  4. Configure the native layer

    Write the offline texts in your app's voice and language, decide on offlinescreenautoretry, set the Android background color, and if you want the fallback page, copy native-fallback/index.html to local-html/index.html and switch on offlinelocalhtmlswitch or FALLBACK_USE_LOCAL_HTML_FOLDER_IF_OFFLINE (Android Reconnect docs).

  5. Break the network on purpose

    Airplane mode at launch and mid-session on real devices. On iPhone, the Network Link Conditioner in the Developer settings (available after the device was connected to Xcode) simulates very bad networks and total loss; on Android, use airplane mode or the emulator's network settings.

  6. Tell the reviewer

    If the app needs an internet connection, say so in the review notes, together with what the app shows offline. For the rest of the submission, see the Codex release workflow.

Airplane-mode script (run it before the reviewer does)

  • Cold start in airplane mode: the native offline screen appears, branded and readable, and Try again works once the network is back.
  • Airplane mode mid-session: the offline banner appears, the current screen keeps its content, and actions show a clear message.
  • Back online: the banner disappears, Try again reloads, and nothing was submitted twice.
  • A hanging server (stop your API, or a 100 percent loss profile in the Network Link Conditioner): every spinner ends within about ten seconds.
  • Fallback page, if enabled: it loads without network, has no broken images or fonts, and reconnect:// returns to the last page.
  • Leave the app in the background for a while and return: with autoRefreshEnabled or AUTO_REFRESH_ENABLED the page refreshes; without it, stale screens still behave.
  • The logged-in session survives an offline launch; nobody gets logged out by a network error.

Pitfalls

  • Trusting navigator.onLine. It stays true behind a captive Wi-Fi portal or on a dead connection; treat failed and timed-out requests as offline too.
  • A fallback page that loads web fonts, a CDN script or a remote logo. Offline, those requests fail and the page looks broken. Inline everything.
  • Retry storms: buttons that fire several requests per tap, or automatic retries without backoff that hammer an API that is already struggling.
  • Express or a proxy answering with an HTML error page, which the client tries to parse as JSON ("Unexpected token <"). The JSON error middleware fixes it at the source.
  • Building the offline plan on a service worker. On iOS, WKWebView generally only runs service workers for apps configured with App-Bound Domains, so treat one as a bonus for browser users.
  • A free hosting tier that sleeps: the first request after a quiet period takes long enough to look like a hang. Keep the API warm or upgrade before you submit.

Frequently asked questions

Does my app have to work fully offline to pass review?

No. Apps that need a server may require a connection, as long as they handle its absence gracefully: a clear offline screen, error states with retry, no frozen or blank screens. Mention in the review notes that the app needs an internet connection.

What is the difference between the offline screen and the fallback page?

The offline screen is native and configured with a few texts and options. The fallback page is your own HTML in the local-html folder of the project, shown instead when offlinelocalhtmlswitch (iOS) or FALLBACK_USE_LOCAL_HTML_FOLDER_IF_OFFLINE (Android) is on, and it can offer a reconnect:// button.

Can Codex test offline behavior?

Yes, for the web layer. Playwright can abort requests, leave them hanging and switch the browser context offline, all inside the Codex sandbox. The native offline screen and the fallback page still need a device test with airplane mode.

Should I enable Firestore offline persistence?

For read-heavy apps, yes: lists render from the cache and writes sync later. Show when data comes from the cache, and think about shared devices before caching sensitive data.

Is AbortSignal.timeout supported inside the app?

Yes in current system WebViews; Safari supports it from version 16. If you support older iOS versions, ask Codex for an AbortController with setTimeout as a fallback inside request.ts.

Three ways to get your app into the stores

Same result, different amount of work on your side. All three come with the approval guarantee and 24/7 human support.

Do it yourself

$109 per platform, one-time

The WebViewGold license: a finished Xcode or Android Studio project. Paste your URL, set name and icon, switch on features, build and submit. Free updates forever.

See plans on the main page

Cloud Builder

$349 per license, one-time add-on

No Mac, no Xcode, no Android Studio: configure in the browser, we build both apps in the cloud and hand them to App Store Connect and the Play Console.

How the Cloud Builder works Try the free demo

Full Service

$698 per platform $798

Send us your URL and icon. Our team configures, builds and delivers your app within 72 hours, white-label, with source projects. Both platforms for $1,158.

Full Service details Powered by WeSetupYourWebViewApp

Winter Sale prices valid until December 31, 2026.

Ready to ship your app?

App Store & Google Play approval guarantee – or your money back within 14 days

Rather hand it over? Full Service: we configure, build and deliver within 72 hours, from $698 per platform. Questions? Chat with us 24/7.

Refunds are granted under the conditions listed in our FAQ: a non-content rejection by Apple and/or Google, claimed within 14 days after purchase.