Files
paseo/docs/notifications.md
T
Mohamed BoudraandKassam Khoja 12e7a448f0 fix(app): open notification agents once in their current workspace (#7612)
* fix(app): open notification agents once in their current workspace

Keep tap ownership across Activity and React remounts, clear native responses, and resolve agent workspaces through existing navigation after startup settles.

Co-authored-by: Kassam Khoja <kassam@shopwrk.com>

* fix(app): hydrate agent targets with the compact sidebar closed

Hold directory demand for the agent route while resolving its workspace; the compact sidebar does not supply demand before it opens.

* fix(app): keep deferred workspace opens in the layout model

Key pending tab opens by workspace so redirects and retained screens cannot lose or consume another workspace's target. Legacy open URLs seed this state at their route boundary.

* docs(app): document notification tap ownership and native replay

* fix(ci): route feature-owned Electron sources to desktop checks

* refactor(app): separate notification API from React integration

* fix(app): consume pending opens before painting the restored tab

A legacy open link showed the previously focused terminal for a frame before switching to the requested terminal. Seed and consume the pending open in layout effects so a ready target switches before paint.

* fix(e2e): focus the visible retained terminal surface

---------

Co-authored-by: Kassam Khoja <kassam@shopwrk.com>
2026-10-22 14:41:38 +07:00

3.3 KiB

Notifications

Use notification-open for OS notification taps. It owns delivery, parsing, process-local deduplication, startup readiness, and native acknowledgement. Keep agent and workspace navigation in their existing navigation modules. Import the pure API from @/notification-open and the app-shell component from @/notification-open/react; senders must not load the React routing runtime.

Agent payloads name a host and agent. Ignore their workspace hint when opening: the agent can move after a notification is sent. Use navigateToAgent, which uses the current store record or the agent route to resolve the workspace from the host. Terminal payloads name their workspace and use navigateToWorkspace.

See agent targets and deferred opens for route ownership and workspace recovery. Notification generation, presence, and push eligibility remain daemon concerns.

Native delivery

Keep the router instance at module scope. Android's singleTask Activity can finish on Back while its JS context remains alive. A React ref is lost when the root remounts; process-local handled IDs must survive that remount.

Clear lastNotificationResponse after dispatch. Reading a launch response does not acknowledge it. Without clearing it, a later Activity launch can reopen the old agent.

With expo-notifications 57, Android also replays its pending response list to new native listeners. Clearing the last response does not drain that list. Deduplication covers listener registration within one JS context. A development JS reload creates another context and can replay a cold-launch response; do not add persisted handled IDs to compensate. Recheck this limitation when upgrading expo-notifications.

iOS drains pending responses when its delegate first handles them. Push data read by Expo arrives under userInfo.body; simulator payloads must match:

{
  "aps": { "alert": { "title": "Agent finished", "body": "Open the agent" }, "sound": "default" },
  "body": {
    "serverId": "host",
    "workspaceId": "workspace",
    "agentId": "agent",
    "reason": "finished"
  }
}

An Android FCM launch intent needs google.message_id and a JSON body extra. An arbitrary Activity launch with data extras does not exercise Expo's FCM path.

Foreground pushes remain in Notification Center without a banner or sound. This preserves notifications sent with stale presence information.

Startup and verification

Wait for the root navigator, host registry, remembered selection, and startup redirects before handing off a tap. A compact agent route must demand the host's directory itself; its sidebar can be closed for the entire cold launch.

Verify warm and cold taps, stale and missing workspace hints, two distinct taps, and Android Back followed by launcher reopen or a different notification tap. Check the focused tab and every workspace's saved layout, since a foreign pinned tab survives navigation. Use the daemon's payload shape on both iOS and Android; a browser event alone does not test native replay or acknowledgement.

Existing foreign pins belong to the reconciliation fix in #6900. Desktop main-process window selection is tracked separately in #6875.