Skip to content

feat(song): per-note keys hand assignment (hand) on the Note wire#990

Open
ChrisBeWithYou wants to merge 1 commit into
mainfrom
feat/note-hand-field
Open

feat(song): per-note keys hand assignment (hand) on the Note wire#990
ChrisBeWithYou wants to merge 1 commit into
mainfrom
feat/note-hand-field

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What

Adds a per-note keys hand assignment to the Note model and wire: hand: 'lh' | 'rh' | None (None = unassigned — the heuristic hand split keeps owning unassigned notes).

  • Emit: default-omitted, validated on emit so a directly-constructed Note can't put junk on the wire. Spelled-out hand key — rh is taken by right_hand (the bass plucking finger).
  • Decode: strict enum — anything but 'lh'/'rh' (junk, wrong case, bools) decodes to unassigned rather than poisoning downstream hand-split / hands-separate-practice logic.
  • Older readers ignore the key (feedpak permits unknown note keys; the notation schema is additionalProperties: true).

Why

The editor's keys LH/RH hand-assignment arc: MusicXML grand-staff imports carry authored per-note hands (editor PR feedBack-plugin-editor#299 + musicxml-import#8), and the editor emits the field on its save wire — but core's note_from_wire drops unknown keys, so the assignment died on every sloppak reopen. This makes the round-trip real.

Follow-ups land separately: notation_lift.split_hands respecting per-note overrides (so edited bars keep authored hands), and the editor's hand surface (roll shading, split-point stamping, hands-separate practice).

Tests

Three new wire round-trip tests in tests/test_song.py (literal key pinned, default-omitted, junk rejected both directions), matching the teaching-marks test style. Suite: 1721 passed locally (+3 vs main; the 99 pre-existing failures/errors reproduce identically on pristine main — local env, missing mido etc.).

🤖 Generated with Claude Code

https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q

Summary by CodeRabbit

  • New Features

    • Added optional left- or right-hand assignments to individual notes.
    • Hand assignments are preserved when saving and loading song data.
  • Bug Fixes

    • Invalid hand values are safely ignored rather than displayed or stored.

The editor's keys LH/RH hand arc needs a per-note hand assignment
('lh'/'rh', from a MusicXML grand staff import today, hand-editable
later) to survive a sloppak save → reload: the editor already emits it
on the wire, but note_from_wire dropped unknown keys, so the field died
on every reopen.

- Note gains `hand: str | None = None` (None = unassigned; the
  heuristic hand split keeps owning unassigned notes). Distinct from
  `right_hand` (the bass plucking finger) — hence the spelled-out
  `hand` wire key, since `rh` is taken.
- note_to_wire emits it default-omitted and validates on emit; older
  readers ignore it (feedpak: unknown note keys are permitted).
- note_from_wire decodes it as a strict enum — anything but 'lh'/'rh'
  (junk, wrong case, bools) falls back to unassigned rather than
  poisoning downstream hand-split / hands-separate practice logic.

Groundwork consumers land separately: notation_lift.split_hands
respecting per-note overrides, and the editor's hand surface.
Editor counterpart: feedBack-plugin-editor #299.

Tests: three new wire round-trip tests in tests/test_song.py (literal
key, default-omitted, junk rejection both directions), matching the
teaching-marks test style.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

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: eb50ede7-23f3-49d7-a3ea-957377f9108d

📥 Commits

Reviewing files that changed from the base of the PR and between 3717e43 and b085978.

📒 Files selected for processing (2)
  • lib/song.py
  • tests/test_song.py

📝 Walkthrough

Walkthrough

Changes

Note hand assignment

Layer / File(s) Summary
Hand field and wire conversion
lib/song.py
Note gains an optional hand field, and serialization/deserialization accepts only "lh" or "rh" while omitting invalid or unset values.
Hand wire behavior tests
tests/test_song.py
Tests cover valid round trips, omission of unset values, and invalid input handling.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: byrongamatos

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers what, why, and tests, but it omits the required feedpak surface section for this wire-format change. Add the feedpak surface section and state whether the spec/FEP already landed; also complete the checklist items required by the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 clearly summarizes the main change: adding per-note hand assignment on Note wire serialization.
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 feat/note-hand-field

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

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