Skip to content

feat(desktop): per-event notification sounds and alert controls#968

Merged
wesbillman merged 13 commits into
mainfrom
tho/per-kind-notification-sounds
Jun 11, 2026
Merged

feat(desktop): per-event notification sounds and alert controls#968
wesbillman merged 13 commits into
mainfrom
tho/per-kind-notification-sounds

Conversation

@tellaho

@tellaho tellaho commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator
Screen Recording 2026-06-11 at 7 10 02 AM

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 with resolveSlotSound (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
NotificationSettings gains singleSound, per-slot sounds overrides, slotAlertsEnabled, and notifyWhileViewing; the old top-level mentions/needsAction booleans 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) and onThreadReplyDesktopNotification, 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

  1. Open Settings → Notifications. The sound section shows a Sound row with a waveform picker and master toggle, and one row per event type below a divider.
  2. Click any ▶ to preview a sound (icon swaps to pause while playing). Open a picker — each option shows its waveform, with the recommended sound marked rec. and a "Default · {sound}" inherit option on event rows.
  3. Change the top-level Sound — every row still on Default follows it.
  4. Toggle an event row off — its picker fades and that event stops alerting. Toggle the Sound master off — all rows fade and alerts stop; flipping it back restores each row's stored state.
  5. Click "View all" below the container to reveal the four disabled coming-soon job rows.
  6. Thread replies: from a second account, reply in a thread you started while you view a different channel — a "Reply in {channel}" notification fires with your default sound.
  7. Notify while viewing: enable the toggle, then receive a DM in the conversation you have open — it now notifies (off restores the old suppression).

Screenshots/Demos

(to be added before marking ready for review)

tellaho and others added 13 commits June 11, 2026 01:20
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 wesbillman marked this pull request as ready for review June 11, 2026 14:31
@wesbillman wesbillman merged commit 4e4dc72 into main Jun 11, 2026
15 checks passed
@wesbillman wesbillman deleted the tho/per-kind-notification-sounds branch June 11, 2026 14:31
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