Skip to content

fix(geofence): restrict crossing alerts to creator, add per-geofence opt-in#6117

Merged
jamesarich merged 4 commits into
mainfrom
claude/strange-napier-4b3d21
Jul 6, 2026
Merged

fix(geofence): restrict crossing alerts to creator, add per-geofence opt-in#6117
jamesarich merged 4 commits into
mainfrom
claude/strange-napier-4b3d21

Conversation

@jamesarich

@jamesarich jamesarich commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Why

Users reported that every node in range was receiving geofence crossing notifications, not just the geofence's creator. Geofence waypoints are mesh-broadcast, so every device stored the waypoint and GeofenceMonitor evaluated crossings for all of them — so everyone got the creator's alerts.

🐛 Fix

  • GeofenceMonitor now tracks only geofences this device created (DataPacket.isFromLocal), combined with myNodeNum so the filter recomputes once our node number is known (a waypoint can arrive before we're connected).

🌟 Per-geofence opt-in

Receivers can still choose to be alerted on someone else's geofence:

  • Tapping a foreign geofence opens a new read-only WaypointInfoDialog — reached for both locked and unlocked foreign geofences, so the previously dead-end locked case (which used to only toast / show a delete dialog) now has a view.
  • It carries one interactive control, "Notify me of crossings", which writes a local preference — it never edits or re-broadcasts the waypoint.
  • GeofenceMonitor tracks a geofence when isFromLocal(myNodeNum) || id in optedIn.
  • Unlocked foreign geofences keep an Edit button into the full editor; locked ones stay read-only.
  • The opt-in set is stored insertion-ordered and capped (oldest evicted past the max) so it can't grow unbounded as waypoints churn.

🧹 Housekeeping

  • Extracted the monitor predicate into a pure, unit-tested Collection<DataPacket>.geofencesToMonitor(myNodeNum, optedInIds) in core:model.
  • WaypointInfoDialog's toggle row reuses the shared core:ui BasicListItem rather than a hand-rolled layout.

Reviewer notes

  • Deliberate behavior change: unlocked foreign geofences now open the read-only dialog (with an Edit button) instead of dropping straight into the editor. Editing/re-broadcasting someone else's geofence is still possible via that Edit button, just no longer the default tap action.
  • Own geofences are unaffected — they still open the editor as before.
  • The NotificationPrefs interface gained geofenceAlertOptIns + setGeofenceAlertOptIn; BaseMapViewModel gained the opt-in accessors and isMyWaypoint, rippling to the flavor ViewModels.

Testing Performed

  • New/updated unit tests: ActiveWaypointsTest (creator-only / opt-in-adds-foreign / drops-silent), NotificationPrefsTest (set round-trip + cap eviction), GeofenceMonitorTest (remote-created geofence stays silent), plus MeshDataHandlerTest / BaseMapViewModelTest constructor updates.
  • ./gradlew spotlessCheck detekt — clean.
  • ./gradlew :core:data:allTests :feature:map:allTests :core:prefs:allTests — green.
  • ./gradlew assembleDebug — both google and fdroid flavors compile.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a dedicated geofence/waypoint info dialog from map waypoint markers, including a crossing-alert opt-in toggle for eligible foreign geofences.
    • Added persistent per-geofence alert opt-in support wired through the app and shown in the dialog.
  • Bug Fixes

    • Updated geofence monitoring so notifications are triggered only for the correct active set (locally created or explicitly opted in).
    • Improved geofence monitoring failure handling across the combined inputs.
  • Tests

    • Updated/added unit tests for geofence opt-in storage and notification behavior.
    • Added screenshot coverage for the new dialog states.

Screenshots

New read-only WaypointInfoDialog, shown when you tap a geofence you didn't create. Locked geofences are view-only; unlocked ones keep an Edit path into the full editor. The "Notify me of crossings" switch is the receiver-local opt-in.

Read-only (locked) Opted-in (unlocked, with Edit)
Light
Dark

…opt-in

Geofence waypoints are mesh-broadcast, so every device in range stored the
waypoint and GeofenceMonitor evaluated crossings for all of them — meaning
*every* user got the creator's crossing notifications (the reported bug).

Now the monitor only tracks geofences this device created (DataPacket.isFromLocal),
combined with myNodeNum so the filter recomputes once our node number is known.
Receivers can still opt in to a specific foreign geofence: tapping one opens a new
read-only WaypointInfoDialog (reached for both locked and unlocked foreign
geofences, so the previously dead-end locked case now has a view) with a
"Notify me of crossings" toggle. The toggle writes a local pref only — it never
edits or re-broadcasts the waypoint. Unlocked foreign geofences keep an Edit
button into the full editor.

The opt-in set is stored insertion-ordered and capped (oldest evicted past the
max) so it can't grow unbounded as waypoints churn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 058df7e4-5c44-4461-bf11-db3786e52c30

📥 Commits

Reviewing files that changed from the base of the PR and between 07bc399 and 19cf151.

📒 Files selected for processing (1)
  • desktopApp/src/test/kotlin/org/meshtastic/desktop/notification/DesktopNotificationManagerTest.kt

📝 Walkthrough

Walkthrough

This PR adds per-geofence crossing alert opt-in support across preferences, geofence monitoring, map view models, and map UI. It also adds a new waypoint info dialog for foreign geofence waypoints with localization, previews, and screenshot coverage.

Changes

Geofence Alert Opt-In Feature

Layer / File(s) Summary
NotificationPrefs opt-in storage
core/repository/.../AppPreferences.kt, core/prefs/.../NotificationPrefsImpl.kt, core/testing/.../FakeNotificationPrefs.kt, core/prefs/.../NotificationPrefsTest.kt
NotificationPrefs gains geofenceAlertOptIns and setGeofenceAlertOptIn, backed by a CSV DataStore preference with capped storage, plus fake implementation and unit tests.
Geofence filtering logic
core/model/.../ActiveWaypoints.kt, core/model/.../ActiveWaypointsTest.kt
Adds geofencesToMonitor(myNodeNum, optedInIds) filtering local-created or opted-in waypoints that have crossing notifications enabled, with tests.
GeofenceMonitor combined stream wiring
core/data/.../GeofenceMonitor.kt, core/data/.../GeofenceMonitorTest.kt, core/data/.../MeshDataHandlerTest.kt
Combines waypoint stream, node number, and opt-ins to recompute active geofences and crossing store; tests updated including a remote-created geofence scenario.
ViewModel wiring for NotificationPrefs
feature/map/.../BaseMapViewModel.kt, feature/map/.../SharedMapViewModel.kt, feature/map/.../BaseMapViewModelTest.kt, androidApp/.../MapViewModel.kt (fdroid, google)
Injects NotificationPrefs into the map view model hierarchy, exposing geofenceAlertOptIns, setGeofenceAlertOptIn, and isMyWaypoint.
WaypointInfoDialog component
feature/map/.../WaypointInfoDialog.kt, feature/map/.../WaypointInfoDialogPreviews.kt, core/resources/.../strings.xml, .skills/compose-ui/strings-index.txt, screenshot-tests/.../MapScreenshotTests.kt
New composable dialog shows geofence details, an alert toggle, and optional edit action; adds localized strings, previews, and screenshot tests.
Map UI integration for geofence info dialog
androidApp/.../MapView.kt (fdroid, google), WaypointMarkers.kt
Wires long-press/click handling to route foreign geofence waypoints to WaypointInfoDialog and conditionally enable editing based on lock state.

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

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: creator-only geofence crossing alerts plus per-geofence opt-in.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/strange-napier-4b3d21

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: 1

🧹 Nitpick comments (6)
core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.kt (1)

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

Consider reusing FakeNotificationPrefs instead of mocking here.

core/testing.FakeNotificationPrefs already defaults geofenceAlertOptIns to emptySet(), so this could be notificationPrefs = FakeNotificationPrefs() instead of manually mocking and stubbing the property — less boilerplate, consistent with the fake-based approach already used for meshBeaconRepository/fakeBeaconPrefs in this same file.

Also applies to: 151-154

core/prefs/src/commonTest/kotlin/org/meshtastic/core/prefs/notification/NotificationPrefsTest.kt (1)

98-118: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider testing the "retoggle moves to most-recent" guarantee.

Existing tests cover basic add/remove and cap eviction (FIFO), but not the specific behavior documented in NotificationPrefsImpl.setGeofenceAlertOptIn where re-enabling an already-opted-in id resets its eviction priority to newest. A test filling to the cap, then retoggling the oldest surviving id, then adding one more to trigger eviction, would confirm that behavior and guard against regressions.

🤖 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
`@core/prefs/src/commonTest/kotlin/org/meshtastic/core/prefs/notification/NotificationPrefsTest.kt`
around lines 98 - 118, The current tests in NotificationPrefsTest cover
add/remove and FIFO eviction, but they do not verify the retoggle-to-newest
behavior implemented in NotificationPrefsImpl.setGeofenceAlertOptIn. Add a test
that fills geofenceAlertOptIns to MAX_GEOFENCE_OPT_INS, re-enables an already
present id to make it most recent, then adds one more id to force eviction and
assert the retoggled id is preserved while the true oldest is removed. Use the
existing geofenceAlertOptIns and setGeofenceAlertOptIn symbols to keep the test
aligned with the implementation.
core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/GeofenceMonitorTest.kt (1)

84-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider an end-to-end positive test for opted-in foreign geofences.

The harness already supports optedInIds, but no test exercises "foreign + opted-in → notification fires" through the full GeofenceMonitor pipeline (only remoteCreatedGeofenceDoesNotNotify covers the negative case here; the positive path is only unit-tested in ActiveWaypointsTest.kt). Adding one would close the integration-level coverage gap for the new opt-in feature.

🤖 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
`@core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/GeofenceMonitorTest.kt`
around lines 84 - 104, Add an end-to-end positive test in GeofenceMonitorTest
that exercises the full GeofenceMonitor pipeline for a foreign geofence with
opted-inIds set, verifying that a notification is emitted when the remote
creator is opted in. Reuse the existing mocks(...) helper and the
GeofenceMonitor flow setup, and mirror the negative remote-created case with an
asserted positive notification path so the opt-in behavior is covered at
integration level.
core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/GeofenceMonitor.kt (1)

94-106: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Combined flow has no retry after a single failure.

.catch { ... } on the combined flow logs and then completes the flow — there's no retry/retryWhen, so any single exception from getWaypoints(), myNodeNum, or notificationPrefs.geofenceAlertOptIns permanently stops geofence tracking for the rest of the session. This is now a 3-way surface (previously only the waypoints stream), and geofenceAlertOptIns is backed by a DataStore-preferences read that can throw IOException on a read/corruption failure.

♻️ Add retry before collecting
             combine(
                 packetRepository.value.getWaypoints(),
                 nodeManager.myNodeNum,
                 notificationPrefs.geofenceAlertOptIns,
             ) { packets, myNodeNum, optIns ->
                 packets.activeWaypointPackets(nowSeconds).values.geofencesToMonitor(myNodeNum, optIns)
             }
+                .retryWhen { cause, attempt ->
+                    Logger.e(cause) { "Geofence waypoint stream failed (attempt $attempt); retrying" }
+                    delay(RETRY_BACKOFF_MS)
+                    true
+                }
                 .catch { Logger.e(it) { "Geofence waypoint stream failed; geofence tracking paused" } }
                 .collect { active ->

Please confirm whether the DataStore instance backing NotificationPrefsImpl.geofenceAlertOptIns configures a corruptionHandler/upstream .catch that already converts IOException to a fallback value, which would reduce the likelihood of this reaching GeofenceMonitor.

🤖 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
`@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/GeofenceMonitor.kt`
around lines 94 - 106, The combined geofence flow in GeofenceMonitor currently
logs failures with catch and then ends, so tracking can stop permanently after
one transient error. Update the flow built in the scope.launch/combine chain to
retry before collect, using a retry or retryWhen strategy around the upstream
getWaypoints, myNodeNum, and notificationPrefs.geofenceAlertOptIns sources while
preserving the existing error log in the catch block. Also verify whether
NotificationPrefsImpl.geofenceAlertOptIns already handles IOException via a
corruptionHandler or upstream catch so the retry scope is only applied where
needed.
feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt (1)

111-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a unit test for isMyWaypoint.

The new public isMyWaypoint(id) has no direct test coverage in BaseMapViewModelTest (only the constructor wiring was updated). Given it now gates whether the UI shows an editable vs. read-only dialog, a couple of focused tests (local waypoint, foreign waypoint, waypoint missing from map) would guard against regressions.

🤖 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
`@feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt`
around lines 111 - 119, The new public BaseMapViewModel.isMyWaypoint(id) helper
is not directly covered by tests. Add focused unit tests in BaseMapViewModelTest
that exercise the isMyWaypoint path for a local waypoint, a foreign waypoint,
and a missing waypoint, using the BaseMapViewModel constructor setup and
waypoint state so the method’s return value is verified independently of the UI.
androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt (1)

725-745: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Stale waypoint snapshot while dialog is open.

waypoint is captured once from geofenceInfoWaypoint when the dialog opens; if the underlying waypoint packet updates (or expires) while the dialog is showing, the dialog won't reflect the change (e.g. locked_to toggling would leave a stale Edit button enablement). Low impact since the dialog is short-lived and self-corrects on next open.

🤖 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 `@androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt` around lines
725 - 745, The dialog is using a stale waypoint snapshot from
geofenceInfoWaypoint, so changes to the underlying packet while
WaypointInfoDialog is open won’t be reflected. Update MapView.kt so the dialog
reads the latest waypoint state on each recomposition instead of relying on the
captured waypoint value, especially for fields like locked_to that control
onEdit. Use the existing geofenceInfoWaypoint selection and MapViewModel state
to resolve the current waypoint data before passing it into WaypointInfoDialog.
🤖 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 `@androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapView.kt`:
- Around line 828-848: The edit action in WaypointInfoDialog is currently
enabled for any unlocked foreign geofence, even when the app is disconnected.
Update the onEdit assignment in MapView.kt for both map flavors so it is only
provided when waypoint.locked_to == 0 and isConnected is true, keeping onEdit
null otherwise; use the existing showGeofenceInfoDialog branch and
WaypointInfoDialog callsite to apply the same gate consistently.

---

Nitpick comments:
In `@androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt`:
- Around line 725-745: The dialog is using a stale waypoint snapshot from
geofenceInfoWaypoint, so changes to the underlying packet while
WaypointInfoDialog is open won’t be reflected. Update MapView.kt so the dialog
reads the latest waypoint state on each recomposition instead of relying on the
captured waypoint value, especially for fields like locked_to that control
onEdit. Use the existing geofenceInfoWaypoint selection and MapViewModel state
to resolve the current waypoint data before passing it into WaypointInfoDialog.

In
`@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/GeofenceMonitor.kt`:
- Around line 94-106: The combined geofence flow in GeofenceMonitor currently
logs failures with catch and then ends, so tracking can stop permanently after
one transient error. Update the flow built in the scope.launch/combine chain to
retry before collect, using a retry or retryWhen strategy around the upstream
getWaypoints, myNodeNum, and notificationPrefs.geofenceAlertOptIns sources while
preserving the existing error log in the catch block. Also verify whether
NotificationPrefsImpl.geofenceAlertOptIns already handles IOException via a
corruptionHandler or upstream catch so the retry scope is only applied where
needed.

In
`@core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/GeofenceMonitorTest.kt`:
- Around line 84-104: Add an end-to-end positive test in GeofenceMonitorTest
that exercises the full GeofenceMonitor pipeline for a foreign geofence with
opted-inIds set, verifying that a notification is emitted when the remote
creator is opted in. Reuse the existing mocks(...) helper and the
GeofenceMonitor flow setup, and mirror the negative remote-created case with an
asserted positive notification path so the opt-in behavior is covered at
integration level.

In
`@core/prefs/src/commonTest/kotlin/org/meshtastic/core/prefs/notification/NotificationPrefsTest.kt`:
- Around line 98-118: The current tests in NotificationPrefsTest cover
add/remove and FIFO eviction, but they do not verify the retoggle-to-newest
behavior implemented in NotificationPrefsImpl.setGeofenceAlertOptIn. Add a test
that fills geofenceAlertOptIns to MAX_GEOFENCE_OPT_INS, re-enables an already
present id to make it most recent, then adds one more id to force eviction and
assert the retoggled id is preserved while the true oldest is removed. Use the
existing geofenceAlertOptIns and setGeofenceAlertOptIn symbols to keep the test
aligned with the implementation.

In
`@feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt`:
- Around line 111-119: The new public BaseMapViewModel.isMyWaypoint(id) helper
is not directly covered by tests. Add focused unit tests in BaseMapViewModelTest
that exercise the isMyWaypoint path for a local waypoint, a foreign waypoint,
and a missing waypoint, using the BaseMapViewModel constructor setup and
waypoint state so the method’s return value is verified independently of the UI.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 005a4a27-3c54-4453-b169-20e640e72777

📥 Commits

Reviewing files that changed from the base of the PR and between cd98090 and 28195a5.

📒 Files selected for processing (20)
  • .skills/compose-ui/strings-index.txt
  • androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapView.kt
  • androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt
  • androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt
  • androidApp/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt
  • androidApp/src/google/kotlin/org/meshtastic/app/map/component/WaypointMarkers.kt
  • core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/GeofenceMonitor.kt
  • core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/GeofenceMonitorTest.kt
  • core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.kt
  • core/model/src/commonMain/kotlin/org/meshtastic/core/model/geofence/ActiveWaypoints.kt
  • core/model/src/commonTest/kotlin/org/meshtastic/core/model/geofence/ActiveWaypointsTest.kt
  • core/prefs/src/commonMain/kotlin/org/meshtastic/core/prefs/notification/NotificationPrefsImpl.kt
  • core/prefs/src/commonTest/kotlin/org/meshtastic/core/prefs/notification/NotificationPrefsTest.kt
  • core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/AppPreferences.kt
  • core/resources/src/commonMain/composeResources/values/strings.xml
  • core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeNotificationPrefs.kt
  • feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/component/WaypointInfoDialog.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/BaseMapViewModelTest.kt

Comment thread androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapView.kt
Address CodeRabbit review on #6117:
- WaypointInfoDialog's Edit button now requires `isConnected` in both map
  flavors — editing a foreign geofence re-broadcasts it, which needs a live
  connection, so the affordance is hidden when offline.
- MeshDataHandlerTest uses FakeNotificationPrefs instead of a hand-stubbed mock.
- Add a NotificationPrefs test for retoggle-refreshes-eviction-order on the
  capped opt-in set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jamesarich
jamesarich enabled auto-merge July 6, 2026 19:07
Two previews (locked read-only, unlocked opted-in with Edit) × light/dark,
wired into MapScreenshotTests with generated reference PNGs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the bugfix PR tag label Jul 6, 2026
DesktopNotificationManagerTest has its own private FakeNotificationPrefs; add
geofenceAlertOptIns + setGeofenceAlertOptIn so :desktopApp:test compiles after
the interface gained the geofence opt-in members. (The shard-app CI shard runs
:desktopApp:test, which wasn't covered by the earlier assembleDebug check.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the desktop Desktop target label Jul 6, 2026
@jamesarich
jamesarich added this pull request to the merge queue Jul 6, 2026
Merged via the queue into main with commit e400acf Jul 6, 2026
17 checks passed
@jamesarich
jamesarich deleted the claude/strange-napier-4b3d21 branch July 6, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag desktop Desktop target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant