Skip to content

feat(editor): handshape / arpeggio region authoring (E2) - #8

Merged
byrongamatos merged 1 commit into
mainfrom
feat/editor-handshape-authoring
Jun 20, 2026
Merged

byrongamatos merged 1 commit into
mainfrom
feat/editor-handshape-authoring

Conversation

@byrongamatos

Copy link
Copy Markdown
Collaborator

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 } whose chord_id indexes arr.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

  • LoadloadCDLC 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). The server already emits them per-arrangement (_song_to_dict); the editor kept them verbatim but never normalized them.
  • 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 (_ensureHandshapes / _bumpHandshapesDirty / _handshapesAreDirty) mirrors the anchors pattern. Unlike index-free anchors, handshapes ship on non-empty (not dirty-only): reconstructChords() reindexes chord_templates on every save, so a dirty-only gate would let the backend's absent→preserve path keep stale chord_ids pointing at freshly-rebuilt templates.
  • chord_id remapreconstructChords() 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

tests/chord_relink.test.js extended — name-matched remap, arpeggio orphan-template append + dedupe, invalid-dropped, defensive empties. 10/10 pass; node --check screen.js clean.

Backend

routes.py already accepts + validates handshapes (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; arp and numeric coercion matching the backend _safe_bool/_safe_int).

Closes #5. Part of got-feedBack/feedBack#334.

🤖 Generated with Claude Code

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

[E2] Handshape / arpeggio (chord-shape) region authoring

1 participant