Add notification ding when agent needs attention - #2373

Closed
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding
Closed

Add notification ding when agent needs attention#2373
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding

Conversation

@D3OXY

@D3OXYD3OXY commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

What Changed

Adds an opt-in audible ding that plays when an agent needs the user's attention.

Important

This is strictly an in-app sound effectnot a desktop notification, not a native OS notification, not a popup or toast. There's no permission flow, no system tray icon, no notification center entry. It just plays a short bell through the page's audio output.

New per-device (ClientSettings) controls in a new Notifications section in Settings → General:

  • Notification sound — master toggle + a Play sound preview button
  • When agent finishes — fires on turn completed / error / interrupted
  • When approval requested — fires when the agent asks to run a command, edit a file, or proposes a plan
  • When question asked — fires when the agent asks a clarifying question
  • Play sound when — focus rule: Always / Window not focused / Window not focused or viewing a different thread

Behaviour:

  • All toggles default to off — fully opt-in.
  • Rising-edge only — no double-dings if another approval lands while one is already pending.
  • Throttled to one ding per 5 seconds across all event types and threads.
  • Archived threads suppressed.
  • Silent-fail + console.warn if browser autoplay is blocked or the audio asset fails to load. The Play sound preview button surfaces errors via toast since it's an explicit user action.

Why

When an agent runs a long task, it's common to switch to another window or another thread while waiting. Without an audible cue, you keep glancing back to check if it's finished or needs an approval. A short ding closes that loop without hijacking attention the way a popup or system notification would.

Keeping it as an in-app sound (rather than the Notifications API) avoids the permission prompt, works identically across browser and Electron, and stays scoped to the app — the user is always in T3 Code when they hear it.

UI Changes

New Notifications section in Settings → General, between General and Providers:

image

The bell sound that plays (a short servant-bell ring):

notification.mp3

Implementation notes

  • Pure derivation in apps/web/src/notificationSound.ts:
    • deriveNotificationTriggers(prevShells, nextShells, events) — rising-edge detection on latestTurn.state, hasPendingApprovals || hasActionableProposedPlan, hasPendingUserInput. Skips archived threads. Requires prev to exist (no spurious dings on initial bootstrap).
    • shouldPlay(triggers, settings, focusContext, nowMs, lastPlayAtMs) — applies sub-toggle filters, focus rule, and 5s throttle.
    • Both fully unit-tested (28 tests in notificationSound.test.ts).
  • Side-effecting notificationSoundManager singleton owns a single HTMLAudioElement for /sounds/notification.mp3 (~54 KB MP3 bundled in apps/web/public/sounds/). Lazy-initialized; SSR-safe guards on document / window access.
  • Wired into apps/web/src/environments/runtime/service.ts at both applyRecoveredEventBatch and applyShellEvent paths so server-pushed shell upserts (the common case for fresh approvals / turn-end) reliably trigger.
  • Current-thread accessor wired via useLocation().pathname in __root.tsx; the manager itself is non-React and exposes a setCurrentThreadAccessor injection point.
  • Schema additions in packages/contracts/src/settings.ts use the existing Schema.withDecodingDefault pattern; ClientSettingsPatch updated in lockstep. Backward-compatible — missing fields decode to defaults.
  • New settings included in useSettingsRestore so "Restore defaults" wipes them.

Verification

  • bun fmt — clean
  • bun lint — 0 errors (pre-existing warnings unrelated)
  • bun typecheck — clean for @t3tools/contracts and @t3tools/web
  • bun run test — 998/998 pass (28 new in notificationSound.test.ts)

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Add notification sound when agent needs attention

  • Adds a /sounds/notification.mp3 audio asset and a NotificationSoundManager singleton (notificationSound.ts) that plays a chime when a turn ends, an approval is requested, or a question is asked.
  • Detects rising-edge transitions in thread shell snapshots via deriveNotificationTriggers, then gates playback with shouldPlay based on per-event toggles, focus rules (always, unfocused-only, unfocused-or-different-thread), and a 5 s throttle.
  • Mounts a NotificationSoundBootstrap component in the app root (__root.tsx) to wire thread snapshots to the manager on every render cycle.
  • Adds a Notifications section in General Settings with a master toggle, per-event toggles, a test-play button, and a focus-rule selector; all preferences are persisted in ClientSettingsSchema (default: sounds off, focus rule unfocused-or-different-thread).

Macroscope summarized dec585a.

@coderabbitai

coderabbitaiBot commented Apr 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67b6124e-099e-47d6-a174-531f8cd96d4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added the size:XL 500-999 changed lines (additions + deletions). label Apr 27, 2026
@macroscopeapp

macroscopeappBot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new notification sound feature with new settings, UI controls, and audio infrastructure (~600 lines of new code). While disabled by default and well-tested, the scope of new user-facing capability warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@D3OXY

Copy link
Copy Markdown
ContributorAuthor

@juliusmarminge Could you take a look at this when you have the time? Thanks! It’s off by default, doesn’t use any OS level notification APIs. Just a Ding. It should work across all the environments.

@github-actionsgithub-actionsBot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label May 3, 2026
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch 3 times, most recently from 71b56d0 to e962104CompareJune 27, 2026 14:15
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from d03e3d0 to 48701d5CompareJuly 17, 2026 13:57
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 48701d5 to 8b9f2ddCompareJuly 29, 2026 14:12
D3OXY added 4 commits August 9, 2026 20:46
Opt-in audible ding (not a desktop/OS notification or popup) that plays
when an agent finishes a turn, requests approval, or asks a question.
Per-device settings with focus-aware playback and a 5s throttle. New
"Notifications" section in Settings with master toggle, three event
sub-toggles, focus rule, and a Play Sound preview button.
`latestTurn.state` is not a reliable end-of-turn signal: the projector
flips it to "completed" on every `thread.turn-diff-completed` event,
which fires mid-turn whenever a checkpoint is captured for a git repo
(see ProviderRuntimeIngestion turn.diff.updated handling). The next
session-set running event then flips it back, producing
running -> completed -> running oscillations through a single turn and
spurious notification dings on the first mid-turn diff capture.
Switch turn-end detection to `session.orchestrationStatus`, which the
provider keeps as "running" continuously through tool calls and only
transitions out at actual turn end. "starting" is treated as still
active so session restarts/resumes mid-turn don't trigger.
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 8b9f2dd to dec585aCompareAugust 9, 2026 15:17
@t3dotggt3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the broader sound implementation in #3892. That PR covers more notification cases and clients, and this branch has no separate behavior that we need to keep.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@D3OXY@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add notification ding when agent needs attention - #2373

Closed
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding
Closed

Add notification ding when agent needs attention#2373
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding

Conversation

@D3OXY

@D3OXYD3OXY commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

What Changed

Adds an opt-in audible ding that plays when an agent needs the user's attention.

Important

This is strictly an in-app sound effectnot a desktop notification, not a native OS notification, not a popup or toast. There's no permission flow, no system tray icon, no notification center entry. It just plays a short bell through the page's audio output.

New per-device (ClientSettings) controls in a new Notifications section in Settings → General:

  • Notification sound — master toggle + a Play sound preview button
  • When agent finishes — fires on turn completed / error / interrupted
  • When approval requested — fires when the agent asks to run a command, edit a file, or proposes a plan
  • When question asked — fires when the agent asks a clarifying question
  • Play sound when — focus rule: Always / Window not focused / Window not focused or viewing a different thread

Behaviour:

  • All toggles default to off — fully opt-in.
  • Rising-edge only — no double-dings if another approval lands while one is already pending.
  • Throttled to one ding per 5 seconds across all event types and threads.
  • Archived threads suppressed.
  • Silent-fail + console.warn if browser autoplay is blocked or the audio asset fails to load. The Play sound preview button surfaces errors via toast since it's an explicit user action.

Why

When an agent runs a long task, it's common to switch to another window or another thread while waiting. Without an audible cue, you keep glancing back to check if it's finished or needs an approval. A short ding closes that loop without hijacking attention the way a popup or system notification would.

Keeping it as an in-app sound (rather than the Notifications API) avoids the permission prompt, works identically across browser and Electron, and stays scoped to the app — the user is always in T3 Code when they hear it.

UI Changes

New Notifications section in Settings → General, between General and Providers:

image

The bell sound that plays (a short servant-bell ring):

notification.mp3

Implementation notes

  • Pure derivation in apps/web/src/notificationSound.ts:
    • deriveNotificationTriggers(prevShells, nextShells, events) — rising-edge detection on latestTurn.state, hasPendingApprovals || hasActionableProposedPlan, hasPendingUserInput. Skips archived threads. Requires prev to exist (no spurious dings on initial bootstrap).
    • shouldPlay(triggers, settings, focusContext, nowMs, lastPlayAtMs) — applies sub-toggle filters, focus rule, and 5s throttle.
    • Both fully unit-tested (28 tests in notificationSound.test.ts).
  • Side-effecting notificationSoundManager singleton owns a single HTMLAudioElement for /sounds/notification.mp3 (~54 KB MP3 bundled in apps/web/public/sounds/). Lazy-initialized; SSR-safe guards on document / window access.
  • Wired into apps/web/src/environments/runtime/service.ts at both applyRecoveredEventBatch and applyShellEvent paths so server-pushed shell upserts (the common case for fresh approvals / turn-end) reliably trigger.
  • Current-thread accessor wired via useLocation().pathname in __root.tsx; the manager itself is non-React and exposes a setCurrentThreadAccessor injection point.
  • Schema additions in packages/contracts/src/settings.ts use the existing Schema.withDecodingDefault pattern; ClientSettingsPatch updated in lockstep. Backward-compatible — missing fields decode to defaults.
  • New settings included in useSettingsRestore so "Restore defaults" wipes them.

Verification

  • bun fmt — clean
  • bun lint — 0 errors (pre-existing warnings unrelated)
  • bun typecheck — clean for @t3tools/contracts and @t3tools/web
  • bun run test — 998/998 pass (28 new in notificationSound.test.ts)

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Add notification sound when agent needs attention

  • Adds a /sounds/notification.mp3 audio asset and a NotificationSoundManager singleton (notificationSound.ts) that plays a chime when a turn ends, an approval is requested, or a question is asked.
  • Detects rising-edge transitions in thread shell snapshots via deriveNotificationTriggers, then gates playback with shouldPlay based on per-event toggles, focus rules (always, unfocused-only, unfocused-or-different-thread), and a 5 s throttle.
  • Mounts a NotificationSoundBootstrap component in the app root (__root.tsx) to wire thread snapshots to the manager on every render cycle.
  • Adds a Notifications section in General Settings with a master toggle, per-event toggles, a test-play button, and a focus-rule selector; all preferences are persisted in ClientSettingsSchema (default: sounds off, focus rule unfocused-or-different-thread).

Macroscope summarized dec585a.

@coderabbitai

coderabbitaiBot commented Apr 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67b6124e-099e-47d6-a174-531f8cd96d4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added the size:XL 500-999 changed lines (additions + deletions). label Apr 27, 2026
@macroscopeapp

macroscopeappBot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new notification sound feature with new settings, UI controls, and audio infrastructure (~600 lines of new code). While disabled by default and well-tested, the scope of new user-facing capability warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@D3OXY

Copy link
Copy Markdown
ContributorAuthor

@juliusmarminge Could you take a look at this when you have the time? Thanks! It’s off by default, doesn’t use any OS level notification APIs. Just a Ding. It should work across all the environments.

@github-actionsgithub-actionsBot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label May 3, 2026
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch 3 times, most recently from 71b56d0 to e962104CompareJune 27, 2026 14:15
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from d03e3d0 to 48701d5CompareJuly 17, 2026 13:57
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 48701d5 to 8b9f2ddCompareJuly 29, 2026 14:12
D3OXY added 4 commits August 9, 2026 20:46
Opt-in audible ding (not a desktop/OS notification or popup) that plays
when an agent finishes a turn, requests approval, or asks a question.
Per-device settings with focus-aware playback and a 5s throttle. New
"Notifications" section in Settings with master toggle, three event
sub-toggles, focus rule, and a Play Sound preview button.
`latestTurn.state` is not a reliable end-of-turn signal: the projector
flips it to "completed" on every `thread.turn-diff-completed` event,
which fires mid-turn whenever a checkpoint is captured for a git repo
(see ProviderRuntimeIngestion turn.diff.updated handling). The next
session-set running event then flips it back, producing
running -> completed -> running oscillations through a single turn and
spurious notification dings on the first mid-turn diff capture.
Switch turn-end detection to `session.orchestrationStatus`, which the
provider keeps as "running" continuously through tool calls and only
transitions out at actual turn end. "starting" is treated as still
active so session restarts/resumes mid-turn don't trigger.
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 8b9f2dd to dec585aCompareAugust 9, 2026 15:17
@t3dotggt3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the broader sound implementation in #3892. That PR covers more notification cases and clients, and this branch has no separate behavior that we need to keep.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@D3OXY@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add notification ding when agent needs attention - #2373

Closed
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding
Closed

Add notification ding when agent needs attention#2373
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding

Conversation

@D3OXY

@D3OXYD3OXY commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

What Changed

Adds an opt-in audible ding that plays when an agent needs the user's attention.

Important

This is strictly an in-app sound effectnot a desktop notification, not a native OS notification, not a popup or toast. There's no permission flow, no system tray icon, no notification center entry. It just plays a short bell through the page's audio output.

New per-device (ClientSettings) controls in a new Notifications section in Settings → General:

  • Notification sound — master toggle + a Play sound preview button
  • When agent finishes — fires on turn completed / error / interrupted
  • When approval requested — fires when the agent asks to run a command, edit a file, or proposes a plan
  • When question asked — fires when the agent asks a clarifying question
  • Play sound when — focus rule: Always / Window not focused / Window not focused or viewing a different thread

Behaviour:

  • All toggles default to off — fully opt-in.
  • Rising-edge only — no double-dings if another approval lands while one is already pending.
  • Throttled to one ding per 5 seconds across all event types and threads.
  • Archived threads suppressed.
  • Silent-fail + console.warn if browser autoplay is blocked or the audio asset fails to load. The Play sound preview button surfaces errors via toast since it's an explicit user action.

Why

When an agent runs a long task, it's common to switch to another window or another thread while waiting. Without an audible cue, you keep glancing back to check if it's finished or needs an approval. A short ding closes that loop without hijacking attention the way a popup or system notification would.

Keeping it as an in-app sound (rather than the Notifications API) avoids the permission prompt, works identically across browser and Electron, and stays scoped to the app — the user is always in T3 Code when they hear it.

UI Changes

New Notifications section in Settings → General, between General and Providers:

image

The bell sound that plays (a short servant-bell ring):

notification.mp3

Implementation notes

  • Pure derivation in apps/web/src/notificationSound.ts:
    • deriveNotificationTriggers(prevShells, nextShells, events) — rising-edge detection on latestTurn.state, hasPendingApprovals || hasActionableProposedPlan, hasPendingUserInput. Skips archived threads. Requires prev to exist (no spurious dings on initial bootstrap).
    • shouldPlay(triggers, settings, focusContext, nowMs, lastPlayAtMs) — applies sub-toggle filters, focus rule, and 5s throttle.
    • Both fully unit-tested (28 tests in notificationSound.test.ts).
  • Side-effecting notificationSoundManager singleton owns a single HTMLAudioElement for /sounds/notification.mp3 (~54 KB MP3 bundled in apps/web/public/sounds/). Lazy-initialized; SSR-safe guards on document / window access.
  • Wired into apps/web/src/environments/runtime/service.ts at both applyRecoveredEventBatch and applyShellEvent paths so server-pushed shell upserts (the common case for fresh approvals / turn-end) reliably trigger.
  • Current-thread accessor wired via useLocation().pathname in __root.tsx; the manager itself is non-React and exposes a setCurrentThreadAccessor injection point.
  • Schema additions in packages/contracts/src/settings.ts use the existing Schema.withDecodingDefault pattern; ClientSettingsPatch updated in lockstep. Backward-compatible — missing fields decode to defaults.
  • New settings included in useSettingsRestore so "Restore defaults" wipes them.

Verification

  • bun fmt — clean
  • bun lint — 0 errors (pre-existing warnings unrelated)
  • bun typecheck — clean for @t3tools/contracts and @t3tools/web
  • bun run test — 998/998 pass (28 new in notificationSound.test.ts)

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Add notification sound when agent needs attention

  • Adds a /sounds/notification.mp3 audio asset and a NotificationSoundManager singleton (notificationSound.ts) that plays a chime when a turn ends, an approval is requested, or a question is asked.
  • Detects rising-edge transitions in thread shell snapshots via deriveNotificationTriggers, then gates playback with shouldPlay based on per-event toggles, focus rules (always, unfocused-only, unfocused-or-different-thread), and a 5 s throttle.
  • Mounts a NotificationSoundBootstrap component in the app root (__root.tsx) to wire thread snapshots to the manager on every render cycle.
  • Adds a Notifications section in General Settings with a master toggle, per-event toggles, a test-play button, and a focus-rule selector; all preferences are persisted in ClientSettingsSchema (default: sounds off, focus rule unfocused-or-different-thread).

Macroscope summarized dec585a.

@coderabbitai

coderabbitaiBot commented Apr 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67b6124e-099e-47d6-a174-531f8cd96d4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added the size:XL 500-999 changed lines (additions + deletions). label Apr 27, 2026
@macroscopeapp

macroscopeappBot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new notification sound feature with new settings, UI controls, and audio infrastructure (~600 lines of new code). While disabled by default and well-tested, the scope of new user-facing capability warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@D3OXY

Copy link
Copy Markdown
ContributorAuthor

@juliusmarminge Could you take a look at this when you have the time? Thanks! It’s off by default, doesn’t use any OS level notification APIs. Just a Ding. It should work across all the environments.

@github-actionsgithub-actionsBot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label May 3, 2026
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch 3 times, most recently from 71b56d0 to e962104CompareJune 27, 2026 14:15
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from d03e3d0 to 48701d5CompareJuly 17, 2026 13:57
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 48701d5 to 8b9f2ddCompareJuly 29, 2026 14:12
D3OXY added 4 commits August 9, 2026 20:46
Opt-in audible ding (not a desktop/OS notification or popup) that plays
when an agent finishes a turn, requests approval, or asks a question.
Per-device settings with focus-aware playback and a 5s throttle. New
"Notifications" section in Settings with master toggle, three event
sub-toggles, focus rule, and a Play Sound preview button.
`latestTurn.state` is not a reliable end-of-turn signal: the projector
flips it to "completed" on every `thread.turn-diff-completed` event,
which fires mid-turn whenever a checkpoint is captured for a git repo
(see ProviderRuntimeIngestion turn.diff.updated handling). The next
session-set running event then flips it back, producing
running -> completed -> running oscillations through a single turn and
spurious notification dings on the first mid-turn diff capture.
Switch turn-end detection to `session.orchestrationStatus`, which the
provider keeps as "running" continuously through tool calls and only
transitions out at actual turn end. "starting" is treated as still
active so session restarts/resumes mid-turn don't trigger.
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 8b9f2dd to dec585aCompareAugust 9, 2026 15:17
@t3dotggt3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the broader sound implementation in #3892. That PR covers more notification cases and clients, and this branch has no separate behavior that we need to keep.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@D3OXY@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add notification ding when agent needs attention - #2373

Closed
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding
Closed

Add notification ding when agent needs attention#2373
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding

Conversation

@D3OXY

@D3OXYD3OXY commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

What Changed

Adds an opt-in audible ding that plays when an agent needs the user's attention.

Important

This is strictly an in-app sound effectnot a desktop notification, not a native OS notification, not a popup or toast. There's no permission flow, no system tray icon, no notification center entry. It just plays a short bell through the page's audio output.

New per-device (ClientSettings) controls in a new Notifications section in Settings → General:

  • Notification sound — master toggle + a Play sound preview button
  • When agent finishes — fires on turn completed / error / interrupted
  • When approval requested — fires when the agent asks to run a command, edit a file, or proposes a plan
  • When question asked — fires when the agent asks a clarifying question
  • Play sound when — focus rule: Always / Window not focused / Window not focused or viewing a different thread

Behaviour:

  • All toggles default to off — fully opt-in.
  • Rising-edge only — no double-dings if another approval lands while one is already pending.
  • Throttled to one ding per 5 seconds across all event types and threads.
  • Archived threads suppressed.
  • Silent-fail + console.warn if browser autoplay is blocked or the audio asset fails to load. The Play sound preview button surfaces errors via toast since it's an explicit user action.

Why

When an agent runs a long task, it's common to switch to another window or another thread while waiting. Without an audible cue, you keep glancing back to check if it's finished or needs an approval. A short ding closes that loop without hijacking attention the way a popup or system notification would.

Keeping it as an in-app sound (rather than the Notifications API) avoids the permission prompt, works identically across browser and Electron, and stays scoped to the app — the user is always in T3 Code when they hear it.

UI Changes

New Notifications section in Settings → General, between General and Providers:

image

The bell sound that plays (a short servant-bell ring):

notification.mp3

Implementation notes

  • Pure derivation in apps/web/src/notificationSound.ts:
    • deriveNotificationTriggers(prevShells, nextShells, events) — rising-edge detection on latestTurn.state, hasPendingApprovals || hasActionableProposedPlan, hasPendingUserInput. Skips archived threads. Requires prev to exist (no spurious dings on initial bootstrap).
    • shouldPlay(triggers, settings, focusContext, nowMs, lastPlayAtMs) — applies sub-toggle filters, focus rule, and 5s throttle.
    • Both fully unit-tested (28 tests in notificationSound.test.ts).
  • Side-effecting notificationSoundManager singleton owns a single HTMLAudioElement for /sounds/notification.mp3 (~54 KB MP3 bundled in apps/web/public/sounds/). Lazy-initialized; SSR-safe guards on document / window access.
  • Wired into apps/web/src/environments/runtime/service.ts at both applyRecoveredEventBatch and applyShellEvent paths so server-pushed shell upserts (the common case for fresh approvals / turn-end) reliably trigger.
  • Current-thread accessor wired via useLocation().pathname in __root.tsx; the manager itself is non-React and exposes a setCurrentThreadAccessor injection point.
  • Schema additions in packages/contracts/src/settings.ts use the existing Schema.withDecodingDefault pattern; ClientSettingsPatch updated in lockstep. Backward-compatible — missing fields decode to defaults.
  • New settings included in useSettingsRestore so "Restore defaults" wipes them.

Verification

  • bun fmt — clean
  • bun lint — 0 errors (pre-existing warnings unrelated)
  • bun typecheck — clean for @t3tools/contracts and @t3tools/web
  • bun run test — 998/998 pass (28 new in notificationSound.test.ts)

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Add notification sound when agent needs attention

  • Adds a /sounds/notification.mp3 audio asset and a NotificationSoundManager singleton (notificationSound.ts) that plays a chime when a turn ends, an approval is requested, or a question is asked.
  • Detects rising-edge transitions in thread shell snapshots via deriveNotificationTriggers, then gates playback with shouldPlay based on per-event toggles, focus rules (always, unfocused-only, unfocused-or-different-thread), and a 5 s throttle.
  • Mounts a NotificationSoundBootstrap component in the app root (__root.tsx) to wire thread snapshots to the manager on every render cycle.
  • Adds a Notifications section in General Settings with a master toggle, per-event toggles, a test-play button, and a focus-rule selector; all preferences are persisted in ClientSettingsSchema (default: sounds off, focus rule unfocused-or-different-thread).

Macroscope summarized dec585a.

@coderabbitai

coderabbitaiBot commented Apr 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67b6124e-099e-47d6-a174-531f8cd96d4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added the size:XL 500-999 changed lines (additions + deletions). label Apr 27, 2026
@macroscopeapp

macroscopeappBot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new notification sound feature with new settings, UI controls, and audio infrastructure (~600 lines of new code). While disabled by default and well-tested, the scope of new user-facing capability warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@D3OXY

Copy link
Copy Markdown
ContributorAuthor

@juliusmarminge Could you take a look at this when you have the time? Thanks! It’s off by default, doesn’t use any OS level notification APIs. Just a Ding. It should work across all the environments.

@github-actionsgithub-actionsBot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label May 3, 2026
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch 3 times, most recently from 71b56d0 to e962104CompareJune 27, 2026 14:15
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from d03e3d0 to 48701d5CompareJuly 17, 2026 13:57
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 48701d5 to 8b9f2ddCompareJuly 29, 2026 14:12
D3OXY added 4 commits August 9, 2026 20:46
Opt-in audible ding (not a desktop/OS notification or popup) that plays
when an agent finishes a turn, requests approval, or asks a question.
Per-device settings with focus-aware playback and a 5s throttle. New
"Notifications" section in Settings with master toggle, three event
sub-toggles, focus rule, and a Play Sound preview button.
`latestTurn.state` is not a reliable end-of-turn signal: the projector
flips it to "completed" on every `thread.turn-diff-completed` event,
which fires mid-turn whenever a checkpoint is captured for a git repo
(see ProviderRuntimeIngestion turn.diff.updated handling). The next
session-set running event then flips it back, producing
running -> completed -> running oscillations through a single turn and
spurious notification dings on the first mid-turn diff capture.
Switch turn-end detection to `session.orchestrationStatus`, which the
provider keeps as "running" continuously through tool calls and only
transitions out at actual turn end. "starting" is treated as still
active so session restarts/resumes mid-turn don't trigger.
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 8b9f2dd to dec585aCompareAugust 9, 2026 15:17
@t3dotggt3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the broader sound implementation in #3892. That PR covers more notification cases and clients, and this branch has no separate behavior that we need to keep.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@D3OXY@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add notification ding when agent needs attention - #2373

Closed
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding
Closed

Add notification ding when agent needs attention#2373
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding

Conversation

@D3OXY

@D3OXYD3OXY commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

What Changed

Adds an opt-in audible ding that plays when an agent needs the user's attention.

Important

This is strictly an in-app sound effectnot a desktop notification, not a native OS notification, not a popup or toast. There's no permission flow, no system tray icon, no notification center entry. It just plays a short bell through the page's audio output.

New per-device (ClientSettings) controls in a new Notifications section in Settings → General:

  • Notification sound — master toggle + a Play sound preview button
  • When agent finishes — fires on turn completed / error / interrupted
  • When approval requested — fires when the agent asks to run a command, edit a file, or proposes a plan
  • When question asked — fires when the agent asks a clarifying question
  • Play sound when — focus rule: Always / Window not focused / Window not focused or viewing a different thread

Behaviour:

  • All toggles default to off — fully opt-in.
  • Rising-edge only — no double-dings if another approval lands while one is already pending.
  • Throttled to one ding per 5 seconds across all event types and threads.
  • Archived threads suppressed.
  • Silent-fail + console.warn if browser autoplay is blocked or the audio asset fails to load. The Play sound preview button surfaces errors via toast since it's an explicit user action.

Why

When an agent runs a long task, it's common to switch to another window or another thread while waiting. Without an audible cue, you keep glancing back to check if it's finished or needs an approval. A short ding closes that loop without hijacking attention the way a popup or system notification would.

Keeping it as an in-app sound (rather than the Notifications API) avoids the permission prompt, works identically across browser and Electron, and stays scoped to the app — the user is always in T3 Code when they hear it.

UI Changes

New Notifications section in Settings → General, between General and Providers:

image

The bell sound that plays (a short servant-bell ring):

notification.mp3

Implementation notes

  • Pure derivation in apps/web/src/notificationSound.ts:
    • deriveNotificationTriggers(prevShells, nextShells, events) — rising-edge detection on latestTurn.state, hasPendingApprovals || hasActionableProposedPlan, hasPendingUserInput. Skips archived threads. Requires prev to exist (no spurious dings on initial bootstrap).
    • shouldPlay(triggers, settings, focusContext, nowMs, lastPlayAtMs) — applies sub-toggle filters, focus rule, and 5s throttle.
    • Both fully unit-tested (28 tests in notificationSound.test.ts).
  • Side-effecting notificationSoundManager singleton owns a single HTMLAudioElement for /sounds/notification.mp3 (~54 KB MP3 bundled in apps/web/public/sounds/). Lazy-initialized; SSR-safe guards on document / window access.
  • Wired into apps/web/src/environments/runtime/service.ts at both applyRecoveredEventBatch and applyShellEvent paths so server-pushed shell upserts (the common case for fresh approvals / turn-end) reliably trigger.
  • Current-thread accessor wired via useLocation().pathname in __root.tsx; the manager itself is non-React and exposes a setCurrentThreadAccessor injection point.
  • Schema additions in packages/contracts/src/settings.ts use the existing Schema.withDecodingDefault pattern; ClientSettingsPatch updated in lockstep. Backward-compatible — missing fields decode to defaults.
  • New settings included in useSettingsRestore so "Restore defaults" wipes them.

Verification

  • bun fmt — clean
  • bun lint — 0 errors (pre-existing warnings unrelated)
  • bun typecheck — clean for @t3tools/contracts and @t3tools/web
  • bun run test — 998/998 pass (28 new in notificationSound.test.ts)

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Add notification sound when agent needs attention

  • Adds a /sounds/notification.mp3 audio asset and a NotificationSoundManager singleton (notificationSound.ts) that plays a chime when a turn ends, an approval is requested, or a question is asked.
  • Detects rising-edge transitions in thread shell snapshots via deriveNotificationTriggers, then gates playback with shouldPlay based on per-event toggles, focus rules (always, unfocused-only, unfocused-or-different-thread), and a 5 s throttle.
  • Mounts a NotificationSoundBootstrap component in the app root (__root.tsx) to wire thread snapshots to the manager on every render cycle.
  • Adds a Notifications section in General Settings with a master toggle, per-event toggles, a test-play button, and a focus-rule selector; all preferences are persisted in ClientSettingsSchema (default: sounds off, focus rule unfocused-or-different-thread).

Macroscope summarized dec585a.

@coderabbitai

coderabbitaiBot commented Apr 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67b6124e-099e-47d6-a174-531f8cd96d4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added the size:XL 500-999 changed lines (additions + deletions). label Apr 27, 2026
@macroscopeapp

macroscopeappBot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new notification sound feature with new settings, UI controls, and audio infrastructure (~600 lines of new code). While disabled by default and well-tested, the scope of new user-facing capability warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@D3OXY

Copy link
Copy Markdown
ContributorAuthor

@juliusmarminge Could you take a look at this when you have the time? Thanks! It’s off by default, doesn’t use any OS level notification APIs. Just a Ding. It should work across all the environments.

@github-actionsgithub-actionsBot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label May 3, 2026
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch 3 times, most recently from 71b56d0 to e962104CompareJune 27, 2026 14:15
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from d03e3d0 to 48701d5CompareJuly 17, 2026 13:57
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 48701d5 to 8b9f2ddCompareJuly 29, 2026 14:12
D3OXY added 4 commits August 9, 2026 20:46
Opt-in audible ding (not a desktop/OS notification or popup) that plays
when an agent finishes a turn, requests approval, or asks a question.
Per-device settings with focus-aware playback and a 5s throttle. New
"Notifications" section in Settings with master toggle, three event
sub-toggles, focus rule, and a Play Sound preview button.
`latestTurn.state` is not a reliable end-of-turn signal: the projector
flips it to "completed" on every `thread.turn-diff-completed` event,
which fires mid-turn whenever a checkpoint is captured for a git repo
(see ProviderRuntimeIngestion turn.diff.updated handling). The next
session-set running event then flips it back, producing
running -> completed -> running oscillations through a single turn and
spurious notification dings on the first mid-turn diff capture.
Switch turn-end detection to `session.orchestrationStatus`, which the
provider keeps as "running" continuously through tool calls and only
transitions out at actual turn end. "starting" is treated as still
active so session restarts/resumes mid-turn don't trigger.
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 8b9f2dd to dec585aCompareAugust 9, 2026 15:17
@t3dotggt3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the broader sound implementation in #3892. That PR covers more notification cases and clients, and this branch has no separate behavior that we need to keep.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@D3OXY@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add notification ding when agent needs attention - #2373

Closed
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding
Closed

Add notification ding when agent needs attention#2373
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding

Conversation

@D3OXY

@D3OXYD3OXY commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

What Changed

Adds an opt-in audible ding that plays when an agent needs the user's attention.

Important

This is strictly an in-app sound effectnot a desktop notification, not a native OS notification, not a popup or toast. There's no permission flow, no system tray icon, no notification center entry. It just plays a short bell through the page's audio output.

New per-device (ClientSettings) controls in a new Notifications section in Settings → General:

  • Notification sound — master toggle + a Play sound preview button
  • When agent finishes — fires on turn completed / error / interrupted
  • When approval requested — fires when the agent asks to run a command, edit a file, or proposes a plan
  • When question asked — fires when the agent asks a clarifying question
  • Play sound when — focus rule: Always / Window not focused / Window not focused or viewing a different thread

Behaviour:

  • All toggles default to off — fully opt-in.
  • Rising-edge only — no double-dings if another approval lands while one is already pending.
  • Throttled to one ding per 5 seconds across all event types and threads.
  • Archived threads suppressed.
  • Silent-fail + console.warn if browser autoplay is blocked or the audio asset fails to load. The Play sound preview button surfaces errors via toast since it's an explicit user action.

Why

When an agent runs a long task, it's common to switch to another window or another thread while waiting. Without an audible cue, you keep glancing back to check if it's finished or needs an approval. A short ding closes that loop without hijacking attention the way a popup or system notification would.

Keeping it as an in-app sound (rather than the Notifications API) avoids the permission prompt, works identically across browser and Electron, and stays scoped to the app — the user is always in T3 Code when they hear it.

UI Changes

New Notifications section in Settings → General, between General and Providers:

image

The bell sound that plays (a short servant-bell ring):

notification.mp3

Implementation notes

  • Pure derivation in apps/web/src/notificationSound.ts:
    • deriveNotificationTriggers(prevShells, nextShells, events) — rising-edge detection on latestTurn.state, hasPendingApprovals || hasActionableProposedPlan, hasPendingUserInput. Skips archived threads. Requires prev to exist (no spurious dings on initial bootstrap).
    • shouldPlay(triggers, settings, focusContext, nowMs, lastPlayAtMs) — applies sub-toggle filters, focus rule, and 5s throttle.
    • Both fully unit-tested (28 tests in notificationSound.test.ts).
  • Side-effecting notificationSoundManager singleton owns a single HTMLAudioElement for /sounds/notification.mp3 (~54 KB MP3 bundled in apps/web/public/sounds/). Lazy-initialized; SSR-safe guards on document / window access.
  • Wired into apps/web/src/environments/runtime/service.ts at both applyRecoveredEventBatch and applyShellEvent paths so server-pushed shell upserts (the common case for fresh approvals / turn-end) reliably trigger.
  • Current-thread accessor wired via useLocation().pathname in __root.tsx; the manager itself is non-React and exposes a setCurrentThreadAccessor injection point.
  • Schema additions in packages/contracts/src/settings.ts use the existing Schema.withDecodingDefault pattern; ClientSettingsPatch updated in lockstep. Backward-compatible — missing fields decode to defaults.
  • New settings included in useSettingsRestore so "Restore defaults" wipes them.

Verification

  • bun fmt — clean
  • bun lint — 0 errors (pre-existing warnings unrelated)
  • bun typecheck — clean for @t3tools/contracts and @t3tools/web
  • bun run test — 998/998 pass (28 new in notificationSound.test.ts)

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Add notification sound when agent needs attention

  • Adds a /sounds/notification.mp3 audio asset and a NotificationSoundManager singleton (notificationSound.ts) that plays a chime when a turn ends, an approval is requested, or a question is asked.
  • Detects rising-edge transitions in thread shell snapshots via deriveNotificationTriggers, then gates playback with shouldPlay based on per-event toggles, focus rules (always, unfocused-only, unfocused-or-different-thread), and a 5 s throttle.
  • Mounts a NotificationSoundBootstrap component in the app root (__root.tsx) to wire thread snapshots to the manager on every render cycle.
  • Adds a Notifications section in General Settings with a master toggle, per-event toggles, a test-play button, and a focus-rule selector; all preferences are persisted in ClientSettingsSchema (default: sounds off, focus rule unfocused-or-different-thread).

Macroscope summarized dec585a.

@coderabbitai

coderabbitaiBot commented Apr 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67b6124e-099e-47d6-a174-531f8cd96d4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added the size:XL 500-999 changed lines (additions + deletions). label Apr 27, 2026
@macroscopeapp

macroscopeappBot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new notification sound feature with new settings, UI controls, and audio infrastructure (~600 lines of new code). While disabled by default and well-tested, the scope of new user-facing capability warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@D3OXY

Copy link
Copy Markdown
ContributorAuthor

@juliusmarminge Could you take a look at this when you have the time? Thanks! It’s off by default, doesn’t use any OS level notification APIs. Just a Ding. It should work across all the environments.

@github-actionsgithub-actionsBot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label May 3, 2026
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch 3 times, most recently from 71b56d0 to e962104CompareJune 27, 2026 14:15
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from d03e3d0 to 48701d5CompareJuly 17, 2026 13:57
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 48701d5 to 8b9f2ddCompareJuly 29, 2026 14:12
D3OXY added 4 commits August 9, 2026 20:46
Opt-in audible ding (not a desktop/OS notification or popup) that plays
when an agent finishes a turn, requests approval, or asks a question.
Per-device settings with focus-aware playback and a 5s throttle. New
"Notifications" section in Settings with master toggle, three event
sub-toggles, focus rule, and a Play Sound preview button.
`latestTurn.state` is not a reliable end-of-turn signal: the projector
flips it to "completed" on every `thread.turn-diff-completed` event,
which fires mid-turn whenever a checkpoint is captured for a git repo
(see ProviderRuntimeIngestion turn.diff.updated handling). The next
session-set running event then flips it back, producing
running -> completed -> running oscillations through a single turn and
spurious notification dings on the first mid-turn diff capture.
Switch turn-end detection to `session.orchestrationStatus`, which the
provider keeps as "running" continuously through tool calls and only
transitions out at actual turn end. "starting" is treated as still
active so session restarts/resumes mid-turn don't trigger.
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 8b9f2dd to dec585aCompareAugust 9, 2026 15:17
@t3dotggt3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the broader sound implementation in #3892. That PR covers more notification cases and clients, and this branch has no separate behavior that we need to keep.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@D3OXY@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add notification ding when agent needs attention - #2373

Closed
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding
Closed

Add notification ding when agent needs attention#2373
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding

Conversation

@D3OXY

@D3OXYD3OXY commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

What Changed

Adds an opt-in audible ding that plays when an agent needs the user's attention.

Important

This is strictly an in-app sound effectnot a desktop notification, not a native OS notification, not a popup or toast. There's no permission flow, no system tray icon, no notification center entry. It just plays a short bell through the page's audio output.

New per-device (ClientSettings) controls in a new Notifications section in Settings → General:

  • Notification sound — master toggle + a Play sound preview button
  • When agent finishes — fires on turn completed / error / interrupted
  • When approval requested — fires when the agent asks to run a command, edit a file, or proposes a plan
  • When question asked — fires when the agent asks a clarifying question
  • Play sound when — focus rule: Always / Window not focused / Window not focused or viewing a different thread

Behaviour:

  • All toggles default to off — fully opt-in.
  • Rising-edge only — no double-dings if another approval lands while one is already pending.
  • Throttled to one ding per 5 seconds across all event types and threads.
  • Archived threads suppressed.
  • Silent-fail + console.warn if browser autoplay is blocked or the audio asset fails to load. The Play sound preview button surfaces errors via toast since it's an explicit user action.

Why

When an agent runs a long task, it's common to switch to another window or another thread while waiting. Without an audible cue, you keep glancing back to check if it's finished or needs an approval. A short ding closes that loop without hijacking attention the way a popup or system notification would.

Keeping it as an in-app sound (rather than the Notifications API) avoids the permission prompt, works identically across browser and Electron, and stays scoped to the app — the user is always in T3 Code when they hear it.

UI Changes

New Notifications section in Settings → General, between General and Providers:

image

The bell sound that plays (a short servant-bell ring):

notification.mp3

Implementation notes

  • Pure derivation in apps/web/src/notificationSound.ts:
    • deriveNotificationTriggers(prevShells, nextShells, events) — rising-edge detection on latestTurn.state, hasPendingApprovals || hasActionableProposedPlan, hasPendingUserInput. Skips archived threads. Requires prev to exist (no spurious dings on initial bootstrap).
    • shouldPlay(triggers, settings, focusContext, nowMs, lastPlayAtMs) — applies sub-toggle filters, focus rule, and 5s throttle.
    • Both fully unit-tested (28 tests in notificationSound.test.ts).
  • Side-effecting notificationSoundManager singleton owns a single HTMLAudioElement for /sounds/notification.mp3 (~54 KB MP3 bundled in apps/web/public/sounds/). Lazy-initialized; SSR-safe guards on document / window access.
  • Wired into apps/web/src/environments/runtime/service.ts at both applyRecoveredEventBatch and applyShellEvent paths so server-pushed shell upserts (the common case for fresh approvals / turn-end) reliably trigger.
  • Current-thread accessor wired via useLocation().pathname in __root.tsx; the manager itself is non-React and exposes a setCurrentThreadAccessor injection point.
  • Schema additions in packages/contracts/src/settings.ts use the existing Schema.withDecodingDefault pattern; ClientSettingsPatch updated in lockstep. Backward-compatible — missing fields decode to defaults.
  • New settings included in useSettingsRestore so "Restore defaults" wipes them.

Verification

  • bun fmt — clean
  • bun lint — 0 errors (pre-existing warnings unrelated)
  • bun typecheck — clean for @t3tools/contracts and @t3tools/web
  • bun run test — 998/998 pass (28 new in notificationSound.test.ts)

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Add notification sound when agent needs attention

  • Adds a /sounds/notification.mp3 audio asset and a NotificationSoundManager singleton (notificationSound.ts) that plays a chime when a turn ends, an approval is requested, or a question is asked.
  • Detects rising-edge transitions in thread shell snapshots via deriveNotificationTriggers, then gates playback with shouldPlay based on per-event toggles, focus rules (always, unfocused-only, unfocused-or-different-thread), and a 5 s throttle.
  • Mounts a NotificationSoundBootstrap component in the app root (__root.tsx) to wire thread snapshots to the manager on every render cycle.
  • Adds a Notifications section in General Settings with a master toggle, per-event toggles, a test-play button, and a focus-rule selector; all preferences are persisted in ClientSettingsSchema (default: sounds off, focus rule unfocused-or-different-thread).

Macroscope summarized dec585a.

@coderabbitai

coderabbitaiBot commented Apr 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67b6124e-099e-47d6-a174-531f8cd96d4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added the size:XL 500-999 changed lines (additions + deletions). label Apr 27, 2026
@macroscopeapp

macroscopeappBot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new notification sound feature with new settings, UI controls, and audio infrastructure (~600 lines of new code). While disabled by default and well-tested, the scope of new user-facing capability warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@D3OXY

Copy link
Copy Markdown
ContributorAuthor

@juliusmarminge Could you take a look at this when you have the time? Thanks! It’s off by default, doesn’t use any OS level notification APIs. Just a Ding. It should work across all the environments.

@github-actionsgithub-actionsBot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label May 3, 2026
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch 3 times, most recently from 71b56d0 to e962104CompareJune 27, 2026 14:15
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from d03e3d0 to 48701d5CompareJuly 17, 2026 13:57
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 48701d5 to 8b9f2ddCompareJuly 29, 2026 14:12
D3OXY added 4 commits August 9, 2026 20:46
Opt-in audible ding (not a desktop/OS notification or popup) that plays
when an agent finishes a turn, requests approval, or asks a question.
Per-device settings with focus-aware playback and a 5s throttle. New
"Notifications" section in Settings with master toggle, three event
sub-toggles, focus rule, and a Play Sound preview button.
`latestTurn.state` is not a reliable end-of-turn signal: the projector
flips it to "completed" on every `thread.turn-diff-completed` event,
which fires mid-turn whenever a checkpoint is captured for a git repo
(see ProviderRuntimeIngestion turn.diff.updated handling). The next
session-set running event then flips it back, producing
running -> completed -> running oscillations through a single turn and
spurious notification dings on the first mid-turn diff capture.
Switch turn-end detection to `session.orchestrationStatus`, which the
provider keeps as "running" continuously through tool calls and only
transitions out at actual turn end. "starting" is treated as still
active so session restarts/resumes mid-turn don't trigger.
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 8b9f2dd to dec585aCompareAugust 9, 2026 15:17
@t3dotggt3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the broader sound implementation in #3892. That PR covers more notification cases and clients, and this branch has no separate behavior that we need to keep.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@D3OXY@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add notification ding when agent needs attention - #2373

Closed
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding
Closed

Add notification ding when agent needs attention#2373
D3OXY wants to merge 4 commits into
pingdotgg:mainfrom
D3OXY:feat/notification-ding

Conversation

@D3OXY

@D3OXYD3OXY commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

What Changed

Adds an opt-in audible ding that plays when an agent needs the user's attention.

Important

This is strictly an in-app sound effectnot a desktop notification, not a native OS notification, not a popup or toast. There's no permission flow, no system tray icon, no notification center entry. It just plays a short bell through the page's audio output.

New per-device (ClientSettings) controls in a new Notifications section in Settings → General:

  • Notification sound — master toggle + a Play sound preview button
  • When agent finishes — fires on turn completed / error / interrupted
  • When approval requested — fires when the agent asks to run a command, edit a file, or proposes a plan
  • When question asked — fires when the agent asks a clarifying question
  • Play sound when — focus rule: Always / Window not focused / Window not focused or viewing a different thread

Behaviour:

  • All toggles default to off — fully opt-in.
  • Rising-edge only — no double-dings if another approval lands while one is already pending.
  • Throttled to one ding per 5 seconds across all event types and threads.
  • Archived threads suppressed.
  • Silent-fail + console.warn if browser autoplay is blocked or the audio asset fails to load. The Play sound preview button surfaces errors via toast since it's an explicit user action.

Why

When an agent runs a long task, it's common to switch to another window or another thread while waiting. Without an audible cue, you keep glancing back to check if it's finished or needs an approval. A short ding closes that loop without hijacking attention the way a popup or system notification would.

Keeping it as an in-app sound (rather than the Notifications API) avoids the permission prompt, works identically across browser and Electron, and stays scoped to the app — the user is always in T3 Code when they hear it.

UI Changes

New Notifications section in Settings → General, between General and Providers:

image

The bell sound that plays (a short servant-bell ring):

notification.mp3

Implementation notes

  • Pure derivation in apps/web/src/notificationSound.ts:
    • deriveNotificationTriggers(prevShells, nextShells, events) — rising-edge detection on latestTurn.state, hasPendingApprovals || hasActionableProposedPlan, hasPendingUserInput. Skips archived threads. Requires prev to exist (no spurious dings on initial bootstrap).
    • shouldPlay(triggers, settings, focusContext, nowMs, lastPlayAtMs) — applies sub-toggle filters, focus rule, and 5s throttle.
    • Both fully unit-tested (28 tests in notificationSound.test.ts).
  • Side-effecting notificationSoundManager singleton owns a single HTMLAudioElement for /sounds/notification.mp3 (~54 KB MP3 bundled in apps/web/public/sounds/). Lazy-initialized; SSR-safe guards on document / window access.
  • Wired into apps/web/src/environments/runtime/service.ts at both applyRecoveredEventBatch and applyShellEvent paths so server-pushed shell upserts (the common case for fresh approvals / turn-end) reliably trigger.
  • Current-thread accessor wired via useLocation().pathname in __root.tsx; the manager itself is non-React and exposes a setCurrentThreadAccessor injection point.
  • Schema additions in packages/contracts/src/settings.ts use the existing Schema.withDecodingDefault pattern; ClientSettingsPatch updated in lockstep. Backward-compatible — missing fields decode to defaults.
  • New settings included in useSettingsRestore so "Restore defaults" wipes them.

Verification

  • bun fmt — clean
  • bun lint — 0 errors (pre-existing warnings unrelated)
  • bun typecheck — clean for @t3tools/contracts and @t3tools/web
  • bun run test — 998/998 pass (28 new in notificationSound.test.ts)

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Add notification sound when agent needs attention

  • Adds a /sounds/notification.mp3 audio asset and a NotificationSoundManager singleton (notificationSound.ts) that plays a chime when a turn ends, an approval is requested, or a question is asked.
  • Detects rising-edge transitions in thread shell snapshots via deriveNotificationTriggers, then gates playback with shouldPlay based on per-event toggles, focus rules (always, unfocused-only, unfocused-or-different-thread), and a 5 s throttle.
  • Mounts a NotificationSoundBootstrap component in the app root (__root.tsx) to wire thread snapshots to the manager on every render cycle.
  • Adds a Notifications section in General Settings with a master toggle, per-event toggles, a test-play button, and a focus-rule selector; all preferences are persisted in ClientSettingsSchema (default: sounds off, focus rule unfocused-or-different-thread).

Macroscope summarized dec585a.

@coderabbitai

coderabbitaiBot commented Apr 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67b6124e-099e-47d6-a174-531f8cd96d4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added the size:XL 500-999 changed lines (additions + deletions). label Apr 27, 2026
@macroscopeapp

macroscopeappBot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new notification sound feature with new settings, UI controls, and audio infrastructure (~600 lines of new code). While disabled by default and well-tested, the scope of new user-facing capability warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@D3OXY

Copy link
Copy Markdown
ContributorAuthor

@juliusmarminge Could you take a look at this when you have the time? Thanks! It’s off by default, doesn’t use any OS level notification APIs. Just a Ding. It should work across all the environments.

@github-actionsgithub-actionsBot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label May 3, 2026
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch 3 times, most recently from 71b56d0 to e962104CompareJune 27, 2026 14:15
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from d03e3d0 to 48701d5CompareJuly 17, 2026 13:57
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 48701d5 to 8b9f2ddCompareJuly 29, 2026 14:12
D3OXY added 4 commits August 9, 2026 20:46
Opt-in audible ding (not a desktop/OS notification or popup) that plays
when an agent finishes a turn, requests approval, or asks a question.
Per-device settings with focus-aware playback and a 5s throttle. New
"Notifications" section in Settings with master toggle, three event
sub-toggles, focus rule, and a Play Sound preview button.
`latestTurn.state` is not a reliable end-of-turn signal: the projector
flips it to "completed" on every `thread.turn-diff-completed` event,
which fires mid-turn whenever a checkpoint is captured for a git repo
(see ProviderRuntimeIngestion turn.diff.updated handling). The next
session-set running event then flips it back, producing
running -> completed -> running oscillations through a single turn and
spurious notification dings on the first mid-turn diff capture.
Switch turn-end detection to `session.orchestrationStatus`, which the
provider keeps as "running" continuously through tool calls and only
transitions out at actual turn end. "starting" is treated as still
active so session restarts/resumes mid-turn don't trigger.
@D3OXY
D3OXYforce-pushed the feat/notification-ding branch from 8b9f2dd to dec585aCompareAugust 9, 2026 15:17
@t3dotggt3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the broader sound implementation in #3892. That PR covers more notification cases and clients, and this branch has no separate behavior that we need to keep.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@D3OXY@t3dotgg