Add "Play test sound" and "Send test push" to the alarm settings - #424
Add "Play test sound" and "Send test push" to the alarm settings#424nedtwigg wants to merge 4 commits into
Conversation
An alarm you cannot observe until it fires unattended is one you cannot trust, and that first firing is exactly when being wrong costs the most. Give each alarm sink a way to try it now. Both sit outside the switch's dimming and stay enabled while the sink is off: checking that the speakers work, or that the phone buzzes, is most useful before committing to the alarm. Each reports its outcome inline, because for both sinks a working path and a broken one produce the same observation — silence. Play test sound deliberately does not route through `speak()`: that publishes the transient per-Session speaking/spoken state that Panes and Doors render, and no Session rang. A test that lit up a pane would be claiming some terminal wants attention. It reports a webview with no speech backend rather than degrading silently the way the alarm path correctly does. Send test push goes through the real Host, ACL and server, so what it proves is what the alarm will do. That needed the delivery path to become observable: `sendPush` now returns a summary instead of `void`, and a new `pushTest` service command surfaces failures rather than warning to the console. The ring path's rule that a failed push must never break the alert path is right for an alarm and exactly wrong for a test — it would report success over a fan-out that reached nobody. Four outcomes are distinguished: nothing targeted (the ordinary answer on a freshly enrolled machine, not a failure), nothing delivered, a partial fan-out, and success. Note the signature change: `sendPush` returns `Promise<PushSendSummary>` rather than `Promise<void>`. The ring path ignores the value, so behavior is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDNJHHA95nvRdo4Cv3rAoi
Deploying mouseterm with
|
| Latest commit: |
46e2c4b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://8ca08330.mouseterm.pages.dev |
| Branch Preview URL: | https://alarm-test-buttons.mouseterm.pages.dev |
dormouse-bot
left a comment
There was a problem hiding this comment.
Nice feature, and the framing is right — routing the push test through the real Host, ACL and server rather than a mock is what makes it worth having, and #pushTest being the deliberate inverse of #push on failure-swallowing is the correct call. Three notes below; only the first is a bug.
Outside the diff: alert-speech.ts's module docstring still claims that "speak() and cancelSpeech() are the only two places this module touches the engine — the seam a future native PlatformAdapter.speak?() would slot into for hosts whose webview has no speech backend (Tauri on Linux/WebKitGTK)". speakTestUtterance is now a third, calling synth.cancel() and synth.speak() directly. That seam now has three sites, and a native-speech implementation written against that docstring would leave the test button broken on exactly the host it exists to diagnose. I can push a commit fixing the docstring if you'd like it.
Also minor: the spec says the button "distinguishes four outcomes", and AlarmTestButtons.test.tsx covers three — the partial fan-out branch (Sent to N; M failed.) is the one with no test.
…, a11y - PushSendSummary moves to service-protocol.ts — the module that exists so the two ends of the bridge cannot drift — and push-delivery.ts and host-status-store.ts both import it instead of declaring parallel copies. - speakTestUtterance no longer calls cancel(): the engine queue is shared with real alarms, and cancel() drops queued announcements without a callback, so another Session's alarm could go out silently. A stacked short phrase on a double-press is the smaller problem. - The test buttons' result line announces via role="status"/aria-live. Also document Send test push in the pairing walkthrough (docs/stories/ pairing.mdx), merged up from origin/main. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AKx9GdrDzBZmiXx8v9abRN
dormouse-bot
left a comment
There was a problem hiding this comment.
The three threads from the last pass are all handled — PushSendSummary in service-protocol.ts with both ends importing it, speakTestUtterance no longer reaching for cancel(), and the result line announcing. One new note, inline.
targeted: 0 is the outcome this PR argues hardest is not a failure — alert.md calls it "the ordinary answer on a freshly enrolled machine", the new PushSendSummary docstring repeats it, and the line's own comment says "Not a failure" — yet it is shown with tone: 'bad', which ResultLine renders in text-error (--color-error → terminal.ansiRed in theme.css). So the first press on a correctly-enrolled machine that simply has no phone subscribed yet paints a red line visually identical to the two branches that really did fail, which is the one distinction the four-outcome split exists to make. 'ok' is an awkward name for this case, but it is the tone that renders text-muted — the neutral treatment the case wants; a third 'info' tone is the alternative if the name grates.
targeted: 0 is the expected answer on a freshly enrolled machine — the copy and the spec already say so; now the color agrees. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AKx9GdrDzBZmiXx8v9abRN
An alarm you cannot observe until it fires unattended is one you cannot trust, and that first firing is exactly when being wrong costs the most. Each alarm sink gets a way to try it now.
Both controls sit outside the switch's dimming and stay enabled while the sink is off: checking that the speakers work, or that the phone buzzes, is most useful before committing to the alarm. Each reports its outcome inline and clears it after a few seconds — because for both sinks, a working path and a broken one produce the same observation: silence.
Play test sound
Speaks a fixed phrase through the same sanitizer a real alarm uses, but deliberately not through
speak(). That publishes the transient per-Sessionspeaking/spokenstate that Panes and Doors render, and no Session rang — a test that lit up a pane would be claiming some terminal wants attention.It reports "no speech engine available" rather than degrading silently the way the alarm path correctly does, because a webview with no backend and one with the volume down are indistinguishable otherwise.
Send test push
Goes through the real Host, ACL, and server, so what it proves is what the alarm will do.
That required making the delivery path observable.
sendPushnow returns a summary instead ofvoid, and a newpushTestservice command surfaces failures rather than warning to the console. The ring path's rule that a failed push must never break the alert path is right for an alarm and exactly wrong for a test — it would report success over a fan-out that reached nobody.Four outcomes are distinguished, because they call for different responses:
targeted: 0delivered: 0Hidden entirely where no Host service exists, matching the Remote control section.
API change worth flagging
sendPushreturnsPromise<PushSendSummary>rather thanPromise<void>. The ring path ignores the value, so behavior there is unchanged — but it is a signature change.Verification
typecheck clean; 1689 tests passing;
pnpm lint:specsOK. 11 new tests: 4 at the service level (not-connected, nothing-targeted, a real send asserting ACL-derived recipients and the fixed collapse tag, and a refused send), 7 at the component level covering every outcome above plus the missing-speech-engine and no-Host-service cases.alert.mdupdated with the behavior and the rationale for each rule.🤖 Generated with Claude Code
https://claude.ai/code/session_01GDNJHHA95nvRdo4Cv3rAoi