Conversation
Coding agents emit their "done" notification while their TUI is still redrawing, so the undeferred ring summoned the user to a pane that was still animating. The switch stays as an escape hatch for the protocols' literal timing, and the AlertManager field now follows DEFAULT_ALERT_SETTINGS so a manager that never receives a settings blob behaves like one that does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Deploying mouseterm with
|
| Latest commit: |
f1287f6
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://88d96995.mouseterm.pages.dev |
| Branch Preview URL: | https://alert-defer-default-on.mouseterm.pages.dev |
dormouse-bot
left a comment
There was a problem hiding this comment.
Feedback on work in progress, not a merge verdict — mark the PR ready when you want the full review.
The flag gates a second behavior, and nothing a user reads mentions it. deferAlertsUntilQuiet also gates withdrawResumedWatchingRing, which docs/specs/alert.md states as "With deferAlertsUntilQuiet, withdraw a WATCHING ring when watched work resumes confirmed BUSY." That is why the two tests here had to switch the flag off — ALERT_RINGING latches when user has no attention (view hidden) and leaves a stale WATCHING ring alone once output has resumed. So the default flip ships ring withdrawal out of the box as well, while the switch's helper text in lib/src/components/SettingsDialog.tsx ("When the animation watcher is fully armed, terminal notifications wait for the pane to become quiet") and the Alarm settings row both describe only the deferral half. A latched claude-finished ring that disappears when the agent starts a new turn is the case a user will actually hit, with nothing to read about it. Either widen that copy or put withdrawal on its own condition.
"Users who saved the setting keep their value" reaches further than the body says. updateAlertSettings normalizes and persists the whole blob, so a user who once changed only speakDelayMs has deferAlertsUntilQuiet: false sitting in dormouse:alert-settings and never picks up the new default — despite never having expressed a preference about deferral. For a change motivated by an experience every agent user has, the set that keeps the old behavior is "anyone who has opened Settings and touched anything", not "anyone who set this switch". Worth deciding whether that is intended, or whether the persisted key should be dropped when it still matches the old default.
The rationale's closing clause reads as attributing indefinite deferral to the switch being off, when the on state is what produces it — scheduleDeferredNotification re-arms at the detector's quiet deadline with no cap. Since that unbounded case is now the shipped default, it is the sentence most worth being unambiguous. Inline suggestion below.
One small thing outside the diff: standalone/src/browser-sidecar-adapter.test.ts builds its canonical fixture as { ...DEFAULT_ALERT_SETTINGS, deferAlertsUntilQuiet: true }, now identical to DEFAULT_ALERT_SETTINGS — so the assertion no longer distinguishes "forwarded the blob it was handed" from "emitted the default". Flipping that override to false restores it.
| ## Alarm settings | ||
|
|
||
| **Why animation deferral defaults off.** BEL and notification OSCs explicitly ask to alert now, while continuously changing output may never become quiet. Opt-in preserves their established timing and makes indefinite deferral a deliberate choice. | ||
| **Why animation deferral defaults on.** Coding agents (`claude`, `codex`) send their notification OSC while their TUI is still redrawing its spinner, so an undeferred ring summons the user to a pane that is still animating (2026-09). The gate engages only while the private detector is fully armed, so a BEL from an otherwise quiet shell still rings at once. Turning the switch off restores the protocols' literal timing, including a ring that continuous output can never quiet. |
There was a problem hiding this comment.
The clause as written reads as though the off state is the one where continuous output can never quiet the ring, but off is exactly the position that rings immediately — the unbounded case belongs to on. Stating it in that order also puts the trade-off the default now carries in front of the reader.
| **Why animation deferral defaults on.** Coding agents (`claude`, `codex`) send their notification OSC while their TUI is still redrawing its spinner, so an undeferred ring summons the user to a pane that is still animating (2026-09). The gate engages only while the private detector is fully armed, so a BEL from an otherwise quiet shell still rings at once. Turning the switch off restores the protocols' literal timing, including a ring that continuous output can never quiet. | |
| **Why animation deferral defaults on.** Coding agents (`claude`, `codex`) send their notification OSC while their TUI is still redrawing its spinner, so an undeferred ring summons the user to a pane that is still animating (2026-09). The gate engages only while the private detector is fully armed, so a BEL from an otherwise quiet shell still rings at once. Deferral is unbounded, so continuous output can hold a ring indefinitely; turning the switch off is the escape hatch that restores the protocols' literal timing. |
First of a stacked series simplifying the alert system.
Coding agents (
claude,codex) emit their "done" notification while their TUI is still redrawing, so an undeferred ring summons you to a pane that is still animating.deferAlertsUntilQuietnow defaults on; the switch stays as an escape hatch for the protocols' literal timing. TheAlertManagerfield followsDEFAULT_ALERT_SETTINGSso a manager that never receives a settings blob behaves like one that does.docs/specs/alert.mdAlarm settings row and the rationale paragraph updated.normalizeAlertSettings.DeferralEnabled→DeferralDisabled(the non-default state is the one worth a snapshot).Installs that have never saved an alert-settings blob pick the new default up. Any install that has changed any alert setting has the whole blob persisted, so it keeps
deferAlertsUntilQuiet: falseuntil the switch is flipped in Settings; the blob has no version field to key a one-shot migration on, and dropping a persistedfalseon read would make the preference unpersistable (decision recorded in the rationale).🤖 Generated with Claude Code