M2-08: The route on a map #51

Merged
wk merged 1 commit from m2-08-route-map into main 2026-08-10 00:07:26 -04:00
Member

Closes #35.

A route section on the memo detail screen: the walk as a line, start and end flagged, a marker that
follows the playhead and lands on the right stretch after a transcript seek, and a tap that opens
the map full size. Most memos have no track and show no section at all.

The three decisions worth reviewing

The map shares the track's strict parser. The transcript's rule is the opposite — a file going
on screen is read leniently, because refusing a whole transcript over one bad cue shows somebody an
empty screen where their words should be. A route is not a list of independent claims. Drop a point
out of the middle and the line does not get shorter, it moves, straight through whatever lies
between the two points either side of the hole — drawn with exactly the confidence of the parts that
are real. A track that will not parse is reported instead.

The marker is absent rather than wrong. Nothing before the first fix, on another memo's player,
at a standing zero, or without a recording start. Nothing past thirty seconds of a fix either, which
covers a missing part, a tunnel, and a route that ends before its audio. And nothing when the two
clocks are known to disagree: an interruption is spliced out of the audio and not out of the route,
so a memo interrupted by a call is shorter than the walk it describes. The track file records no
mapping between wall time and audio time, so that cannot be corrected — but a route spanning more
time than its memo can be recognised. Correcting it is #50.

The opt-in copy changed, and the security review is why. It promised the route "is not sent
anywhere", which was true of every build until this one. A map view is a network client: Apple Maps
is sent the area on screen in order to return the tiles for it, so opening a route tells Apple
roughly where it is. The promise is narrowed to the one the code keeps — Stash never uploads the
route — and the disclosure is added beside it, in Settings and in both permission prompts.

Still open for you: whether the map should be behind an explicit tap rather than drawn on open.
People who enabled collection under the old wording consented to something narrower than what the
app now does, and changing the copy does not retroactively fix that.

One visible side effect

A memo with no transcript now shows its own header, which it did not before. The route cannot live
inside the one branch of the detail screen that happened to be a list, so all four transcript
outcomes are one list now.

What was validated

On the simulator, against synthetic fixtures written into the documents directory:

  1. A drag starting on the map scrolls the list; the map does not pan.
  2. Tapping the map opens the sheet and the memo goes on playing.
  3. The marker walks the route during playback and lands on the right stretch after a cue seek.
  4. Replacing the .gpx from outside and re-foregrounding redraws the route and re-frames the
    camera on the new one.
  5. A memo with no track shows no section; one with unreadable parts says so; one whose track file
    holds no positions says that instead.
  6. The app-switcher snapshot shows the hidden panel — checked by decoding the .ktx iOS actually
    wrote, not by inference.

575 tests pass, up from 534. make lint is clean.

What was not

Nothing on the phone. The build is installed on wes-iphone and was pushed at your instruction
before the device pass, so nothing in this PR has been exercised against a route Stash collected by
walking — every fixture was written by a script. Specifically unverified: a real track drawn from
real fixes, what the map costs in battery or data over a long memo, and the interruption case, which
is reasoned about and unit-tested but has never been produced by taking a call mid-recording.

Codex

Plan review, code review and security review are in .agent/m2-08-route-map/, with every finding's
disposition in SUMMARY.md. Seventeen findings, no criticals, all taken; three changed the design
and are the three above.

The decision file is in stash-docs on m2-08-route-map-decision.

Closes #35. A route section on the memo detail screen: the walk as a line, start and end flagged, a marker that follows the playhead and lands on the right stretch after a transcript seek, and a tap that opens the map full size. Most memos have no track and show no section at all. ## The three decisions worth reviewing **The map shares the track's strict parser.** The transcript's rule is the opposite — a file going on screen is read leniently, because refusing a whole transcript over one bad cue shows somebody an empty screen where their words should be. A route is not a list of independent claims. Drop a point out of the middle and the line does not get shorter, it moves, straight through whatever lies between the two points either side of the hole — drawn with exactly the confidence of the parts that are real. A track that will not parse is reported instead. **The marker is absent rather than wrong.** Nothing before the first fix, on another memo's player, at a standing zero, or without a recording start. Nothing past thirty seconds of a fix either, which covers a missing part, a tunnel, and a route that ends before its audio. And nothing when the two clocks are known to disagree: an interruption is spliced out of the audio and not out of the route, so a memo interrupted by a call is shorter than the walk it describes. The track file records no mapping between wall time and audio time, so that cannot be corrected — but a route spanning more time than its memo can be recognised. Correcting it is #50. **The opt-in copy changed, and the security review is why.** It promised the route "is not sent anywhere", which was true of every build until this one. A map view is a network client: Apple Maps is sent the area on screen in order to return the tiles for it, so opening a route tells Apple roughly where it is. The promise is narrowed to the one the code keeps — Stash never uploads the route — and the disclosure is added beside it, in Settings and in both permission prompts. **Still open for you:** whether the map should be behind an explicit tap rather than drawn on open. People who enabled collection under the old wording consented to something narrower than what the app now does, and changing the copy does not retroactively fix that. ## One visible side effect A memo with no transcript now shows its own header, which it did not before. The route cannot live inside the one branch of the detail screen that happened to be a list, so all four transcript outcomes are one list now. ## What was validated On the simulator, against synthetic fixtures written into the documents directory: 1. A drag starting on the map scrolls the list; the map does not pan. 2. Tapping the map opens the sheet and the memo goes on playing. 3. The marker walks the route during playback and lands on the right stretch after a cue seek. 4. Replacing the `.gpx` from outside and re-foregrounding redraws the route **and** re-frames the camera on the new one. 5. A memo with no track shows no section; one with unreadable parts says so; one whose track file holds no positions says that instead. 6. The app-switcher snapshot shows the hidden panel — checked by decoding the `.ktx` iOS actually wrote, not by inference. 575 tests pass, up from 534. `make lint` is clean. ## What was not **Nothing on the phone.** The build is installed on wes-iphone and was pushed at your instruction before the device pass, so nothing in this PR has been exercised against a route Stash collected by walking — every fixture was written by a script. Specifically unverified: a real track drawn from real fixes, what the map costs in battery or data over a long memo, and the interruption case, which is reasoned about and unit-tested but has never been produced by taking a call mid-recording. ## Codex Plan review, code review and security review are in `.agent/m2-08-route-map/`, with every finding's disposition in `SUMMARY.md`. Seventeen findings, no criticals, all taken; three changed the design and are the three above. The decision file is in `stash-docs` on `m2-08-route-map-decision`.
A route section on the memo detail screen: the walk as a line, start and
end flagged, and a marker that follows the playhead and lands right after
a transcript seek. Tapping opens it full size. Most memos have no track
and show no section at all.

The map shares the track's strict reader rather than growing a lenient
one. The transcript's leniency is right because a transcript is a list of
independent claims and refusing all of it over one bad cue shows somebody
an empty screen; a route is one continuous claim, and a line drawn from a
file with points dropped out of it goes through streets nobody walked
down, with the confidence of one they did.

The marker is absent rather than wrong. Nothing before the first fix, on
another memo's player, at a standing zero, or without a recording start.
Nothing past thirty seconds of a fix either, which is what covers a
missing part, a tunnel, and a route that ends before its audio — holding
the last known fix through a hole says the file knows something it has
said it does not, and produces a marker that sits still and then jumps.

And nothing at all when the two clocks are known to disagree. An
interruption is spliced out of the audio and not out of the route, so a
memo interrupted by a call is shorter than the walk it describes. The
track file records no mapping between wall time and audio time so it
cannot be corrected, but a route spanning more time than its memo can be
recognised. Correcting it needs the file format to carry the mapping.

TrackPoint's equality is written out because an unknown radius is a NaN,
which made every route read from a third-party GPX unequal to itself —
SwiftUI compares a view's inputs, so that is a route that has always just
changed, re-framing the camera on every update and snapping a pan back in
the larger map.

The framing is measured both ways round the world and the narrower wins.
A route across the antimeridian is otherwise framed on the whole planet.

The map is the first network client this app has had, and the opt-in copy
promised the route "is not sent anywhere". Apple Maps is sent the area on
screen in order to return the tiles for it, so looking at a route tells
Apple roughly where it is. The promise is narrowed to the one the code
keeps — Stash never uploads the route — and the disclosure is added
beside it, in Settings and in both permission prompts.

The detail screen is one list in all four transcript outcomes, because
the route does not depend on there being words. A memo with no transcript
now shows its own header, which it did not before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
wk merged commit 9d62c5f581 into main 2026-08-10 00:07:26 -04:00
wk deleted branch m2-08-route-map 2026-08-10 00:07:26 -04:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Stash/stash-ios!51
No description provided.