R5 + R6 + zombie-tone fix — everything left from the review, one PR#50
Open
adyz wants to merge 3 commits into
Open
R5 + R6 + zombie-tone fix — everything left from the review, one PR#50adyz wants to merge 3 commits into
adyz wants to merge 3 commits into
Conversation
…te, title) From the post-review refactor plan (plan.md, R5) — the battery-drain finding. Old e2e untouched and green (37/37); 79/79 unit. radioMachine: the error state's wait-for-recovery loop moves into substates (backoff -> offlineRecheck). Entry fx and the sound supervisor run ONCE per error cycle; an offline recheck only re-arms its own child timer (reenter on the atomic child leaves the parent alone). Before, every 10s tick re-entered 'error' and re-ran the whole entry pipeline — new MediaMetadata (lock-screen artwork refetch + widget flicker), 6 action handlers re-registered, poster img.src reassigned, supervisor interval torn down and recreated — all night, for as long as the phone sat offline. The offlineRecheck context flag and its two actions are gone; the cadence and backoff values are unchanged (the old cadence unit tests pass as-is). mediaSession: updateMediaSession memoizes the presentation (MediaMetadata, poster img.src, document.title, loadingMsg) on (state, station title) — identical consecutive renders are skipped and cloudinaryImageUrl is computed once instead of twice. Deliberately NOT memoized: action-handler re-registration and playbackState — iOS resets those out from under us (d798cc9), so they re-assert on every call, exactly as before. New unit test pins the phase's point: 50 offline rechecks produce zero new side-effect calls (mediaSession/sounds/buttons/supervisor), and the loop still recovers on the first online check. NEEDS DEVICE SMOKE before merge: phone offline in error 5+ min, locked — error tone keeps playing, Now Playing widget no longer flickers every 10s; wifi back on -> recovers to playing by itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
CI Summary
|
…ter stop Device-observed on master (R4b): sometimes the loading tone played UNDER the live radio — the exact overlapping-sounds class the state machine exists to forbid. Root cause: carrySound()'s never-trade-audible-for- silent revert ran in a .catch with NO generation guard. When the swap's play() settled late (iOS latency) and the machine had meanwhile recovered to 'playing' (stop already ran: paused, src cleared, isPlaying=false), the catch reverted the src and RESTARTED the element — and nothing could stop it again, because isPlaying was already false so every later stop() skipped it. The revert now runs only while still wanted: same generation and not an AbortError (our own stop/reclaim interrupting the pending play). This was the only unguarded resurrect path in the file — every other catch already checks the generation. New unit test pins the repro: stop lands while the swap's play() is in flight; the late rejection must leave the element dead (no revert, no restart). 80/80 unit, 37/37 e2e. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
Author
|
Also carries the zombie-tone fix (c5e6bc8): a late-settling carry swap rejection used to revert-and-restart the element after |
…-blocking SW writes Same PR as R5 + the zombie fix, per Adrian (one PR for everything left). - main.ts: the ~22 status/station image fetches no longer compete with the stream connection and sound-blob preloads at startup — deferred to requestIdleCallback (setTimeout fallback for Safari). The precache LIST stays complete, deliberately: the untouched e2e (and the product) pin all-station posters working offline; only the timing moves. - sw.js: the app-shell cache.put leaves the response path (event.waitUntil, same pattern as the cloudinary branch) — pages get first bytes without waiting for a body download + disk write; /downloads/ (the 2.4MB APK) is streamed straight through instead of landing in the app cache; APP_CACHE bumped to v4 so existing installs evict the cached APK. - plan.md: R5/R6 statuses + the deliberate deviations from the initial R6 sketch (full list kept, page-level put kept — first-load SW claim race). 80/80 unit, 37/37 e2e (image tests untouched and green), typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Per Adrian: one PR for everything remaining from the post-review refactor plan. Three things, three commits:
1. R5 — offline rechecks re-run no effects; presentation memoized
errorstate's recovery wait loop moves into substates (backoff→offlineRecheck): entry fx and the sound supervisor run once per error cycle; an offline recheck only re-arms its own child timer. Before: a phone sitting offline re-enterederrorevery 10s and re-ran the whole entry pipeline (MediaMetadata rebuild → artwork refetch + widget flicker, 6 handlers re-registered, posterimg.srcreassigned, supervisor torn down/recreated) — all night. Cadence/backoff values unchanged; theofflineRecheckcontext flag is gone.updateMediaSessionmemoizes the presentation (metadata, poster, title) on (state, station title); handler re-registration andplaybackStatestay unmemoized (iOS resets them — d798cc9).2. Zombie-tone fix (device-observed on master, born in R4b)
Sometimes the loading tone played under the live radio.
carrySound()'s never-trade-audible-for-silent revert ran in a.catchwith no generation guard: a late-settling swap rejection afterstop()reverted the src and restarted the element — unstoppable, sinceisPlayingwas already false and later stops skipped it. The revert now runs only while still wanted (same generation, not AbortError). Regression test included.3. R6 — startup off the critical path
requestIdleCallback(Safari fallback: 3s) — no longer competing with the stream connect + sound preloads. The precache list stays complete, deliberately: the untouched e2e (and the product) pin all-station posters working offline.sw.js: app-shellcache.putleaves the response path (event.waitUntil, like the cloudinary branch);/downloads/(2.4MB APK) streams straight through instead of landing in the app cache;APP_CACHE→ v4 so existing installs evict the APK.Verification
80/80 unit, 37/37 e2e untouched, clean typecheck.
Device smoke: (a) phone locked offline in error 5+ min → error tone keeps playing, Now Playing widget no longer flickers every 10s; wifi on → recovers alone; (b) a few wifi off/on cycles during playback → no tone ever left under the stream (the zombie); (c) quick lock-screen prev/next sanity.
🤖 Generated with Claude Code