aeharding / aeharding/wingover

Plan map comes back on the continental default after a flight (arrival framing races the pin load)

Open
#155 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
0
Forks
0
Avg merge
4h 2m
Merged PRs (30d)
6

Description

Land a flight, tap **Plan**, and the plan map can come back on MapKit's
construction camera — the whole of North America — with the pilot's route
loaded but off in another continent. The route pill in the corner reads the
correct distance the entire time, so the pins are there; only the camera is
wrong. A long-press in that state drops a pin in Kansas.

Found while debugging #151 on a live iPhone 11 simulator (iOS 26.5). The
drill long-pressed the plan map right after a flight and the pin it created
landed at `{"latitude":39.79999999999999,"longitude":-98.5}` — MapKit JS's
construction default, the geographic centre of the contiguous US. A
screenshot of the same moment shows North America under a "Route: 7.38 mi"
pill.

## Mechanism

A flight replaces the entire tab shell:

```tsx
// src/ui/App.tsx:95
if (inFlight) return ;
```

so `PlanPage` unmounts at flight start and mounts fresh at flight end. On
that fresh mount two async things race:

- `loadPlan()` → `listPins()` → PouchDB (`src/ui/pages/PlanPage.tsx:93`,
kicked off by the mount effect at :104 and `useIonViewWillEnter` at :99);
- `createBackend()` → the MapKit JS map (`src/ui/map/MapCanvas.tsx`).

The arrival framing runs exactly once, when the map wins:

```tsx
// src/ui/pages/PlanPage.tsx:246-266
// Initial camera, once — reads whatever pins exist when the map arrives.
const frameInitial = useEffectEvent((next: MapView) => {
if (pins.length === 1) { … } else if (pins.length > 1) { … fitBounds … }
});

useEffect(() => {
if (!map) return;
if (skipArrivalFrameRef.current) { skipArrivalFrameRef.current = false; return; }
frameInitial(map);
}, [map]);
```

Straight after a flight MapKit is warm and the map arrives before PouchDB
answers, so `frameInitial` reads `pins.length === 0`, frames nothing, and
nobody frames again when the pins land a moment later. On a cold launch the
DB usually wins, which is why this reads as intermittent.

`skipArrivalFrameRef` does not cover it: that flag is for a MapCanvas
re-create that carried its predecessor's camera over
(`next.restoredCamera`), and a post-flight mount is a brand new instance
with nothing to restore.

## Fix sketch (not attempted here)

Frame on the FIRST resolved `listPins()` rather than on map arrival — i.e.
track "the plan has loaded at least once" and let the arrival framing wait
for both that and the map, still exactly once per map instance. It must not
become "frame whenever pins change", or dropping your first pin on an empty
plan would yank the camera to it.

## Not the cause of #151

Called out because #151's fix had to work around it (the drill re-taps
Center-on-me after its calibration flight for exactly this reason), but the
announcement drill never returned to the Plan tab before, so this is not
what turned it red.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.