feat(editor): handshape / arpeggio region authoring (E2) - #8
Merged
Merged
Conversation
PR-A of E2 (handshape / arpeggio region authoring): the data foundation
that lets the editor round-trip handshapes safely. PR-B adds the span-lane
authoring UI.
A handshape is a time span { chord_id, start_time, end_time, arp } whose
chord_id indexes arr.chord_templates. The editor was handshape-blind in
three ways, all closed here:
- Load: loadCDLC now normalizes each arrangement's handshapes into robust
editable dicts via _normalizeHandshape (wire field names; numeric-string
+ _safe_bool-style coercion matching the backend; sorted by start_time).
- Serialize: _buildSaveBody (single-arr archive) and editorBuild (arrEntry)
ship handshapes whenever any exist; the sloppak full snapshot already
rides them through `rest` (now also strips _handshapeEditCount). Dirty
tracking via _ensureHandshapes/_bumpHandshapesDirty/_handshapesAreDirty
mirrors the anchors pattern. Unlike index-free anchors, handshapes are
shipped on non-empty (not dirty-only): reconstructChords reindexes
chord_templates every save, so a dirty-only gate would let the backend
preserve stale chord_ids against freshly-rebuilt templates.
- chord_id remap: reconstructChords now remaps handshape chord_id old->new
by fret pattern via two pure helpers in the @pure:chord-relink block
(buildHandshapeChordIdMap, remapHandshapeChordIds). Arpeggio handshapes
whose voicing produced no same-time chord get their preserved template
appended (deduped) so they survive; unmappable references are dropped to
match the backend validator.
tests/chord_relink.test.js extended: name-matched remap, arpeggio
orphan-template append + dedupe, invalid-dropped, defensive empties.
routes.py already accepts + validates `handshapes` (chord_id < len(
chord_templates)); no backend change needed.
Closes #5 (progressively — PR-A). Part of got-feedBack/feedBack#334.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR-A of E2 — the data foundation for handshape / arpeggio (chord-shape) region authoring. PR-B (follow-up) adds the span-lane authoring UI.
A handshape is a time span
{ chord_id, start_time, end_time, arp }whosechord_idindexesarr.chord_templates[](arp=true= arpeggio framing,false= held shape). The highway already renders handshapes; the editor was handshape-blind in three ways, all closed here.What this PR does
loadCDLCnormalizes each arrangement's handshapes into robust editable dicts via_normalizeHandshape(wire field names; numeric-string +_safe_bool-style coercion matching the backend; sorted bystart_time). The server already emits them per-arrangement (_song_to_dict); the editor kept them verbatim but never normalized them._buildSaveBody(single-arr archive) andeditorBuild(arrEntry) ship handshapes whenever any exist; the sloppak full snapshot already rides them throughrest(now also strips_handshapeEditCount). Dirty tracking (_ensureHandshapes/_bumpHandshapesDirty/_handshapesAreDirty) mirrors the anchors pattern. Unlike index-free anchors, handshapes ship on non-empty (not dirty-only):reconstructChords()reindexeschord_templateson every save, so a dirty-only gate would let the backend's absent→preserve path keep stalechord_ids pointing at freshly-rebuilt templates.reconstructChords()remaps handshapechord_idold→new by fret pattern via two pure helpers in the@pure:chord-relinkblock (buildHandshapeChordIdMap,remapHandshapeChordIds). Arpeggio handshapes whose voicing produced no same-time chord get their preserved template appended (deduped) so they survive; unmappable references are dropped to match the backend validator.Tests
tests/chord_relink.test.jsextended — name-matched remap, arpeggio orphan-template append + dedupe, invalid-dropped, defensive empties. 10/10 pass;node --check screen.jsclean.Backend
routes.pyalready accepts + validateshandshapes(chord_id < len(chord_templates), dropping invalid) — no backend change needed.Review
Codex preflight (read-only) run; 3 findings addressed (dirty-gate→non-empty shipping to avoid stale chord_ids;
arpand numeric coercion matching the backend_safe_bool/_safe_int).Closes #5. Part of got-feedBack/feedBack#334.
🤖 Generated with Claude Code