Skip to content

feat(web): vendored MapLibre GL JS picker, and data-source coverage on the map - #1219

Open
HuggeK wants to merge 2 commits into
srcfl:masterfrom
HuggeK:maplibre-coverage
Open

feat(web): vendored MapLibre GL JS picker, and data-source coverage on the map#1219
HuggeK wants to merge 2 commits into
srcfl:masterfrom
HuggeK:maplibre-coverage

Conversation

@HuggeK

@HuggeK HuggeK commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Salvages the two pieces of #734 that were never part of the removed scope: the location picker's move to a vendored MapLibre GL JS, and the data-source coverage registry that tells an operator — on the map, before they commit to a location — which external sources actually serve it. No STRÅNG, no irradiance-source selection, no roof geometry, no panel drawing, and nothing new to configure: normal setup is unchanged, exactly as the #1214 boundary requires.

Addresses the coverage-visibility half of #726: the registry and the map panel are the "documented, discoverable per-site" part; the static/time-of-use tariff that would extend planning beyond Europe stays with #1051.

Both pieces already went through review in #734: the security pass found the vendor swap reduces surface versus Leaflet-from-CDN, and the maintainer review asked for exactly two changes to the coverage half, both delivered here.

Commit 1 — vendored MapLibre GL JS 6.9.0 (631d7e65)

The Settings map moves off Leaflet onto MapLibre GL JS, vendored under web/vendor/maplibre like /vendor/three and /vendor/ace: no third-party CDN JavaScript, and the picker loads with no internet access. Same OSM raster tiles, same attribution, same numeric-fields fallback when WebGL is missing. Static assets now serve with pinned Content-Types, because http.ServeFile trusts the OS MIME table and a Windows host mapping .mjs to text/plain plus our nosniff header makes the browser (correctly) refuse the vendored ES module.

Bundle integrity — the spot-check #734's security review asked for

The five files are byte-identical to maplibre-gl@6.9.0 from the npm registry. web/vendor/maplibre/README.md records the tarball's signed sha512 integrity (sha512-vFMwMK0Zs+NM/rOMSdtu8bO30DIexhBEVi5KC6f70/XtI+L/K2wC3LsDCAXFZ4s8ik5gAuDugfCNbpllpdJ9bA==, verified against the registry metadata at vendor time) and per-file SHA-256 hashes, so anyone can re-verify with npm pack maplibre-gl@6.9.0 without trusting this tree. 6.9.0 is the newest release; #734 carried 6.7.0.

Commit 2 — every source declares where it works (a555c094)

New go/internal/coverage registry + GET /api/data-sources (Read tier): each source's kind, coverage area, countries, licence, and an advisory covers verdict for the configured site or an explicit ?lat=&lon= preview. The Weather tab renders it under the picker and refreshes as the pin drags — a site outside Europe learns up front that price-driven planning has no source there (#726), instead of an empty price curve and silence. covers=false is definitive; covers=true means "worth trying"; with no site known the field is omitted rather than defaulting to a misleading yes.

The two review items from #734, delivered
  1. Zone-table lockstepTestEuropeanPriceCountriesMatchZoneTable derives the expected country set from prices.Zones() itself, so the two tables cannot drift: a zone added there fails the test here until the coverage list follows. The list consequently carries ME and UA and no invented IE, per the review.
  2. Browser check — rendered and inspected in a real browser, this tip, screenshots below.
Relationship to open PR #1051

#1051's docs/data-coverage.md names go/internal/coverage and GET /api/data-sources as the machine-readable source of truth its prose mirrors — but doesn't ship them. This PR is that missing half, and deliberately does not touch docs/data-coverage.md or README.md, which #1051 owns. Whichever lands second needs no rework; if #1051 changes shape, only its doc prose would need a line.

Verification

  • Web: node --test — 3 vendor-contract tests (same-origin, no CDN hosts, OSM referrerPolicy opt-in honored by the bundle, code-split files present) and 9 weather-tab tests (3 new: coverage host rendered, stale-response guard while dragging, endpoint failure never breaks the tab). All pass.
  • Go: coverage package builds and is gofmt-clean locally; the full go test matrix (including the new api_datasources_test.go, api_static_mime_test.go, and the lockstep test) runs in CI — this dev host has no C compiler, and master's DuckDB dependency now requires cgo everywhere.
  • Browser (AGENTS.md): Settings → Weather in headless Edge 152 against the real web/ tree with stubbed API responses. The vendored MapLibre map renders with OSM tiles, marker drag and click work, attribution shows. Coverage panel at Stockholm: all seven sources ✓. Typing Sydney: map flies there, forecast row stays ✓, all three price sources flip to amber ✕ with "3 sources do not cover this location. Hover for details."
Screenshots

Stockholm — vendored MapLibre picker, all sources available:

Stockholm

Sydney — price sources correctly declared unavailable:

Sydney

🤖 Generated with Claude Code

https://claude.ai/code/session_01Wepiu9kFxZkLTEZhoTJTx4

claude and others added 2 commits September 12, 2026 12:55
The Settings map moves off Leaflet onto MapLibre GL JS, vendored under
web/vendor/maplibre like /vendor/three and /vendor/ace: no third-party CDN
JavaScript, and the picker loads with no internet access. Same OSM raster
tiles, same attribution, same numeric-fields fallback when WebGL is missing.

The vendored files are byte-identical to maplibre-gl@6.9.0 from the npm
registry; the vendor README records the tarball's signed sha512 integrity
and per-file SHA-256 hashes so the copy can be re-verified without trusting
this tree.

v6 is ESM-only and code-split, so the tab loads it with a dynamic import()
and the entry pulls its shared chunk and worker by relative URL — which is
why static assets now serve with pinned Content-Types: http.ServeFile asks
the OS MIME table, and a Windows host that maps .mjs to text/plain plus our
X-Content-Type-Options: nosniff makes the browser (correctly) refuse the
module. web/maplibre-vendor.test.mjs pins the same-origin contract and the
OSM referrerPolicy opt-in that keeps volunteer tile servers from 403ing.

Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wepiu9kFxZkLTEZhoTJTx4
New package go/internal/coverage is the machine-readable registry of every
external source's geographic reach: the four forecast providers are
worldwide, every price provider is European, and each bounded source carries
an advisory lat/lon box, country list and licence. GET /api/data-sources
(Read tier) answers for the configured site, or for an explicit ?lat=&lon=
so the Weather tab can preview a pin before it is saved. The tab renders the
verdict under the location picker and refreshes it as the pin drags — a site
outside Europe now learns up front that no price source serves it (srcfl#726)
instead of getting an empty price curve and silence.

Bounds are advisory supersets: covers=false is definitive, covers=true means
worth trying, and the upstream API stays authoritative. Without a known site
the covers field is omitted entirely rather than defaulting to a misleading
yes.

The European price-country list is locked to prices/zones.go by
TestEuropeanPriceCountriesMatchZoneTable — one registry, not two — which is
also why it now carries ME and UA and no invented IE.

Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wepiu9kFxZkLTEZhoTJTx4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants