feat(web): vendored MapLibre GL JS picker, and data-source coverage on the map - #1219
Open
HuggeK wants to merge 2 commits into
Open
feat(web): vendored MapLibre GL JS picker, and data-source coverage on the map#1219HuggeK wants to merge 2 commits into
HuggeK wants to merge 2 commits into
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/maplibrelike/vendor/threeand/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, becausehttp.ServeFiletrusts the OS MIME table and a Windows host mapping.mjstotext/plainplus ournosniffheader 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.0from the npm registry.web/vendor/maplibre/README.mdrecords 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 withnpm pack maplibre-gl@6.9.0without 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/coverageregistry +GET /api/data-sources(Read tier): each source's kind, coverage area, countries, licence, and an advisorycoversverdict 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=falseis definitive;covers=truemeans "worth trying"; with no site known the field is omitted rather than defaulting to a misleading yes.The two review items from #734, delivered
TestEuropeanPriceCountriesMatchZoneTablederives the expected country set fromprices.Zones()itself, so the two tables cannot drift: a zone added there fails the test here until the coverage list follows. The list consequently carriesMEandUAand no inventedIE, per the review.Relationship to open PR #1051
#1051's
docs/data-coverage.mdnamesgo/internal/coverageandGET /api/data-sourcesas the machine-readable source of truth its prose mirrors — but doesn't ship them. This PR is that missing half, and deliberately does not touchdocs/data-coverage.mdorREADME.md, which #1051 owns. Whichever lands second needs no rework; if #1051 changes shape, only its doc prose would need a line.Verification
node --test— 3 vendor-contract tests (same-origin, no CDN hosts, OSMreferrerPolicyopt-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.coveragepackage builds and is gofmt-clean locally; the fullgo testmatrix (including the newapi_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.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:
Sydney — price sources correctly declared unavailable:
🤖 Generated with Claude Code
https://claude.ai/code/session_01Wepiu9kFxZkLTEZhoTJTx4