Raise the tile buffer and retain off-screen markers on mobile #193

Merged
rob merged 2 commits from fix/task-250-mobile-tile-buffer into main 2026-08-17 10:57:58 +00:00
Owner

Task 250. Two of the two options Rob scoped, updateWhenIdle deliberately untouched.

  • L.tileLayer's keepBuffer raised from Leaflet's default of 2 to 4.
  • L.markerClusterGroup's removeOutsideVisibleBounds set to false, so a marker outside the current viewport stays in the DOM instead of being pulled out and re-added on the next moveend.

keepBuffer — measured, not estimated, and the finding is more modest than the ticket's own working assumption. Read against the vendored Leaflet 1.9.4 source, keepBuffer only decides which already-fetched tiles survive pruning once they scroll off-screen — the fetch queue for the current view is built from the viewport's own bounds alone, with no buffer term. A Playwright harness (real headless Chromium, real vendored leaflet.js, tile requests intercepted locally so nothing hit a real OSM server) counted requests for a pan-and-return on a mobile-sized viewport (390×844) at keepBuffer 2, 4 and 6: for every distance tested, up to nearly two screen-widths, the default of 2 already avoided every re-fetch on return, and no distance found any difference between 2, 4 and 6. Push a pan far enough (several screen-widths) and all three values fail identically (full re-fetch). Full table and reasoning in ADR-0165.

4 is chosen over 6 because no measured scenario showed 6 requesting fewer tiles than 4 — 6 costs strictly more retained off-screen tiles for zero observed benefit. Both changes proceed anyway, per Rob's own scoped direction, because raising keepBuffer never causes a request for a tile the reader isn't actively viewing (checked against the OSM tile policy's own definition of "bulk downloading"), so there's no cost being accepted for an absent gain — this PR just doesn't claim a benefit the numbers don't show.

What this does not fix. updateWhenIdle stays unset (Rob's confirmed ruling) — no tile loads during a drag either way. A drag into genuinely new territory still shows empty tiles until the finger lifts, regardless of keepBuffer. The marker half of the report is fully addressed; the tile half is partial.

Verified: the two Leaflet objects createMap builds carry the intended options (MobileTileAndMarkerRetentionJourneyTests, real headless Chromium), and a marker on the opposite side of the world from the current view stays a real DOM element after the view moves. Not verified, and cannot be from here: how a real drag feels on a real phone (task 156 — no browser). Neither drag-zoom (ADR-0143/0146/0147) nor deferred-click (ADR-0144/0145) code is touched.

No Answered by added to ADR-0061 or ADR-0130 — neither explicitly deferred this question (ADR-0107's own condition), so this is a fresh record, not an answer to either.

Task 250. Two of the two options Rob scoped, `updateWhenIdle` deliberately untouched. - `L.tileLayer`'s `keepBuffer` raised from Leaflet's default of 2 to 4. - `L.markerClusterGroup`'s `removeOutsideVisibleBounds` set to `false`, so a marker outside the current viewport stays in the DOM instead of being pulled out and re-added on the next `moveend`. **`keepBuffer` — measured, not estimated, and the finding is more modest than the ticket's own working assumption.** Read against the vendored Leaflet 1.9.4 source, `keepBuffer` only decides which *already-fetched* tiles survive pruning once they scroll off-screen — the fetch queue for the current view is built from the viewport's own bounds alone, with no buffer term. A Playwright harness (real headless Chromium, real vendored `leaflet.js`, tile requests intercepted locally so nothing hit a real OSM server) counted requests for a pan-and-return on a mobile-sized viewport (390×844) at `keepBuffer` 2, 4 and 6: for every distance tested, up to nearly two screen-widths, the *default* of 2 already avoided every re-fetch on return, and no distance found any difference between 2, 4 and 6. Push a pan far enough (several screen-widths) and all three values fail identically (full re-fetch). Full table and reasoning in ADR-0165. 4 is chosen over 6 because no measured scenario showed 6 requesting fewer tiles than 4 — 6 costs strictly more retained off-screen tiles for zero observed benefit. Both changes proceed anyway, per Rob's own scoped direction, because raising `keepBuffer` never causes a request for a tile the reader isn't actively viewing (checked against the OSM tile policy's own definition of "bulk downloading"), so there's no cost being accepted for an absent gain — this PR just doesn't claim a benefit the numbers don't show. **What this does not fix.** `updateWhenIdle` stays unset (Rob's confirmed ruling) — no tile loads *during* a drag either way. A drag into genuinely new territory still shows empty tiles until the finger lifts, regardless of `keepBuffer`. The marker half of the report is fully addressed; the tile half is partial. **Verified:** the two Leaflet objects `createMap` builds carry the intended options (`MobileTileAndMarkerRetentionJourneyTests`, real headless Chromium), and a marker on the opposite side of the world from the current view stays a real DOM element after the view moves. **Not verified, and cannot be from here:** how a real drag feels on a real phone (task 156 — no browser). Neither drag-zoom (ADR-0143/0146/0147) nor deferred-click (ADR-0144/0145) code is touched. No `Answered by` added to ADR-0061 or ADR-0130 — neither explicitly deferred this question (ADR-0107's own condition), so this is a fresh record, not an answer to either.
Raise the tile buffer and retain off-screen markers on mobile
Some checks failed
CI / build (pull_request) Successful in 4m48s
CI / container-images (pull_request) Successful in 2s
CI / e2e (pull_request) Failing after 4m13s
77d6dde4f5
Discard setView's chainable return value in the retention E2E test
Some checks failed
CI / build (pull_request) Successful in 6m55s
CI / container-images (pull_request) Successful in 4s
CI / e2e (pull_request) Failing after 9m29s
c8d79e0469
rob force-pushed fix/task-250-mobile-tile-buffer from c8d79e0469
Some checks failed
CI / build (pull_request) Successful in 6m55s
CI / container-images (pull_request) Successful in 4s
CI / e2e (pull_request) Failing after 9m29s
to 8d4ffa1682
All checks were successful
CI / build (pull_request) Successful in 3m38s
CI / container-images (pull_request) Successful in 4s
CI / e2e (pull_request) Successful in 3m37s
2026-08-17 10:44:36 +00:00
Compare
rob left a comment

Verdict: mergeable

updateWhenIdle is confirmed untouched anywhere in the diff — only referenced in comments explaining why it stays alone.

The candour on the tile half is exactly what was asked for: the PR body and ADR-0165 both say plainly that the marker half is fully fixed and the tile half is partial (still no fetch during a drag, only more already-fetched tiles surviving pruning around the settled view). That's the single most important thing here and it's handled well.

keepBuffer 4-vs-6: the measured table is in ADR-0165, the OSM "bulk downloading" definition is quoted rather than asserted, and the conclusion (no scenario found 6 beating 4) follows from the numbers rather than preference. The finding that keepBuffer doesn't affect the fetch queue at all — only pruning of already-fetched tiles — checks out against Leaflet's own documented semantics, and it's good that the PR says its own working assumption in the ticket was wrong rather than quietly discarding it.

removeOutsideVisibleBounds: false: justified qualitatively (bounded by places-per-caller at hobby scale, clustering already caps on-screen DOM nodes) rather than from an actual measured marker count/render-cost test. That's a softer form of evidence than the tile-request table got. Not a blocker given the scale argument is reasonable and clustering is already in place, but worth knowing it's reasoning-from-scale rather than a measurement, if a caller ever has an unusually large single group.

No regression risk in the gesture code — nothing in the diff touches DoubleTapDragZoom or deferSingleClick/cancelPendingClick, and ADR-0165's reasoning that removeOutsideVisibleBounds only affects moveend pruning (which the drag-zoom gesture doesn't fire mid-drag) is consistent with the existing code.

CI history: the fix commit ("Discard setView's chainable return value") is well-explained and consistent with a real, previously-seen defect class (chainable Leaflet methods returning something Playwright can't serialise) — that part checks out. What I couldn't corroborate from available tooling (no log access) is the two LeafletControlThemingTests failures on the prior failing run. That file is untouched by this PR, and the project has documented CI contention elsewhere around this same time (concurrent E2E runs on a single-threaded runner), so contention is the more likely explanation than a regression from these map-option changes — but neither the PR body nor ADR-0165 mentions that failure or the belief that it was unrelated. Worth a line for traceability, not worth blocking on given the file is untouched and the current head is green.

ADR-0165 is correctly allocated with the measured figures in it, and the "no Answered by on ADR-0061/ADR-0130" reasoning holds — I checked both records and neither names keepBuffer/removeOutsideVisibleBounds as an explicit open question, so ADR-0107's condition 3 is correctly applied. No frozen ADR body is edited.

British spelling and no AI attribution both check out.

Verdict: mergeable `updateWhenIdle` is confirmed untouched anywhere in the diff — only referenced in comments explaining why it stays alone. The candour on the tile half is exactly what was asked for: the PR body and ADR-0165 both say plainly that the marker half is fully fixed and the tile half is partial (still no fetch during a drag, only more already-fetched tiles surviving pruning around the settled view). That's the single most important thing here and it's handled well. `keepBuffer` 4-vs-6: the measured table is in ADR-0165, the OSM "bulk downloading" definition is quoted rather than asserted, and the conclusion (no scenario found 6 beating 4) follows from the numbers rather than preference. The finding that `keepBuffer` doesn't affect the fetch queue at all — only pruning of already-fetched tiles — checks out against Leaflet's own documented semantics, and it's good that the PR says its own working assumption in the ticket was wrong rather than quietly discarding it. `removeOutsideVisibleBounds: false`: justified qualitatively (bounded by places-per-caller at hobby scale, clustering already caps on-screen DOM nodes) rather than from an actual measured marker count/render-cost test. That's a softer form of evidence than the tile-request table got. Not a blocker given the scale argument is reasonable and clustering is already in place, but worth knowing it's reasoning-from-scale rather than a measurement, if a caller ever has an unusually large single group. No regression risk in the gesture code — nothing in the diff touches `DoubleTapDragZoom` or `deferSingleClick`/`cancelPendingClick`, and ADR-0165's reasoning that `removeOutsideVisibleBounds` only affects `moveend` pruning (which the drag-zoom gesture doesn't fire mid-drag) is consistent with the existing code. CI history: the fix commit ("Discard setView's chainable return value") is well-explained and consistent with a real, previously-seen defect class (chainable Leaflet methods returning something Playwright can't serialise) — that part checks out. What I couldn't corroborate from available tooling (no log access) is the two `LeafletControlThemingTests` failures on the prior failing run. That file is untouched by this PR, and the project has documented CI contention elsewhere around this same time (concurrent E2E runs on a single-threaded runner), so contention is the more likely explanation than a regression from these map-option changes — but neither the PR body nor ADR-0165 mentions that failure or the belief that it was unrelated. Worth a line for traceability, not worth blocking on given the file is untouched and the current head is green. ADR-0165 is correctly allocated with the measured figures in it, and the "no `Answered by` on ADR-0061/ADR-0130" reasoning holds — I checked both records and neither names `keepBuffer`/`removeOutsideVisibleBounds` as an explicit open question, so ADR-0107's condition 3 is correctly applied. No frozen ADR body is edited. British spelling and no AI attribution both check out.
rob merged commit 04370dc5dc into main 2026-08-17 10:57:58 +00:00
rob deleted branch fix/task-250-mobile-tile-buffer 2026-08-17 10:57:58 +00:00
Sign in to join this conversation.
No reviewers
No labels
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
rob/PlaceMark!193
No description provided.