New map-corner "Add note" control (public/js/notes.js), mirroring
measure.js's click-to-place UX. Click the map, write a short note,
pick an existing .geojson layer file or name a new one — saved via a
new GpxStore#append_feature (grows the target file in place, unlike
save()'s always-disambiguate-the-filename behavior) through a new
POST /api/gpx/notes route.
No new rendering path: a note is just another point in a .geojson
file the Tracks panel's tree already lists, checkboxes, and pops up —
visibility toggling ("layers") comes for free from that existing
machinery, per-file, with no changes needed there.
Points only for now — a polygon mode would need gpx.js's point/line
split taught a third Polygon bucket first, or a saved polygon
wouldn't render back on reload (see TODO.adoc).
Covered by new tests: GpxStore#append_feature (create/append/missing-
path/filename-safety) and the /api/gpx/notes route (auth gate, create,
append, malformed/missing-geometry rejection, 404 on a missing
target). Full suite: 97 runs, 0 failures.
public/sw.js: network-falling-back-to-cache for RainViewer radar tile
requests specifically (nothing else — the app itself is served by the
same local server that's already the offline resource in the van, so
there's no app-shell to precache). Registered from layout.erb, a no-op
where it can't register (insecure context) rather than an error.
Verified: registers and controls the page, tiles get cached on a
normal fetch, cache.match() correctly retrieves them by the same
request. CDP's network-blocking simulation doesn't interact cleanly
with Service Worker fetch interception (a known tooling quirk, not
specific to this), so the actual offline fallback path is the
standard try/catch pattern, verified by code review rather than a
simulated-offline screenshot.
Radar flyout now also shows a 3-day Open-Meteo forecast (free, no API
key) for the map's current center. Radar frames and the forecast fetch
independently — one being unreachable doesn't hide the other. The last
successful forecast is cached in localStorage and shown (with a
"cached from" note) when the live fetch fails, so there's still
something useful once actually offline.
Radar tile images themselves aren't cached — that needs a Service
Worker, left as a separate follow-up (see TODO.adoc).
Reuses the same raster-dem source the hillshade toggle already added
— a color-relief layer tints by elevation band (green valleys through
brown mid-slopes to pale grey peaks, in this app's own palette) under
the hillshade layer, so one toggle gives the combined shaded-relief
look instead of two separate layers.
Sidebar toggle (new HillshadeControl) adds a raster-dem hillshade
layer: public Terrarium tiles when online, or a local
data/terrain.pmtiles when present (mirrors offline.pmtiles' optional-
local-file pattern, new /api/terrain-tiles + /tiles/terrain.pmtiles
endpoints). No offline terrain data is bundled, so the toggle is
online-only until one is generated — see docs/development.adoc for
how.
Offline: fill in protomaps' "other" road-kind color (unset before,
so tracks/paths silently fell back to plain white against the cream
background); also fixes a dead "minor" theme key that was never read
by the actual paint expression (only minor_a/minor_b is), so ordinary
streets were quietly using the base theme's default yellow instead of
this app's cream.
Online: Liberty's own road_service_track layer merges paved service
roads with unpaved tracks into one color, which isn't safe to just
recolor brown (real driveways aren't unpaved). Clones it into a
second layer scoped to class=="track" only, dashed and dirt-brown,
matching the offline treatment without mislabeling paved service
roads.
Small green/gray indicator reusing the existing /api/offline-tiles
check, showing at a glance whether this server has offline map data.
Closes out the top banner TODO item — DEM elevation and a hamburger
menu are intentionally left out (no DEM layer yet, and the sidebar
isn't mobile-collapsed).
Legend items are now toggle buttons (icon grid, not a color-swatch
list) that hide/show that category's waypoints on the map. Filtering
re-slices each file's source data rather than using a layer filter,
since MapLibre clusters a GeoJSON source's full data regardless of
what a layer filter hides.
Captures ideas for offline maps/POI data, an NPS Unigrid-inspired
visual redesign, a 4-module menu reorg, and custom layer support.
Hardware-dependent features (leveling, Victron/HA telemetry) are
parked under Nice to have for later.