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
offlineCvfrUnmeteredOnlyon (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: reducekeeps 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.