Skip to content

Add "Play test sound" and "Send test push" to the alarm settings - #424

Open
nedtwigg wants to merge 4 commits into
mainfrom
alarm-test-buttons
Open

Add "Play test sound" and "Send test push" to the alarm settings#424
nedtwigg wants to merge 4 commits into
mainfrom
alarm-test-buttons

Conversation

@nedtwigg

Copy link
Copy Markdown
Member

Stacked on #423 — review that one first. The base is settings-remote-control, so this diff shows only the alarm-test work. It will retarget to main once #423 merges.

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-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 "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. 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, because they call for different responses:

Outcome Reads as
targeted: 0 No paired phone has enabled alerts yet — not a failure; the ordinary answer on a freshly enrolled machine
delivered: 0 No device accepted the push
partial Sent to N, M failed
success Sent to N devices

Hidden entirely where no Host service exists, matching the Remote control section.

API change worth flagging

sendPush returns Promise<PushSendSummary> rather than Promise<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:specs OK. 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.md updated with the behavior and the rationale for each rule.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GDNJHHA95nvRdo4Cv3rAoi

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
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 20, 2026

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 46e2c4b
Status: ✅  Deploy successful!
Preview URL: https://8ca08330.mouseterm.pages.dev
Branch Preview URL: https://alarm-test-buttons.mouseterm.pages.dev

View logs

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread lib/src/lib/alert-speech.ts Outdated
Comment thread lib/src/remote/host/host-status-store.ts Outdated
Comment thread lib/src/components/AlarmTestButtons.tsx Outdated
Base automatically changed from settings-remote-control to main August 25, 2026 23:30
nedtwigg and others added 2 commits August 25, 2026 16:51
…, 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 dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-errorterminal.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.

Comment thread lib/src/components/AlarmTestButtons.tsx Outdated
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants