feat(desktop): per-event notification sounds and alert controls#968
Merged
Conversation
Add a per-event-kind sound system on top of the existing single-sound notification path. Each event class (DMs, mentions, needs-action, agent job lifecycle) gets its own selectable sound, with a recommended default the user can override via a new settings UI. - Refactor sound.ts to a Map-cached audio API that takes a resolved SoundName, with a slotForFeedKind() resolver and RECOMMENDED_SOUND_BY_SLOT defaults - Extend notification settings with sounds: Record<SoundSlot, SoundName> and jobProgressSoundEnabled (off by default to avoid constant beeps during streaming JOB_PROGRESS events) - Wire item.kind into use-feed-desktop-notifications and pass the DM sound from AppShell - Add SoundPicker dropdown (recommended sound floats to the top with a "rec." marker, preview play button next to the trigger) and per-slot rows in NotificationSettingsCard - Ship 11 candidate mp3s in desktop/public/sounds/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DM notifications were suppressed when the user had that DM channel open. Add a 'Notify while viewing' setting (default off) that bypasses the active-channel gate in useLiveChannelUpdates. Window-focus level suppression never existed; mentions and job events already notify while the app is focused. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y/pause Waveform generator reworked: 24 bars at 200x80, harder tail trim, narrow-window sampling to preserve amplitude modulation, and an unsharp-mask pass so each sound's silhouette reads distinctly. SoundPicker renders waveforms at 24x60 in both trigger and rows, and the preview button toggles play/pause with playback state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tions
Sound settings move to a default + per-event-override model: the Sound
row is always visible and acts as the default; per-event rows inherit it
("Default") unless overridden, fade the default's controls while open,
and slide open/closed. Seeded per-event defaults and descriptions.
New thread_reply notification class: replies in threads the user
authored, participated in, or follows fire a desktop notification and
their own sound slot. Mention-tagged replies and DM channels are owned
by their existing paths; the active channel is suppressed unless
notify-while-viewing is on.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each event class is one row with its own sound picker and alert toggle: - @Mentions / Needs action top-level toggles fold into the per-event rows; the toggles gate alerts (notification + sound), with legacy stored values migrated. DM and thread-reply alerts become disableable for the first time. - The single/custom sound mode is gone — rows always show pickers and default to inheriting the top-level sound; changing it updates every row still on Default. - The job rows render disabled with a coming-soon badge (no emitter exists for the job protocol yet). - The Sound master switch cascades: off renders every row off/inert and suppresses those alerts; stored values restore on re-enable. - Rows are flat siblings of the Sound row, separated by a divider. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The four job-event rows hide behind a centered secondary View all / Show less button until the job protocol has an emitter. Controls disabled by a parent toggle now fade in place instead of unmounting, so the layout never shifts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ggle Remove the default-sound concept: every event row holds its own sound, flutter by default. The Sound master switch is now derived from its children — on when any live event row is on — and toggling it bulk enables or disables every row, so turning each row off individually also unsets the parent. Coming-soon rows show cursor: not-allowed instead of swallowing pointer events. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bulk-off snapshots the per-row alert states; turning the master back on restores them (or enables all live rows if nothing was on). Any manual row toggle invalidates the snapshot so it never resurrects stale state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… settings The per-event rows move out of the Sound group into a separate container so the master toggle reads as its own control. Settings storage bumps to v2 — the model changed shape enough (flutter defaults, slotAlertsEnabled, no singleSound/soundEnabled) that v1 values are abandoned, and the dead v1 migration is removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Sound group, per-event rows, and View all button unmount with an AnimatePresence popLayout fade while the Home badge group glides into place via layout animation, instead of fading in place. Their internal desktop-off disabled states are gone — they only render when desktop alerts is on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The event-rows container and View all button unmount behind a nested AnimatePresence keyed on the derived master state — off collapses the section to just the Sound row, on reveals it (restoring granular picks via the snapshot). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
layout animates size with transform scale, which stretched the panels into place; layout="position" translates them instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The popLayout/position transitions didn't read right; the panels now mount and unmount instantly. Visibility logic unchanged: Desktop alerts gates the sound section, the Sound master gates the event rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wesbillman
approved these changes
Jun 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Category: new-feature
User Impact: Users can now choose a notification sound — or a different one per event type — preview each sound from a waveform picker, and toggle alerts on or off per event, including new notifications for thread replies and for DMs in the conversation they have open.
Problem: Notifications were one-size-fits-all: a single hardwired sound, no control over which event classes alert, no notifications at all for replies in your threads, and DMs always suppressed for the conversation being viewed.
Solution: The Notifications settings are rebuilt around per-event rows — each event class gets its own alert toggle and sound picker that inherits a top-level default until overridden, with waveform thumbnails generated from the actual audio so sounds are recognizable at a glance. Two new notification classes ship with it: thread replies (threads you authored, participated in, or follow) and an opt-in "Notify while viewing" for open DMs. Job-lifecycle rows are wired end-to-end but render as "coming soon" behind a View-all toggle, since nothing emits job events (43001–43006) yet.
File changes
*desktop/public/sounds/*.mp3, .svg
Eleven new notification sounds plus the renamed original (
flutter). Each ships with a waveform SVG generated from its PCM data, used as the picker thumbnail (fill="currentColor"so they tint with the theme).desktop/scripts/generate-sound-waveforms.mjs
Generator for the waveform SVGs: ffmpeg decode, energy-based tail trim, narrow-window sampling, and an unsharp-mask pass so each sound's silhouette reads distinctly. Run with
node scripts/generate-sound-waveforms.mjs public/sounds.desktop/src/features/notifications/lib/sound.ts
The sound model: sound names, event slots (
SoundSlot), per-slot labels/descriptions/recommendations, per-slot overrides withresolveSlotSound(override ?? default), per-slot alert enablement defaults, and the coming-soon slot set. Audio playback moves to a per-sound element cache.desktop/src/features/notifications/hooks.ts
NotificationSettingsgainssingleSound, per-slotsoundsoverrides,slotAlertsEnabled, andnotifyWhileViewing; the old top-levelmentions/needsActionbooleans migrate into their slots via the sanitizer, so stored settings survive without a version bump.desktop/src/features/notifications/use-feed-desktop-notifications.ts
Feed-driven notifications (mentions, needs-action) respect the per-slot alert toggles and play the per-slot resolved sound.
desktop/src/features/channels/useLiveChannelUpdates.ts
Two new dispatch options:
notifyForActiveChannel(DM gate bypass for the open conversation) andonThreadReplyDesktopNotification, fired for replies in authored/participated/followed threads (non-DM channels; same active-channel rule as DMs).desktop/src/app/AppShell.tsx
Handlers for the two new notification classes — thread replies (skipping @-mention replies, which the mention path owns) and DM-while-viewing — plus per-slot sound resolution at both play sites and settings prop wiring.
desktop/src/features/settings/ui/NotificationSettingsCard.tsx
The rebuilt panel: Desktop alerts + Notify while viewing; a Sound row (default picker + master toggle) with per-event rows as flat siblings below a divider; per-row picker + alert toggle; parent toggles cascade by fading rows in place; coming-soon rows behind a centered View all / Show less button.
desktop/src/features/settings/ui/SoundPicker.tsx
New dropdown picker: waveform thumbnails per sound, recommended marker, a "Default · {sound}" inherit option, and a play/pause preview button.
desktop/src/features/settings/ui/SettingsOptionGroup.tsx
Groups restyled to the member-profile look — muted fill, rounded corners, no borders — shared across all settings panels.
desktop/src/features/settings/ui/SettingsPanels.tsx, SettingsScreen.tsx, SettingsView.tsx
Prop threading for the new setters.
Reproduction Steps
rec.and a "Default · {sound}" inherit option on event rows.Screenshots/Demos
(to be added before marking ready for review)