Skip to content

feat(coaching): practice/coaching plugin suite (5 additive plugins) - #1051

Open
ahonnecke wants to merge 6 commits into
got-feedBack:mainfrom
ahonnecke:coaching-suite
Open

feat(coaching): practice/coaching plugin suite (5 additive plugins)#1051
ahonnecke wants to merge 6 commits into
got-feedBack:mainfrom
ahonnecke:coaching-suite

Conversation

@ahonnecke

@ahonnecke ahonnecke commented Jul 27, 2026

Copy link
Copy Markdown

What

Five self-contained, additive practice plugins (new dirs only — no core changes):

Plugin Purpose
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)

Ported onto feedBack's own contracts (window.feedBack bus + the native minigames framework — gamekit was not ported, it's redundant with the minigames plugin; the three games register/score with no shims).

Validation

  • All 5 register cleanly on current main (backend + practice_journal routes load, no errors).
  • coaching suite: 66/66 node tests pass.
  • New plugin directories only → cannot conflict with core changes.

Note

Loads + backend/tests are verified; frontend runtime behaviour on current main wants a quick review (these were adapted to feedBack's bus but the app has moved since).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Coaching “drill all” end-of-song session UI with skip/end/again flow and manual drillAll/endDrillSession triggers.
    • Added Coaching settings (enable switch, model selection, browser-only API key).
    • Added new minigames: Metronome Lock, Mute Master, Pitch Match.
    • Added Practice Journal dashboards, session history, and persisted tracking.
  • Bug Fixes
    • Improved Metronome Lock pitch-onset debouncing to prevent double-counting.
  • Documentation
    • Added/expanded Coaching and Mute Master docs; added Practice Journal README.
  • Tests
    • Added extensive coaching unit tests, panel integration tests, and headless end-to-end emission checks.
  • Chores
    • Escaped Practice Journal arrangement text in recent-session rows.

…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>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds 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.

Changes

Coaching feedback pipeline

Layer / File(s) Summary
Feedback contract and drill analytics
plugins/coaching/test/feedback-schema.test.js, plugins/coaching/test/skill-drills.test.js
Validates PlayFeedback structure, fault classification, hotspot clustering, drill recommendations, and skill-drill state behavior.
Coach request and summary rendering
plugins/coaching/settings.html, plugins/coaching/test/coach.test.js
Covers settings persistence, structured Anthropic requests, defensive parsing, escaped rendering, analytics, and drill-data validation.
Browser event wiring and drill sessions
plugins/coaching/coaching.js, plugins/coaching/test/panel_integration.test.js, plugins/coaching/tools/headless-emit-check.js, plugins/coaching/README.md, plugins/coaching/plugin.json
Connects note-detect events to feedback panels, adds worst-first multi-hotspot drilling and browser controls, documents the pipeline, and validates emitted feedback.

Pitch-based skill drills

Layer / File(s) Summary
Metronome Lock timing debounce
plugins/metronome_lock/game.js, plugins/metronome_lock/plugin.json
Requires a sustained confidence drop before re-arming onset scoring while preserving timing classification.
Mute Master muting drill
plugins/mute_master/*
Adds continuous pitch scoring for mute failures, target progression, drill completion, UI feedback, documentation, and registration.
Pitch Match intonation drill
plugins/pitch_match/*
Adds sustained pitch evaluation with sharp/flat feedback, streak completion, UI rendering, and registration.

Practice Journal

Layer / File(s) Summary
Session storage and statistics API
plugins/practice_journal/routes.py
Creates SQLite persistence and endpoints for recording sessions, aggregate statistics, and song history.
Dashboard and plugin packaging
plugins/practice_journal/screen.html, plugins/practice_journal/screen.js, plugins/practice_journal/plugin.json, plugins/practice_journal/README.md, plugins/practice_journal/.gitignore
Adds the dashboard layout, plugin metadata, documentation, cache ignore rules, and escaped arrangement rendering.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers What and validation, but it omits the required feedpak surface section and the checklist items. Add the feedpak surface section (or explicitly note it is unchanged) and include the checklist items for changelog, tests, and DCO sign-off.
Docstring Coverage ⚠️ Warning Docstring coverage is 18.97% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: an additive suite of five practice plugins, centered on coaching.
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 unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (6)
plugins/coaching/test/coach.test.js (1)

100-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tautological override assertion — the override value equals the default.

COACH_MODEL is claude-haiku-4-5, so this passes even if opts.model were ignored. Same issue at lines 190-191, where setModel('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 win

Header comment contradicts the implementation; SONG/ARR are dead config.

The script never loads a song or plays anything — it dispatches synthetic notedetect:* events (as the line 19-20 comment correctly states). SONG and ARR are parsed and never used, and the waitForFunction on window.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 value

Add 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 value

Rename the local settings in _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

beforeunload handler uses fetch, which can be cancelled mid-flight.

Browsers may abort in-flight fetch requests during page unload, so the last session on tab-close can silently fail to record. navigator.sendBeacon is 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 win

Use 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 with datetime.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

📥 Commits

Reviewing files that changed from the base of the PR and between eef58c8 and ff4699f.

📒 Files selected for processing (22)
  • plugins/coaching/README.md
  • plugins/coaching/coaching.js
  • plugins/coaching/plugin.json
  • plugins/coaching/settings.html
  • plugins/coaching/test/coach.test.js
  • plugins/coaching/test/feedback-schema.test.js
  • plugins/coaching/test/panel_integration.test.js
  • plugins/coaching/test/skill-drills.test.js
  • plugins/coaching/tools/headless-emit-check.js
  • plugins/metronome_lock/game.js
  • plugins/metronome_lock/plugin.json
  • plugins/mute_master/README.md
  • plugins/mute_master/game.js
  • plugins/mute_master/plugin.json
  • plugins/pitch_match/game.js
  • plugins/pitch_match/plugin.json
  • plugins/practice_journal/.gitignore
  • plugins/practice_journal/README.md
  • plugins/practice_journal/plugin.json
  • plugins/practice_journal/routes.py
  • plugins/practice_journal/screen.html
  • plugins/practice_journal/screen.js

Comment on lines +1111 to +1116
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}`;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 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.

Comment thread plugins/coaching/README.md Outdated
Comment on lines +44 to +48
`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 +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 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.

Comment on lines +143 to +149
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: [] });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 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.

Comment thread plugins/metronome_lock/game.js
Comment thread plugins/pitch_match/game.js Outdated
Comment thread plugins/practice_journal/routes.py
Comment thread plugins/practice_journal/routes.py
Comment thread plugins/practice_journal/screen.js Outdated
started_at: _pjSessionStart,
duration: duration,
avg_speed: avgSpeed,
loops_used: [],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 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 hardcoded loops_used: [] with real tracking of loops activated during the session (e.g., accumulate loop-start/loop-end events into an array before _pjEndSession fires), 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.

Comment thread plugins/practice_journal/screen.js
ahonnecke and others added 3 commits July 28, 2026 08:17
- 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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
plugins/practice_journal/screen.js (1)

100-113: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Preserve sessions submitted during tab close.

The beforeunload backstop calls _pjEndSession(), but it fires a normal POST fetch; non-keepalive fetches can be terminated as the document unloads, dropping the final session. Use sendBeacon() or fetch(..., { 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

📥 Commits

Reviewing files that changed from the base of the PR and between 517e8b5 and a5b6929.

📒 Files selected for processing (4)
  • plugins/mute_master/README.md
  • plugins/mute_master/game.js
  • plugins/practice_journal/routes.py
  • plugins/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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
plugins/metronome_lock/game.js (1)

173-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add 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

📥 Commits

Reviewing files that changed from the base of the PR and between a5b6929 and 030fc55.

📒 Files selected for processing (2)
  • plugins/metronome_lock/game.js
  • plugins/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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 030fc55 and 3302eca.

📒 Files selected for processing (2)
  • plugins/coaching/coaching.js
  • plugins/coaching/plugin.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/coaching/plugin.json

Comment on lines +1648 to +1653
function _sessionEnd() {
const nd = window.noteDetect;
try { if (nd && typeof nd.endDrill === 'function') nd.endDrill('session-end'); } catch (_) {}
_sessionStop();
_sessionClearStepper();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 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.

Suggested change
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.

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