Follow-on to #164. #164 guarantees the track always draws. This issue is about keeping the basemap too, so an in-flight restart out of coverage still shows the ground you were just flying over.
The key finding: the tiles are already on the device
MapKit serves tiles with cache-control: public, max-age=604800 (7 days), and WebKit's cache persists across launches. So a flight flown with service has already downloaded the tiles for that exact area. Nothing is missing offline. MapKit simply refuses to start, so the data is locked, not absent.
Proven: capture every MapKit response online, then load the app in a fresh browser with a hard network block, serving only the captured bytes. MapKit initialises completely and paints a full Apple basemap (terrain, labels, coastlines, attribution) with zero network. 75/75 replayed, no console errors.
Note no-store on the bootstrap does not prevent this. It binds the browser's HTTP cache, not storage we control.
What actually blocks an offline relaunch
Only two responses. Everything else is already cached and long-lived.
| endpoint |
cache-control |
offline? |
/mk/6.0.x/libs/mapkit.core.*.js |
max-age=31322996 (~362d) |
cached, fine |
/md/v1/vtile, /ttile, /icon |
max-age=604800 (7d), s-maxage=2 |
cached, fine |
/mk/6/mapkit.core.js (version pointer) |
max-age=23..275 |
expires, blocks |
/ma/bootstrap (auth) |
max-age=0, no-cache, no-store |
never cached, blocks |
So a shim serving just those two from local storage could let WebKit's own cache supply the rest.
Open question: does reauth cap it?
/ma/bootstrap returns an accessKey with expiresInSeconds: 1800, and the key embeds an issue timestamp. Forcing expiresInSeconds: 0 in a replay produced an initialised map that painted no tiles (empty grid) - but that test tells MapKit the key is dead, which a verbatim replay does not. A stored bootstrap still says 1800, so MapKit may compute the deadline from receipt and never learn otherwise, with no server present to disagree.
An experiment is running: capture untouched, wait out the full 30 minutes, replay verbatim. Result decides scope:
- Tiles paint: no practical ceiling; the shim is worth building.
- Empty grid: reauth caps continuity at ~30 min from last bootstrap, and only owned tiles give multi-hour coverage.
Mechanism (PWA and native are different problems)
- PWA (
https://wingover.app): service workers work. src/sw.ts (workbox) already exists. Note no registerSW call was found in src/ or index.html, so the worker may not be registering at all today - worth checking separately.
- Native (
tauri://localhost): a custom scheme, where WebKit does not run service workers. The PWA worker does nothing for the app. NSURLProtocol does not see WKWebView traffic and setURLSchemeHandler only handles custom schemes, so neither helps. The candidate is WKWebsiteDataStore.proxyConfigurations (public API, iOS 17+), routing webview traffic through a proxy we run. Untested.
Before building
Apple's MapKit JS terms need a real read. Their own max-age=604800 sanctions a browser cache; a custom store that replays their responses is a different question, and this ships on the App Store.
Follow-on to #164. #164 guarantees the track always draws. This issue is about keeping the basemap too, so an in-flight restart out of coverage still shows the ground you were just flying over.
The key finding: the tiles are already on the device
MapKit serves tiles with
cache-control: public, max-age=604800(7 days), and WebKit's cache persists across launches. So a flight flown with service has already downloaded the tiles for that exact area. Nothing is missing offline. MapKit simply refuses to start, so the data is locked, not absent.Proven: capture every MapKit response online, then load the app in a fresh browser with a hard network block, serving only the captured bytes. MapKit initialises completely and paints a full Apple basemap (terrain, labels, coastlines, attribution) with zero network. 75/75 replayed, no console errors.
Note
no-storeon the bootstrap does not prevent this. It binds the browser's HTTP cache, not storage we control.What actually blocks an offline relaunch
Only two responses. Everything else is already cached and long-lived.
/mk/6.0.x/libs/mapkit.core.*.jsmax-age=31322996(~362d)/md/v1/vtile,/ttile,/iconmax-age=604800(7d),s-maxage=2/mk/6/mapkit.core.js(version pointer)max-age=23..275/ma/bootstrap(auth)max-age=0, no-cache, no-storeSo a shim serving just those two from local storage could let WebKit's own cache supply the rest.
Open question: does reauth cap it?
/ma/bootstrapreturns anaccessKeywithexpiresInSeconds: 1800, and the key embeds an issue timestamp. ForcingexpiresInSeconds: 0in a replay produced an initialised map that painted no tiles (empty grid) - but that test tells MapKit the key is dead, which a verbatim replay does not. A stored bootstrap still says1800, so MapKit may compute the deadline from receipt and never learn otherwise, with no server present to disagree.An experiment is running: capture untouched, wait out the full 30 minutes, replay verbatim. Result decides scope:
Mechanism (PWA and native are different problems)
https://wingover.app): service workers work.src/sw.ts(workbox) already exists. Note noregisterSWcall was found insrc/orindex.html, so the worker may not be registering at all today - worth checking separately.tauri://localhost): a custom scheme, where WebKit does not run service workers. The PWA worker does nothing for the app.NSURLProtocoldoes not see WKWebView traffic andsetURLSchemeHandleronly handles custom schemes, so neither helps. The candidate isWKWebsiteDataStore.proxyConfigurations(public API, iOS 17+), routing webview traffic through a proxy we run. Untested.Before building
Apple's MapKit JS terms need a real read. Their own
max-age=604800sanctions a browser cache; a custom store that replays their responses is a different question, and this ships on the App Store.