Skip to content

feat(map): in-app Site Planner coverage estimate round-trip (#2058)#2081

Merged
garthvh merged 4 commits into
meshtastic:mainfrom
bruschill:feat/site-planner-coverage-2058
Jul 16, 2026
Merged

feat(map): in-app Site Planner coverage estimate round-trip (#2058)#2081
garthvh merged 4 commits into
meshtastic:mainfrom
bruschill:feat/site-planner-coverage-2058

Conversation

@bruschill

@bruschill bruschill commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Closes #2058.

What changed?

Implements the outbound half of the Site Planner integration (Option A — headless WKWebView bridge): a user can start a coverage run from inside the app and get the styled GeoJSON back onto the map without manually round-tripping through the browser + share sheet. Builds on the already-merged styling (#2037) and import (#2056) work.

  • SitePlannerParameters — the flat query-contract model + URL builder (run=1, bridge=1, percent-encoded name), validation bounds mirroring the planner's ranges, and radio prefill: device dBm→W (W = 10^((dBm−30)/10), guarding the firmware "0 = region max" case), a preset→receiver-sensitivity table (per the planner's parameters.md, e.g. LONG_FAST → −139 dBm), and firmware-accurate frequency via the existing LoRaChannelCalculator.
  • CoverageEstimateRunner — a headless WKWebView that injects a __meshtasticNative shim at .atDocumentStart (WKWebView has no object injection, unlike Android's addJavascriptInterface) forwarding coverage GeoJSON to a WKScriptMessageHandler. Attached full-size / alpha 0 behind the progress UI so WebGL/WASM gets a GL context and the planner's autorun completes. 45 s timeout, deterministic + idempotent teardown (removes the message handler → no retain cycle), publishes state + importedResult.
  • CoverageEstimateForm — a SwiftUI sheet mirroring the planner's collapsible panels (Site/Transmitter → Receiver → Simulation Options → Display), with My Location / Node / Map Center shortcuts, palette picker, live validation, a cancelable "Estimating coverage…" overlay, and graceful failure.
  • MapDataManager.importFromString(_:name:) — imports an in-memory GeoJSON string via the existing processUploadedFile pipeline (reuses fix(map): import GeoJSON handed to the app via Open In / Share Sheet #2056/Render GeoJSON overlays that style with color/rgb() #2037 validation + render + styling for free), with name sanitization and the 10 MB cap.
  • Entry points — a map-toolbar button (MeshMapMK) and an Estimate Coverage action on node detail (Logs section, gated on a valid position) via a new .coverageEstimate(nodeNum) map state, consumed from onChange(mapState) / onChange(selectedTab) / onAppear so a cold Map-tab handoff isn't dropped. On import: the overlay master toggle is enabled, the imported file's config is added to enabledOverlayConfigs, and the map recenters on the transmitter.

Why did it change?

Issue #2058 asks for the missing outbound estimate: letting a user kick off a coverage run from the app (map control or node detail), have the planner compute it, and get the styled GeoJSON back onto the map — instead of manually round-tripping through the browser + share sheet. This matches the Android reference (meshtastic/Meshtastic-Android#6136) for a fully in-app flow.

How is this tested?

  • SitePlannerParametersTests — query-URL builder (keys / omission / percent-encoding), validation bounds, high-res range clamp, dBm→W conversion, the sensitivity table, and prefill (incl. US LongFast slot 20 → 906.875 MHz and the region-max txPower==0 fallback).
  • MapDataManagerImportFromStringTests — valid import, .geojson extension enforcement + name sanitization, invalid-JSON and oversized-string rejection.
  • xcodebuild -scheme Meshtastic -destination 'generic/platform=iOS Simulator' builds clean; sourcekit-lsp reports no diagnostics on the new/changed files.

Targets the hosted site.meshtastic.org — the native bridge + full flat query contract (planner #74) are merged + deployed, so there is no deploy gate.

Screenshots/Videos (when applicable)

09-coverage-estimate 08-coverage-map

Checklist

  • My code adheres to the project's coding and style guidelines.
  • I have conducted a self-review of my code.
  • I have commented my code, particularly in complex areas.
  • I have verified whether these changes require updates to the in-app documentation under docs/user/ or docs/developer/, and updated accordingly (added a "Coverage Estimate (Site Planner)" section to docs/user/map.md, updated docs/user/nodes.md, and regenerated the bundled docs).
  • I have tested the change to ensure that it works as intended.

Summary by CodeRabbit

  • New Features
    • Added “Estimate Coverage” via the map and from a node’s Logs, opening a full Site Planner estimate form with transmitter/receiver/simulation/display controls.
    • Prefills fields using connected-radio and available location context, then runs the estimate and—on success—imports the styled GeoJSON overlay and recenters the map.
    • Supports importing GeoJSON from an in-memory string with safe naming, .geojson enforcement, and size limits.
  • Documentation
    • Updated Map and Nodes user guides with the new Coverage Estimate (Site Planner) flow details.
  • Tests
    • Added tests for in-memory GeoJSON import and Site Planner parameters (URL generation, validation, and prefill).
    • Updated a SwiftUI snapshot test to include router environment state.

…ic#2058)

Adds the outbound half of the Site Planner integration: a user can kick off a
coverage run from inside the app and get the styled GeoJSON back on the map
without manually round-tripping through the browser + share sheet.

- SitePlannerParameters: the flat query-contract model + URL builder (run=1,
  bridge=1), validation bounds, and radio prefill — device dBm→W, preset→rx
  sensitivity table, and firmware-accurate frequency via LoRaChannelCalculator.
- CoverageEstimateRunner: a headless WKWebView + WKUserScript __meshtasticNative
  shim forwarding to a WKScriptMessageHandler (WKWebView has no object injection),
  attached full-size/alpha-0 so WebGL/WASM gets a GL context; 45s timeout,
  deterministic teardown, publishes state + importedResult.
- CoverageEstimateForm: SwiftUI sheet mirroring the planner's collapsible panels
  (Transmitter / Receiver / Simulation / Display) with device/node/map-center
  location shortcuts, progress + cancel, and error handling.
- MapDataManager.importFromString: import an in-memory GeoJSON string via the
  existing processUploadedFile pipeline (reuses meshtastic#2056/meshtastic#2037 validation + styling).
- Entry points: map-toolbar button (MeshMapMK) and an "Estimate Coverage" action
  on node detail (Logs section) via a new .coverageEstimate(nodeNum) map state,
  consumed from onChange(mapState) / onChange(selectedTab) / onAppear so a cold
  Map tab handoff isn't dropped. On import: enable the overlay, add its config,
  and recenter on the transmitter.

Tests: query-URL builder, validation, prefill math, and importFromString.
Docs: docs/user/map.md (+nodes.md) regenerated into the bundled docs.
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ad1b786c-d140-4f93-b3f1-3c14197a74bc

📥 Commits

Reviewing files that changed from the base of the PR and between 4ab4420 and 871de38.

📒 Files selected for processing (6)
  • Meshtastic.xcodeproj/project.pbxproj
  • Meshtastic/Resources/docs/index.json
  • Meshtastic/Resources/docs/markdown/user/nodes.md
  • Meshtastic/Resources/docs/user/nodes.html
  • Meshtastic/Views/Nodes/Helpers/NodeDetail.swift
  • docs/user/nodes.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • Meshtastic/Resources/docs/user/nodes.html
  • Meshtastic/Views/Nodes/Helpers/NodeDetail.swift
  • Meshtastic.xcodeproj/project.pbxproj

📝 Walkthrough

Walkthrough

Adds a Site Planner coverage-estimate flow with validated parameters, radio-prefilled settings, hidden WebView execution, GeoJSON overlay import, map and node entry points, tests, and user documentation.

Changes

Site Planner coverage workflow

Layer / File(s) Summary
Planner parameters and GeoJSON import
Meshtastic/Helpers/SitePlanner/SitePlannerParameters.swift, Meshtastic/Helpers/MapDataManager.swift, MeshtasticTests/SitePlannerParametersTests.swift, MeshtasticTests/MapDataManagerTests.swift
Defines planner validation, URL construction, radio-prefill helpers, and in-memory GeoJSON importing with tests.
Headless planner execution
Meshtastic/Helpers/SitePlanner/CoverageEstimateRunner.swift
Runs the planner in a hidden WebView, receives coverage GeoJSON through the bridge, handles cancellation, timeout, navigation failures, and publishes imported or failed states.
Estimate form and map integration
Meshtastic/Views/Nodes/Helpers/Map/CoverageEstimateForm.swift, Meshtastic/Views/Nodes/MeshMapMK.swift, Meshtastic/Views/Nodes/Helpers/NodeDetail.swift, Meshtastic/Router/NavigationState.swift, Meshtastic.xcodeproj/project.pbxproj, MeshtasticTests/SwiftUIViewSnapshotTests.swift
Adds form controls, location shortcuts, node and map entry points, overlay activation, map recentering, routing, debug recentering, target wiring, and snapshot dependency setup.
Documentation and validation support
Meshtastic/Resources/docs/..., docs/user/...
Documents the coverage-estimate flow and updates searchable documentation metadata.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • meshtastic/design#119 — Covers the same Site Planner round-trip, WebView bridge, GeoJSON overlay, radio-prefill, and map integration objectives.

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant NodeDetail
  participant MeshMapMK
  participant CoverageEstimateForm
  participant CoverageEstimateRunner
  participant SitePlanner
  participant MapDataManager
  NodeDetail->>MeshMapMK: request coverage estimate for node
  MeshMapMK->>CoverageEstimateForm: present prefilled form
  CoverageEstimateForm->>CoverageEstimateRunner: start with parameters
  CoverageEstimateRunner->>SitePlanner: load autorun planner URL
  SitePlanner-->>CoverageEstimateRunner: send coverage GeoJSON
  CoverageEstimateRunner->>MapDataManager: import GeoJSON string
  MapDataManager-->>CoverageEstimateRunner: return overlay metadata
  CoverageEstimateRunner-->>MeshMapMK: publish imported result
  MeshMapMK->>MeshMapMK: enable overlay and recenter map
Loading

Poem

A rabbit clicked “Estimate” with a hop,
The hidden planner spun without a stop.
GeoJSON flowers bloomed on the map,
Coverage stretched across each gap.
The bunny smiled, ears standing tall.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main feature: an in-app Site Planner coverage estimate round-trip.
Description check ✅ Passed The description includes the required sections and covers what changed, why, testing, screenshots, and checklist items.
Linked Issues check ✅ Passed The changes implement the #2058 flow: map/node entry points, planner form, WKWebView bridge, import pipeline, tests, and recentering.
Out of Scope Changes check ✅ Passed No clear out-of-scope changes stand out; the docs, tests, and project updates all support the Site Planner feature.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Meshtastic/Resources/docs/index.json (1)

358-390: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the coverage terms to the Nodes keywords.
Search matches page.keywords and translated keywords, so this page won’t surface for queries like coverage, estimate, planner, or cellularbars unless those terms are added to the index.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Meshtastic/Resources/docs/index.json` around lines 358 - 390, Add the missing
coverage-related terms “coverage”, “estimate”, “planner”, and “cellularbars” to
the keywords array for the Nodes page in index.json, preserving the existing
keyword entries and structure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Meshtastic/Helpers/SitePlanner/CoverageEstimateRunner.swift`:
- Around line 122-127: Update CoverageEstimateRunner.cancel() to set didFinish =
true when cancellation occurs, matching the terminal-state handling in fail()
and handleCoverage(_:). Keep the existing timeout cancellation, teardown, and
state reset behavior unchanged.

In `@Meshtastic/Views/Nodes/Helpers/Map/CoverageEstimateForm.swift`:
- Around line 130-137: The numeric fields in CoverageEstimateForm, especially
the bindings used by labeledNumber for latitude, longitude, and receiver
sensitivity, should use text-backed bindings instead of TextField(value:format:
.number). Add manual string-to-number parsing and number-to-string formatting
while preserving the existing parameter updates and support for intermediate “-”
and “.” input.

---

Outside diff comments:
In `@Meshtastic/Resources/docs/index.json`:
- Around line 358-390: Add the missing coverage-related terms “coverage”,
“estimate”, “planner”, and “cellularbars” to the keywords array for the Nodes
page in index.json, preserving the existing keyword entries and structure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 103ac2ce-6409-43be-91f0-bbcca4928510

📥 Commits

Reviewing files that changed from the base of the PR and between 8e0eaaf and fb36edd.

📒 Files selected for processing (18)
  • Meshtastic.xcodeproj/project.pbxproj
  • Meshtastic/Helpers/MapDataManager.swift
  • Meshtastic/Helpers/SitePlanner/CoverageEstimateRunner.swift
  • Meshtastic/Helpers/SitePlanner/SitePlannerParameters.swift
  • Meshtastic/Resources/docs/index.json
  • Meshtastic/Resources/docs/markdown/user/map.md
  • Meshtastic/Resources/docs/markdown/user/nodes.md
  • Meshtastic/Resources/docs/user/map.html
  • Meshtastic/Resources/docs/user/nodes.html
  • Meshtastic/Router/NavigationState.swift
  • Meshtastic/Views/Nodes/Helpers/Map/CoverageEstimateForm.swift
  • Meshtastic/Views/Nodes/Helpers/NodeDetail.swift
  • Meshtastic/Views/Nodes/MeshMapMK.swift
  • MeshtasticTests/MapDataManagerTests.swift
  • MeshtasticTests/SitePlannerParametersTests.swift
  • MeshtasticTests/SwiftUIViewSnapshotTests.swift
  • docs/user/map.md
  • docs/user/nodes.md

Comment thread Meshtastic/Helpers/SitePlanner/CoverageEstimateRunner.swift
Comment thread Meshtastic/Views/Nodes/Helpers/Map/CoverageEstimateForm.swift
Replace the wrapping/hyphenating location shortcut labels with compact
equal-width icon-over-label cells under a 'Set coordinates from' header, so
'My Location' / 'Map Center' stay on one line.
@bruschill
bruschill force-pushed the feat/site-planner-coverage-2058 branch from 817a82c to 2069157 Compare July 12, 2026 21:13
…ge fields

Address CodeRabbit feedback on meshtastic#2081:
- CoverageEstimateRunner.cancel() now sets didFinish so an in-flight bridge
  message can't import stale coverage after teardown (matches fail()/handleCoverage).
- Latitude/Longitude/Sensitivity use a text-backed DecimalField so intermediate
  '-'/'.' input parses reliably instead of resetting the bound value to nil.
…erage-2058

# Conflicts:
#	Meshtastic/Resources/docs/index.json
@garthvh
garthvh merged commit 6762575 into meshtastic:main Jul 16, 2026
4 checks passed
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.

Site Planner coverage estimate (outbound) — headless WKWebView bridge round-trip

2 participants