Skip to content

feat: native desktop notifications for task status changes#20

Merged
johannesjo merged 2 commits intojohannesjo:mainfrom
cledoux95:feature/macos-notifications
Mar 17, 2026
Merged

feat: native desktop notifications for task status changes#20
johannesjo merged 2 commits intojohannesjo:mainfrom
cledoux95:feature/macos-notifications

Conversation

@cledoux95
Copy link

Summary

  • Adds native macOS desktop notifications when tasks transition to "ready" or "waiting" states while the app window is unfocused
  • Notifications are batched with a 3-second debounce to avoid spamming
  • Clicking a notification brings the window to focus and navigates to the relevant task

Changes

  • New IPC channels: ShowNotification and NotificationClicked
  • New desktopNotifications.ts store module that watches task status transitions
  • Electron main process creates native Notification instances and forwards click events back to the renderer

Test plan

  • Start tasks, unfocus the app window, and verify notifications appear when tasks complete
  • Click a notification and verify it focuses the window and selects the task
  • Verify no notifications appear while the window is focused

…status changes

Sends native desktop notifications when tasks transition to "ready" or "waiting"
states while the app window is unfocused. Notifications are batched with a 3-second
debounce to avoid spamming. Clicking a notification brings the window to focus and
navigates to the relevant task.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cledoux95
Copy link
Author

@johannesjo Sorry I didn't open an issue for any of this, I had some time last night and wanted to be helpful. Feel free to reject this if you'd prefer to have a longer discussion before entertaining changes.

I also figured this would give you the clearest picture of the proposed changes.

Really love using Parallel Code - nice job!

Copy link
Owner

@johannesjo johannesjo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for the PR and the kind words – really appreciate you taking the time! This is a feature I've been thinking about, and your implementation is clean and follows the existing patterns well. A few things I'd like to see addressed before merging:

  1. Should be configurable – Not everyone wants desktop notifications, so this should be opt-in (or at least opt-out) via a user preference. A store.desktopNotificationsEnabled toggle with a UI control in settings would be the way to go. This is the main blocker for me.

  2. PR title says "macOS" but this works on Linux tooElectron.Notification works on both platforms we ship, so the title/description should reflect that.

  3. threadId is passed but unused – You validate and send threadId to the main process, but Electron's Notification constructor doesn't accept it. This is dead code – either remove it or add a comment explaining the intent if it's meant for future use.

  4. package-lock.json noise – There are ~80 lines of unrelated peer flag changes in the lockfile, likely from running npm install with a different npm version. Could you regenerate with the project's npm version to keep the diff clean?

  5. Type safety on notification click – The data as { taskIds: string[] } cast in desktopNotifications.ts:113 is unsafe. A runtime check would be more robust:

    const msg = data as Record<string, unknown>;
    const taskIds = Array.isArray(msg?.taskIds) ? (msg.taskIds as string[]) : [];
  6. ipcMain.handle vs ipcMain.onShowNotification uses handle but doesn't return a meaningful value. Since the renderer only does .catch(console.warn), ipcMain.on with the existing fireAndForget helper would be a better semantic fit.

Happy to discuss any of these – and equally happy to take it from here if you'd prefer. Let me know!

@cledoux95
Copy link
Author

cledoux95 commented Mar 16, 2026

Absolutely, I'll make the proposed changes tomorrow.

  1. I believe for macOs the first time an app fires a notification there's a popup where the user can disable ongoing notifications, but I'll definitely add a way to disable them in app. Should I add a settings button and menu underneath the Arena button?
  2. Sweet, I don't have a linux os machine, so that's nice it just works OOTB. Will retitle.
    3-6. 10-4 - these all make sense.

I didn't have any other questions, if you wanted to have a sidebar about the settings menu I'm all ears (maybe implementing a macOs "preferences" menu item?).

Bis morgen!

@johannesjo
Copy link
Owner

  1. I believe for macOs the first time an app fires a notification there's a popup where the user can disable ongoing notifications, but I'll definitely add a way to disable them in app. Should I add a settings button and menu underneath the Arena button?

There already is a settings button and a settings page, right next to the app logo :)

@cledoux95
Copy link
Author

All this time, and I never even saw that button...

@cledoux95 cledoux95 changed the title feat: native macOS desktop notifications for task status changes feat: native desktop notifications for task status changes Mar 17, 2026
@cledoux95
Copy link
Author

Requested changes have been pushed.

Screenshot 2026-03-16 at 11 10 52 PM

Notifications are working, but for some reason when running the electron container in dev (even from my OS terminal) I'm getting these errors:
[1] [preload-sync] IPC channels missing from preload.cjs ALLOWED_CHANNELS: spawn_agent, write_to_agent, resize_agent, pause_agent, resume_agent, kill_agent, count_running_agents, kill_all_agents, list_agents, create_task, delete_task, get_changed_files, get_changed_files_from_branch, get_all_file_diffs, get_all_file_diffs_from_branch, get_file_diff, get_file_diff_from_branch, get_gitignored_dirs, get_worktree_status, check_merge_status, merge_task, get_branch_log, push_task, rebase_task, get_main_branch, get_current_branch, commit_all, discard_uncommitted, save_app_state, load_app_state, __window_is_focused, __window_is_maximized, __window_minimize, __window_toggle_maximize, __window_close, __window_force_close, __window_hide, __window_maximize, __window_unmaximize, __window_set_size, __window_set_position, __window_get_position, __window_get_size, __window_focus, __window_blur, __window_resized, __window_moved, __window_close_requested, __dialog_confirm, __dialog_open, __shell_reveal, __shell_open_file, __shell_open_in_editor, save_arena_data, load_arena_data, create_arena_worktree, remove_arena_worktree, check_path_exists, start_remote_server, stop_remote_server, get_remote_status, plan_content, read_plan_content, ask_about_code, cancel_ask_about_code, show_notification, notification_clicked [1] Error occurred in handler for 'spawn_agent': Error: posix_spawnp failed. [1] at new UnixTerminal (/Users/aslan/Projects/parallel-code/node_modules/node-pty/lib/unixTerminal.js:92:24) [1] at Module.spawn (/Users/aslan/Projects/parallel-code/node_modules/node-pty/lib/index.js:30:12) [1] at spawnAgent (file:///Users/aslan/Projects/parallel-code/dist-electron/ipc/pty.js:106:22) [1] at file:///Users/aslan/Projects/parallel-code/dist-electron/ipc/register.js:57:24 [1] at Session.<anonymous> (node:electron/js2c/browser_init:2:116459) [1] at Session.emit (node:events:508:28) [1] Error occurred in handler for 'resize_agent': Error: Agent not found: c5f8a655-7977-4ec3-95c8-647a541ce723 [1] at resizeAgent (file:///Users/aslan/Projects/parallel-code/dist-electron/ipc/pty.js:206:15) [1] at file:///Users/aslan/Projects/parallel-code/dist-electron/ipc/register.js:77:16 [1] at Session.<anonymous> (node:electron/js2c/browser_init:2:116459) [1] at Session.emit (node:events:508:28)

as well as this in the UI:
Screenshot 2026-03-16 at 11 27 38 PM

Is that expected? I don't recall running into this issue the other night in dev, but I may have missed it since I was compiling to a .dmg and launching it there.

@johannesjo johannesjo merged commit b541919 into johannesjo:main Mar 17, 2026
2 checks passed
@johannesjo
Copy link
Owner

johannesjo commented Mar 17, 2026

@cledoux95 Thank you very much for this and great work on the notifications! 🎉

Is that expected?

Those errors are unrelated to your notification changes — they're about node-pty failing to spawn the agent process (posix_spawnp failed). This typically happens when running in dev mode because Electron's child process can't find the shell binary (e.g. zsh/bash) or the claude CLI in its PATH. The Agent not found error is just a downstream consequence of the spawn failure.

Building to a .dmg worked because the packaged app inherits a different (fuller) PATH from macOS. In dev mode, Electron often starts with a minimal environment. You can work around it by launching Electron from a terminal that has the correct PATH, or by setting PATH explicitly in the Electron main process.

Short version: it's a known dev-mode environment quirk, not a bug in your code.

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