resolveVolume previously clamped any value >=1 to full blast. If a
future settings change ever leaks a 0–100 scale through (instead of
0–1), the user gets jump-scared at max volume. Treat any v>1 as a
scale-drift bug and play at DEFAULT_VOLUME instead.
Also reject NaN / Infinity explicitly rather than relying on the
<=0 branch catching them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hands-off feedback for recording lifecycle: a short C5 pitch at
record start, a falling G4 at record stop, and a staggered C5–E5–G5
major third when the finalise flow completes. Synthesised at runtime
via the Web Audio API (OscillatorNode + linear-ramped GainNode
envelope) rather than shipping WAV assets — keeps the binary size
flat and lets us tweak timbre without touching bundled files.
Off by default. Settings → Output exposes the toggle with a volume
slider (0–100%, default 15%) and a "Test" button that plays the
completion cue so the user can confirm loudness without recording.
Hooked at three call sites in DictationPage:
- playStartCue after page.recording = true in startRecording
- playStopCue at the top of stopRecording
- playCompleteCue just before `saved = true` at the end of
finaliseTranscription's transcript-present branch
All three no-op when settings.soundCues is false. The Web Audio
context is lazily constructed on first cue (most browsers suspend
it until a user gesture — Tauri's webview inherits that). If the
AudioContext can't be built we silently drop the cue rather than
throwing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>