Repository navigation
fix(map): use the configured tile provider everywhere (#332) - #344
Conversation
…vider
Keyless CARTO raster tiles have been stamped "API KEY REQUIRED" since
August 2026, and this fork has no CARTO basemap-key plumbing. Several
maps still built their tile URL from a hard-coded CARTO template instead
of the operator's configured dark/light provider, so they rendered
watermarked tiles even on an instance configured for Esri/OpenTopoMap.
- map-tile-providers.js: the built-in fallback defaults become the
keyless OSM baseline ('osm-dark' / 'osm-standard') instead of
'carto-dark' / 'carto-light'. Those two ids are now registered
regardless of provider gating, so the active id always resolves and
stays selectable in the layer picker. New MC_getTileSpec(type) is the
single resolver for url + attribution + refUrl + maxZoom +
invertFilter, per theme.
- roles.js: TILE_DARK / TILE_LIGHT are no longer CARTO templates, and
getTileUrl() honours the configured provider in LIGHT mode too — it
previously returned TILE_LIGHT unconditionally, ignoring
map.tiles.lightDefault. getActiveTileProvider() resolves both themes,
so the node-detail inset map credits the configured light provider.
- customize-v2.js: both geo-filter preview maps resolve the configured
light provider through a shared _cv2PreviewTile() helper.
- live.js / map.js: the tile resolvers go through MC_getTileSpec, and
their url + attribution fallbacks no longer name CARTO.
- geofilter-builder.html: this standalone page has no registry, so it
uses the same keyless OSM baseline plus a tile-pane invert filter to
keep its dark look.
- config.example.json: defaults updated, with a note on why CARTO is no
longer the default.
Relates to #332
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rapport — CS-Macmini PR#344 #332 — head aac9445Status: Implemented and verified — every map now takes its tile URL and attribution from the configured dark/light provider, the fallback is non-CARTO, 43 unit assertions and an 11-step browser check are green, 13/13 mutants killed, CI all green on the first run (3 jobs pass, 3 skipped by the fork guard). Evidence tags: [T] test run, [A] assertion/code reference, [K] killed mutant. Requirements
Unit suite: 43 passed, 0 failed [T]. Mutants: 13 applied, 13 killed, 0 survivors [T]. One mutant (legacy branch, wrong accessor per theme) survived the first round and a test was added to close that gap before re-running. Browser check — recorded tile requestsChromium, local server on the committed
The CI job runs the no-config variant, which is the default-install case the issue describes. Areas was already non-CARTO on master (it uses plain OSM) and is asserted for regression only. Existing tests updatedTwo tests pinned the behaviour this issue changes; both were updated with the reason recorded inline, not deleted.
CI per jobRun 37596269519, no re-runs needed.
The known-flaky #271 test did not fail, so no job was re-run. Local gates
Residuals
|
Review — CS-pve-agent1 PR#344 — head aac9445Dom: REQUEST CHANGES The core of the PR is right. With a non-CARTO provider configured, I measured 0 requests to Two things block. First, the change of default dark style introduces a dark-mode regression on a map the PR doesn't touch: the analytics subpath minimap. Second, the E2E misses two of the surfaces the review brief lists. Packet-path and the analytics minimap are not covered, and the "Areas" step asserts nothing. Both fixes are small. Evidence tags: [T] test or browser run by me, [A] code reading or analysis, [K] mutant run by me. Findings
Answers to the review brief
Standing checks
Tests and mutantsAll local runs were on the merged tree.
Mutants (my own; each was applied to a scratch copy of the merged tree):
Not verified
Head was checked with |
…e-provider-fallback
Round-2 review findings on #344 (#332). F1 (blocking) — dark-mode regression. The Analytics -> Subpaths detail minimap did `L.tileLayer(getTileUrl())` and never applied the provider's invertFilter. #332 makes `osm-dark` the dark default, and that style is the LIGHT OSM template plus an invert filter, so on a default install in dark mode this map rendered an un-inverted light basemap inside a dark page. It now goes through the one shared helper every other inset map uses (nodes.js `_applyTilesToNodeMap`), which resolves the configured provider for the current theme and applies its invertFilter to that map's own tile pane. It was the only `L.tileLayer(getTileUrl())` call left in the frontend. F2 (blocking) — E2E coverage gaps. Added steps for the Analytics -> Subpaths minimap and the packet-path modal, and replaced the vacuous "Areas tab" step: that tab mounts no Leaflet map at all (its position-gap rows are not clickable there), so the step passed on zero tiles. The Areas map that exists is AreaNodesMap, from the Position-Fix Coverage Gaps tool. Every map step now also asserts that the resolved provider's invertFilter reaches the tile pane -- read from the page, so it holds for any configured provider. F3 — attribution on the new default. `osm-standard` / `osm-dark` credited "Maps (c) Mapbox/Thunderforest/MapTiler" unconditionally, but a keyless install uses none of those three. The credit is now resolved the same way `_getOsmUrl()` picks the URL: plain OSM by default, the one configured vendor when a token is set. F4 — the comment and config.example.json said "tiles to zoom 19"; both OSM styles declare maxZoom 18. F5 — the tile picker's last-resort ids were 'carto-dark' / 'carto-light'. They now come from the registry's own exported defaults. Relates to #332
Rapport — CS-Macmini PR#344 runde-2 — head 26beef7Review feedback addressed (commit Evidence tags: [T] test or browser run by me, [A] code reading, [K] killed mutant. 1. F1 (blocking) — dark-mode regression on the Analytics → Subpaths minimap — fixed
const map = L.map('subpathMap', { zoomControl: false, attributionControl: false });
if (typeof window._applyTilesToNodeMap === 'function') {
window._applyTilesToNodeMap(map);
} else { /* loud warn + keyless OSM */ }That is the same call the node-detail inset, node-reach, packet-path, area-nodes and rx-coverage maps already make, so there is no second resolver: the helper picks the provider for the current theme, carries its attribution and I swept the frontend for other call sites missing it. Verified in a real browser on a no-config server: [T] 2. F2 (blocking) — E2E coverage gaps — fixed
The test can fail. Four mutants, each run against both suites:
Mutant B is the one that survived everything in round 1; D is the Areas-shaped equivalent and is killed only by the repaired Areas step. [K] 3. F3 — attribution on the new default is now correct for a keyless install — fixed
It is resolved into a plain string at registry-build time, so every reader stays unchanged ( Browser, no-config server: [T] the 4. F4 — zoom text corrected to 18
5. F5 — last CARTO id fallback removed
F6 (info) — acknowledged, no change
TestsNew assertions in
Mutants for the non-blocking findings as well — 8 total, 8 killed, 0 survivors [T]:
Local gates
CI per jobRun 37641463819, attempt 1, head
Notes and residuals
|
Review — CS-pve-agent3 PR#344 — head 26beef7Dom: APPROVE med nits This is an independent re-review of round 2. All five round-1 findings (F1–F5) are fixed, and the fixes hold up under my own mutants. Across 51 routes, both themes and two configs, the browser sweep saw 0 requests of any resource type to Evidence tags: [T] test or browser run by me, [A] code reading, [K] mutant run by me. Findings
Answers to the brief1. F1 (dark mode): fixed.
2. F2 (E2E): fixed, and the test can fail.
3. F3–F5: fixed.
4. Browser check, dark and light: 0 CARTO requests.
Standing checks
Tests and mutants
My own mutants. Each was applied to a copy of the merged tree, served by its own local server, and run against the #332 unit suite and the #332 E2E (plus Kpa-clawbot#1420/Kpa-clawbot#1470/Kpa-clawbot#1614):
Not verified
Head was checked with |
Relates to #332
Keyless CARTO raster tiles have been stamped
API KEY REQUIREDsince August 2026, and this fork has no CARTO basemap-key plumbing (#13 proposed one and was closed). Several maps still built their tile URL — and their attribution credit — from a hard-coded CARTO template instead of the operator's configured dark/light provider, so they rendered watermarked tiles even on an instance configured for Esri/OpenTopoMap.Plan (as implemented)
getTileUrl(), the two customizer call sites and the two map resolvers, plus a Playwright E2E that records every tile request a page makes.map-tile-providers.jsthe single source of truth with a newMC_getTileSpec(type)resolver, and move the built-in fallback off CARTO.test-all.sh/ the Playwright job.What changed
public/map-tile-providers.jsosm-dark/osm-standardinstead ofcarto-dark/carto-light. OSM Standard is the keyless baseline: no token, tiles to zoom 19, and it is already the unconditional provider for the home mini-map, node-reach, packet-path and area maps — so this default contacts no third party the app did not already contact. A configuredmap.tiles.darkDefault/lightDefaultstill takes priority, so an operator who wants CARTO keeps it.REGISTRYwithout the active id, every caller falls through to a URL of its own — which is how the hard-coded CARTO templates spread in the first place — and the default would not be selectable in the layer picker, so a user who switched away could not switch back.enabledstill governs every other style of a provider.MC_getTileSpec(type)returns{ id, url, attribution, refUrl, maxZoom, invertFilter }for'dark'/'light'.urlis always a string template (lazyurlfunctions are invoked) andattributionalways comes from the resolved provider.public/roles.jsTILE_DARK/TILE_LIGHTare no longer CARTO templates. They are the last-resort values used when the registry has not loaded at all (standalone pages, unit tests); the legacytiles.dark/map.tiles.darkUrlconfig overrides still apply.getTileUrl()honours the configured provider in light mode too. Before this it returnedTILE_LIGHTunconditionally, somap.tiles.lightDefaultwas ignored on every consumer of the helper — the node-detail inset map and the analytics map among them. The legacy accessor path (MC_TILE_PROVIDERS+MC_get*TileProviderwithoutgetTileSpec) is kept and now also resolves light.getActiveTileProvider()resolves both themes, so the node-detail map credits the configured light provider instead of a generic fallback. A light style carries noinvertFilter, so nothing about the rendering changes.public/customize-v2.js— both geo-filter preview maps (tab inset and full-screen modal) resolve the configured light provider through a shared_cv2PreviewTile()helper. They stay light-styled whatever the app theme is, which is why they ask for'light'in both themes.public/live.js,public/map.js— the tile resolvers preferMC_getTileSpec, and their url + attribution fallbacks no longer name CARTO. The old bodies hard-coded CARTO three times each: the light-mode url fallback, thereg['carto-dark']registry fallback and the© OpenStreetMap © CartoDBcredit.public/geofilter-builder.html— this standalone page loads neitherroles.jsnor the registry, so it has nothing to consult. It gets the same keyless OSM baseline plus a#map .leaflet-tile-paneinvert filter (the registry's ownINVERT_CSSstring) so it keeps the dark map it had under CARTOdark_all.config.example.json— defaults updated, with a comment explaining why CARTO is no longer the default and that its styles remain available.cmd/serveris untouched:map.tilesalready reaches the client verbatim, so no Go change was needed.Browser check
test-issue-332-no-carto-tiles-e2e.jsdrives Chromium over every surface the issue names — node detail, the main map, Live, Areas, and both customizer geo-filter previews — in both themes, plus the standalone builder. It records every off-origin image request and asserts two things per surface: zero requests tobasemaps.cartocdn.com, and at least one request to the host the configured provider should produce — so "0 CARTO requests" cannot pass by the map simply failing to load. Off-origin scripts and stylesheets (Leaflet, Chart.js) are allowed through unchanged; only tile images are stubbed with a 1×1 PNG, since the point is which URL the page asks for and CI has no business reaching a tile CDN.Verified four ways against a local server on the committed
e2e-fixture.db, prepared as the Playwright job does:config.jsondarkDefault: esri-darkgray-labels,lightDefault: opentopomapserver.arcgisonline.com, light mapstile.opentopomap.orgorigin/mastertile.openstreetmap.orgorigin/masterThe CI job runs the no-config variant, which is the default-install case the issue describes.
Tests
test-issue-332-tile-provider-fallback.js— 43 assertions in a purevmsandbox, registered intest-all.sh. It runs the real production call sites rather than grepping: the two customizerL.tileLayer(...)statements and both map resolvers are extracted from source and executed against a spy, so re-hard-coding a URL in either place fails the suite. Coverage: the fallback defaults per theme; the defaults staying resolvable with every optional provider disabled; a configured default still winning;MC_getTileSpecurl/attribution/refUrl/maxZoom/invertFilter and function-valued urls;getTileUrl()for carto-dark, carto-voyager-dark, positron-dark, esri, carto-light, carto-voyager, opentopomap, usgs-topo, usgs-imagery, maptiler-upgraded OSM and Stamen; four fallback paths including a malformed style; the legacy accessor shape for both themes;getActiveTileProvider()in both themes; and a static guard that the onlybasemaps.cartocdn.comliteral left underpublic/is inside_getCartoBase().13 mutants, one per requirement and then some, all killed: CARTO defaults restored; the always-register guarantee dropped;
getTileSpecignoring itstype;getTileSpecleaking a function-valued url;getTileUrl()short-circuiting light toTILE_LIGHT;TILE_DARK/TILE_LIGHTback to CARTO;getActiveTileProvider()nulling in light mode; the customizer previews re-hard-coded; the preview helper asking for'dark'; the CartoDB attribution fallback restored inlive.js;map.jsskippingMC_getTileSpec; the legacy branch using the wrong accessor per theme; and the builder back on CARTO.Two existing tests pinned the behaviour this issue changes and were updated, with the reason recorded inline:
test-issue-1420-tile-providers.js— the fallback-id expectations, the "default registry contains only Carto" premise, the OSM gating assertion (the two fallback styles surviveosm.enabled: false), and the registry-rebuild test (now observes a token-gated Stamen style instead). 39/39.test-issue-1470-node-tile-helper.js—getActiveTileProvider returns null in light modebecomesreturns the LIGHT provider in light mode, and now also asserts the light style carries no invert filter and follows the selected light provider. 6/6.Local runs:
sh test-all.sh228/228 files green;go test ./...green in all 18 modules;node scripts/check-css-vars.jsclean (no colours added — the one new CSS declaration is a filter);scripts/check-xss-sinks.sh --diff origin/masterclean;npx eslint public/*.js0 errors; fork guards unchanged (9 indeploy.yml, 1 inrelease-fast-path.yml). Affected Playwright suites green against the local fixture server: the new #332 E2E,test-e2e-playwright.js(132/135, 3 skipped — identical toorigin/master), map modal/lifecycle/pagination/mobile/controls, live layout/fullscreen/GPU animations, node-reach and node-reach-coverage, route-map a11y, analytics subtab deeplinks.Notes
darkDefault/lightDefaultstill accept them — this changes only what an unconfigured instance resolves to. A CARTO basemap-key path remains possible later;_getCartoBase()is still the single place a CARTO URL is built.osm.enabled: falseno longer removesosm-standard/osm-darkfrom the registry, since they are the built-in defaults. The flag's only effect was registry membership (_getOsmUrl()never read it), so nothing else about the provider changes.nodes.js_applyTilesToNodeMap()still hard-codesmaxZoom: 18, so a provider capped lower (OpenTopoMap at 17, USGS at 16) over-zooms into blank tiles on the node-detail inset map. Pre-existing and independent of this change.esri-darkgray-labelsstyle declaresmaxZoom: 19, but the Esri Canvas service serves real imagery only to z16 and a blank tile above it (verified by probing z14/16/17/18/19 over land). That is why the fallback here is OSM rather than the Esri canvas.public/geofilter-builder.htmlstyles its chrome with hard-coded hex colours rather than CSS variables. Pre-existing across the whole file and left alone.🤖 Generated with Claude Code