Skip to content

fix(sounds): keep focus sounds playing off the Pomodoro page - #386

Closed
kYem wants to merge 21 commits into
mainfrom
kes/eng-145-sounds-leaving-the-pomodoro-page-leaves-music-playing-with
Closed

kYem wants to merge 21 commits into
mainfrom
kes/eng-145-sounds-leaving-the-pomodoro-page-leaves-music-playing-with

Conversation

@kYem

@kYem kYem commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Fixes ENG-145

Chosen behaviour: sound follows the timer, not the page. Leaving #pomodoro leaves music playing in both auto-start modes. Auto-start still pauses it when the timer pauses and stops it at idle, wherever you are.

  • Sounds leader election, storage sync and the auto-start effect move from PomodoroTimer to usePomodoroSounds, mounted once in App. Before this, leaving the page also tore down YouTube while the store kept claiming it was playing.
  • NowPlayingPill pauses or resumes sound. It sits in the home nav row and floats bottom-left on Goals, Quotes, Insights and Concepts. Stopping the sound hides it.
  • With leadership now held on every page, a cold new tab would restart playback the last closed tab persisted. A tab that becomes leader when no other tab held the lock now discards that state. Handoffs between live tabs still resume.

kYem added 21 commits October 4, 2026 11:01
…pause control everywhere

Sounds leader election, storage sync and the auto-start effect move from PomodoroTimer to an
app-level usePomodoroSounds hook, so leaving #pomodoro no longer drops YouTube or stops music
mid-session. A NowPlayingPill pauses sound from home and the content pages. The first sounds
leader in a page no other tab was leading discards persisted playback, so a cold new tab
doesn't restart yesterday's playlist.

Fixes ENG-145
…ives

Any page can now hold the audio, but Insights/Quotes/Goals/Concepts load neither store, so the
auto-start effect there never acted. usePomodoroSounds loads both; settings initialize shares an
in-flight call so the page and the hook read once.

Also from review: auto-start acts only when the timer's wanted sound changes (a reload no longer
overrides a user pause), a running break pauses music when it shouldn't play during breaks, a failed
lock probe no longer double-claims leadership, and the pill tracks source switches.
…der election

A sound picked while the timer is idle, or handed over by a closing tab, was stopped at once
because the first observation counted as a change. Only a resume acts on a first look now.

A failed lock probe is logged and treated as a handoff; the no-Web-Locks fallback no longer
catches every error. The pomodoro initialize shares its in-flight call so a failed load toasts
once, and sounds don't follow a timer that failed to load. setIsLeader requires fresh exactly
when gaining leadership.

Tests now mount the app-level hook beside PomodoroTimer for the unmount case, and cover timer
transitions, regained leadership, the probe, and shared initialize.
…lied wish on every pause

A later save or settings-reload failure also sets the pomodoro error, which stopped sounds following
the timer for the rest of the tab; the error is now sampled only as loading ends. Turning music,
auto-start or the source off now resets the applied wish, so a sound picked later while idle isn't
stopped by a stale resume. A failed sounds hydration is logged and cancels the pending discard.

Tests cover the remount probe cache, App mounting the hook and floating the pill, re-initialize
after a settled load, the music-off gate, and the handover restart.
…e freshness by the grant

Off home and Pomodoro nothing ticked the timer, so music following it played past the session end;
usePomodoroLeader now mounts in App. The leader no longer resumes a sound already playing, since
resume() starts ambient locally and the picking tab is already playing it. Freshness now comes
from whether the lock was free when asked (ifAvailable), remembered per page for StrictMode, instead
of a once-per-page query that tabs opened together all answered the same way.

The shared lock-manager mock learns ifAvailable.
…an early failed hydration

The already-playing skip also caught a takeover, where persisted isPlaying most likely describes
ambient that died with the closing tab; a new leader now restarts ambient. A hydration that fails
before this tab leads is remembered, so the discard never waits on a listener nothing cancels.

Comment and type touch-ups from review (LeaderOptions, mock header, pill and first-look wording).
…eview's remaining gaps

A throw while taking the lead released the lock but left isLeader set, so two tabs could both
drive the audio. Tests now pin the YouTube takeover skipping resume, a successful hydration
re-arming the discard, and App's hooks on a content page with clean call history. Comment
wording fixes from review.
Also narrow the failed-hydration comment to the first load, and fit a test doc in the line width.
…sing the first look

Ambient sounds only in the tab that started it, so on a handoff a playing ambient this tab isn't
sounding most likely closed with the old leader. The store now marks it paused, which keeps the UI
honest and lets the timer's ordinary resume restart it whenever a session runs, not only on the
first look after taking over.

Also: reuse the existing SessionType from pomodoro-styles, and tests for the fresh leader's YouTube
init and for discarding an inherited pause.
… it paused on takeover

Ambient plays in the tab that pressed it, leader or not, so silence in the new leader didn't prove
it had died: a live sound in a third tab was paused and silenced. Tabs sounding ambient now hold a
shared Web Lock, and a taking-over leader asks for it with ifAvailable before leading, passing the
answer to setIsLeader so the store updates in the same render the hook first leads in.

Also: the store takes SessionType from pomodoro-styles, an already-pending discard isn't queued
twice, comment touch-ups, and a test running the real store through the hook.
…tween tests

Comment touch-ups on the ambient-sounding lock and LeaderOptions.
…ent sounds when unsure

A finished test's lock hold let go into the shared mock, so the throw test passed without its fix.
Comment touch-ups on the lock mock's shared mode and the stale-playback discard.
@kYem

kYem commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #390: sound now stops on leaving the Pomodoro page instead of following the timer.

@kYem kYem closed this Oct 5, 2026
@kYem
kYem deleted the kes/eng-145-sounds-leaving-the-pomodoro-page-leaves-music-playing-with branch October 5, 2026 02:29
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