Skip to content

feat: show notification badge on app icon#2645

Open
janburzinski wants to merge 7 commits into
mainfrom
emdash/notification-number-xgw4r
Open

feat: show notification badge on app icon#2645
janburzinski wants to merge 7 commits into
mainfrom
emdash/notification-number-xgw4r

Conversation

@janburzinski

Copy link
Copy Markdown
Collaborator

Description

  • sync native app badge with visible task notification
  • keep badge counts alinged when archived or restored
  • exclude archived tasks from notification counts
  • clear app badge on startup/quit

Screenshot/Recording (if applicable)

https://streamable.com/fx54bk

Checklist
  • I kept this PR small and focused
  • I ran a self-review before opening this PR
  • I ran the relevant local checks or explained why not
  • I updated docs when behavior or setup changed
  • I added or updated tests when behavior changed, or explained why not
  • I only added comments where the logic is not obvious
  • I used Conventional Commits for commit
    messages and, when possible, the PR title

@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a native app icon badge that reflects the current task notification count, synced reactively from the renderer via MobX and IPC. It extracts notification logic into a shared task-notifications.ts module used by both the command palette and the new badge sync component.

  • AppBadgeService (main process) wraps app.setBadgeCount with deduplication, clearing on startup and quit.
  • NotificationBadgeSync (renderer) sets up a MobX reaction with fireImmediately: true that calls the new setNotificationBadgeCount IPC handler whenever the observable count changes.
  • task-notifications.ts extracts the notification computation logic previously inlined in the palette, now shared between the badge and palette with a consistent granularity: task-level count for non-current tasks, conversation-level count for the currently-viewed task.

Confidence Score: 5/5

Safe to merge — the badge sync is additive and non-destructive, the main-process service is well-isolated, and the shared notification logic is used consistently by both the badge and the command palette.

The change introduces a new reactive side-effect (badge sync) and extracts existing notification logic into a shared module. The deduplication concern raised in the prior review has been resolved by removing the force option entirely. Badge count and palette items use the same granularity (conversation-level for the active task, task-level for all others) so they stay in sync. Startup/quit badge lifecycle is handled correctly via initialize() and the before-quit finally block before app.exit(0).

No files require special attention.

Important Files Changed

Filename Overview
apps/emdash-desktop/src/renderer/features/tasks/stores/task-notifications.ts Extracted notification logic shared by badge and palette; badge and palette use the same granularity (conversation-level for current task, task-level otherwise) so they stay in sync.
apps/emdash-desktop/src/renderer/app/notification-badge-sync.tsx Null-rendering component that wires a MobX reaction to the IPC badge call; reaction lifecycle is correctly tied to component mount/unmount via useEffect.
apps/emdash-desktop/src/main/core/app/app-badge-service.ts New service wrapping app.setBadgeCount with deduplication; clean and straightforward, previous concerns about a dead force option have been resolved.
apps/emdash-desktop/src/main/core/app/controller.ts Adds setNotificationBadgeCount IPC handler; types are inferred from the router shape, no separate type definition needed.
apps/emdash-desktop/src/main/index.ts Badge service initialized on app ready and cleared inside the before-quit finally block before app.exit(0), ensuring the badge is cleared at shutdown.
apps/emdash-desktop/src/renderer/App.tsx Mounts NotificationBadgeSync inside the auth-gated workspace tree, so the badge is only synced when the user is logged in.
apps/emdash-desktop/src/renderer/features/command-palette/palette-notifications-group.tsx Palette now delegates to the shared getTaskNotificationItems helper, removing duplicated traversal logic.
apps/emdash-desktop/src/main/core/tasks/task-service.ts Formatting-only change adding braces to a single-line if; no logic change.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Nav as Navigation State (MobX)
    participant Sync as NotificationBadgeSync
    participant Store as task-notifications.ts
    participant IPC as rpc.app
    participant Badge as AppBadgeService
    participant OS as app.setBadgeCount

    Note over Sync: useEffect mounts reaction (fireImmediately: true)
    Nav->>Sync: observable change (currentViewId / taskId)
    Sync->>Store: getVisibleTaskNotificationCount(projectId, taskId)
    Store->>Store: "iterate projects/tasks, skip archived,<br/>current task = conversation-level count,<br/>other tasks = +1 per task"
    Store-->>Sync: count (number)
    Sync->>IPC: setNotificationBadgeCount(count)
    IPC->>Badge: setVisibleNotificationCount(count)
    Badge->>Badge: "setCount - deduplicate<br/>(skip if count === unreadCount)"
    Badge->>OS: app.setBadgeCount(count)

    Note over Badge: On before-quit: appBadgeService.clear() then app.exit(0)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Nav as Navigation State (MobX)
    participant Sync as NotificationBadgeSync
    participant Store as task-notifications.ts
    participant IPC as rpc.app
    participant Badge as AppBadgeService
    participant OS as app.setBadgeCount

    Note over Sync: useEffect mounts reaction (fireImmediately: true)
    Nav->>Sync: observable change (currentViewId / taskId)
    Sync->>Store: getVisibleTaskNotificationCount(projectId, taskId)
    Store->>Store: "iterate projects/tasks, skip archived,<br/>current task = conversation-level count,<br/>other tasks = +1 per task"
    Store-->>Sync: count (number)
    Sync->>IPC: setNotificationBadgeCount(count)
    IPC->>Badge: setVisibleNotificationCount(count)
    Badge->>Badge: "setCount - deduplicate<br/>(skip if count === unreadCount)"
    Badge->>OS: app.setBadgeCount(count)

    Note over Badge: On before-quit: appBadgeService.clear() then app.exit(0)
Loading

Reviews (2): Last reviewed commit: "fix(app): align notification badge count" | Re-trigger Greptile

Comment thread apps/emdash-desktop/src/main/core/app/app-badge-service.ts Outdated
Comment thread apps/emdash-desktop/src/main/core/app/app-badge-service.ts
Comment thread apps/emdash-desktop/src/renderer/features/tasks/stores/task-notifications.ts Outdated
@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

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.

1 participant