fix(notifications): open node detail when tapping 'New Node Seen' notification#5752
Merged
jamesarich merged 4 commits intoJun 17, 2026
Merged
Conversation
Contributor
Author
|
Bug report to come, will update here when report has been appropriately filed. |
Collaborator
|
@LesterCheng please sign CLA |
…ification
The 'New Node Seen' notification dispatched from NodeManagerImpl had no
content intent attached, so tapping it did nothing. AndroidNotificationManager.dispatch
built a NotificationCompat.Builder without ever calling setContentIntent(...).
Enrich the generic KMP Notification model with an optional deepLinkUri and
have the Android dispatcher synthesize a TaskStackBuilder PendingIntent when
one is provided. NodeManagerImpl now sets deepLinkUri to meshtastic://meshtastic/nodes/{nodeNum}
and id to the node num (so repeated emissions update the existing notification
instead of duplicating).
The existing deep-link plumbing in MainActivity -> DeepLinkRouter ->
MultiBackstack handles the rest: /nodes/{n} routes to [NodesRoute.Nodes,
NodesRoute.NodeDetail(n)] and MultiBackstack.handleDeepLink switches to the
Nodes tab and replaces the stack, so back from NodeDetail returns naturally
to the Nodes list.
Tests:
- AndroidNotificationManagerTest: 3 new cases covering PendingIntent is
attached with correct ACTION_VIEW + URI + component, contentIntent stays
null when deepLinkUri is null, and explicit notification id is honored.
- Test-only stub MainActivity at org.meshtastic.app so Class.forName resolves
under Robolectric without pulling :androidApp into the :core:service test
classpath. Activity registered dynamically via shadowOf(packageManager)
so TaskStackBuilder.addNextIntentWithParentStack succeeds.
Note: MeshServiceNotificationsImpl.showNewNodeSeenNotification already wires
this deep link correctly but is dead code (never invoked from production).
Left as-is; can be cleaned up in a follow-up PR.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4406a77 to
0a67fc3
Compare
8 tasks
jamesarich
approved these changes
Jun 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
The "New Node Seen" notification does nothing when tapped.
Root cause
NodeManagerImpl.handleReceivedUserdispatches the notification through the generic KMP path:AndroidNotificationManager.dispatchbuilds aNotificationCompat.Builderbut never callssetContentIntent(...), so taps are no-ops.A dedicated
MeshServiceNotificationsImpl.showNewNodeSeenNotification(node)already wires the correct deep link viacreateOpenNodeDetailIntent(nodeNum)→/nodes/{nodeNum}, but it is dead code — nothing in production invokes it. (Likely regressed when notifications were reworked to go through the genericNotificationManager.dispatchpath.)Fix
Smallest KMP-friendly change: enrich the generic
Notificationmodel with an optionaldeepLinkUriand have the Android dispatcher synthesize aPendingIntentwhen present.core/repository/.../Notification.kt— addval deepLinkUri: String? = null.core/data/.../NodeManagerImpl.kt— setdeepLinkUri = "meshtastic://meshtastic/nodes/${next.num}"andid = next.num(so repeated emissions for the same node update the existing notification instead of duplicating, matching whatshowNewNodeSeenNotificationdid).core/service/androidMain/.../AndroidNotificationManager.kt— whendeepLinkUri != null, build aTaskStackBuilderPendingIntentwithACTION_VIEWtargetingMainActivityand attach viasetContentIntent(...). UsesClass.forName("org.meshtastic.app.MainActivity")(mirroringMeshServiceNotificationsImpl) to avoid a:core:service→:androidAppdependency.The rest works because the deep-link infrastructure already exists end-to-end:
Because
MultiBackstack.handleDeepLinkreplaces the active stack with[Nodes, NodeDetail], pressing back fromNodeDetailnaturally returns to the Nodes list — exactly the requested behavior.The manifest already declares the URI scheme/host on
MainActivity:Tests
AndroidNotificationManagerTest— 3 new cases:dispatch attaches deep-link PendingIntent when deepLinkUri is set— assertsposted.contentIntentnon-null and the wrappedIntentisACTION_VIEWwith the correct URI +MainActivitycomponent.dispatch leaves contentIntent unset when deepLinkUri is null— guards against false positives.dispatch uses provided notification id as system id— verifies explicit-id cancellation works.Test infrastructure:
core/service/src/androidHostTest/.../org/meshtastic/app/MainActivity.ktextendingActivitysoClass.forName("org.meshtastic.app.MainActivity")resolves under Robolectric without pulling:androidAppinto:core:service's test classpath.registerStubMainActivity()helper usingshadowOf(packageManager).addOrUpdateActivity(ActivityInfo)soTaskStackBuilder.addNextIntentWithParentStackdoesn't throwNameNotFoundException.Why not just revive
showNewNodeSeenNotification?The KMP-friendly fix benefits Desktop too —
DesktopNotificationManagercurrently ignoresdeepLinkUribut can honor it in a follow-up. Reviving the dead Android-specific path would have re-coupledNodeManagerImplto theMeshServiceNotificationsinterface and left Desktop with the same broken behavior.Why hardcode the URI string in
NodeManagerImpl?DEEP_LINK_BASE_URIlives incore/navigation/Routes.kt, which depends on Compose Navigation 3. Pulling that intocore/data(a pure data layer) would be a heavy cross-cutting change for one string constant. The literal is documented with a comment pointing atDEEP_LINK_BASE_URIso they stay in sync.Verification
./gradlew :core:repository:allTests✓./gradlew :core:data:allTests✓./gradlew :core:service:allTests :core:service:testAndroidHostTest :core:service:detekt✓./gradlew spotlessApply spotlessCheck detekt assembleDebug✓./gradlew test allTests -x :desktopApp:test✓ (full repo)Follow-up (out of scope)
MeshServiceNotifications.showNewNodeSeenNotification+ its Android implementation can be removed in a cleanup PR now that nothing calls it.