Skip to content

Add opt-in desktop notifications for finished and waiting sessions - #62

Open
emircan-sahin wants to merge 5 commits into
hardbeat920:mainfrom
emircan-sahin:feat/desktop-notifications
Open

Add opt-in desktop notifications for finished and waiting sessions#62
emircan-sahin wants to merge 5 commits into
hardbeat920:mainfrom
emircan-sahin:feat/desktop-notifications

Conversation

@emircan-sahin

Copy link
Copy Markdown
Contributor

What changed

A Notifications toggle in Settings, off by default. When on, a system notification appears when a turn finishes or an agent waits on an approval or question in a session that is not on screen, whether MonoCode is in the background or another session is open. Clicking the banner or its Show button focuses the window and opens that session.

  • macOS uses UNUserNotificationCenter directly (already linked for the Dock badge): real permission state, a Show action category, and a delegate that presents banners while the app is frontmost and routes clicks back to the session. When alerts are blocked, the Settings row shows "Permission needed" with an Open System Settings button that deep-links to MonoCode's notification page.
  • Linux uses notify-rust over the freedesktop bus; no permission model there. Other platforms report unsupported and the toggle says so.
  • The Sounds setting decides whether the notification carries a sound. When a banner fires, the in-app turn-finished cue is skipped so nothing chimes twice.
  • Notification layout: title MonoCode, subtitle is the session title, body is the first paragraph of the reply (or the pending approval / question).

Also fixes tauri dev on macOS: the dev bundle is now ad-hoc signed with com.monocode.desktop. The linker's signature carried a monocode-<hash> identifier, and UNUserNotificationCenter refuses authorization without prompting when that disagrees with CFBundleIdentifier. The binary is copied instead of hard-linked because re-signing rewrites the file the running process was linked to.

Why

Sessions often finish or stall while you are in another app or another tab. Sounds already cover the first case; a clickable banner covers the second and gets you back to the right session in one click.

UI

New Settings row under Sounds: "Notifications" toggle, with "Permission needed · Open System Settings" beside it when macOS has alerts blocked.

Checklist

  • I ran npm run check (the two git_stash_* Rust tests fail on main for me too, unrelated)
  • This PR is small and focused
  • I did not mix unrelated changes

@emircan-sahin

Copy link
Copy Markdown
Contributor Author

I think everyone's going to love this feature.

I was using cmux purely for the notifications, now MonoCode will have that too 🙈

@hardbeat920

Copy link
Copy Markdown
Owner

Thanks for the PR @emircan-sahin. Before merging, could you address these issues:

  1. macOS requests badge-only permission at startup, consuming the one-time prompt before Notifications is enabled.
  2. Linux waits for a "default" click action but never registers one.
  3. Linux notification bodies should escape/strip markup because they contain untrusted agent output.
  4. Only suppress the completion sound after notification dispatch succeeds.

@emircan-sahin

Copy link
Copy Markdown
Contributor Author

Thanks for the review, all four addressed in 93604cf:

  1. macOS startup prompt: request_badge_authorization now checks getNotificationSettings first and only re-requests the badge option when the user has already answered. While the status is still undecided it does nothing, so the one-time system dialog is reserved for the Notifications toggle (which asks for alert + sound + badge together). The Dock badge starts working once that prompt has been answered.
  2. Linux click: the notification now registers .action("default", "Show"), so servers actually report the click that wait_for_action was waiting on.
  3. Linux markup: bodies go through an escape_markup helper (&, <, >) before dispatch, with a unit test.
  4. Sound suppression: notifySession is now async and resolves true only after the show_notification invoke succeeds. The turn-finished cue plays whenever it resolves false, including a rejected dispatch.

The linker's ad-hoc signature carries a monocode-<hash> identifier while
Info.plist says com.monocode.desktop. UNUserNotificationCenter refuses
authorization without prompting when the two disagree, so tauri dev could
never show a notification. Copy the binary instead of hard-linking it,
since re-signing rewrites the file the running process was linked to.
Off by default. When on, a system notification appears when a turn ends
or an agent waits on an approval or question in a session that is not on
screen, whether MonoCode is in the background or another session is open.
Clicking it, or its Show button, focuses the window and opens the session.

macOS goes through UNUserNotificationCenter directly: the app already links
it for the Dock badge, it reports the real authorization state, and the
Settings row offers a System Settings link when alerts are blocked. Linux
uses notify-rust over the freedesktop bus. The Sounds setting decides
whether the notification carries a sound; the in-app cue is skipped when
a banner fires so nothing chimes twice.
…, keep cue on failed dispatch

- macOS no longer requests badge-only authorization at startup while the
  prompt is undecided; the Notifications toggle owns the one-time dialog.
- Linux registers the "default" action so servers report the click, and
  escapes notification bodies since agent output is rendered as markup.
- The in-app turn-finished cue is skipped only after the OS accepted the
  notification, not on dispatch.
@emircan-sahin
emircan-sahin force-pushed the feat/desktop-notifications branch from 93604cf to aa20395 Compare September 4, 2026 16:32
Clippy's items_after_test_module lint fails CI on Linux with -D warnings.
@hardbeat920

Copy link
Copy Markdown
Owner

Thanks @emircan-sahin, the permission flow and Linux issues look fixed.

One remaining issue: macOS show_notification returns success before its authorization and scheduling callbacks complete, so the frontend can still suppress the completion cue when no notification was delivered.

Could the command await the actual scheduling result before returning success?

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 participants