Offline and PWA - msupino/NavigationApp GitHub Wiki

๐Ÿ‡ฎ๐Ÿ‡ฑ ืขื‘ืจื™ืช

Offline and PWA

NavAid is installable as a PWA on iOS, Android, and desktop. After an online load, its service worker can serve the cached app shell and fetched datasets.

What "offline" covers, exactly. The planner works without a connection โ€” the chart you downloaded, the route, the navigation log, the plates you have opened. What needs a connection is anything published by somebody else while you fly: weather, NOTAM, traffic, the satellite and street-map layers, and airfield plates you have not opened before. The pages used to say runs fully offline once loaded, which was more than the product does. When the connection comes back NavAid catches up at once instead of at its next poll: chart tiles that failed while offline reload, NOTAM / SIGMET / AIRMET and the weather are fetched again, and a message says Back online: map, NOTAMs and weather updated. It goes by the phone's own online signal, so on Wi-Fi without internet it can come early; the regular 10-minute poll still follows.

The active navigation dataset is layer-specific (cvfr-route-graph.json, lsa-route-graph.json, or heli-route-graph.json). The app projects it into reporting points, leg altitudes, and comm-change points. Airfields and other fetched app assets are cached as they are used.

The CVFR chart is kept offline for you

The chart you fly with is no longer something to remember to download. On the live site, NavAid keeps the whole CVFR chart on the device automatically โ€” zooms 7โ€“13, the full sheet, not just the part you happened to look at. It audits what is already stored on each start and fetches only what is missing.

  • It waits for a suitable connection. With offlineCvfrUnmeteredOnly on (default), the automatic transfer holds off on cellular, on a connection reporting 2G/3G, and when the browser's data-saver is on. The status then reads waiting for connection.
  • The route goes first. Tiles within about 1.5 tiles of the planned route are fetched before the rest of the country, keeping the low-to-high zoom order inside each group. Change the route and the queue is re-prioritised around the new one โ€” so the corridor you are about to fly is complete first, even if the download is interrupted.
  • Only on production. PR previews, staging and local runs never start the large transfer; they share the same origin and cache, and a preview must not mutate a pilot's real pack. The manual button still works there.

The Offline maps sheet

The Charts section carries a compact status button โ€” Offline CVFR: checkingโ€ฆ / ready โœ“ / โฌ‡ Download CVFR offline โ€” 62%. It opens the Offline maps sheet, which shows tiles stored against tiles needed, a progress bar, and two actions:

Action What it does
โฌ‡ Download missing CVFR tiles Fetches only what is absent, now, regardless of connection type
Clear offline CVFR Removes the stored chart from this device. Automatic maintenance resumes next time the app opens

Pressing the compact button while coverage is incomplete starts the download as well as opening the sheet.

Other charts, as many as you like

Below the CVFR card the sheet lists every other chart that can be kept, and any number of them can be kept at once:

Chart What is kept
Navigation ยท Low Alt ยท Helicopters The whole chart, same zooms as CVFR. โฌ‡ Download, then a progress bar, Download missing tiles and Delete
ATS routes One image, fetched into the app's own cache (inside the app in the App Store / APK builds)
OpenFlightMaps The area on screen. Choose a Detail level โ€” up to zoom 8, 9, 10, 11 or 12 โ€” each shown with its tile count and approximate size; levels over 80,000 tiles are dimmed. Most of Europe fits to zoom 10 in about 1 GB. Save as many areas as you like, each with its own progress and Delete. Dimmed, with the reason, over Israel (no data) or for an area already kept at that detail

Downloads run side by side. Deleting one pack never removes a tile another pack still uses, and clearing CVFR leaves the other packs alone. Packs you chose that were cut short finish on the next suitable connection, on the same terms as CVFR.

Online only: Satellite and OpenStreetMap. Their providers do not allow downloading for offline use (OpenStreetMap's tile policy forbids it outright), and the sheet says so.

The world map needs no download. Country outlines and names ship with the app and are always drawn under every chart โ€” see Map Layers. With no connection and no pack, the map still shows where you are.

Storage lives in the dedicated navaid-tiles-v1 cache, filled by offline-tiles.js from the CORS tile mirror. The service worker serves a matching tile request cache-first when the pack has it and goes to the network on a miss. Packs survive app-shell service-worker upgrades and are removed only by Clear offline CVFR, a pack's Delete, or by browser storage tools. Which packs you chose is kept per device in navaid.offlinePacks.

Key Default
offlineAutoCvfr true Keep the CVFR chart available offline
offlineCvfrUnmeteredOnly true Update only on a suitable connection
offlineCvfrMinZoom 7 Widest zoom stored
offlineCvfrMaxZoom 13 Closest zoom stored

The app shell uses a separate versioned cache. HTML navigation is network-first with an offline shell fallback; versioned assets are cache-first and prune older siblings. See Service Worker.

The loading screen

Before the stylesheet or any script has arrived, index.html already draws NavAid's mark โ€” a departure fix, the leg climbing away from it, and the aircraft flying it โ€” with a second, smaller aircraft flying a circuit around it. The circuit is a real circle in a plane tilted away from the camera, so the browser foreshortens it, scales the aircraft with distance, and hides it behind the mark on the far side of the turn.

  • It is held for at least 2 s (bootLogoMinMs). On a warm cache the map paints a few hundred ms after the page, and the mark used to flash by half-drawn. It is a floor, not a wait: a slow map still clears the moment it arrives, and a 6 s belt-and-braces timeout clears it whatever happens.
  • Once the map is up behind it the screen stops taking pointer events, so the first tap of a quick start reaches the app instead of the marker.
  • prefers-reduced-motion: reduce keeps the mark and the circuit and stops the lap.

The same mark is generated into every icon the app is installed from โ€” favicon, both PWA icons (inside the maskable safe circle), the Android launcher square / round / adaptive foreground at five densities, and the splash screens โ€” by scripts/make-icons.mjs, from one description, so they cannot drift apart.

Mobile app (Android APK)

Installing. The APK is on GitHub Releases (android-v*). Open it and allow Install unknown apps; a new APK installs over the old one, keeping your routes โ€” don't uninstall first, that erases them. With Obtainium you are told of every new release: add https://github.com/msupino/NavigationApp, filter release titles by NavAid Android APK and APKs by navaid-.*\.apk (see the README).

It starts with no network at all. Since 1.9 the whole app is inside the APK, together with:

  • the CVFR chart at zooms 7โ€“12 (about 1,500 tiles) โ€” on first launch they are also copied into the offline chart store; zoom 13 is still a Wi-Fi download;
  • the settings of the day โ€” a copy of the live tuning gist taken when the APK was built, so a phone that has never been online starts with them (then the copy the phone last downloaded, then the live gist, each overriding the one before).

It is served at https://navaid.supino.org inside the phone, so routes, recordings and settings saved by the older APK (which loaded the website) are the same ones. Approach plates come from GitHub; the old service worker is retired.

Updates without a new APK. Every web deploy publishes an update bundle. The app downloads it by itself on Wi-Fi and switches to it the next time it starts โ€” never mid-flight; one that does not start is rolled back. Updates wait until the built-in tiles have been copied (a copy that could not finish, e.g. storage full, stops holding them back). Settings โ†’ App version shows the build and says what the update is waiting for:

  • Up to date (Check again);
  • Update available ยท 40 MB โ€” downloads by itself on Wi-Fi, or โ€ฆ after the charts finish copying right after a new APK (Download now takes it at once โ€” on mobile data too, after asking โ€” with a progress bar);
  • Downloadingโ€ฆ 45%, with its progress bar and nothing to press โ€” whether you pressed Download now or the app started it by itself on Wi-Fi; asking again meanwhile joins that download instead of fetching the same bundle twice;
  • Update ready: restart is offered when your position stops showing, or at the next start (Restart now).

It looks again whenever Wi-Fi arrives โ€” including just after start, before a flight โ€” and when the app comes back to the screen after 30 minutes, not only once at start; the row refreshes on every network change, so it says what is true now. A new APK is needed only for native changes.

It asks. Once an update has downloaded, the app asks once per version: Update ready โ€” Restart now / Later. Restart now switches in a couple of seconds and keeps the route and settings; Later leaves it for the next start. From 1.23, a new APK is announced the same way, once: NavAid 1.24 is available โ€” Download / Later; Download opens its release page, the browser downloads it, and Android's installer puts it over the app (its Open is the restart). Neither question is asked while recording, showing your location or connected to a simulator โ€” it waits until that stops.

Font size. The phone's font size (Settings โ†’ Display) scales all of the app's text by the same factor; the Save / My routes buttons stack one per row when their words no longer fit.

Background GPS and native Google sign-in for Drive are part of the APK.

iPhone and iPad

The repository supports both a remote development shell and an embedded iOS release build. Check the project's current distribution announcement for App Store or TestFlight access; repository build support alone does not establish availability.

Safari's Add to Home Screen remains an option. Keep the app in the foreground for location and alerts; do not rely on background or lock-screen tracking on iOS.

For distribution questions, contact [email protected].

Privacy and external data flows

Editing is local by default, but NavAid is not an absolute local-only system:

Feature Data sent
Google Drive (opt-in) Saved routes and, if enabled, allowlisted settings
AI assistant (BYOK) The prompt/context required for the request, to the chosen provider
Flight-plan filing Pilot, aircraft, route, and filing form fields to the configured filing service
Remote tuning configuration A small GitHub Gist JSON is requested automatically at startup unless ?nogist or ?gist=0 is used; no route/profile payload is sent. A copy of it ships with the app (data/gist-snapshot.js) for a device that has never downloaded it
Analytics Google Analytics (GA4) on the public web site only โ€” anonymous usage, no route, position or profile data. Not loaded in the Android/iOS app, on staging, PR-preview or branch builds, or locally. See privacy
Maps/assets and live aviation/weather Ordinary asset/map requests plus requests needed to fetch selected live data

API keys and endpoint/destination overrides remain device-local. BYOP PDFs are public files served from the stable root docs/byop/ Pages URL space.

The App Store build carries the app inside it

The remote development shell loads the live site. The Android APK (above) and the iOS release build bundle the app locally and fetches data and verified web updates over the network. Packaging code in the repository does not establish App Store or TestFlight availability.

Two consequences worth knowing:

  • Offline charts are stored differently there. The Cache API is not usable on the app's own origin, so the downloaded CVFR tiles live in the app's container through the Filesystem plugin, and the map and the magnifier read them from there. Same chart, same download, no service worker involved.
  • Web updates. The bundled app checks for a newer web bundle, verifies it, and arms it for the next cold start โ€” never mid-flight, never on a metered connection. A bundle that does not come up is rolled back to the one that did, ultimately to the bundle Apple reviewed. Native changes โ€” plugins, permissions, an SDK bump โ€” still need a release.

featureOtaUpdates (default true) is the gist switch for that check: turn it off and the embedded build stays on the bundle it shipped with until the next release. For maintainers: docs/app/ota.js holds the client, mobile/scripts/build-ota.mjs packs a bundle, mobile/scripts/bundle-web.mjs builds the embedded web root, and tests/ota-updates.spec.js and tests/ota-bundle.spec.js cover both ends. The one storage key involved is navaid.otaInstalling, which limits a pending bundle to one install attempt โ€” see Local storage schema.