feat: show all topology overlay on the map (closes #72) #77

Merged
skobkin merged 7 commits from agent/feat_72_show_all_topology into master 2026-06-08 21:20:19 +03:00
Owner

Summary

  • Adds a 🕸️ Show all topology toggle to the map view's bottom-right corner.
  • When enabled, the UI fetches a fresh snapshot of /api/v1/topology/edges and draws every edge whose endpoints have positions.
  • The label switches to 🕸️ (N) once the snapshot loads and adds a + if the response was truncated.
  • Toggle state is in-memory only — no localStorage, no URL persistence.

Backend

  • New web.map.topology_max_edges config (default 2000, ENV MML_WEB__MAP__TOPOLOGY_MAX_EDGES, normalized to default when zero/negative).
  • repo.TopologyEdgeQuery gains Limit int; SQLite appends LIMIT ? when positive.
  • New single-slot TTL cache for /api/v1/topology/edges (keyed on the parsed query + relevance cut-off) that reuses the existing web.map.topology_cache_ttl (default 10m).
  • Breaking change (intentional and documented): the response shape is now {items: TopologyEdge[], truncated: boolean}. The previous bare-array shape was only consumed by the repo's own client.ts which is updated in this PR.

Frontend

  • New useTopologyAllStore (zustand) — edges, truncated, loading, error, lastFetchedAt, plus setEnabled / refresh / reset.
  • New MapTopologyToggle component (bottom-right, safe-area-inset-bottom aware).
  • LeafletMapAdapter gets a separate topologyAllLayer and renderAllTopology(); the layer sits below the focal-node layer so per-node polylines read first.
  • New topologyColorFromEdge helper in web/src/utils/topology.ts mirroring the existing topologyColor colour buckets for NodeNeighbor.
  • The topology legend now also appears when only the global mode is on (was previously gated on a focal node).

Follow-ups

  • #TBD: ?bbox= viewport filtering on /api/v1/topology/edges (perf + SQLite indexing — see suggested follow-up body in this PR's first comment). #78
  • Live topology updates via WS events (deferred).

Test plan

☑ go test ./... — all green.
☑ go vet ./... — clean.
☑ golangci-lint run ./internal/... — 0 issues.
☑ npm run typecheck — clean.
☑ npm run lint — clean.
☑ npm test — 136/136 pass.
☑ npm run build — succeeds, refreshes internal/frontend/assets/dist/.
☑ go build ./cmd/server — succeeds.

Manual QA

Toggle off → on: snapshot fetches, label flips to 🕸️ (N), edges render.
Toggle on → off: edges disappear, store resets on next page navigation.
Refreshing the page with the toggle on: toggle is off (in-memory only).
Cache: rapid toggle off/on within the TTL window does not refetch.
Truncation: at topology_max_edges: 3, the response shows truncated: true and the label shows 🕸️ (3+).
Mobile: control sits above the iOS Safari home indicator and does not overlap Leaflet attribution.

## Summary - Adds a 🕸️ Show all topology toggle to the map view's bottom-right corner. - When enabled, the UI fetches a fresh snapshot of /api/v1/topology/edges and draws every edge whose endpoints have positions. - The label switches to 🕸️ (N) once the snapshot loads and adds a + if the response was truncated. - Toggle state is in-memory only — no localStorage, no URL persistence. ## Backend - New web.map.topology_max_edges config (default 2000, ENV MML_WEB__MAP__TOPOLOGY_MAX_EDGES, normalized to default when zero/negative). - repo.TopologyEdgeQuery gains Limit int; SQLite appends LIMIT ? when positive. - New single-slot TTL cache for /api/v1/topology/edges (keyed on the parsed query + relevance cut-off) that reuses the existing web.map.topology_cache_ttl (default 10m). - Breaking change (intentional and documented): the response shape is now {items: TopologyEdge[], truncated: boolean}. The previous bare-array shape was only consumed by the repo's own client.ts which is updated in this PR. ## Frontend - New useTopologyAllStore (zustand) — edges, truncated, loading, error, lastFetchedAt, plus setEnabled / refresh / reset. - New MapTopologyToggle component (bottom-right, safe-area-inset-bottom aware). - LeafletMapAdapter gets a separate topologyAllLayer and renderAllTopology(); the layer sits below the focal-node layer so per-node polylines read first. - New topologyColorFromEdge helper in web/src/utils/topology.ts mirroring the existing topologyColor colour buckets for NodeNeighbor. - The topology legend now also appears when only the global mode is on (was previously gated on a focal node). ## Follow-ups - #TBD: ?bbox= viewport filtering on /api/v1/topology/edges (perf + SQLite indexing — see suggested follow-up body in this PR's first comment). #78 - Live topology updates via WS events (deferred). ## Test plan ☑ go test ./... — all green. ☑ go vet ./... — clean. ☑ golangci-lint run ./internal/... — 0 issues. ☑ npm run typecheck — clean. ☑ npm run lint — clean. ☑ npm test — 136/136 pass. ☑ npm run build — succeeds, refreshes internal/frontend/assets/dist/. ☑ go build ./cmd/server — succeeds. ## Manual QA Toggle off → on: snapshot fetches, label flips to 🕸️ (N), edges render. Toggle on → off: edges disappear, store resets on next page navigation. Refreshing the page with the toggle on: toggle is off (in-memory only). Cache: rapid toggle off/on within the TTL window does not refetch. Truncation: at topology_max_edges: 3, the response shows truncated: true and the label shows 🕸️ (3+). Mobile: control sits above the iOS Safari home indicator and does not overlap Leaflet attribution.
skobkin self-assigned this 2026-06-08 15:43:50 +03:00
feat: show all topology overlay on the map (closes #72)
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
48f36a954c
Add a 'Show all topology' toggle to the map view. When enabled, the UI
fetches a fresh snapshot of /api/v1/topology/edges and renders every
edge that has both endpoints positioned. The toggle lives in the
bottom-right corner of the map stage, uses a spider-web emoji, and
shows the live edge count in the label. Truncation is reflected when
the backend hits its configurable cap.

Backend:
- Add web.map.topology_max_edges config (default 2000,
  MML_WEB__MAP__TOPOLOGY_MAX_EDGES override, normalised to default
  when zero or negative).
- Reuse the existing web.map.topology_cache_ttl for a new server-side
  cache of /api/v1/topology/edges responses. The cache key includes
  the parsed query so different filters get their own slot.
- Wrap the /api/v1/topology/edges response in {items, truncated}.
  This is an intentional, documented shape change.
- Apply the cap as a SQL LIMIT on the existing newest-first ordering
  and signal truncation when the result fills the slot.
- Update openapi.yaml, config.example.yaml, and README.md.

Frontend:
- Reuse the existing meta.map.topology_cache_ttl for an in-memory TTL
  check so toggling off and on within the window does not refetch.
- Topology toggle state is in-memory only; no localStorage or URL
  persistence, matching the rest of the project rules for transient
  UI state.
- Add topologyColorFromEdge helper, MapTopologyToggle component,
  useTopologyAllStore, and a LeafletMapAdapter.renderAllTopology()
  method that draws the global layer under the focal-node layer.
skobkin-agent force-pushed agent/feat_72_show_all_topology from 48f36a954c
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
to e314a52670
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-06-08 16:08:29 +03:00
Compare
Brings in 7 commits from master: hop count before MQTT gateway (#67)
plus the hop_limit=0 preservation, mobile hop badge fix, and the
related docs and lint cleanups. Manual merge (not rebase) to preserve
the existing #72 commit shape for review.
fixup! Merge origin/master (v0.23.0 hop-semantics) into agent/feat_72_show_all_topology
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
0d604e84bb
The checkbox keeps its accessible name via aria-label, so the visible
text on the toggle was redundant — the spider-web glyph alone signals
the action and reclaims the right-hand corner of the map stage for
the actual map content. The off-state label was the only state that
exposed the long string; the on-state already collapsed to the icon
plus a count.
style(web): shrink and translucent the topology toggle container
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
b76b0b0b2e
The 'show all topology' toggle sat in the bottom-right corner with a
~94% opaque white card, which read as a heavier UI element than the
glyph-only action warrants. Drop the background to 70% (let the map
show through, keep the frosted blur) and trim the outer padding plus
the inner gap so the checkbox hugs the glyph instead of floating in
a 16-px+ white well.

70% is a starting point; if the toggle disappears over busy map
tiles we can drop further, but keep this commit self-contained.
fix(web): shrink 'show all topology' strip to its content
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
49a01b31f2
PicoCSS's [role=group] rule sets 'width: 100%' on the element, and the
default 'align-items: stretch' on the column flex container then pulled
each child (the label, the hint) to the full width of the map stage.
The white background strip ended up the entire width of the map area
even though the only child was a tiny checkbox plus an emoji glyph.

Add 'width: fit-content' (overrides Pico's width: 100%) and
'align-items: flex-start' (so children don't stretch on the cross
axis). The hint span still gets a flex: 1 1 auto on the main axis from
Pico, but in a column container that means vertical grow, which is
what we want.

Keep role='group' for screen-reader semantics; the visual fix is
local to the .map-topology-toggle rule.
refactor(web): unify global and focal-node polyline styles
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/tag/ci Pipeline was successful
c53363679e
The 'show all topology' layer used weight 1.5/2.5 and opacity 0.55,
which left its lines almost invisible over busy OpenStreetMap tiles,
while the per-focal-node layer used weight 2.5/3 and opacity 0.82.
The asymmetry was a guess at the time, and the global half was
clearly too faint in practice.

Match the global layer to the focal-node layer: weight 2.5, opacity
0.82, and drop the fromIsFocal branch (no longer needed since both
layers now draw the same kind of line). The focal-node layer keeps
its 3px bump for the selected node since that bump is its primary
visual job.

While here, collapse the duplicated colour functions. Previously
topologyColor(NodeNeighbor) and topologyColorFromEdge(TopologyEdge)
were near-mirrors differing only in field names. Replace them with
one topologyColor() that takes a small structural shape
(TopologyColorInput) and build that shape at each call site. The
policy now lives in one place; the API types' quirks live where they
should, in the adapter.
skobkin merged commit c53363679e into master 2026-06-08 21:20:19 +03:00
skobkin deleted branch agent/feat_72_show_all_topology 2026-06-08 21:20:19 +03:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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
skobkin/meshmap-lite!77
No description provided.