Skip to content

fix(editor): rigid group-move — snap once, move the whole selection - #142

Merged
byrongamatos merged 1 commit into
mainfrom
fix/editor-group-move-snap
Jul 9, 2026
Merged

fix(editor): rigid group-move — snap once, move the whole selection#142
byrongamatos merged 1 commit into
mainfrom
fix/editor-group-move-snap

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What & why

Tester bug: "mass moving notes seem to have a limit, only a few of selected
notes get moved."

Root cause: the live move loop snapped each note's absolute time
independently (snapTime(origTime + dt) per note). With snap on (the default),
a small group drag quantised every note to its own nearest grid line — so only
the notes already near a line crossed it, the rest snapped back and appeared
not to move, and the selection's internal timing was silently destroyed.

How

  • _groupTimeDeltaPure (@pure:group-time-delta) snaps the primary
    (grabbed)
    note's target once, takes that as the single group delta,
    applies it uniformly to every selected note, and clamps it so the earliest
    note can't cross t=0. The group moves rigidly, snaps as a unit, and keeps
    its internal spacing. snapFn is injected (pure/testable).
  • onMouseDown records the grabbed note's time (primaryOrigTime); the move
    handler computes one snappedDt and applies origTimes[i] + snappedDt to
    every dragged note (keys + guitar-lane branches).
  • DAW-style grid unlock: hold Alt while dragging to move free of the
    grid (verified against Ableton's Alt / Logic's off-grid step). Only the time
    grid is bypassed — vertical stays semitone/string-quantized.

Tests

tests/group_move_snap.test.js (6) — single-note snap unchanged, a group moves
by one rigid delta with spacing kept, the delta anchors on the grabbed note (not
note[0]), the t=0 clamp, snap-off raw delta, and the adversarial guards. All
fail on main. Full JS suite green (72/72).

Summary by CodeRabbit

  • Bug Fixes

    • Fixed multi-note dragging with snap enabled so the entire selection applies one consistent, clamped time offset anchored on the grabbed note, preserving relative spacing.
    • Prevented grouped moves from looking partially unmoved or desynchronized, including correct behavior near the start of the timeline.
    • Added DAW-style grid unlock: hold Alt while dragging to move without grid snapping.
  • Tests

    • Added automated coverage for grouped snap behavior, spacing preservation, clamping at t=0, and snap-off (no-snapping) moves.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e763e69-d1ca-424a-933f-ff1c3e527484

📥 Commits

Reviewing files that changed from the base of the PR and between 5887b59 and e2fa980.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • screen.js
  • tests/group_move_snap.test.js

📝 Walkthrough

Walkthrough

Adds group-move snapping that anchors on the grabbed note, applies one clamped delta to the whole selection, and supports Alt-drag to bypass grid snapping. Also adds tests and a changelog note.

Changes

Group time delta snapping fix

Layer / File(s) Summary
Group time delta helper
screen.js
Adds _groupTimeDeltaPure, a pure function that snaps a delta once using the primary/grabbed note as the anchor and clamps it so the earliest note cannot cross t=0.
Drag wiring for group snapping
screen.js
Stores primaryOrigTime on drag state and replaces per-note time snapping in both move paths with one shared snappedDt, including Alt-key snap bypass.
Tests and changelog
tests/group_move_snap.test.js, CHANGELOG.md
Adds tests for rigid group movement, anchoring, clamping, snap-off behavior, and edge cases; documents the fix and Alt-drag grid unlock.

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

🚥 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 clearly summarizes the main change: snapping once and moving the selection rigidly as a group.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 fix/editor-group-move-snap

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.1)
screen.js

ast-grep timed out on this file


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

Tester bug: "mass moving notes seem to have a limit, only a few of selected
notes get moved."

Root cause: the live move loop snapped each note's ABSOLUTE time independently
(snapTime(origTime + dt) per note). With snap on (the default) a small group
drag quantised every note to its own nearest grid line, so only notes already
near a line crossed it - the rest snapped back and appeared not to move, and
the selection's internal timing was silently destroyed.

- @pure:group-time-delta: _groupTimeDeltaPure snaps the PRIMARY (grabbed)
  note's target ONCE, takes that as the single group delta, applies it
  uniformly to every selected note, and clamps it so the earliest note can't
  cross t=0. The whole group moves rigidly, snaps as a unit, and keeps its
  internal spacing. snapFn is injected (pure/testable).
- onMouseDown records the grabbed note's time (primaryOrigTime); the move
  handler computes one snappedDt and applies origTimes[i] + snappedDt to every
  dragged note (keys + guitar-lane branches).
- DAW-style grid unlock: hold Alt while dragging to move FREE of the grid
  (verified against Ableton's Alt / Logic's off-grid step). Only the time grid
  is bypassed - vertical stays semitone/string-quantized.

Tests: tests/group_move_snap.test.js (6) - single-note snap unchanged, a group
moves by one rigid delta with spacing kept, the delta anchors on the grabbed
note (not note[0]), the t=0 clamp, snap-off raw delta, and the adversarial
guards. All fail on main. Full JS suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013JgxKh99UAeQqmhzSc73tv
Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
@byrongamatos
byrongamatos force-pushed the fix/editor-group-move-snap branch from 5887b59 to e2fa980 Compare July 9, 2026 07:46
@byrongamatos
byrongamatos merged commit 2a55ab9 into main Jul 9, 2026
2 of 3 checks passed
@byrongamatos
byrongamatos deleted the fix/editor-group-move-snap branch July 9, 2026 07:47
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