Exercise UX fixes (issue #2885 items 4, audio freeze, page update)#2889
Merged
Exercise UX fixes (issue #2885 items 4, audio freeze, page update)#2889
Conversation
Addresses item 4 of issue #2885: users reported the implicit jump from Repeat (Interact) into Solve (Task) was confusing and hid the moment when the step actually changed. The Listen → Interact auto-transition is kept. Solve is now entered only when the user clicks the Solve button; it lights up in the "next" style as soon as every option has been heard, and a title tooltip hints that the step can be started when ready. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Frontend test coverage: 71.55% (+0.13% compared to 71.42% on base) |
Addresses the audio bug in issue #2885: users reported that after 3+ words, audio "freezes" until the mouse moves, and individual words can go silent. Root cause: the playback loop used a wall-clock setTimeout to wait for each word to finish. Browsers (Safari, and Chrome under timer throttling) suspend idle AudioContexts. When that happens, source.start(0) queues playback but never produces sound until a user gesture resumes the context — yet the wall-clock timer keeps firing, so the loop advances right over the silent clips. Fix: - Resume a suspended AudioContext before starting each native source, so queued playback does not pile up. - Await the AudioBufferSourceNode onended event (with duration+1s as a safety fallback) instead of trusting setTimeout to match real playback duration. The Tone-based path (signal exercises, no onended) still uses timeout; this path was never reported as affected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses the "fix update page after task done" item of issue #2885: after completing an exercise, the green check disappeared if the user navigated away and came back. Root cause: enableNextExercise only mutated isManuallyCompleted on the in-memory record. tasksManager.completedExerciseIds was the durable source used by the subgroup's exerciseAvailabilityCalculationTask, but it was populated solely from loadTodayCompletedExercises on app boot and login — never when the user finished an exercise. Fix: add the just-completed exercise's id to completedExerciseIds as part of enableNextExercise. A fresh Set is assigned so @Tracked picks up the change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Frontend test coverage: 71.34% (-0.17% compared to 71.51% on base) |
Tests cover the three behavior changes in the previous commits: - enableNextExercise: marks current completed, enables next sibling, adds id to tasksManager.completedExerciseIds (new Set, coerced to string), preserves prior ids, skips update when id is null. - playTask: awaits the source onended event for AudioBufferSourceNode (so it advances in real time, not on wall-clock); falls back to duration+1s timeout when onended never fires. - ExerciseSteps: Solve button gets the "next" style when @interactReady is true during Interact, stays default otherwise, exposes the ready-when-you-are hint via title attribute. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- audio.ts: wrap source.start(0) in try/catch so a synchronous throw (closed context, re-started source) bails out of the word-loop instead of stranding it in the duration+1s safety net. - audio-test.js: replace real-AudioContext probes with prototype-injected fake sources to avoid CI flake on headless Chrome suspended contexts. Tighten fallback lower bound from >=1000ms to >=1050ms so the extra second of margin is actually pinned. Add a test for the sync-start error path. - heard-words-test.js: remove the tautological exerciseSequenceTask module — it re-defined and asserted the same fixture; the real body is already covered in component-test.js:272-347. - exercise-test.js: combine null and undefined into one id-guard test so `!= null` semantics are pinned, not the accidental choice of one literal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Solve-NEXT test: walk through LISTEN first via a @Tracked state so setLastMode populates `modes` with both LISTEN and INTERACT. Without this, modeForTask stays DISABLED and taskBtnClass returns STATE_LOCKED regardless of interactReady. - Title test: match the repo's existing ember-intl test pattern — setupIntl in this env returns the `t:<key>` placeholder when translations aren't loaded (see doctor-feedback/component-test.gjs). Verified locally: tests 50-53 in the exercise-steps module all pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Frontend test coverage: 72.47% (+1.00% compared to 71.47% on base) |
|
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.



Summary
Addresses the exercise-screen items from issue #2885 that weren't in the landing PR #2832.
1. Remove Interact→Solve auto-transition (item 4)
exerciseSequenceTaskstops after INTERACT; Solve is entered manually.allOptionsHeard).2. Fix silent words and mid-task audio freeze
User report: after 3+ words, audio "freezes" until the mouse moves; individual words can also come out silent.
Root cause in
services/audio.tsplayTask: the playback loop used a wall-clocksetTimeoutto wait for each word. Browsers (Safari, and Chrome under timer throttling) suspend idle AudioContexts. When that happens,source.start(0)queues playback instead of emitting sound, yet the wall-clock timer keeps firing — so the loop advances right over silent clips and only the user's mouse gesture resumes the context.Fix:
AudioBufferSourceNode.onendedevent (withduration + 1ssafety timeout) instead of trusting setTimeout to match real playback.onended) still use the timeout — that path was never reported as affected.3. Persist exercise completion across navigation
User report: the green check disappears on the next visit to a subgroup after completing an exercise.
Root cause in
controllers/group/series/subgroup/exercise.tsenableNextExercise: only mutatedisManuallyCompletedon the in-memory record.tasksManager.completedExerciseIds— the durable source used by the subgroup's availability calc task — was populated solely fromloadTodayCompletedExerciseson app boot / login and never on in-session completion.Fix: add the just-completed exercise id to
completedExerciseIds(new Set, so@trackedpicks it up) as part ofenableNextExercise.Files
app/components/task-player/index.gts— drop trailingsetMode(MODES.TASK); pass@interactReady={{this.allOptionsHeard}}to ExerciseSteps.app/components/exercise-steps/index.gts— accept@interactReady, style Solve as "next", addtitlefor the hint.app/services/audio.ts— resume AudioContext if suspended; awaitonendedfor native sources.app/controllers/group/series/subgroup/exercise.ts— add completed id totasksManager.completedExerciseIds.translations/en-us.yaml,translations/ru-ru.yaml— addcontrol_exercises.solve_hint.tests/unit/components/task-player/heard-words-test.js— update to assert listen→interact only (no TASK).Still out of scope
Test plan
pnpm test— unit tests fortask-playerstill green.🤖 Generated with Claude Code