Skip to content

feat(web): waypoint create/edit + geofences (design#114)#1231

Open
danditomaso wants to merge 10 commits into
meshtastic:mainfrom
danditomaso:feature/waypoint-geofences
Open

feat(web): waypoint create/edit + geofences (design#114)#1231
danditomaso wants to merge 10 commits into
meshtastic:mainfrom
danditomaso:feature/waypoint-geofences

Conversation

@danditomaso

@danditomaso danditomaso commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the Waypoint geofences client
spec
end-to-end for the
web client. Also introduces the missing waypoint create/edit flow (the web
app previously had no authoring UI at all) so the geofence controls have a
surface to live on.

Proto

  • Waypoint gains geofence_radius, bounding_box, notify_on_enter, notify_on_exit, notify_favorites_only (fields 9–13); new BoundingBox message uses GeoJSON-style WSEN order.
  • @meshtastic/protobufs workspace override pinned via pnpm-workspace.yaml so the SDK consumes local stubs instead of the JSR-pinned copy.
  • Generated dist stays out of git — @bufbuild/buf added as a devDep with a postinstall regen hook.

Feature scope (per design#114)

  • Create/edit dialog — name / description / icon / coords / expiry, plus the geofence section.
  • Radius selector — "Off + presets" only, locale-aware (metric: 100 m / 500 m / 1 km / 5 km; imperial: 528 ft / 0.5 mi / 1 mi / 5 mi / 10 mi). Wire value always meters.
  • Bounding box — drag-to-define rectangle on the map (useBoundingBoxDraw + BoundingBoxOverlay); Edit / Remove buttons after set.
  • Progressive reveal — enter/exit toggles hidden until a shape exists; favorites-only hidden until enter or exit is on.
  • Overlays — turf circle + WSEN polygon per waypoint, toggled via the layer tool.
  • Alert engineGeofenceCrossings implements baseline-first per-(waypointId, nodeNum) state (in-memory, not persisted, matches spec). useGeofenceAlerts runs on every incoming position, applies the favorites-only gate via MeshClient.nodes.byNum(from).isFavorite, and emits an enter/exit toast with a View on map action that deep-links via router.navigate + a new focusWaypointId slice on the app store.
  • Placement mode — toolbar button flips the map cursor to a MapPinPlus SVG and captures the next click as the new waypoint location.

Notes

  • Geofence-carrying waypoints only travel to firmware ≥ 2.7.26 / 06d729a; older receivers drop the unknown fields silently.
  • Chat persistence is unrelated but was surfaced during review — sqlocal init in apps/web/src/core/connections/sdkClient.ts:39-65 fails silently on OPFS timeout / unavailability and falls back to InMemoryMessageRepository. Not touched in this PR.

Closes #1227.

Test plan

  • pnpm --filter meshtastic-web test — 275 pass (8 new suites:
    point-in-region math, baseline-first crossings, overlay features,
    drag-to-define hook, editor dialog interaction, focus-waypoint app-store
    slice, deviceStore geofence persistence, plus existing suites)
  • pnpm --filter meshtastic-web build — clean
  • pnpm run test — 419 tests pass across the workspace
  • Manual: create a waypoint via the placement toggle, set a radius
    preset, draw a bounding box, save, confirm circle + rectangle overlays
    render
  • Manual: edit an existing waypoint, verify Notify Enter/Exit hidden
    until radius/box exists and Favorites Only hidden until Enter or Exit is on
  • Manual: with a second node on the mesh, verify baseline-first
    behavior — first position from that node emits no toast, subsequent
    boundary crossings emit enter/exit toasts, "View on map" action recentres
    and opens the popup
  • Manual: switch browser locale to en-US and confirm imperial radius
    presets, other locales show metric
  • Manual: firmware ≥ 2.7.26 to verify geofence fields survive the round
    trip through the device

Summary by CodeRabbit

  • New Features
    • Added waypoint geofencing (radius circles and bounding boxes), including map rendering and a “Geofences” layer toggle.
    • Added waypoint add/edit dialog with geofence radius/box settings and enter/exit/favorites-only notification options.
    • Added real-time geofence alert toasts with one-click focus/navigation to the relevant waypoint.
    • Added map placement/editing workflow with draw-box support for bounding boxes.
  • Bug Fixes
    • Improved deep-link focus behavior for selecting a specific waypoint on the map.
  • Tests
    • Added coverage for geofence rendering, crossings, unit/radius conversion, and bounding-box draw interactions.

@danditomaso danditomaso requested a review from Copilot July 2, 2026 01:55
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

@danditomaso is attempting to deploy a commit to the Meshtastic Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

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
📝 Walkthrough

Walkthrough

This PR adds waypoint geofence support across protobufs, geofence utilities, alerting, map rendering, waypoint editing, and map-page integration. It also updates protobuf build and workspace configuration.

Changes

Waypoint Geofences Feature

Layer / File(s) Summary
Waypoint geofence protobuf contract
packages/protobufs/meshtastic/mesh.proto
Adds geofence radius, bounding box, and notification flags to Waypoint, plus a new BoundingBox message.
Geofence math utilities
apps/web/src/core/utils/geofence.ts, apps/web/src/core/utils/geofence.test.ts, apps/web/src/core/utils/geofenceCrossings.ts, apps/web/src/core/utils/geofenceCrossings.test.ts
Adds unit conversion, geofence containment, coordinate conversion, geofence/notify helper functions, and baseline-first crossing tracking with tests.
Alert hook and state wiring
apps/web/src/core/hooks/useGeofenceAlerts.ts, apps/web/src/App.tsx, apps/web/src/core/stores/appStore/*, apps/web/src/core/stores/deviceStore/deviceStore.test.ts
Adds useGeofenceAlerts, wires it into App, adds focusWaypointId to app state, and preserves geofence fields in device store tests.
Bounding box draw interaction
apps/web/src/core/hooks/useBoundingBoxDraw.ts, apps/web/src/core/hooks/useBoundingBoxDraw.test.tsx, apps/web/src/components/PageComponents/Map/BoundingBoxOverlay.tsx
Adds the drag-based bounding-box drawing hook, its overlay, and hook tests.
Geofence map layer and visibility toggle
apps/web/src/components/PageComponents/Map/Layers/GeofenceLayer.tsx, apps/web/src/components/PageComponents/Map/Layers/GeofenceLayer.test.ts, apps/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx
Adds geofence GeoJSON generation and map layers, plus a geofences visibility toggle in the layer tool.
Waypoint edit dialog with geofence controls
apps/web/public/i18n/locales/en/map.json, apps/web/src/components/Dialog/WaypointEditDialog.tsx, apps/web/src/components/Dialog/WaypointEditDialog.test.tsx
Adds the waypoint editor for geofence radius presets, bounding-box controls, notification toggles, and save flow, with translations and tests.
Popup, layer wiring, and map page integration
apps/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx, apps/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx, apps/web/src/pages/Map/index.tsx
Adds geofence details and edit actions to waypoint popups, wires editing through the layer and map page, and renders placement and geofence UI on the map.

Protobuf Build Tooling

Layer / File(s) Summary
Protobuf generation and workspace config
packages/protobufs/package.json, pnpm-workspace.yaml
Adds a postinstall generation script and updates workspace build and override settings.

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

Possibly related issues

Poem

A rabbit hopped through map and code,
Where geofence circles softly glowed.
With boxes drawn and alerts that sing,
New waypoints learned to do their thing.
🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: waypoint create/edit support plus geofences.
Description check ✅ Passed The description covers the feature summary, linked issue, major changes, and testing, despite not mirroring every template section.
Linked Issues check ✅ Passed The PR implements the linked geofence requirements: editor controls, overlays, persistence, and baseline-first alerting with favorites gating.
Out of Scope Changes check ✅ Passed The visible changes are all in service of waypoint create/edit and geofence support, with no clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

Copilot AI 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.

Pull request overview

Implements end-to-end waypoint geofences for the Meshtastic web client (per design#114), including waypoint create/edit UI, geofence rendering overlays, and an alert engine that emits enter/exit toasts with a deep-link “View on map” action.

Changes:

  • Adds waypoint create/edit flow (including placement mode) and geofence controls (radius presets + bounding-box draw).
  • Renders geofence overlays (circle + bounding box) on the map and adds a layer visibility toggle.
  • Introduces a baseline-first geofence crossing tracker and hook-driven toast notifications with map-focus deep linking.

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Allows buf binary builds and pins @meshtastic/protobufs to the workspace for local stubs.
pnpm-lock.yaml Locks dependency changes needed for buf + updated protoc-gen-es and workspace protobuf override.
packages/protobufs/package.json Adds buf tooling and postinstall regeneration for protobuf TS stubs.
packages/protobufs/meshtastic/mesh.proto Extends Waypoint with geofence fields and adds the BoundingBox message (WSEN order).
apps/web/src/pages/Map/index.tsx Wires geofence layer, waypoint editor/creator, placement mode, bounding-box drawing overlay, and focus-by-waypoint-id deep link handling.
apps/web/src/core/utils/geofenceCrossings.ts Adds baseline-first (waypointId,nodeNum) geofence crossing state machine.
apps/web/src/core/utils/geofenceCrossings.test.ts Tests baseline-first semantics and per-node/per-waypoint independence.
apps/web/src/core/utils/geofence.ts Adds locale unit helpers and point-in-circle/box geofence evaluation helpers.
apps/web/src/core/utils/geofence.test.ts Tests units helpers and point-in-region logic (including anti-meridian boxes).
apps/web/src/core/stores/deviceStore/deviceStore.test.ts Ensures geofence waypoint fields persist through the device store.
apps/web/src/core/stores/appStore/index.ts Adds focusWaypointId slice + setter for deep-link focusing.
apps/web/src/core/stores/appStore/appStore.test.ts Tests focusWaypointId action/state behavior.
apps/web/src/core/hooks/useGeofenceAlerts.ts Subscribes to position packets, evaluates crossings, applies favorites gate, and shows enter/exit toasts with “View on map”.
apps/web/src/core/hooks/useBoundingBoxDraw.ts Adds a promise-based drag-to-define bounding-box draw hook for MapLibre.
apps/web/src/core/hooks/useBoundingBoxDraw.test.tsx Tests draw normalization, cancellation, and zero-area behavior.
apps/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx Adds “geofences” visibility toggle state and UI entry.
apps/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx Displays geofence metadata and adds an “Add/Edit geofence” action.
apps/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx Plumbs an edit callback from waypoint popup into the waypoint editor flow.
apps/web/src/components/PageComponents/Map/Layers/GeofenceLayer.tsx Adds a layer that generates and renders circle + bounding-box polygon features for waypoint geofences.
apps/web/src/components/PageComponents/Map/Layers/GeofenceLayer.test.ts Tests GeoJSON feature generation for circle/box/both.
apps/web/src/components/PageComponents/Map/BoundingBoxOverlay.tsx Adds a screen-space rectangle overlay used during bounding-box drawing.
apps/web/src/components/Dialog/WaypointEditDialog.tsx Adds waypoint create/edit dialog including geofence presets, draw/edit/remove bounding box, and notify toggles.
apps/web/src/components/Dialog/WaypointEditDialog.test.tsx Tests progressive reveal rules, draw callback wiring, and outbound geofence field persistence.
apps/web/src/App.tsx Installs a bridge component to run geofence alert evaluation at app scope.
apps/web/public/i18n/locales/en/map.json Adds strings for geofence layer toggle, waypoint editor UI, toast text, and unit strings.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread apps/web/src/components/Dialog/WaypointEditDialog.tsx Outdated
Comment thread apps/web/src/components/Dialog/WaypointEditDialog.tsx Outdated
Comment thread apps/web/src/components/Dialog/WaypointEditDialog.tsx
Comment thread apps/web/src/components/Dialog/WaypointEditDialog.tsx Outdated
Comment thread apps/web/src/core/utils/geofence.ts
Comment thread apps/web/src/components/PageComponents/Map/Layers/GeofenceLayer.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (5)
pnpm-workspace.yaml (1)

2-2: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Allowing build scripts for @bufbuild/buf weakens pnpm's default script-blocking protection.

This is necessary to let buf's postinstall fetch its native binary, but it's worth flagging as a conscious trust decision on that dependency, since pnpm blocks arbitrary install scripts by default specifically to mitigate supply-chain risk.

🤖 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 `@pnpm-workspace.yaml` at line 2, The pnpm workspace is explicitly allowing
build scripts for `@bufbuild/buf`, which bypasses pnpm’s default install-script
blocking. Keep this allowlist only if the dependency is fully trusted and the
native binary fetch is required, and make the trust decision clearly scoped to
`@bufbuild/buf` in pnpm-workspace.yaml rather than broadening script execution for
other packages.
apps/web/src/core/hooks/useGeofenceAlerts.ts (2)

42-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consolidate the duplicate client.nodes.byNum(from) lookup.

byNum(from) is called twice — once for the favorites gate, once for nodeName. A single lookup avoids duplicated work and keeps both usages guaranteed-consistent.

♻️ Suggested fix
-        if (wp.notifyFavoritesOnly) {
-          const node = client.nodes.byNum(from);
-          if (!node?.isFavorite) continue;
-        }
-        const node = client.nodes.byNum(from);
+        const node = client.nodes.byNum(from);
+        if (wp.notifyFavoritesOnly && !node?.isFavorite) continue;
         const nodeName = node?.user?.longName ?? String(from);
🤖 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 `@apps/web/src/core/hooks/useGeofenceAlerts.ts` around lines 42 - 47, The
`useGeofenceAlerts` logic is doing a duplicate `client.nodes.byNum(from)`
lookup, once in the `wp.notifyFavoritesOnly` gate and again for `nodeName`.
Refactor the surrounding block to fetch the node once, reuse that same `node`
for the favorites check and the `node?.user?.longName ?? String(from)` fallback,
and keep the existing behavior unchanged.

34-34: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Use coordToDeg instead of dividing by 1e7 inline.

geofence.ts already exports the shared conversion helper, so this hook can reuse it and stay aligned with the geofence math.

🤖 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 `@apps/web/src/core/hooks/useGeofenceAlerts.ts` at line 34, The geofence
coordinate conversion in useGeofenceAlerts is hardcoded inline instead of using
the shared helper. Update the point creation logic in useGeofenceAlerts to call
coordToDeg from geofence.ts for both longitudeI and latitudeI, so the hook
reuses the same conversion path as the rest of the geofence math and stays
consistent with shared behavior.
apps/web/src/core/hooks/useBoundingBoxDraw.ts (1)

47-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate pixel→lngLat conversion in onPointerDown/onPointerMove.

Both handlers repeat the same getBoundingClientRect() + map.unproject(...) logic. Extract a small helper to avoid divergence risk.

♻️ Suggested refactor
+  const toMapLngLat = useCallback(
+    (event: React.PointerEvent): [number, number] | undefined => {
+      const map = mapRef?.getMap();
+      if (!map) return undefined;
+      const rect = map.getContainer().getBoundingClientRect();
+      const lngLat = map.unproject([event.clientX - rect.left, event.clientY - rect.top]);
+      return [lngLat.lng, lngLat.lat];
+    },
+    [mapRef],
+  );
+
   const onPointerDown = useCallback(
     (event: React.PointerEvent) => {
-      const map = mapRef?.getMap();
-      if (!map) return;
-      const rect = map.getContainer().getBoundingClientRect();
-      const lngLat = map.unproject([event.clientX - rect.left, event.clientY - rect.top]);
-      startRef.current = [lngLat.lng, lngLat.lat];
-      currentRef.current = [lngLat.lng, lngLat.lat];
+      const point = toMapLngLat(event);
+      if (!point) return;
+      startRef.current = point;
+      currentRef.current = point;
       setState({ active: true, start: startRef.current, current: currentRef.current });
       event.currentTarget.setPointerCapture(event.pointerId);
     },
-    [mapRef],
+    [toMapLngLat],
   );
🤖 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 `@apps/web/src/core/hooks/useBoundingBoxDraw.ts` around lines 47 - 72, The
pointer handlers in useBoundingBoxDraw repeat the same pixel-to-lngLat
conversion logic in onPointerDown and onPointerMove. Extract the shared
getBoundingClientRect() plus map.unproject(...) code into a small helper within
useBoundingBoxDraw so both handlers call the same conversion path, keeping
startRef/currentRef updates unchanged and reducing the risk of divergence.
apps/web/src/components/Dialog/WaypointEditDialog.tsx (1)

34-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated large-unit threshold/conversion constants; inconsistent mile-preset label.

The unitSystem === "imperial" ? value >= 1609.344 : value >= 1000 threshold check and the 1609.344 magic number are repeated in three places (initial form derivation, preset application, and label rendering), independent of the displayToMeters/metersToDisplay helpers already imported from @core/utils/geofence.ts. This is fragile if the threshold logic in those utilities ever diverges from the copies here.

Separately, line 390's special case toFixed(meters === 1609 ? 1 : 0) produces "1.0 mi" for the 1-mile preset while the 5/10-mile presets render as "5 mi"/"10 mi" — an inconsistent, likely unintended display quirk.

♻️ Suggested cleanup
-                    ? `${(meters / 1609.344).toFixed(meters === 1609 ? 1 : 0)} ${t("unit.mile.plural")}`
+                    ? `${Math.round(meters / 1609.344)} ${t("unit.mile.plural")}`

Consider extracting a shared isLargeUnit(meters, system) helper (alongside displayToMeters/metersToDisplay in geofence.ts) to eliminate the duplicated threshold logic.

Also applies to: 90-91, 181-190, 384-394

🤖 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 `@apps/web/src/components/Dialog/WaypointEditDialog.tsx` around lines 34 - 42,
The waypoint radius logic in WaypointEditDialog duplicates the imperial/metric
threshold and 1609.344 conversion in multiple spots, and the 1-mile preset label
is rendered inconsistently. Replace the repeated `unitSystem === "imperial" ?
value >= 1609.344 : value >= 1000` checks with the existing
`displayToMeters`/`metersToDisplay` helpers or a shared helper near
`geofence.ts`, and update the preset label rendering so the mile presets format
consistently (including the 1-mile case) within `WaypointEditDialog`.
🤖 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 `@apps/web/src/components/Dialog/WaypointEditDialog.tsx`:
- Around line 267-269: The save path in WaypointEditDialog is still copying
notifyOnEnter, notifyOnExit, and notifyFavoritesOnly from form into base even
after hasAnyGeofence becomes false. Update the form/state handling in
WaypointEditDialog so these flags are cleared or ignored whenever the radius/box
geofence is removed, and make sure the save logic that builds base only persists
notification flags when a geofence actually exists.
- Around line 271-273: Replace the 32-bit random waypoint ID generation in
WaypointEditDialog’s new-waypoint creation path with the existing
collision-resistant randId() helper (or an equivalent stronger generator) so
device.addWaypoint() does not accidentally dedupe and overwrite another
waypoint. Update the isCreating && base.id === 0 branch in WaypointEditDialog to
use the shared ID generator consistently with the rest of the waypoint flow.
- Around line 275-280: The waypoint flow in WaypointEditDialog currently updates
local state via device.addWaypoint before the network send, which can leave the
web store out of sync if sendWaypoint fails. Update the add/update path to
follow the same send-first pattern used by removeWaypoint: call
device.connection.sendWaypoint from the relevant handler before committing with
device.addWaypoint, and only persist locally after the send succeeds. Keep the
fix within WaypointEditDialog and preserve the existing targetChannel/fromNode
handling.

In `@apps/web/src/core/utils/geofence.ts`:
- Around line 10-17: The locale check in unitSystemFromLocale only normalizes
casing for the en-US fallback, so lowercase en-LR and en-MM are still missed and
return the wrong unit system. Update the matching logic in unitSystemFromLocale
and the IMPERIAL_LOCALES lookup to compare normalized locale strings for all
imperial entries, so casing-insensitive matches work uniformly for every
imperial locale.

In `@apps/web/src/pages/Map/index.tsx`:
- Around line 313-327: The WaypointEditDialog is being unmounted/closed during
box drawing, which causes its form state to reset when it reopens. Update the
Map page’s WaypointEditDialog usage so its open prop is driven only by
editorOpen, and handle boxDraw.active with a separate visual-hide state instead
of toggling the dialog itself. Keep editorWaypoint and editorInitialLngLat
intact across the draw flow so the dialog’s open-triggered reset effect in
WaypointEditDialog does not wipe unsaved edits mid-session.
- Around line 341-346: The box-draw pointer-capture overlay in Map/index.tsx is
missing touch gesture suppression, so touch drags can be converted into browser
scrolling/panning and cancel the draw flow. Update the fixed overlay div that
wires boxDraw.onPointerDown/onPointerMove/onPointerUp to disable touch panning
(for example via touch-action none on that element or equivalent styling) so
touch input stays with the box-draw handlers.

In `@packages/protobufs/package.json`:
- Around line 19-21: The postinstall script in package.json is triggering
protobuf generation on every install, which should be removed. Update the
scripts so buf/pnpm run gen is only invoked from an explicit build or other
opt-in command, and keep the build flow centered around the existing gen script
rather than postinstall.

---

Nitpick comments:
In `@apps/web/src/components/Dialog/WaypointEditDialog.tsx`:
- Around line 34-42: The waypoint radius logic in WaypointEditDialog duplicates
the imperial/metric threshold and 1609.344 conversion in multiple spots, and the
1-mile preset label is rendered inconsistently. Replace the repeated `unitSystem
=== "imperial" ? value >= 1609.344 : value >= 1000` checks with the existing
`displayToMeters`/`metersToDisplay` helpers or a shared helper near
`geofence.ts`, and update the preset label rendering so the mile presets format
consistently (including the 1-mile case) within `WaypointEditDialog`.

In `@apps/web/src/core/hooks/useBoundingBoxDraw.ts`:
- Around line 47-72: The pointer handlers in useBoundingBoxDraw repeat the same
pixel-to-lngLat conversion logic in onPointerDown and onPointerMove. Extract the
shared getBoundingClientRect() plus map.unproject(...) code into a small helper
within useBoundingBoxDraw so both handlers call the same conversion path,
keeping startRef/currentRef updates unchanged and reducing the risk of
divergence.

In `@apps/web/src/core/hooks/useGeofenceAlerts.ts`:
- Around line 42-47: The `useGeofenceAlerts` logic is doing a duplicate
`client.nodes.byNum(from)` lookup, once in the `wp.notifyFavoritesOnly` gate and
again for `nodeName`. Refactor the surrounding block to fetch the node once,
reuse that same `node` for the favorites check and the `node?.user?.longName ??
String(from)` fallback, and keep the existing behavior unchanged.
- Line 34: The geofence coordinate conversion in useGeofenceAlerts is hardcoded
inline instead of using the shared helper. Update the point creation logic in
useGeofenceAlerts to call coordToDeg from geofence.ts for both longitudeI and
latitudeI, so the hook reuses the same conversion path as the rest of the
geofence math and stays consistent with shared behavior.

In `@pnpm-workspace.yaml`:
- Line 2: The pnpm workspace is explicitly allowing build scripts for
`@bufbuild/buf`, which bypasses pnpm’s default install-script blocking. Keep this
allowlist only if the dependency is fully trusted and the native binary fetch is
required, and make the trust decision clearly scoped to `@bufbuild/buf` in
pnpm-workspace.yaml rather than broadening script execution for other packages.
🪄 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: f0e514c7-7817-49bf-8f42-4b711f341b2f

📥 Commits

Reviewing files that changed from the base of the PR and between 14ab140 and 7e2fca7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • apps/web/public/i18n/locales/en/map.json
  • apps/web/src/App.tsx
  • apps/web/src/components/Dialog/WaypointEditDialog.test.tsx
  • apps/web/src/components/Dialog/WaypointEditDialog.tsx
  • apps/web/src/components/PageComponents/Map/BoundingBoxOverlay.tsx
  • apps/web/src/components/PageComponents/Map/Layers/GeofenceLayer.test.ts
  • apps/web/src/components/PageComponents/Map/Layers/GeofenceLayer.tsx
  • apps/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx
  • apps/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx
  • apps/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx
  • apps/web/src/core/hooks/useBoundingBoxDraw.test.tsx
  • apps/web/src/core/hooks/useBoundingBoxDraw.ts
  • apps/web/src/core/hooks/useGeofenceAlerts.ts
  • apps/web/src/core/stores/appStore/appStore.test.ts
  • apps/web/src/core/stores/appStore/index.ts
  • apps/web/src/core/stores/deviceStore/deviceStore.test.ts
  • apps/web/src/core/utils/geofence.test.ts
  • apps/web/src/core/utils/geofence.ts
  • apps/web/src/core/utils/geofenceCrossings.test.ts
  • apps/web/src/core/utils/geofenceCrossings.ts
  • apps/web/src/pages/Map/index.tsx
  • packages/protobufs/meshtastic/mesh.proto
  • packages/protobufs/package.json
  • pnpm-workspace.yaml

Comment thread apps/web/src/components/Dialog/WaypointEditDialog.tsx Outdated
Comment thread apps/web/src/components/Dialog/WaypointEditDialog.tsx
Comment thread apps/web/src/components/Dialog/WaypointEditDialog.tsx
Comment thread apps/web/src/core/utils/geofence.ts Outdated
Comment thread apps/web/src/pages/Map/index.tsx
Comment thread apps/web/src/pages/Map/index.tsx
Comment thread packages/protobufs/package.json Outdated
danditomaso and others added 2 commits July 6, 2026 20:05
Implements the Waypoint geofences client spec (meshtastic/design#114) end
to end in the web client:

* Proto extended with `geofence_radius`, `bounding_box`, `notify_on_enter`,
  `notify_on_exit`, `notify_favorites_only` (fields 9-13) plus a `BoundingBox`
  message (WSEN). Regenerated at install time — `@bufbuild/buf` added as a
  workspace devDep and a `postinstall` hook regenerates `packages/ts/dist`,
  keeping the generated stubs out of git per repo convention.
* SDK protobufs override in `pnpm-workspace.yaml` so all packages consume the
  workspace `@meshtastic/protobufs` instead of the JSR-pinned copy.
* Waypoint create UI: the web client previously had no create/edit flow;
  a new toolbar toggle enters placement mode (cursor swapped to a MapPinPlus
  SVG matching the toolbar affordance) and the map click seeds the editor
  dialog with name/description/icon/coords/expiry fields.
* Geofence controls in the editor: Off + locale-aware radius presets
  (metric: 100 m / 500 m / 1 km / 5 km; imperial: 528 ft / 0.5 mi / 1 mi /
  5 mi / 10 mi), a drag-to-define bounding box (`useBoundingBoxDraw` +
  `BoundingBoxOverlay`), and enter/exit + favorites-only toggles that
  reveal progressively per the spec's UX affordances.
* Map overlays: `GeofenceLayer` renders turf circles + WSEN polygons for
  every waypoint that carries a geofence, toggled via the layer tool.
* Alert engine: `GeofenceCrossings` implements baseline-first per-pair
  `(waypointId, nodeNum)` state, `useGeofenceAlerts` evaluates each incoming
  node position, applies the favorites-only gate against the SDK
  `NodesClient`, and emits an enter/exit toast with a ToastAction that
  deep-links to the waypoint on the map via router.navigate + a new
  `focusWaypointId` slice on the app store.
* Persistence: existing `addWaypoint` spread covers the new fields; a store
  test asserts the geofence payload round-trips through the device store.
* i18n: added `waypointEdit.*` block plus radius/unit/preset strings.
* Tests: 8 new suites covering point-in-region math, baseline-first
  crossings, overlay feature generation, drag-to-define hook, editor
  dialog interaction (preset selection, progressive reveal, save wiring),
  focus-waypoint app-store slice, and geofence persistence in the device
  store. 275 tests pass in apps/web, 419 across the workspace.

Refs: meshtastic/design#114, meshtastic/protobufs#962
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@danditomaso danditomaso force-pushed the feature/waypoint-geofences branch from 0025d03 to 755c09b Compare July 7, 2026 00:13
…string

Review finding: the `unitSystemFromLocale` helper only lowercased for the
`en-US` fallback path, so `en-lr` / `en-mm` etc. would silently fall through
to metric. Rather than harden the locale-string parsing, switch to the
convention already used elsewhere in the app (see the position-precision
selector in `Channels/Channel.tsx`): read `config.display.units` from the
connected device. Waypoint create/edit now:

* Reads `Protobuf.Config.Config_DisplayConfig_DisplayUnits` from
  `useDevice().config.display.units`.
* Exposes `unitSystemFromDisplayUnits(units | undefined)` on the geofence
  helper. Defaults to metric when the config slice hasn't loaded.
* Drops the ad-hoc `IMPERIAL_LOCALES` set and the locale-casing branch.

Tests updated to cover the enum mapping. Dialog test now mocks
`config.display.units` on the fake device instead of `i18n.language`.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@apps/web/src/core/utils/geofence.ts`:
- Around line 24-30: The imperial conversion in metersToDisplay is inconsistent
with WaypointEditDialog.initialForm because it switches to feet below 1 mile
while the editor uses useLargeUnit at 0.5 mile. Update metersToDisplay to use
the same 0.5-mile threshold for imperial display so values between 0.5 and 1
mile stay in miles, and verify the logic stays aligned with the geofenceRadius
flow in WaypointEditDialog.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

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: d38eedea-f427-4c4b-a0e5-76f0a5fd5e03

📥 Commits

Reviewing files that changed from the base of the PR and between 755c09b and c47b47d.

📒 Files selected for processing (4)
  • apps/web/src/components/Dialog/WaypointEditDialog.test.tsx
  • apps/web/src/components/Dialog/WaypointEditDialog.tsx
  • apps/web/src/core/utils/geofence.test.ts
  • apps/web/src/core/utils/geofence.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/src/core/utils/geofence.test.ts
  • apps/web/src/components/Dialog/WaypointEditDialog.test.tsx
  • apps/web/src/components/Dialog/WaypointEditDialog.tsx

Comment thread apps/web/src/core/utils/geofence.ts
danditomaso and others added 6 commits July 6, 2026 21:49
Review finding: `Math.floor(Math.random() * 0xffffffff)` is neither
collision-resistant nor consistent with the rest of the waypoint flow.
`sendWaypoint` in `packages/sdk` already stamps outbound waypoints with
`generatePacketId()` (crypto.getRandomValues-backed), so the local id we
stored via `addWaypoint` was drifting from the id the mesh actually sees
and could collide with an existing waypoint on the device store's
per-id dedup. Reuse the shared generator so the local and broadcast ids
match.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
- Drop postinstall hook so pnpm install stays offline (buf uses remote plugin
  needing BSR access; CodeRabbit flagged on PR).
- Regenerate dist against protoc-gen-es 2.12.1 (stale after meshtastic#1204).
- Export DeviceUi, DeviceOnly, DeviceOnlyLegacy, InterDevice, SerialHal from
  mod.ts to match generated files.
… layer

- Align imperial unit threshold at 0.5 mi across metersToDisplay, useLargeUnit
  calc, applyRadiusPreset, and preset labels so a 0.5 mi waypoint no longer
  round-trips as feet.
- Gate notifyOnEnter/notifyOnExit/notifyFavoritesOnly on hasAnyGeofence in save
  path; removing the geofence no longer persists stale notify flags.
- Send waypoint before addWaypoint so a transport error doesn't desync the
  local store from the mesh (mirrors removeWaypoint ordering).
- Drop `open` from the form-reset useEffect so unmounting during box draw no
  longer wipes unsaved edits when the dialog reopens.
- Split anti-meridian bounding boxes into two polygons in GeofenceLayer so the
  overlay matches pointInBoundingBox containment instead of drawing a
  globe-wrapping polygon.
Resolve pnpm-lock.yaml conflict by taking upstream lock and running
pnpm install to sync with merged package.json ranges.

Copilot AI 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.

Pull request overview

Copilot reviewed 25 out of 52 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread apps/web/src/components/Dialog/WaypointEditDialog.tsx
Comment on lines +245 to +249
if (!Number.isFinite(lat) || !Number.isFinite(lng)) {
toast({ title: t("waypointEdit.errorBadCoords") });
setSaving(false);
return;
}
Comment on lines +53 to +63
action: createElement(
ToastAction,
{
altText: t("waypointEdit.viewOnMap"),
onClick: () => {
setFocusWaypointId(wp.id);
void router.navigate({ to: "/map" });
},
},
t("waypointEdit.viewOnMap"),
) as unknown as ToastActionElement,
Comment thread apps/web/src/core/utils/geofence.ts
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web-test Ready Ready Preview, Comment Jul 10, 2026 2:31am

Request Review

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.8.0] - Waypoint Geofences

2 participants