M2-10: Heat map — every route on one map, to find the ones not taken #39

Closed
opened 2026-08-09 11:25:26 -04:00 by agent · 1 comment
Member

Every memo's route on one map, with a heat map of where I actually go — so I can see where I
don't.

Wes: "This way i can find new paths to take."

The inversion worth building for

A heat map's obvious reading is the bright parts. This feature is for the dark parts. The stated
use is finding a new route, which means the value is in the streets near the hot ones that have
no colour at all. That should shape the rendering: a hot path on a black background is pretty and
useless; a hot path against legible surrounding streets is the feature.

This has been sitting in Wes's notes since 2026-07-27, framed the same way then: "I walk roughly
the same path to the library nearly every day, so that path would be a bright red hotspot"
, and
two uses fall out — choose a different path, and see where the best ideas come from.

Entry point and layout

  • A map icon in the top-left of the memo list, opposite the settings icon.
  • A dedicated view. This is not the per-memo map from M2-08 — that one shows one route in a
    detail page; this one aggregates every route there is.
  • A date filter, with "All" as a shortcut.
  • Opens zoomed to a neighbourhood view, and pinch, pan and zoom behave the way they do in any map
    app. Stock Map gestures, not a custom gesture recogniser.

The open question: what "current location" means here

The initial zoom is specified as neighbourhood-level based on current location. That conflicts
with a rule M2-06 states outright:

There is no state in which Stash is holding the location and not recording.

Opening a map view is not recording. Two ways out, and this needs Wes's call at the planning
checkpoint:

Decided 2026-08-09: this view takes a location fix, and M2-06's rule gets an explicit
exception.
Wes: "yes, this opens up a new view that would require location."

Centring on the last track's endpoint was the alternative and it was rejected — it only works when
you are already at home, and the case that matters most is standing somewhere unfamiliar wanting to
know what is unexplored here.

The exception is narrow, and it gets written down rather than absorbed quietly, because M2-06
states its rule flatly and a reader of that file must not be able to conclude the code contradicts
it:

  • A fix is taken only while this view is on screen, and updates stop when it is dismissed. A
    centring aid, not tracking.
  • It is never written to disk. Nothing about it enters a memo, a track, or a log.
  • If location is denied the map still opens, centred on the last track's endpoint or on the whole
    library's extent. A denied permission never blocks the feature, it only costs the centring.
  • AGENTS.md and M2-06's documentation are amended in this PR to state the exception and its
    bounds. Two rules that disagree is worse than the looser rule stated honestly.

When the feature has never been turned on

Location is opt-in and off by default (M2-04), so the ordinary first visit here has no data and no
permission. That is the moment to explain the feature, and this is the only place a user naturally
arrives already wanting it.

Wes: "there should be a notification and an option to enable it here. The notification should tell
the user what the feature does and that the data doesn't get sent anywhere."

So the empty state is an explainer with an enable action, not a shrug:

  • What it does — records where you walked while a memo is recording, so every route can be
    drawn on one map and the places you never go become visible.
  • When it collects — only while a recording is running. Never otherwise.
  • Where it goes — a .gpx file beside the memo, on this phone. It is not sent anywhere.
    There is no server in M2 at all, and when sync arrives the relay is blind by design and cannot
    read it.
  • An enable action doing exactly what the Settings toggle does, prompting for the entitlement,
    so nobody has to go find Settings to act on what they just read.

This copy and M2-04's Settings copy say the same thing and must not drift. One string source, used
in both places.

A memo recorded before the feature was enabled has no track and never will. Say so, so an empty
map straight after enabling reads as "go for a walk" rather than as broken.

The tension with "no index"

Reading every track file on every open is the honest implementation and it matches the principle
the app is built on — the files are the record, there is no database. It is also the first feature
that genuinely wants an index, and it degrades with the number of memos.

Do the honest thing first and measure it. Read them all, on a real library, and write the
number down. Add caching when there is a measurement saying it is needed, not in anticipation — and
if caching arrives, it is a derived cache that can be deleted and rebuilt from the files, never a
second source of truth.

The heat map itself is custom, and that needs saying out loud

AGENTS.md says reach for the stock component first and say why when you do not. MapKit has no
heat map, so this is a hand-drawn overlay: bin the points into a grid, weight by how many distinct
recordings crossed each cell rather than by raw point count — otherwise standing still for five
minutes outranks walking a street a hundred times — and render density.

Keep it small and say in a comment why it exists. Weighting by distinct visits rather than samples
is the substance of this issue and it is what makes the map answer the question being asked.

⚠️ This is a map of where Wes lives

No screenshots of this view in issues, PRs, commit messages or review artifacts. This
repository is public, and a heat map of daily walks is a home address with extra steps. Test
fixtures are synthetic coordinates, as everywhere else — and here that rule has teeth rather than
being a formality.

Depends on

M2-07 (there is nothing to map until tracks are being written) and M2-08 (the map component and its
conventions). Sequenced after M2-09, as the last thing in the milestone.

Deliberately not in this

The dashboard from the 7/27 note — distance week over week, memo statistics, correlating good ideas
with places — is a real idea and a separate one. So is filtering the map by anything other than
date. This issue is the map, the heat, and the date filter.

Done when

  • The map icon opens a view showing every route from every memo that has a track.
  • The date filter narrows it, and "All" restores it.
  • It opens at neighbourhood zoom and pans and pinches like a map app.
  • The heat reflects how often a street was walked, not how long was spent standing on it.
  • Streets with no coverage are legible, because those are the answer to the question.
  • A library with no tracks shows an empty state that says why.

Milestone 2 of the Stash v3 rebuild. M2 is still app-side — no sync, no account, no server.
Sync arrives in M3.

Origin: the 2026-07-27 walk memo, captured in the vault as "Stash - Walk Path Mapping and
Heatmap" and restated on 2026-08-09.

Working agreement for every issue in this repo: feature branch, tested before the PR opens
(on-device where the label says so), then Wes reviews the PR and we walk the code together. Docs
update in the same commit. A PR that takes more than 20 minutes to review is too big — say so and
split it.

Every memo's route on one map, with a heat map of where I actually go — so I can see where I **don't**. Wes: *"This way i can find new paths to take."* ## The inversion worth building for A heat map's obvious reading is the bright parts. This feature is for the dark parts. The stated use is finding a new route, which means the value is in the streets **near** the hot ones that have no colour at all. That should shape the rendering: a hot path on a black background is pretty and useless; a hot path against legible surrounding streets is the feature. This has been sitting in Wes's notes since 2026-07-27, framed the same way then: *"I walk roughly the same path to the library nearly every day, so that path would be a bright red hotspot"*, and two uses fall out — choose a different path, and see where the best ideas come from. ## Entry point and layout - A **map icon in the top-left** of the memo list, opposite the settings icon. - A dedicated view. **This is not the per-memo map from M2-08** — that one shows one route in a detail page; this one aggregates every route there is. - A **date filter**, with **"All"** as a shortcut. - Opens zoomed to a neighbourhood view, and pinch, pan and zoom behave the way they do in any map app. Stock `Map` gestures, not a custom gesture recogniser. ## The open question: what "current location" means here The initial zoom is specified as neighbourhood-level **based on current location**. That conflicts with a rule M2-06 states outright: > There is no state in which Stash is holding the location and not recording. Opening a map view is not recording. Two ways out, and this needs Wes's call at the planning checkpoint: **Decided 2026-08-09: this view takes a location fix, and M2-06's rule gets an explicit exception.** Wes: *"yes, this opens up a new view that would require location."* Centring on the last track's endpoint was the alternative and it was rejected — it only works when you are already at home, and the case that matters most is standing somewhere unfamiliar wanting to know what is unexplored *here*. The exception is narrow, and it gets written down rather than absorbed quietly, because M2-06 states its rule flatly and a reader of that file must not be able to conclude the code contradicts it: - A fix is taken **only while this view is on screen**, and updates stop when it is dismissed. A centring aid, not tracking. - It is **never written to disk.** Nothing about it enters a memo, a track, or a log. - If location is denied the map still opens, centred on the last track's endpoint or on the whole library's extent. **A denied permission never blocks the feature**, it only costs the centring. - `AGENTS.md` and M2-06's documentation are amended in this PR to state the exception and its bounds. Two rules that disagree is worse than the looser rule stated honestly. ## When the feature has never been turned on Location is opt-in and off by default (M2-04), so the ordinary first visit here has no data and no permission. That is the moment to explain the feature, and this is the only place a user naturally arrives already wanting it. Wes: *"there should be a notification and an option to enable it here. The notification should tell the user what the feature does and that the data doesn't get sent anywhere."* So the empty state is an explainer with an enable action, not a shrug: - **What it does** — records where you walked while a memo is recording, so every route can be drawn on one map and the places you never go become visible. - **When it collects** — only while a recording is running. Never otherwise. - **Where it goes** — a `.gpx` file beside the memo, on this phone. **It is not sent anywhere.** There is no server in M2 at all, and when sync arrives the relay is blind by design and cannot read it. - **An enable action** doing exactly what the Settings toggle does, prompting for the entitlement, so nobody has to go find Settings to act on what they just read. This copy and M2-04's Settings copy say the same thing and must not drift. One string source, used in both places. **A memo recorded before the feature was enabled has no track and never will.** Say so, so an empty map straight after enabling reads as "go for a walk" rather than as broken. ## The tension with "no index" Reading every track file on every open is the honest implementation and it matches the principle the app is built on — the files are the record, there is no database. It is also the first feature that genuinely wants an index, and it degrades with the number of memos. **Do the honest thing first and measure it.** Read them all, on a real library, and write the number down. Add caching when there is a measurement saying it is needed, not in anticipation — and if caching arrives, it is a derived cache that can be deleted and rebuilt from the files, never a second source of truth. ## The heat map itself is custom, and that needs saying out loud `AGENTS.md` says reach for the stock component first and say why when you do not. MapKit has no heat map, so this is a hand-drawn overlay: bin the points into a grid, weight by how many distinct recordings crossed each cell rather than by raw point count — otherwise standing still for five minutes outranks walking a street a hundred times — and render density. Keep it small and say in a comment why it exists. Weighting by distinct visits rather than samples is the substance of this issue and it is what makes the map answer the question being asked. ## ⚠️ This is a map of where Wes lives **No screenshots of this view in issues, PRs, commit messages or review artifacts.** This repository is public, and a heat map of daily walks is a home address with extra steps. Test fixtures are synthetic coordinates, as everywhere else — and here that rule has teeth rather than being a formality. ## Depends on M2-07 (there is nothing to map until tracks are being written) and M2-08 (the map component and its conventions). Sequenced after **M2-09**, as the last thing in the milestone. ## Deliberately not in this The dashboard from the 7/27 note — distance week over week, memo statistics, correlating good ideas with places — is a real idea and a separate one. So is filtering the map by anything other than date. This issue is the map, the heat, and the date filter. ## Done when - [ ] The map icon opens a view showing every route from every memo that has a track. - [ ] The date filter narrows it, and "All" restores it. - [ ] It opens at neighbourhood zoom and pans and pinches like a map app. - [ ] The heat reflects how often a street was walked, not how long was spent standing on it. - [ ] Streets with no coverage are legible, because those are the answer to the question. - [ ] A library with no tracks shows an empty state that says why. --- *Milestone 2 of the Stash v3 rebuild. M2 is still app-side — **no sync, no account, no server**. Sync arrives in M3.* *Origin: the 2026-07-27 walk memo, captured in the vault as "Stash - Walk Path Mapping and Heatmap" and restated on 2026-08-09.* *Working agreement for every issue in this repo: feature branch, tested before the PR opens (on-device where the label says so), then Wes reviews the PR and we walk the code together. Docs update in the same commit. A PR that takes more than 20 minutes to review is too big — say so and split it.*
Owner

This is solid

This is solid
wk closed this issue 2026-08-10 15:04:13 -04:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#39
No description provided.