Goal this serves — Donors arriving from a link or notification reaching the right screen
Why this matters
Two of the main ways a donor returns to the app — following a shared project link, and tapping a notification about a project they support — depend on routing that is only partly built. A donor who taps a notification about a milestone should land on that project; today the machinery to do so is not connected.
Both paths also accept externally supplied input, which makes them a security boundary as well as a usability one.
Evidence
mobile/utils/notifications.ts exports registerDeviceToken and setupNotificationListener, and a search across app/, src/, components/ and hooks/ finds no caller for either. So the backend never receives a device token, and no listener exists to respond to a notification tap.
Deep link parsing exists separately in mobile/hooks/useDeepLink.ts with its own validation, and the two have no shared notion of a destination. There is no Android notification channel, which that platform requires before notification presentation settings take effect.
Why this is hard
Cold start versus warm start differ. A tap on a cold app must hold the destination until navigation is ready, and getting this wrong produces a link that works only when the app is already open — a bug that reproduces inconsistently and wastes enormous debugging time.
External input reaches routing. Both a link and a notification payload are attacker-influenceable, so destinations must be validated against an allowlist rather than constructed from received data.
Permission timing. Requesting notification permission at the wrong moment gets it denied permanently, and Android and iOS differ in how a denial can be recovered.
Token lifecycle. Device tokens rotate, and must be tied to wallet connection and disconnection so notifications do not follow a device after the donor disconnects.
Testing is genuinely hard. Cold-start routing and notification taps need harnesses that do not exist here yet.
Suggested approach
Define one destination model that both entry points resolve into, validated against an allowlist so no externally supplied value becomes a route directly.
Handle cold start explicitly by holding the pending destination until navigation is ready.
Wire token registration into the wallet lifecycle, create the Android channel before any notification arrives, and request permission on explicit user intent with a rationale shown first.
Acceptance criteria
Scope
Roughly 5,000–7,000 lines, including tests.
Relevant files
mobile/utils/notifications.ts
mobile/hooks/useDeepLink.ts
mobile/app/_layout.tsx
mobile/app.json
Why this matters
Two of the main ways a donor returns to the app — following a shared project link, and tapping a notification about a project they support — depend on routing that is only partly built. A donor who taps a notification about a milestone should land on that project; today the machinery to do so is not connected.
Both paths also accept externally supplied input, which makes them a security boundary as well as a usability one.
Evidence
mobile/utils/notifications.tsexportsregisterDeviceTokenandsetupNotificationListener, and a search acrossapp/,src/,components/andhooks/finds no caller for either. So the backend never receives a device token, and no listener exists to respond to a notification tap.Deep link parsing exists separately in
mobile/hooks/useDeepLink.tswith its own validation, and the two have no shared notion of a destination. There is no Android notification channel, which that platform requires before notification presentation settings take effect.Why this is hard
Cold start versus warm start differ. A tap on a cold app must hold the destination until navigation is ready, and getting this wrong produces a link that works only when the app is already open — a bug that reproduces inconsistently and wastes enormous debugging time.
External input reaches routing. Both a link and a notification payload are attacker-influenceable, so destinations must be validated against an allowlist rather than constructed from received data.
Permission timing. Requesting notification permission at the wrong moment gets it denied permanently, and Android and iOS differ in how a denial can be recovered.
Token lifecycle. Device tokens rotate, and must be tied to wallet connection and disconnection so notifications do not follow a device after the donor disconnects.
Testing is genuinely hard. Cold-start routing and notification taps need harnesses that do not exist here yet.
Suggested approach
Define one destination model that both entry points resolve into, validated against an allowlist so no externally supplied value becomes a route directly.
Handle cold start explicitly by holding the pending destination until navigation is ready.
Wire token registration into the wallet lifecycle, create the Android channel before any notification arrives, and request permission on explicit user intent with a rationale shown first.
Acceptance criteria
Scope
Roughly 5,000–7,000 lines, including tests.
Relevant files
mobile/utils/notifications.tsmobile/hooks/useDeepLink.tsmobile/app/_layout.tsxmobile/app.json