Skip to content

feat(editor): group drag for a barline multi-selection (tempo PR 5b) - #230

Merged
byrongamatos merged 2 commits into
feat/editor-tempo-multiselectfrom
feat/editor-tempo-groupdrag
Jul 12, 2026
Merged

feat(editor): group drag for a barline multi-selection (tempo PR 5b)#230
byrongamatos merged 2 commits into
feat/editor-tempo-multiselectfrom
feat/editor-tempo-groupdrag

Conversation

@ChrisBeWithYou

Copy link
Copy Markdown
Contributor

What & why

Tempo-track PR 5b (charrette arch 1, second half). Completes the barline-selection feature #226 (5a) started: after selecting multiple barlines, you can now drag the whole group at once by grabbing any pole in it — not just delete them. This is the natural way to nudge a whole passage's grid onto the recording.

⚠ Stacked on #226 (feat/editor-tempo-multiselect). Base is that branch so the diff is 5b-only. After #226 squash-merges, rebase onto main: git rebase --onto main feat/editor-tempo-multiselect feat/editor-tempo-groupdrag (GitHub auto-retargets the base).

Behaviour

  • Grab any pole in a multi-selection → the whole group slides by one offset.
  • Spans between two selected poles rigid-shift (their tempo is preserved); spans at the selection's edges re-space against the fixed barline just outside — exactly like a single-pole drag generalised.
  • The group stops the moment any member would collide with a fixed neighbour (± the minimum measure), so it can never reorder the grid. Song start / duration bound the ends.
  • Notes ride the move (one TempoMapCmd, undoable as Move N barlines together).
  • Locked barlines are excluded — they stay put and act as fixed anchors the group re-spaces around; the status says how many stayed. A lock's job is to defend a hand-verified time, so excluding it is the least-surprising rule.

How

Two pure functions in src/tempo.js:

  • _tempoGroupDragClampPure(beats, selIdxs, deltaT, minGap, duration) — the whole-group clamp (tightest per-pole headroom against the nearest unselected downbeat).
  • _tempoApplyGroupDragPure(beats, selIdxs, deltaT, minGap, duration) — shifts the selected downbeats, re-spaces every span's interior proportionally between its new endpoints, rigid-shifts a leading pickup / trailing tail when its bounding downbeat moved. Never mutates the input; drops locked poles from the group.

Wiring mirrors the existing single-pole drag: a mousedown on a pole that belongs to a 2+ selection starts a tempo-group drag; _tempoMapOnDragMove rebuilds from the original grid each move (no compounding); the shared _tempoMapOnDragEnd finalises it as one equal-count TempoMapCmd. The multi-selection is index-preserving, so it survives the drag. (src/mouse.js routes the new drag type.)

Verification

  • npm test116 pass. New tests/tempo_group_drag.test.mjs (12 cases): clamp math both directions, a fixed pole between two selected ones binding both sides, rigid interior + edge re-space, pickup/tail shift, locked-pole exclusion, all-locked no-op, purity, and a full exec → undo → redo round-trip that also checks a note rides. Fails on main (the pures don't exist).
  • npm run lint — 0 errors (3 pre-existing ratchet warnings).
  • Live on the testbed: entered Tempo Map on AC/DC — Back In Black, Ctrl+A (105 barlines), dragged a pole → "Moved 105 barlines together — notes ride the grid.", undo enabled, no page errors.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 65d7d3ec-b2d9-4c32-b99f-2db739ba9f19

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-tempo-groupdrag

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

@ChrisBeWithYou
ChrisBeWithYou force-pushed the feat/editor-tempo-multiselect branch from 393a2a0 to f342417 Compare July 12, 2026 20:06
Grabbing any pole in a Tempo Map multi-selection now drags the whole
group by one offset, instead of only the single focused barline.

- _tempoGroupDragClampPure: the group's Δt is clamped by the tightest
  headroom of any selected pole against its nearest UNSELECTED (fixed)
  downbeat ± MIN_MEASURE, so the group moves rigidly and can never
  cross a fixed neighbour or reorder the grid. Song start (0) / passed
  `duration` bound a pole with no fixed neighbour on that side.
- _tempoApplyGroupDragPure: shifts every selected downbeat by the
  clamped Δt, then re-spaces each measure's interior proportionally
  between its (possibly moved) downbeats — a span between two selected
  poles rigid-shifts for free, an edge span re-spaces against its fixed
  outside pole, and a leading pickup / trailing tail rigid-shifts only
  when its bounding downbeat moved. Pure (never mutates the input).
- Locked poles are dropped from the moving group: they stay put and act
  as fixed anchors; a status notes how many stayed. Locks defend
  hand-verified times, so excluding them is least surprising.
- Wiring mirrors the single-pole drag: mousedown starts a 'tempo-group'
  drag when a multi-selection (2+) is grabbed by one of its poles;
  _tempoMapOnDragMove rebuilds from the original grid each move (no
  compounding); the shared _tempoMapOnDragEnd finalizes it as one
  undoable TempoMapCmd (equal-count invariant) — notes ride the grid.
  The selection is index-preserving, so it survives the drag.

tests/tempo_group_drag.test.mjs (12 cases): clamp math both directions,
fixed-pole-between-two-selected binding, rigid interior + edge re-space,
pickup/tail shift, locked-pole exclusion, all-locked no-op, purity, and
an exec -> undo -> redo round-trip that also checks a note rides. Fails
on main (the pures don't exist). 116 JS green, lint 0-err. Verified live
on the testbed: Ctrl+A -> drag a pole -> "Moved 105 barlines together."

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
@ChrisBeWithYou
ChrisBeWithYou force-pushed the feat/editor-tempo-groupdrag branch from 74cb277 to dfdce1b Compare July 12, 2026 20:11
@byrongamatos
byrongamatos merged commit 91b75e8 into feat/editor-tempo-multiselect Jul 12, 2026
1 check passed
byrongamatos added a commit that referenced this pull request Jul 14, 2026
…230) (#258)

* feat(editor): group drag for a barline multi-selection (tempo PR 5b)

Grabbing any pole in a Tempo Map multi-selection now drags the whole
group by one offset, instead of only the single focused barline.

- _tempoGroupDragClampPure: the group's Δt is clamped by the tightest
  headroom of any selected pole against its nearest UNSELECTED (fixed)
  downbeat ± MIN_MEASURE, so the group moves rigidly and can never
  cross a fixed neighbour or reorder the grid. Song start (0) / passed
  `duration` bound a pole with no fixed neighbour on that side.
- _tempoApplyGroupDragPure: shifts every selected downbeat by the
  clamped Δt, then re-spaces each measure's interior proportionally
  between its (possibly moved) downbeats — a span between two selected
  poles rigid-shifts for free, an edge span re-spaces against its fixed
  outside pole, and a leading pickup / trailing tail rigid-shifts only
  when its bounding downbeat moved. Pure (never mutates the input).
- Locked poles are dropped from the moving group: they stay put and act
  as fixed anchors; a status notes how many stayed. Locks defend
  hand-verified times, so excluding them is least surprising.
- Wiring mirrors the single-pole drag: mousedown starts a 'tempo-group'
  drag when a multi-selection (2+) is grabbed by one of its poles;
  _tempoMapOnDragMove rebuilds from the original grid each move (no
  compounding); the shared _tempoMapOnDragEnd finalizes it as one
  undoable TempoMapCmd (equal-count invariant) — notes ride the grid.
  The selection is index-preserving, so it survives the drag.

tests/tempo_group_drag.test.mjs (12 cases): clamp math both directions,
fixed-pole-between-two-selected binding, rigid interior + edge re-space,
pickup/tail shift, locked-pole exclusion, all-locked no-op, purity, and
an exec -> undo -> redo round-trip that also checks a note rides. Fails
on main (the pures don't exist). 116 JS green, lint 0-err. Verified live
on the testbed: Ctrl+A -> drag a pole -> "Moved 105 barlines together."


Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q

* Prevent locked group drag fallthrough

---------

Co-authored-by: ChrisBeWithYou <chris@rifflarr.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.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.

2 participants