Skip to content

R4b: tone-swap feedback sounds — one live element, src swap between tones#49

Merged
adyz merged 3 commits into
masterfrom
r4b-handoff-carry
Jul 4, 2026
Merged

R4b: tone-swap feedback sounds — one live element, src swap between tones#49
adyz merged 3 commits into
masterfrom
r4b-handoff-carry

Conversation

@adyz

@adyz adyz commented Jul 4, 2026

Copy link
Copy Markdown
Owner

The lock-screen requirement phase (plan.md, R4b): the error sound must be audible from the first time, even after play→immediate-lock→wifi-off.

Final design (Adrian's call: "folosim doar 2"): tone-swap is THE mechanism, not a fallback chain.

  • At most one feedback element is live at a time. Changing tones (loading ↔ error, both directions) swaps the src of the element that's already playing — the one continuation backgrounded iOS permits (verified on device in PR Handoff real între sunete: cel vechi cântă până când cel nou chiar se aude #41 and re-verified today). Gapless by construction.
  • A fresh element start happens only from silence (first play, station change while idle) — always foreground/gesture contexts, where iOS allows it.
  • A denied swap restores the element's own tone — never trade something audible for silence.
  • Gesture reconcile: every user tap restarts a desired-but-silent sound inside the gesture call stack (fixes the wasted-gesture repro: unlock + next/prev revives the sound; stop+play is no longer the only way out).

What was deliberately NOT built: the PR #41 fallback chain (try fresh start + deferred stop + carry escalation + settlers). It existed only to serve mechanism 1, which iOS denies in exactly the cases that matter.

Tests — essence only

  • 7 scenario-named unit tests: fresh start from silence, tone swap (other element never starts), reclaim on switch-back, denied-swap revert, user stop silences the carrier, gesture revive, supervisor-vs-pending-blob regression guard. They test OUR bookkeeping (which we can break in refactors), not iOS (which only the phone can).
  • No white-box iOS-simulation e2e — the acceptance instrument for the iOS zone is the device checklist below.
  • Two existing e2e assertions adapted to the design (handoff timing; "a feedback sound is on" instead of an element id). 37/37 e2e, 78/78 unit, clean typecheck.

⚠️ Merge gate: device re-pass (the earlier pass validated the pre-simplification variant)

All on iPhone, wifi off at the right moment:

  1. play → lock immediately → wifi off → error tone audible from the first time
  2. from a silent state: unlock + next/prev revives the sound
  3. stop + play still works
  4. the normal flow (error heard with app open, then lock) stays intact

🤖 Generated with Claude Code

…e reconcile

Ports PR #41's iPhone-verified protocol into the modular codebase, plus
the new gesture-reconcile rule from Adrian's 2026-07-04 repro. The machine
is untouched — all semantics live in soundEffects.ts behind the same
play/stop/ensure contract, with the partner pairing wired in main.ts.

Protocol (see the comment atop soundEffects.ts):
- deferred stop: the OLD sound keeps playing until the NEW one actually
  produces audio ('playing' event) — no silent gap ever opens (the gap is
  where locked iOS kills the session and denies the pending start).
- carry: if the new sound's start keeps being denied while the old one is
  audible, the old ELEMENT carries the new tone (src swap — the one start
  backgrounded iOS allows). One attempt per cycle; a denied carry restores
  the own sound — never trade audible for silent.
- reclaim: play()/stop() on a carrying element restore its own sound.
- gesture reconcile (NEW vs PR #41): warmUp() no longer early-returns on
  the intent flag — a desired-but-silent sound restarts INSIDE the user's
  gesture call stack. This fixes the wasted-gesture repro: unlock + next/
  prev now revive the sound instead of requiring stop+play.

Tests:
- 11 new unit tests (handoff, carry, reclaim, gesture reconcile) on the
  fake-element harness; 85/85 unit total.
- 2 new e2e in the white-box iOS-denial style: the carry outcome (ported
  from PR #41) and the historic all-silent state revived by a single next
  click. 39/39 e2e.
- One existing e2e assertion adapted exactly as PR #41 documented: the
  instant loadingNoise.paused check becomes waiting for the handoff to
  complete — the test's intent (only the error sound ends up audible) is
  unchanged.

NEEDS DEVICE VALIDATION (plan.md R4b acceptance criteria, all wifi-off):
1. play -> lock immediately -> error tone audible FROM THE FIRST TIME
2. from the silent state: unlock + next/prev revives the sound
3. stop + play still works
4. the normal flow (error heard with app open, then lock) stays intact

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
radio Ready Ready Preview, Comment Jul 4, 2026 6:44am

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

CI Summary

Check Status Result
Typecheck PASS tsc --noEmit clean
Unit tests PASS 78/78 passed; Coverage: Lines 100%, Branches 96.20%, Functions 97.14%, Statements 100%
Build PASS Build completed
Playwright browser PASS Chromium installed
E2E tests PASS 37/37 passed

…twork is the known OS-fetch limitation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…allback

"Folosim doar 2." At most one feedback element is live at a time; changing
tones (loading <-> error, both directions) swaps the src of the element
that is already playing — the one continuation backgrounded iOS permits,
gapless by construction. Fresh element starts happen only from silence
(first play, station change while idle — foreground/gesture contexts).

Deleted with mechanism 1: deferred stop, start settlers, startPending,
carry-once bookkeeping. Kept: revert-to-own-tone when a swap is denied
(never trade audible for silent) and gesture reconcile (a tap restarts a
desired-but-silent sound inside the gesture stack).

Tests cut to the essence Adrian asked for:
- 7 scenario-named unit tests (fresh start, swap, reclaim, denied-swap
  revert, user stop silences the carrier, gesture revive, and the
  supervisor-vs-pending-blob regression guard).
- The two white-box iOS-simulation e2e are gone — the acceptance
  instrument for the iOS zone is the device checklist in plan.md.
- One always-audible assertion now checks "a feedback sound is on"
  instead of a specific element id (mid-playback the error tone sounds
  through the carrying element — that IS the design now).

78/78 unit, 37/37 e2e, typecheck clean.

NEEDS A DEVICE RE-PASS: the 4 criteria in plan.md (the earlier pass
validated the pre-simplification variant).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@adyz adyz changed the title R4b: device-verified sound handoff — deferred stop, carry, gesture reconcile R4b: tone-swap feedback sounds — one live element, src swap between tones Jul 4, 2026
@adyz adyz merged commit 64fe6b7 into master Jul 4, 2026
3 checks passed
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.

1 participant