* ✨ feat(desktop): add chat completion sound settings Split completion feedback by window focus so the two sound sources can never overlap: the foreground plays an in-app chime (off by default, own volume and importable audio), while the background leaves the sound to the notification banner. The main process owns the focus decision and the renderer only plays when the banner was skipped or refused. The banner can carry either the macOS default sound or LobeHub's own chime. UNNotificationSound resolves a name only against ~/Library/Sounds and only in AIFF, so the chime is installed there on demand rather than bundled. Claude-Session: https://claude.ai/code/session_01SaZzuGjGHqADBxhWn4HjUa * ✅ test: mock completion sound in agent run specs
@lobechat/electron-mac-notifications
Native N-API addon that posts macOS notifications through
UNUserNotificationCenter, styling them as communication notifications
(sender avatar as the primary icon, via INSendMessageIntent) when a sender
is provided. Consumed by the desktop main process; falls back gracefully
everywhere it can't run.
Build
pnpm --filter @lobechat/electron-mac-notifications build:native
No-op on non-darwin platforms. The desktop packaging pipeline runs this
automatically in beforePack.
Runtime requirements (all empirically verified)
The avatar treatment only renders when the host app satisfies all of:
- Signed with a real certificate and launched as a regular app. Adhoc-signed
dev Electron gets
UNErrorDomain Code=1from the notification center, and this package then reportsisSupported()but fails at show-time — callers fall back to Electron'sNotification. com.apple.developer.usernotifications.communicationentitlement in the signature. Never add it without requirement 3: launchd refuses to spawn the app (POSIX 163).- An Apple provisioning profile authorizing the entitlement, embedded at
Contents/embedded.provisionprofile. Wired throughMAC_PROVISIONING_PROFILE+APPLE_TEAM_IDinelectron-builder.mjs. NSUserActivityTypes = [INSendMessageIntent]in Info.plist (set unconditionally viamac.extendInfo).
Without 2–3 the notification still shows as a plain banner — macOS silently drops the communication styling — so profile-less channels keep working.
Delegate ownership
The addon takes over the process-global UNUserNotificationCenter delegate.
Notifications whose identifier lacks the lobehub- prefix are forwarded to
whatever delegate was installed before (Electron's own Notification
module), so the two paths can coexist during fallback.