Merge FB-10: fix map pan-recenter race

This commit is contained in:
2026-08-25 11:34:54 -05:00
3 changed files with 227 additions and 80 deletions

View File

@@ -1,6 +1,6 @@
# FB-10 — Manual map pan loses a race against the follow-recenter timer
**Depends on** — · **Size** M · **Status** Not started
**Depends on** — · **Size** M · **Status** Done
## Goal
The rider must be able to pan the map and have it stay where they put it. Today a pan
@@ -189,3 +189,53 @@ Any change to how often ambient position ticks arrive (`_interval` in
own needs (V3-04) and is not the bug; the bug is `RideMap` reacting to every tick with a
camera move regardless of what the rider is doing. FB-11's Route Planner tile issue,
tracked separately.
## Outcome
Shipped exactly as designed, with no deviation.
`lib/src/ui/components/ride_map.dart` now has a `_gestureInProgress` field on
`_RideMapState`. The `FlutterMap` is wrapped in a `Listener` that sets it `true` on
`onPointerDown` and `false` on `onPointerUp`/`onPointerCancel`. The `Listener` only
observes pointer events; it does not consume them, so `flutter_map`'s own drag and
pinch-zoom recognizers still work untouched. Both `postFrameCallback` bodies in
`didUpdateWidget` now check `if (_gestureInProgress) return;` right before the
`_controller.move(...)` call, alongside the existing `mounted`/`_following` checks.
`onPositionChanged` was left alone, as the design said.
Added one new widget test to `test/ride_map_test.dart`, in the "always-interactive
map + recenter (FB-06)" group: `a real drag beats an ambient tick that lands
mid-drag (FB-10)`. It starts a real pointer with `tester.startGesture`, holds it
down, then rebuilds the widget with a changed `ambientPosition` to simulate a GPS
tick landing mid-drag, and asserts right there — before any pointer movement — that
the camera has not snapped to the ambient fix. It then moves and lifts the pointer
and asserts the same thing again for the completed drag.
Getting this test to actually catch the bug took one extra iteration. The first
version did the pointer-down, the mid-drag ambient tick, then a real move-and-lift,
and only checked the camera position at the very end. That version passed even with
the fix removed, because the final drag always overwrites the camera regardless of
what the buggy recenter did in between — a drag is relative motion, so wherever the
camera started, the drag moves it away from there, and the end position is never
exactly equal to the ambient fix either way. The fix was to add the same assertion
right after the mid-drag tick, before any pointer movement happens. At that point,
with the pointer down but not yet moved, the old code snaps the camera to exactly
the ambient position (confirmed by temporarily removing the two
`_gestureInProgress` guards and re-running: the test failed with `Actual: <51.5>`,
the exact ambient fix latitude); the fixed code leaves the camera where it was.
Both the "old code fails, fixed code passes" checks were run and confirmed before
finishing.
One unrelated flake showed up during a full `flutter test` run:
`test/geo_test.dart`'s "douglas-peucker handles a full ride without stack overflow
and stays fast" failed once, then passed on every subsequent run in isolation and in
the full suite. It looks like a timing-sensitive performance assertion, not
something this change touches -- `ride_map.dart` and `geo.dart` share no code path.
`flutter analyze` stayed clean: the same 4 pre-existing info-level issues in
`crash_reporter.dart` and `map_connectivity.dart`, nothing new. `flutter test` went
from 434 to 435 passing (the one new test), everything else green.
No Android emulator or `adb` was available in this environment, so the on-device
check in Implementation step 5 was not attempted here. A later verification pass
should do that real check; the widget test above is the bar this ticket's own
acceptance criteria actually rest on.