feat(coaching): practice/coaching plugin suite (5 additive plugins) - #1051
feat(coaching): practice/coaching plugin suite (5 additive plugins)#1051ahonnecke wants to merge 6 commits into
Conversation
…ock, mute_master, pitch_match, practice_journal) Five self-contained, additive plugins ported onto feedBack's own contracts (window.feedBack bus + the native minigames framework — gamekit was NOT ported, it's redundant with the minigames plugin): - coaching — real-time coaching feedback + skill-drill panel - metronome_lock — timing-lock minigame - mute_master — muting/dynamics minigame - pitch_match — intonation minigame - practice_journal— per-song practice metrics dashboard (has routes.py) All 5 register cleanly on current upstream (backend + practice_journal routes load with no errors); coaching's suite passes 66/66 node tests. New plugin dirs only — no core changes, so it can't conflict with core churn. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an event-driven coaching pipeline with optional Anthropic summaries, multi-hotspot drill sessions, three skill-drill plugins, Metronome Lock debounce changes, and a Practice Journal that records sessions and displays aggregate and per-song statistics. ChangesCoaching feedback pipeline
Pitch-based skill drills
Practice Journal
Estimated code review effort: 5 (Critical) | ~120 minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (6)
plugins/coaching/test/coach.test.js (1)
100-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTautological override assertion — the override value equals the default.
COACH_MODELisclaude-haiku-4-5, so this passes even ifopts.modelwere ignored. Same issue at lines 190-191, wheresetModel('claude-haiku-4-5')is indistinguishable from a no-op. Use a distinct string.💚 Proposed fix
- assert.equal(buildCoachRequest(sampleFeedback(), { model: 'claude-haiku-4-5' }).model, 'claude-haiku-4-5'); + assert.equal(buildCoachRequest(sampleFeedback(), { model: 'test-model-override' }).model, 'test-model-override');🤖 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 `@plugins/coaching/test/coach.test.js` around lines 100 - 101, Update the model override assertions in buildCoachRequest and setModel tests to use a model string distinct from the COACH_MODEL default, so they fail when the override is ignored. Keep asserting that the returned/requested model equals the distinct override value.plugins/coaching/tools/headless-emit-check.js (1)
1-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHeader comment contradicts the implementation;
SONG/ARRare dead config.The script never loads a song or plays anything — it dispatches synthetic
notedetect:*events (as the line 19-20 comment correctly states).SONGandARRare parsed and never used, and thewaitForFunctiononwindow.playSong(line 31) is likewise vestigial. Trim the unused env plumbing and fix the header so the tool's contract is unambiguous.🤖 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 `@plugins/coaching/tools/headless-emit-check.js` around lines 1 - 16, The header comment for the headless check incorrectly claims it loads and plays a real song; update it to describe the synthetic notedetect event flow. Remove the unused SONG and ARR environment parsing, and remove the vestigial waitForFunction targeting window.playSong while preserving the actual Playwright check behavior.plugins/coaching/README.md (1)
14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd a language to the fenced block (MD040).
📝 Proposed fix
-``` +```text notedetect:hit/miss ──┐🤖 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 `@plugins/coaching/README.md` at line 14, Add the text language identifier to the fenced code block containing the notedetect example in the coaching README, changing the opening fence to use text while preserving the example content.Source: Linters/SAST tools
plugins/coaching/coaching.js (1)
1516-1519: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRename the local
settingsin_gatherSessionMeta— it shadows the module-scope settings object.The outer
settings(line 1366) is the localStorage-backed coach settings; the local here is note_detect's diagnostic settings. Same identifier, different meaning, in nested scope.♻️ Suggested rename
function _gatherSessionMeta(agg) { const nd = window.noteDetect; - let settings = null, song = agg.song || null, arrangement = agg.arrangement || null; + let ndSettings = null, song = agg.song || null, arrangement = agg.arrangement || null; let recording = null, tuning = agg.tuning || null, capo = agg.capo; try { if (nd && nd.getDiagnostic) { const d = nd.getDiagnostic() || {}; - settings = d.settings || null; + ndSettings = d.settings || null;…and update the returned object at line 1533 to
settings: ndSettings.🤖 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 `@plugins/coaching/coaching.js` around lines 1516 - 1519, Rename the local settings variable in _gatherSessionMeta to ndSettings to avoid shadowing the module-scope settings object, and update all references within the function, including the returned object’s settings property, to use ndSettings.plugins/practice_journal/screen.js (1)
106-108: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
beforeunloadhandler usesfetch, which can be cancelled mid-flight.Browsers may abort in-flight
fetchrequests during page unload, so the last session on tab-close can silently fail to record.navigator.sendBeaconis the standard mechanism for reliable unload-time requests.🤖 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 `@plugins/practice_journal/screen.js` around lines 106 - 108, Update the beforeunload flow around _pjEndSession to use navigator.sendBeacon for the final session-recording request instead of fetch, preserving the existing endpoint and payload and ensuring the unload handler remains non-blocking.plugins/practice_journal/routes.py (1)
70-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse timezone-aware UTC timestamps instead of
datetime.utcnow().
datetime.utcnow()is deprecated in Python 3.12; this route also uses it at line 84. Replace both calls withdatetime.now(timezone.utc).replace(tzinfo=None)or the project’s preferred timezone-aware type to avoid future deprecation/removal issues.🤖 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 `@plugins/practice_journal/routes.py` at line 70, Replace both datetime.utcnow() calls in the route handling this data, including the defaults near the started_at and line-84 timestamp usages, with the project’s timezone-aware UTC timestamp approach: datetime.now(timezone.utc).replace(tzinfo=None) or the established equivalent. Update imports as needed while preserving existing timestamp formatting and behavior.
🤖 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.
Inline comments:
In `@plugins/coaching/coaching.js`:
- Around line 1111-1116: Update _stringLabel so bass string names follow the
low-to-high ordering for extended-range instruments: use B-E-A-D-G for
five-string basses and B-E-A-D-G-C for six-string basses, while preserving
guitar labels and the fallback for unknown indices. Ensure the selected bass
mapping reflects the arrangement’s string count.
In `@plugins/coaching/README.md`:
- Around line 44-48: Update the coaching README’s documented default model from
claude-opus-4-8 to claude-haiku-4-5, including the model description, the
“default is opus-4-8” statement, and the _model sample so they match COACH_MODEL
and settings.html.
- Around line 29-33: Update the mistake.faultVerdict documentation in the
coaching README to reflect that confirmed_detector_bug can be assigned in-app by
_faultVerdict when a no_detection miss has notePresent, and remove the outdated
external-harness-only claim while preserving the other routing semantics.
In `@plugins/coaching/test/panel_integration.test.js`:
- Around line 143-149: Update the clustering-threshold comment in test “no-key:
a clustered miss play surfaces a Practice panel at song end” to state
minMisses=2 and gap=3 seconds, and revise the neighboring clean-play test
comment to reflect that one miss versus two is the boundary. Keep the test
behavior unchanged.
In `@plugins/metronome_lock/game.js`:
- Around line 110-136: Prevent stale _startMetronome continuations from
configuring shared metronome state after a newer start request. Add a
run-generation or cancellation guard spanning _startMetronome’s await resume()
boundary, and ensure only the latest invocation sets timing fields, _armed, and
_schedTimer; clean up any superseded AudioContext without leaving an untracked
interval. Keep _setBpm, _setMode, and _toggleDrop behavior unchanged.
- Around line 160-165: Update _onPitch so low-confidence or invalid pitch
updates do not immediately re-arm the note; use the SDK’s note-off/end signal
when available, or require a stable low/unpitched interval before setting _armed
= true. Preserve the existing single-attack consumption behavior so another
high-confidence update cannot score until a genuine release occurs.
In `@plugins/mute_master/game.js`:
- Line 38: Update the mute-scoring logic associated with FRETS and the `dTarget`
checks so a single matching pitch cannot qualify a repetition as clean. Require
an independent `mute_fail` or multi-pitch signal that detects adjacent
open-string bleed alongside the intended note, and apply the same correction to
the logic around the referenced later scoring block.
In `@plugins/mute_master/README.md`:
- Around line 6-12: Update the README paragraph to state that scoring uses
sdk.scoring.createContinuous() with locally classified pitch frames, removing
the claim that it consumes the live note_detect judgment stream. Preserve the
existing description of mute_fail taxonomy and shared skill-drill mechanics.
In `@plugins/pitch_match/game.js`:
- Around line 129-131: Update the target-advancement flow around _render,
_finish, and _nextTarget so advancing to the next target does not immediately
overwrite the judged feedback rendered for ev. Preserve the existing completion
behavior, while ensuring clean/sharp/flat feedback remains visible before the
next target is rendered.
In `@plugins/practice_journal/routes.py`:
- Around line 56-58: Validate the duration value in the route before the
comparison in the duration handling block: treat None and other non-numeric
inputs as invalid or skipped according to the route’s existing input-validation
behavior, then only evaluate the under-5-second condition for a valid numeric
duration.
- Around line 14-41: Update _get_conn() to use double-checked locking with the
existing _lock: retain the initial _conn check, acquire _lock before creating
the connection and running schema initialization, then recheck _conn inside the
lock before proceeding. Return the single initialized connection while
preserving the existing setup and commit behavior.
In `@plugins/practice_journal/screen.js`:
- Line 79: The session payload in plugins/practice_journal/screen.js at line 79
must not claim loop tracking by always sending an empty loops_used array: either
implement accumulation of loop-start/loop-end events before _pjEndSession, or
remove the field until tracking exists. Update the “Loop tracking” documentation
in plugins/practice_journal/README.md lines 14-16 to remove or clearly caveat
the claim, matching the chosen implementation.
- Around line 185-201: Escape s.arrangement before interpolating it into arrStr
in the recent-session rendering map within the practice journal UI. Reuse the
existing esc() helper, preserving the current conditional display and leaving
the already escaped title and artist handling unchanged.
---
Nitpick comments:
In `@plugins/coaching/coaching.js`:
- Around line 1516-1519: Rename the local settings variable in
_gatherSessionMeta to ndSettings to avoid shadowing the module-scope settings
object, and update all references within the function, including the returned
object’s settings property, to use ndSettings.
In `@plugins/coaching/README.md`:
- Line 14: Add the text language identifier to the fenced code block containing
the notedetect example in the coaching README, changing the opening fence to use
text while preserving the example content.
In `@plugins/coaching/test/coach.test.js`:
- Around line 100-101: Update the model override assertions in buildCoachRequest
and setModel tests to use a model string distinct from the COACH_MODEL default,
so they fail when the override is ignored. Keep asserting that the
returned/requested model equals the distinct override value.
In `@plugins/coaching/tools/headless-emit-check.js`:
- Around line 1-16: The header comment for the headless check incorrectly claims
it loads and plays a real song; update it to describe the synthetic notedetect
event flow. Remove the unused SONG and ARR environment parsing, and remove the
vestigial waitForFunction targeting window.playSong while preserving the actual
Playwright check behavior.
In `@plugins/practice_journal/routes.py`:
- Line 70: Replace both datetime.utcnow() calls in the route handling this data,
including the defaults near the started_at and line-84 timestamp usages, with
the project’s timezone-aware UTC timestamp approach:
datetime.now(timezone.utc).replace(tzinfo=None) or the established equivalent.
Update imports as needed while preserving existing timestamp formatting and
behavior.
In `@plugins/practice_journal/screen.js`:
- Around line 106-108: Update the beforeunload flow around _pjEndSession to use
navigator.sendBeacon for the final session-recording request instead of fetch,
preserving the existing endpoint and payload and ensuring the unload handler
remains non-blocking.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 04b7bdae-2e8b-4c03-8498-e22bbe60561e
📒 Files selected for processing (22)
plugins/coaching/README.mdplugins/coaching/coaching.jsplugins/coaching/plugin.jsonplugins/coaching/settings.htmlplugins/coaching/test/coach.test.jsplugins/coaching/test/feedback-schema.test.jsplugins/coaching/test/panel_integration.test.jsplugins/coaching/test/skill-drills.test.jsplugins/coaching/tools/headless-emit-check.jsplugins/metronome_lock/game.jsplugins/metronome_lock/plugin.jsonplugins/mute_master/README.mdplugins/mute_master/game.jsplugins/mute_master/plugin.jsonplugins/pitch_match/game.jsplugins/pitch_match/plugin.jsonplugins/practice_journal/.gitignoreplugins/practice_journal/README.mdplugins/practice_journal/plugin.jsonplugins/practice_journal/routes.pyplugins/practice_journal/screen.htmlplugins/practice_journal/screen.js
| function _stringLabel(s, arrangement) { | ||
| const bass = ['E', 'A', 'D', 'G', 'C', 'B']; | ||
| const gtr = ['E', 'A', 'D', 'G', 'B', 'e']; | ||
| const names = (arrangement && /bass/i.test(arrangement)) ? bass : gtr; | ||
| return (names[s] != null) ? names[s] : `string ${s}`; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
_stringLabel indices 4-5 are mislabelled for extended-range basses.
Indices are low→high, but a 5/6-string bass is B-E-A-D-G-C, so index 0 isn't E and indices 4-5 aren't C,B in that order. Only affects 5/6-string arrangements, but it tells the player to look at the wrong string.
🤖 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 `@plugins/coaching/coaching.js` around lines 1111 - 1116, Update _stringLabel
so bass string names follow the low-to-high ordering for extended-range
instruments: use B-E-A-D-G for five-string basses and B-E-A-D-G-C for six-string
basses, while preserving guitar labels and the fallback for unknown indices.
Ensure the selected bass mapping reflects the arrangement’s string count.
| `session.summary` is filled by a **client-side** Anthropic Messages API call: | ||
|
|
||
| - **Model** `claude-opus-4-8`, a frozen + prompt-cached system prompt, and | ||
| `output_config.format` (json_schema) so the reply parses deterministically. | ||
| - The request **distills** the feedback to the session block + hotspots + |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Documented default model contradicts the code.
COACH_MODEL is claude-haiku-4-5 (coaching.js line 534) and settings.html line 48 also states haiku is the default, but this section — and line 82 (default is opus-4-8) plus the _model sample on line 71 — says claude-opus-4-8.
🤖 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 `@plugins/coaching/README.md` around lines 44 - 48, Update the coaching
README’s documented default model from claude-opus-4-8 to claude-haiku-4-5,
including the model description, the “default is opus-4-8” statement, and the
_model sample so they match COACH_MODEL and settings.html.
| test('no-key: a clustered miss play surfaces a Practice panel at song end', () => { | ||
| const { host, fireWin } = harness(); | ||
| // 3 misses within 2s → one hotspot (findHotspots minMisses=3, gap=2s). | ||
| fireWin('notedetect:miss', missJudgment(20.0)); | ||
| fireWin('notedetect:miss', missJudgment(20.4)); | ||
| fireWin('notedetect:miss', missJudgment(20.8)); | ||
| fireWin('notedetect:session', { song: 'Gasoline', arrangement: 'bass', sections: [] }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Stale comment: the clustering thresholds are minMisses=2, gap=3.0.
findHotspots defaults (coaching.js lines 347-348) were loosened; the comment still cites the old minMisses=3, gap=2s rule, which also makes the neighbouring "clean play" test at line 183 read as if 1 miss vs 3 were the boundary.
📝 Proposed fix
- // 3 misses within 2s → one hotspot (findHotspots minMisses=3, gap=2s).
+ // 3 misses within 0.8s → one hotspot (findHotspots minMisses=2, gap=3s).🤖 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 `@plugins/coaching/test/panel_integration.test.js` around lines 143 - 149,
Update the clustering-threshold comment in test “no-key: a clustered miss play
surfaces a Practice panel at song end” to state minMisses=2 and gap=3 seconds,
and revise the neighboring clean-play test comment to reflect that one miss
versus two is the boundary. Keep the test behavior unchanged.
| started_at: _pjSessionStart, | ||
| duration: duration, | ||
| avg_speed: avgSpeed, | ||
| loops_used: [], |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
"Loop tracking" is documented but never implemented. plugins/practice_journal/screen.js always posts a hardcoded empty loops_used array, so no loop usage is ever actually recorded, contradicting the README's claim.
plugins/practice_journal/screen.js#L79: replace the hardcodedloops_used: []with real tracking of loops activated during the session (e.g., accumulate loop-start/loop-end events into an array before_pjEndSessionfires), or remove the field until implemented.plugins/practice_journal/README.md#L14-L16: until loop tracking is implemented, remove or caveat the "Loop tracking" bullet so the docs match actual behavior.
📍 Affects 2 files
plugins/practice_journal/screen.js#L79-L79(this comment)plugins/practice_journal/README.md#L14-L16
🤖 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 `@plugins/practice_journal/screen.js` at line 79, The session payload in
plugins/practice_journal/screen.js at line 79 must not claim loop tracking by
always sending an empty loops_used array: either implement accumulation of
loop-start/loop-end events before _pjEndSession, or remove the field until
tracking exists. Update the “Loop tracking” documentation in
plugins/practice_journal/README.md lines 14-16 to remove or clearly caveat the
claim, matching the chosen implementation.
- metronome_lock: guard _startMetronome against overlapping async starts. Rapid BPM/mode/drop changes (or a stop mid-resume) could leave two schedulers ticking; a run token invalidates the stale context after the awaited AudioContext.resume(). - pitch_match: thread the judged event through _nextTarget so the in-tune / sharp-flat feedback flash survives the immediate advance to the next target instead of being blanked by the re-render. - practice_journal: double-checked locking in _get_conn (only one thread builds the connection, published only once fully built); validate that `duration` is numeric before comparing so a bad client can't raise. - coaching: honest _stringLabel comment (extended-bass indices 4–5 are a best-effort fallback, not canonical); README fixes — confirmed_detector_bug is now set in-app on a note-present miss (not harness-only) and the coach model is claude-haiku-4-5; correct stale minMisses/gap threshold comment in panel_integration.test.js. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
screen.js posted `loops_used: []` on every session while claiming to track practice loops. Wire it to the actual A–B loop the player worked: - Accumulate distinct A–B ranges into a per-session Map, fed by the core `loop:restart` bus event (each wrap increments that loop's count), and snapshot a still-set loop from window.feedBack.getLoop() at session close (count 0 = set but never cycled). - POST the collected loops instead of []. The backend already persisted loops_used and the /song history endpoint already returned it, so this completes an end-to-end pipeline that had a dead frontend leg. - Surface it: /stats recent now returns per-session loops, and the journal dashboard shows a "🔁 N" badge on sessions that used loops. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The game scored a CLEAN muting rep whenever it heard the target pitch — but hearing the target proves you played the right note, not that the open string is silent. Worse, it uses a MONOPHONIC YIN scorer with octave folding, which collapses pitch to pitch-class: a target sharing a class with any open string (E-fret5 == open A; E-fret3 == open G an octave up; A-fret7 == open E; …) is indistinguishable from that open ringing, so a FAILED mute scored as clean. 6 of the old 9 targets were structurally unscoreable. - Precompute VALID_TARGETS: exclude every (string, fret) whose folded pitch aliases an open string (< 1 semitone). Widen the candidate fret range so the survivor set is a healthy 17 well-separated targets instead of 3. - Rewrite the header + README to be honest: this infers muting from a self-contained mono pitch scorer, NOT the note_detect judgment stream — note_detect emits no `mute_fail` today, so that leg of the taxonomy is aspirational, and the score is a best-effort proxy (true mute verification needs a polyphonic/spectral signal note_detect would have to produce). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugins/practice_journal/screen.js (1)
100-113: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winPreserve sessions submitted during tab close.
The
beforeunloadbackstop calls_pjEndSession(), but it fires a normal POSTfetch; non-keepalive fetches can be terminated as the document unloads, dropping the final session. UsesendBeacon()orfetch(..., { keepalive: true })for the submission.🤖 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 `@plugins/practice_journal/screen.js` around lines 100 - 113, Update the session submission inside _pjEndSession to use navigator.sendBeacon or a fetch configured with keepalive: true, preserving the existing POST payload and content type so submissions triggered by beforeunload complete during tab close.
🤖 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 `@plugins/practice_journal/screen.js`:
- Around line 100-113: Update the session submission inside _pjEndSession to use
navigator.sendBeacon or a fetch configured with keepalive: true, preserving the
existing POST payload and content type so submissions triggered by beforeunload
complete during tab close.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5c5cb444-0c1e-4285-b03b-ea7ab6098d36
📒 Files selected for processing (4)
plugins/mute_master/README.mdplugins/mute_master/game.jsplugins/practice_journal/routes.pyplugins/practice_journal/screen.js
🚧 Files skipped from review as they are similar to previous changes (3)
- plugins/mute_master/README.md
- plugins/practice_journal/routes.py
- plugins/mute_master/game.js
Two remaining CodeRabbit findings: - SECURITY (critical): practice_journal recent-sessions rendered `s.arrangement` unescaped into innerHTML while its neighbour used esc(). arrangement flows from song metadata → DB → here, so a crafted song file was a stored-XSS vector. Wrap it in esc(). - CORRECTNESS: metronome_lock re-armed the attack detector on a SINGLE sub-CONF_MIN frame, so a momentary confidence dropout mid-note let one played note score as two grid hits. Require the release to persist RELEASE_MS (60ms) before re-arming; reset the release clock on each run. (The same re-arm pattern exists in mute_master/pitch_match but is far less harmful there — an extra rep, not a phantom rhythm onset — so left as-is.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
plugins/metronome_lock/game.js (1)
173-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd regression coverage for the debounce boundary.
Please test sub-threshold intervals just below and at
RELEASE_MS, plus recovery before the threshold, to ensure one sustained release re-arms exactly once and transient confidence dropouts do not create duplicate hits.🤖 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 `@plugins/metronome_lock/game.js` around lines 173 - 181, Add regression tests around the release debounce logic in the note-processing flow containing _releaseSince and _armed. Cover sub-threshold release intervals that do not re-arm, an interval exactly equal to RELEASE_MS that re-arms once, and recovery before the threshold that clears the pending release without creating duplicate hits; assert sustained release produces only one re-arm/grid hit.
🤖 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.
Nitpick comments:
In `@plugins/metronome_lock/game.js`:
- Around line 173-181: Add regression tests around the release debounce logic in
the note-processing flow containing _releaseSince and _armed. Cover
sub-threshold release intervals that do not re-arm, an interval exactly equal to
RELEASE_MS that re-arms once, and recovery before the threshold that clears the
pending release without creating duplicate hits; assert sustained release
produces only one re-arm/grid hit.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 46f591ad-64a6-42ca-96b6-a212b10bb459
📒 Files selected for processing (2)
plugins/metronome_lock/game.jsplugins/practice_journal/screen.js
…ne session The post-play panel already lists miss hotspots (worst-first) each with a one-tap "Practice this" → note_detect.startDrill. This adds the missing session flow: a "▶ Drill all N spots (worst first)" button that drills them in sequence — spot 1 → (nail, or Skip) → spot 2 → … → recap — via a compact inline-styled stepper (Skip ▶ / ✕ End / ▶ Again). Advancement is orchestrated by polling note_detect's public isDrilling() (a spot ends when it graduates or the user closes it), so there are ZERO note_detect changes and no new drill-end event needed. The "Drill all" button is injected in _renderPanel (covers both the no-key and LLM panels) and only appears with ≥2 drillable spots; its distinct class keeps the per-spot button count (and tests) unchanged. Exposed window.coaching.drillAll/endDrillSession for manual use. plugin.json 0.13.0 → 0.14.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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.
Inline comments:
In `@plugins/coaching/coaching.js`:
- Around line 1648-1653: Update _sessionEnd and the public
window.coaching.endDrillSession flow so they only call
noteDetect.endDrill('session-end') when an active coaching session still exists;
after _sessionFinish() clears _session, the completed-session overlay’s End
action must only perform coaching session cleanup and must not terminate an
unrelated noteDetect drill.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0f5c8a39-324a-4fa3-bf60-5c6e73dd3d75
📒 Files selected for processing (2)
plugins/coaching/coaching.jsplugins/coaching/plugin.json
🚧 Files skipped from review as they are similar to previous changes (1)
- plugins/coaching/plugin.json
| function _sessionEnd() { | ||
| const nd = window.noteDetect; | ||
| try { if (nd && typeof nd.endDrill === 'function') nd.endDrill('session-end'); } catch (_) {} | ||
| _sessionStop(); | ||
| _sessionClearStepper(); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not end unrelated drills from the completed-session overlay.
_sessionFinish() sets _session to null but leaves the recap overlay visible. Clicking its End button—or calling window.coaching.endDrillSession()—then ends any separately started noteDetect drill.
Proposed fix
function _sessionEnd() {
- const nd = window.noteDetect;
- try { if (nd && typeof nd.endDrill === 'function') nd.endDrill('session-end'); } catch (_) {}
+ if (_session) {
+ const nd = window.noteDetect;
+ try { if (nd && typeof nd.endDrill === 'function') nd.endDrill('session-end'); } catch (_) {}
+ }
_sessionStop();
_sessionClearStepper();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function _sessionEnd() { | |
| const nd = window.noteDetect; | |
| try { if (nd && typeof nd.endDrill === 'function') nd.endDrill('session-end'); } catch (_) {} | |
| _sessionStop(); | |
| _sessionClearStepper(); | |
| } | |
| function _sessionEnd() { | |
| if (_session) { | |
| const nd = window.noteDetect; | |
| try { if (nd && typeof nd.endDrill === 'function') nd.endDrill('session-end'); } catch (_) {} | |
| } | |
| _sessionStop(); | |
| _sessionClearStepper(); | |
| } |
🤖 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 `@plugins/coaching/coaching.js` around lines 1648 - 1653, Update _sessionEnd
and the public window.coaching.endDrillSession flow so they only call
noteDetect.endDrill('session-end') when an active coaching session still exists;
after _sessionFinish() clears _session, the completed-session overlay’s End
action must only perform coaching session cleanup and must not terminate an
unrelated noteDetect drill.
What
Five self-contained, additive practice plugins (new dirs only — no core changes):
coachingmetronome_lockmute_masterpitch_matchpractice_journalroutes.py)Ported onto feedBack's own contracts (
window.feedBackbus + the nativeminigamesframework — gamekit was not ported, it's redundant with the minigames plugin; the three games register/score with no shims).Validation
main(backend +practice_journalroutes load, no errors).coachingsuite: 66/66 node tests pass.Note
Loads + backend/tests are verified; frontend runtime behaviour on current
mainwants a quick review (these were adapted to feedBack's bus but the app has moved since).🤖 Generated with Claude Code
Summary by CodeRabbit
drillAll/endDrillSessiontriggers.