Skip to content

Study-mode + a11y follow-up fixes (unscored study, focus-gating, play() guard) - #19

Merged
OmikronApex merged 5 commits into
mainfrom
fix/study-focus-and-followups
Jul 8, 2026
Merged

Study-mode + a11y follow-up fixes (unscored study, focus-gating, play() guard)#19
OmikronApex merged 5 commits into
mainfrom
fix/study-focus-and-followups

Conversation

@gionnibgud

@gionnibgud gionnibgud commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Study-mode + a11y follow-up fixes

Post-merge follow-ups to the staffview stack (#8#16). Two commits, both off
current main. Several review items from the first round were already fixed
upstream (zoom-clamp 9497872, velocity gain 70889a9, counter-rollback
fc36b9c) — this PR only carries what's still missing.

Commit 1 — fix(study,a11y)

  • Focus-gate the study audio path — the study preroll/gate-pause loop in
    draw() and the seeked handler now run only when _svIsFocused. The shared
    #audio element is driven by every instance's draw() loop, so in
    split-screen two Study panels were fighting over one track. Single-panel is
    unaffected (_svIsFocused is always true there).
  • Guard audio.play() promise rejection in _svStudyAdvance and the
    preroll onDone — a rejected play() (autoplay policy / decode) no longer
    leaves playback stuck with _svPrerollResuming pinned true.
  • Volume slider a11yaria-label/title so it isn't announced as an
    unlabeled slider.

Commit 2 — fix(study): study mode is unscored practice

Why scoring doesn't make sense in study: study is note-by-note guided
practice — a gate only advances once you play it correctly, so every run is
necessarily ~100%. Feeding that into the scoreboard meant:

  • the per-hand line read a meaningless 100% (and a corrupted value after a
    study seek-back, because the free-play sweep/_svHandleSeek rollback was
    still running under study's gate accounting), and
  • study runs were emitted as fake ~100% "performances" into core's live HUD and
    song_stats / the dashboard.

So study is now fully decoupled from free-play scoring:

  • _svStudyHandleNoteOn no longer mutates the free-play tallies, claims notes
    in the sweep sets, or emits note:hit/note:miss — it only advances the
    gate, reports to the note-detection domain (observability, kept), and
    tracks wrong-note X marks.
  • _svSweepMisses (and its _svHandleSeek rollback) is guarded out of study.
  • _svEmitNoteResult is guarded in study (belt-and-suspenders).
  • Red free-play miss-dots are suppressed in study — only study's orange X marks
    show.
  • Core's #v3-live-performance-hud is hidden in study via a new
    staffview-study-active class + one CSS rule (mirrors the existing
    notation-active plumbing); the per-hand line is guarded off too.

Net: free-play counters stay frozen and aligned with core across
study↔free-play transitions — fixing the "100% on a hand I didn't reach"
divergence at the root rather than patching the display.

@byrongamatos — commit 2 changes how study integrates with core (study was
routed through core note-detection by team direction, and you wrote the gate
refactor + fc36b9c). Study still reports to the note-detection domain; it
just no longer scores. Flagging for your call on whether unscored-study is the
behaviour we want.

Verification

  • Deployed to the local docker stack; verified on the v3 UI with a MIDI
    keyboard: study gate-pause/preroll/X-marks work, core HUD hides in study and
    returns on exit, study + seek-back no longer shows bogus per-hand, and a
    free-play run after study scores cleanly (no leaked study counts).
  • npm test green (104/104).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Enhanced the dedicated study mode experience with consistent live HUD visibility and more reliable study UI/state behavior.
  • Bug Fixes
    • Made study mode unscored practice: it no longer impacts free-play stats/miss tracking or sends note hit/miss telemetry.
    • Improved split-screen focus safety so seeking and study activation only apply to the focused instance.
    • Hardened study resume/preroll audio by safely handling playback promise failures.
  • Accessibility
    • Added title/ARIA labels to the monitor synth volume range control.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 627c4456-e668-4f6e-ab0e-2dfea578c81e

📥 Commits

Reviewing files that changed from the base of the PR and between fff2ffb and 57a0b1d.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • package.json
  • plugin.json
  • screen.js
✅ Files skipped from review due to trivial changes (3)
  • package.json
  • plugin.json
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • screen.js

📝 Walkthrough

Walkthrough

Study mode now tracks active instances, hides Core’s live HUD while studying, blocks free-play scoring side effects, and adds split-screen focus/audio guards. The release also updates accessibility attributes, changelog notes, and version numbers.

Changes

Study Mode Isolation

Layer / File(s) Summary
Study-mode state tracking and Core HUD hiding
screen.js
A module-level Set tracks active study instances, study-mode CSS hides Core’s live-performance HUD, and _svSetStudyActive(instance, on) is wired into activation, deactivation, and teardown.
Free-play scoring and telemetry suppression
screen.js
Study mode now skips Core note-result emission, score-badge accuracy, miss-dot drawing, and miss sweeping, while study note handling stays on the note-detection path.
Splitscreen focus guards and audio.play() handling
screen.js
Focus loss clears stale pause tracking, study gate re-homing runs on refocus, seek events are ignored when unfocused, study preroll/control logic is focus-gated, and rejected audio.play() promises reset preroll state.
Accessibility and release metadata
screen.js, CHANGELOG.md, package.json, plugin.json
Adds volume-slider accessibility labels, updates the 0.3.0 changelog entry, and bumps package/plugin versions to 0.3.0.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: byrongamatos

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main follow-up fixes: study-mode behavior, focus gating, play() handling, and accessibility.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/study-focus-and-followups

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
screen.js (1)

4472-4502: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Reset _svWasAudioPaused when focus is lost

_svUpdateFocusState() stops updating _svWasAudioPaused while a panel is unfocused, so the next focused frame can misread the shared audio’s current play state as a stale pause→play edge and trigger an unnecessary pause/count-in. Clear _svWasAudioPaused in the focus-loss branch so the first focused frame re-observes the current state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@screen.js` around lines 4472 - 4502, Reset the shared audio state tracker
when focus is lost so stale state does not trigger a false pause→play transition
on refocus. Update the focus-loss branch in _svUpdateFocusState to clear
_svWasAudioPaused, ensuring the next focused draw() frame re-observes the
current audio.paused value before any preroll logic runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@screen.js`:
- Around line 4472-4502: Reset the shared audio state tracker when focus is lost
so stale state does not trigger a false pause→play transition on refocus. Update
the focus-loss branch in _svUpdateFocusState to clear _svWasAudioPaused,
ensuring the next focused draw() frame re-observes the current audio.paused
value before any preroll logic runs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 69536eca-8c12-4841-b533-06073d0e69e8

📥 Commits

Reviewing files that changed from the base of the PR and between 1151657 and a51fac6.

📒 Files selected for processing (1)
  • screen.js

gionnibgud and others added 3 commits July 8, 2026 13:31
…l volume slider

- Study mode's preroll/gate-pause loop and its `seeked` handler now run only
  when `_svIsFocused`, so in split-screen two Study panels stop fighting over
  the shared `#audio` element (single-panel is unaffected — always focused).
- `audio.play()` in `_svStudyAdvance` and the preroll `onDone` now catch a
  rejected play() promise (autoplay policy / decode error) and reset
  `_svPrerollResuming`, instead of leaving playback silently stuck.
- The monitor-synth volume slider gets an `aria-label`/`title` so screen
  readers no longer announce it as an unlabeled slider.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: gionnibgud <gionnibgud@gmail.com>
… scoreboard

Study is note-by-note guided practice: a gate only advances once played
correctly, so a run is always ~100%. Feeding that into the scoreboard made the
per-hand line read a meaningless (and, after a study seek-back, corrupted) 100%,
and pushed fake ~100% "performances" into core's live HUD and song_stats.

Decouple study from free-play scoring entirely:
- `_svStudyHandleNoteOn` no longer mutates the free-play tallies
  (`_svHits`/`_svMisses`/per-hand), claims notes in the sweep sets, or emits
  `note:hit`/`note:miss`. It only advances the gate, reports to the
  note-detection domain (observability), and tracks wrong-note X marks.
- Guard `_svSweepMisses` out of study — the free-play time-sweep and its
  `_svHandleSeek` rollback must not run there (that was corrupting the tally on
  a study seek-back).
- Guard `_svEmitNoteResult` in study (belt-and-suspenders) so nothing reaches
  core's scoring bus / dashboard.
- Suppress the red free-play miss-dots in study; the surface shows only study's
  own orange wrong-note X marks.
- Hide core's `#v3-live-performance-hud` while study is active via a new
  `staffview-study-active` class + CSS rule (mirrors the notation-active
  plumbing), and guard the per-hand line off too.

Net: free-play counters stay frozen and aligned with core across study↔free-play
transitions, fixing the "100% on a hand I didn't reach" divergence at the root.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: gionnibgud <gionnibgud@gmail.com>
…reroll)

CodeRabbit review on #19: the study preroll/gate-pause block in draw() is
gated on _svIsFocused, so an unfocused instance's _svWasAudioPaused freezes.
If the shared audio's play state actually changes while unfocused, the stale
value can misread as a pause->play edge on refocus and fire a false preroll
mid-playback. Null it in the focus-loss branch of _svUpdateFocusState so the
first refocused frame just re-observes current state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: gionnibgud <gionnibgud@gmail.com>
@gionnibgud
gionnibgud force-pushed the fix/study-focus-and-followups branch from a51fac6 to 234906c Compare July 8, 2026 11:32
Adds a Fixed section for this branch's three commits: unscored-practice
decoupling (+ the free-play/study sweep collision it fixes), split-screen
study focus-gating (+ the stale pause/play edge reset), and the play()
promise-rejection guard / volume-slider a11y label.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: gionnibgud <gionnibgud@gmail.com>
@OmikronApex

Copy link
Copy Markdown
Contributor

Code review

Solid PR — both commits verified against the branch code.

Verified correct:

  • Study state is per-instance (closure) with the module-level ref-counted _svStudyShowing set — and the teardown path calls _svSetStudyActive(instance, false), so no leaked HUD-hide class on destroy mid-study.
  • _svSweepMisses guard: on study exit the sweep's own >0.5s jump detector re-syncs without false-marking, so no stale-index corruption.
  • _svWasAudioPaused = null on focus loss correctly kills the false-preroll edge on refocus.
  • play().catch guards are right — a sync try/catch alone misses the promise rejection; the _svPrerollResuming unstick works in both sites.
  • Non-v3: _svSetStudyActive on-path early-returns before adding to the set; the off-path delete is harmless.

Findings:

  1. Stale gate index on refocus (split-screen edge case) — the study seeked handler early-returns when !_svIsFocused, and the focus-gain branch of _svUpdateFocusState re-homes nothing. Sequence: study panel loses focus → user scrubs → refocus. _svWasAudioPaused is correctly nulled, but _svStudyGateIdx still points at the pre-seek gate — audio either plays uncontrolled past every gate until the stale gateTime, or pauses instantly at the wrong spot. Fix: on focus gain with _svStudyMode, re-home the gate the same way the seek handler does (the loop is already duplicated in _svStudyActivate — worth extracting).

  2. Stale comment — the _svStudyHandleNoteOn header still says wrong notes "count as a miss" and "Feeds the same core stats / note-detection channels as free-play judging." Commit 2 removed exactly that.

Fixes for both incoming on this branch.

🤖 Generated with Claude Code

Review follow-ups on this branch:
- Extract _svStudyRehomeGate and call it on focus gain — a seek while
  unfocused is ignored by the study seeked handler, leaving a stale
  _svStudyGateIdx on refocus (audio ran uncontrolled past gates or
  paused at the wrong spot).
- Fix stale _svStudyHandleNoteOn header comment still claiming study
  feeds core stats.
- Bump 0.2.0 -> 0.3.0 (unreleased section adds features: study mode,
  detection controls); date the changelog entry, align its study-mode
  Added text with unscored study.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@OmikronApex
OmikronApex merged commit 58ca2ae into main Jul 8, 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.

2 participants