Skip to content

fix(editor): normalize chord-template frets, wire-coerce arp, clear solo provenance - #153

Merged
byrongamatos merged 1 commit into
mainfrom
fix/chord-save-path-152
Jul 9, 2026
Merged

fix(editor): normalize chord-template frets, wire-coerce arp, clear solo provenance#153
byrongamatos merged 1 commit into
mainfrom
fix/chord-save-path-152

Conversation

@byrongamatos

@byrongamatos byrongamatos commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Closes #152. Three pre-existing data-integrity bugs in the chord save path, surfaced by CodeRabbit on #151 and each verified by running the real module, not inferred from the diff.

1. relinkChordTemplate stored an un-normalized fret row

relinkChordTemplate([3,2,0,-1,-1,-1], {}, 8).frets.length   → 6   (expected 8)

It did frets: frets.slice() on whatever it was handed. A preserved template can arrive narrower than the chartbuildHandshapeChordIdMap's preserve-append passes one straight off the wire — and a non-finite slot (undefined / NaN / a hand-edited string) rode through untouched.

The fold _fretKeyForL already applied for the lookup key is now a shared _normFretsToL, used by both. Key, preserved-lookup, and stored row can no longer disagree — the same class of bug as the raw-join dedupe key fixed in #151. fingers has always been padded to L this way; frets now matches. Padding only ever appends -1 (unfretted), so no fret data is lost.

2. arp: !!(old && old.arp) turned the string "false" into true

relinkChordTemplate([3,-1,…], { '3,-1,…': { arp: 'false' } }, 6).arp   → true

_safeWireBool lives in that same module for exactly this — its own comment names the case — and simply wasn't used. A hand-edited or legacy sloppak carrying arp: "false" no longer switches arpeggio on across a load→save. Every input the old code accepted as true (true, 'true', '1', 1) still reads true.

3. Solo notes kept _fromChord / _chordId

A chord reduced to one note pushed the survivor by reference with its chord provenance intact. Now cleared alongside _fn.

But not for the reason CodeRabbit gave. None of the three can reach disk — the backend's _note() whitelists the keys it writes. The reason _fn must go is that _groupFn reads it back: a stale _fn would be adopted by majority vote if that note were later dragged into a chord. The comment there claimed the delete was about the wire; corrected. Also fixed the stale claim above flattenChords that reconstructChords groups by time+_fromChord — it groups by rounded time alone.

Not a bug — checked and closed out

CodeRabbit's fourth finding, that time: cn.time || ch.time drops first-beat notes. It doesn't: chord-member time is absolute, so a chord on beat one has cn.time === ch.time === 0. Verified on Arcturus - The Sham Mirrors - Kinetic.feedpak: 66 chords, every member's time equal to its chord's, none zero.

Verification

  • node --test 86/86, pytest 248/248.

  • Eight new cases in tests/chords.test.mjs — and each was re-run against the un-fixed code to confirm it fails there. A guard that passes both ways guards nothing.

  • Real save → reload round-trip through the running server, on the chord-heavy Arcturus pack (8 templates, 66 chords). I intercepted the actual POST body rather than trusting the UI:

    templates with frets width ≠ 6 0
    templates with a non-finite fret 0
    templates with a non-boolean arp 0
    posted notes carrying _fromChord/_chordId/_fn 0
    status Saved successfully
    templates + chords after reload 8 / 66, unchanged

    To be precise about what that proves: the round-trip shows the new shape is accepted and preserved by routes.py. The discriminating evidence for the bugs themselves is the unit suite above.

  • Headless draw / hit-test / resize harnesses green. Codex preflight: NO ISSUES.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Preserved chord shapes now keep their expected width when reloaded, preventing templates from shifting across different string counts.
    • Chord data now handles invalid fret values more safely, avoiding stray placeholder notes in saved handshapes.
    • Arpeggio settings now respect saved false values correctly, so disabled arpeggios stay disabled.
    • Single-note chords no longer inherit stale chord metadata during rebuilds, reducing unexpected chord carryover.

…olo provenance

Closes #152. Three pre-existing data-integrity bugs in the chord save path,
surfaced by CodeRabbit on #151 and each verified by running the real module.

1. relinkChordTemplate stored `frets.slice()` verbatim. A preserved template can
   arrive narrower than the chart — buildHandshapeChordIdMap's preserve-append
   hands it a template straight off the wire — so a 6-wide row survived on a
   7/8-string chart, and a non-finite slot (undefined / NaN / a hand-edited
   string) rode through untouched. It now stores the width-normalized row.

   The fold that _fretKeyForL already applied for the lookup key is now a shared
   `_normFretsToL`, used by both. Key, preserved-lookup and stored row can no
   longer disagree — the same class of bug as the raw-join dedupe key fixed in
   #151. `fingers` was always padded to L this way; `frets` now matches.

2. `arp: !!(old && old.arp)` turned the STRING "false" into true. _safeWireBool
   exists in that same module for exactly this — its own comment names the case —
   and simply was not used. A hand-edited or legacy sloppak with `arp: "false"`
   no longer switches arpeggio on across a load->save round-trip.

3. A chord reduced to one note left `_fromChord` / `_chordId` on the survivor;
   they are now cleared alongside `_fn`. None of the three can reach disk — the
   backend's `_note()` whitelists the keys it writes — but `_fn` is READ BACK by
   `_groupFn`, so a stale one would be adopted by majority vote if that note were
   later dragged into a chord. The comment there claimed the delete was about the
   wire. It isn't; corrected. Also fixed the stale claim above flattenChords that
   reconstructChords groups by "time+_fromChord" (it groups by rounded time).

NOT a bug, checked and closed out in the issue: `time: cn.time || ch.time` in
_flattenArrChords does not drop first-beat notes. Chord-member `time` is
absolute, so a chord on beat one has cn.time === ch.time === 0. Verified against
Arcturus - The Sham Mirrors - Kinetic.feedpak: 66 chords, every member time equal
to its chord's, none zero.

Verified: node --test 86/86, pytest 248/248. Eight new cases in
tests/chords.test.mjs, and EACH was re-run against the un-fixed code to confirm
it fails there — a guard that passes both ways guards nothing.

End-to-end: a real save -> reload round-trip through the running server on the
chord-heavy Arcturus pack (8 templates, 66 chords). Intercepted the actual POST
body: every fret row L-wide, every fret finite, every `arp` a real boolean, no
editor-internal field on any note; "Saved successfully"; templates and chords
both survive the reload. That proves the new shape is ACCEPTED by routes.py —
the discriminating evidence for the bugs themselves is the unit suite.
Headless draw / hit-test / resize harnesses green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 11:47

Copilot AI 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.

Pull request overview

This PR fixes three data-integrity bugs in the chord-template save/relink path by ensuring fret rows are width-normalized, arp is wire-coerced correctly (avoiding the "false"true trap), and chord-provenance internal fields are cleared when a chord collapses to a solo note.

Changes:

  • Introduces a shared fret-row normalization fold (_normFretsToL) and uses it for both template keying and stored frets rows.
  • Switches relinkChordTemplate’s arp coercion to _safeWireBool to correctly handle "false" and other wire spellings.
  • Clears _fn, _fromChord, and _chordId on lone notes during reconstructChords, and adds targeted regression tests covering all three fixes.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/chords.js Normalizes stored template frets, wire-coerces arp, and clears chord provenance on solo survivors to prevent stale internal state from persisting.
tests/chords.test.mjs Adds regression tests for fret-row widening/sanitization, arp coercion, and removal of chord-provenance fields on solo notes.
CHANGELOG.md Documents the three fixed chord-template save-path issues and clarifies the rationale for clearing _fn.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai

coderabbitai Bot commented Jul 9, 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: 5f95c8a6-b897-4cfa-b9a7-6bf0fa433428

📥 Commits

Reviewing files that changed from the base of the PR and between aa8f09e and 7d0a038.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/chords.js
  • tests/chords.test.mjs

📝 Walkthrough

Walkthrough

Updates chord-template persistence in src/chords.js: fret rows are normalized to chart string width with non-finite slots folded to -1 via a new _normFretsToL helper used by relinkChordTemplate and _fretKeyForL; arp coercion switches to _safeWireBool; solo notes have _fromChord/_chordId cleared alongside _fn. Adds regression tests and changelog entries.

Changes

Chord template and provenance fixes

Layer / File(s) Summary
Fret-row normalization helper
src/chords.js
Adds _normFretsToL to fold non-finite fret slots to -1 and pad/widen rows to chart width L; _fretKeyForL now derives its key from this normalized row.
relinkChordTemplate width and arp fixes
src/chords.js, tests/chords.test.mjs
relinkChordTemplate stores normalized fret rows for lookup and output instead of raw frets.slice(), and switches arp handling from !!old.arp to _safeWireBool so string "false" stays falsy; tests cover widening, non-finite folding, narrowed-input lookup, and arp coercion cases.
Solo note provenance cleanup
src/chords.js, tests/chords.test.mjs
reconstructChords deletes _fromChord and _chordId alongside _fn when a chord reduces to a single note; comment updated to describe time-only grouping; regression test confirms no provenance tags survive flattenChords/reconstructChords.
Changelog
CHANGELOG.md
Documents the fret normalization, arp coercion, and provenance cleanup fixes, plus a note clarifying the time fallback is not a first-beat bug.

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

Possibly related PRs

  • got-feedBack/feedBack-plugin-editor#143: Also modifies the relinkChordTemplate/reconstructChords chord-saving flow and adds a related wire-purity regression test affected by these template/provenance changes.
🚥 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 concisely names the three main chord-save fixes and matches the PR scope.
Linked Issues check ✅ Passed The changes implement all three linked chord-save data-integrity fixes and the noted non-bug clarification.
Out of Scope Changes check ✅ Passed The extra doc/comment updates and tests stay aligned with the linked chord-save fixes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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/chord-save-path-152

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

@byrongamatos
byrongamatos merged commit b9228e9 into main Jul 9, 2026
3 of 4 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.

Chord-template save path: 6-wide frets on 7/8-string charts, arp wire-coercion, and internal fields on the wire

2 participants