Menu ▾ ▴

#11 Fix macOS unread Dock badge (#5) and add a badge-count mode setting

closed
nobody
None
2026-06-28
2026-06-28
Anonymous
No

Originally created by: kristofferR

Fix the macOS unread Dock badge (issue [#5]) + add a badge-count mode

The Dock/taskbar unread badge never appeared on macOS. There were two root causes, both fixed and verified end-to-end (badge read back from the OS, and confirmed visually):

  1. Wrong IPC argument name. messenger.js called plugin:window|set_badge_count with { count: n }, but Tauri's setter! macro names the argument value. So value was always None → setBadgeLabel(nil) → the badge was cleared on every update. The invoke resolved, which is why it looked fine but never showed.
  2. Missing macOS badge authorization. Since macOS 12, [[NSApp dockTile] setBadgeLabel:] (what set_badge_count calls) is silently ignored unless the app has requested UNUserNotificationCenter authorization with the badge option. Carrier notifies via notify-rust and never asked, so even a correct count was dropped. Now requested once on RunEvent::Ready (request_badge_authorization) — granted silently, no prompt.

Also in this PR

  • "Badge counts" setting — choose unread messages (Facebook's total, from the page title's (N)) or unread conversations (chats Facebook renders bold). Conversation detection keys off computed font-weight, so it's language-independent rather than matching English text.
  • Settings survive page reloads. The injected init script baked a settings snapshot at window creation, so Facebook auto-reloading the page reverted in-session changes (e.g. the badge mode flipping back on its own). Settings are now cached in the page's localStorage (written on every change) and the init script prefers it.
  • Prompt badge updates. unreadBadge observes <head> title changes (Facebook updates (N) the instant unread changes) with a short follow-up + safety interval, so the badge tracks new/read messages quickly in both modes.

Dev tooling (debug / mcp feature only — never in release)

  • A tauri-mcp guest bridge (inject/mcp-bridge.js) so execute_js/get_dom work against Carrier's remote facebook.com origin, which can't load the plugin's normal guest bindings. This is what made diagnosing the badge possible.
  • .mcp.json fix — launch the mcp server via node instead of npx. The published tauri-plugin-mcp-server bin ships without a shebang, so npx fell through to an unrelated binary on PATH and the server never started.

Testing

  • cargo fmt / cargo clippy --all-targets -- -D warnings (with and without --features mcp) / cargo test --lib (26 pass) / node --check on injected JS — all green.
  • Verified on macOS 27: badge shows the unread count in both modes; OS-level read-back of the Dock badge label confirms it's set.

Ref [#5]

https://claude.ai/code/session_01TsHXMxZ8b6EdjNg3dKMhMA

Summary by CodeRabbit

  • New Features
  • Added a configurable unread badge mode to show either unread messages or unread conversations, with the choice remembered during the session.
  • Improved macOS badge support by requesting badge authorization so Dock/taskbar counts display more reliably.

  • Bug Fixes

  • Made unread badge updates more accurate by refining how unread state is detected.
  • Reduced badge flicker and improved responsiveness by switching to a debounced, reload-safe update approach.

Related

Tickets: #10
Tickets: #5

Discussion

  • Anonymous

    Anonymous - 2026-06-28

    Originally posted by: chatgpt-codex-connector[bot]

    You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
    To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

     
  • Anonymous

    Anonymous - 2026-06-28

    Originally posted by: coderabbitai[bot]

    Review Change Stack

    📝 Walkthrough ## Walkthrough This PR adds a dev-only MCP webview bridge, introduces `badge_mode` settings persistence and loading, rewrites unread badge counting in the Messenger injector, and adds macOS notification badge authorization with related dependencies. ## Changes **MCP bridge and badge flow** |Layer / File(s)|Summary| |---|---| |**MCP server wiring and dependency**
    `package.json`, `.mcp.json`|Adds `tauri-plugin-mcp-server` to devDependencies and switches `.mcp.json` to launch the built plugin entrypoint with `node`.| |**mcp-bridge.js guest responder**
    `src-tauri/inject/mcp-bridge.js`|Adds the dev-only bridge script that initializes from Tauri internals, guards duplicate install, registers `execute-js` and `got-dom-content` listeners, correlates replies, stringifies results safely, and retries setup until the internals are available.| |**badge_mode settings and persistence**
    `src-tauri/src/lib.rs`|Adds `badge_mode` to `Settings`, sets its default to `messages`, persists runtime settings into `localStorage`, and updates init script loading to merge stored settings with validation of `badge_mode`.| |**Unread badge counting**
    `src-tauri/inject/messenger.js`|Reworks `unreadBadge()` to support message-title counting and conversation-row counting, updates the native badge with `set_badge_count`, emits `carrier:unread`, and changes badge refresh scheduling and settings-triggered recalculation.| |**macOS badge authorization**
    `src-tauri/Cargo.toml`, `src-tauri/src/lib.rs`|Adds macOS notification badge authorization dependencies, defines `request_badge_authorization()`, and invokes it once when the app reaches `RunEvent::Ready`.| ## Estimated code review effort 🎯 4 (Complex) | ⏱️ ~45 minutes ## Possibly related issues - [kristofferR/Carrier#5](https://github.com/kristofferR/Carrier/issues/5): The rewritten `unreadBadge()` and badge-mode handling directly match the unread badge behavior described in the issue. ## Possibly related PRs - [[kristofferR/Carrier#2](https://github.com/kristofferR/Carrier/issues/2)](https://github.com/kristofferR/Carrier/pull/2): Shares the same Messenger badge plumbing and settings/apply path that this PR extends. ## Poem > 🐇 I hop through bridges, new and neat, > With badge modes counting every beat. > A mcp reply, a DOM reply, > And badges shine when I am nigh.
    🚥 Pre-merge checks | ✅ 5
    ✅ Passed checks (5 passed) | Check name | Status | Explanation | | :------------------------: | :------- | :-------------------------------------------------------------------------------------------------------------------------- | | Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. | | Title check | ✅ Passed | The title accurately summarizes the main changes: fixing the macOS unread Dock badge and adding a badge-count mode setting. | | Docstring Coverage | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. | | Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
    ✨ Finishing Touches
    📝 Generate docstrings - [ ] Create stacked PR - [ ] Commit on current branch
    🧪 Generate unit tests (beta) - [ ] Create PR with unit tests - [ ] Commit unit tests in branch `fix/badge-issue-5`

    Comment @coderabbitai help to get the list of available commands.

     
  • Anonymous

    Anonymous - 2026-06-28

    Originally posted by: kristofferR

    @coderabbitai review

     
  • Anonymous

    Anonymous - 2026-06-28

    Originally posted by: coderabbitai[bot]

    ✅ Action performed Review finished. > Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.
     
  • Anonymous

    Anonymous - 2026-06-28

    Originally posted by: kristofferR

    @coderabbitai review

     
  • Anonymous

    Anonymous - 2026-06-28

    Originally posted by: coderabbitai[bot]

    ✅ Action performed Review finished. > Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.
     
  • Anonymous

    Anonymous - 2026-06-28

    Ticket changed by: kristofferR

    • status: open --> closed
     

Log in to post a comment.