Skip to content

feat(editor): loop snap modes — bar / grid / free + no-grid fallback - #92

Merged
byrongamatos merged 2 commits into
mainfrom
feat/editor-loop-snap-modes
Jul 6, 2026
Merged

feat(editor): loop snap modes — bar / grid / free + no-grid fallback#92
byrongamatos merged 2 commits into
mainfrom
feat/editor-loop-snap-modes

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

The loop region was bar-snapped only — every drag ran through _barSpanForTimes — and looping was disabled entirely when the chart has no downbeats, which is the exact starting state of a song that wasn't recorded to a click. Transcribers need to loop a phrase before the tempo map exists, loop in subdivisions, and draw completely free loops.

  • Bar / Grid / Free segmented control in the loop strip. Bar = whole-downbeat spans (the unchanged default — muscle memory preserved). Grid = loop edges snap through snapTime at the current snap subdivision, honoring the snap on/off toggle (snap off ⇒ behaves as Free — one coherent grid setting). Free = unsnapped.
  • Shift-drag = temporary Free in any mode — resolved live each mousemove, so pressing/releasing Shift mid-gesture flips snapping without restarting the drag.
  • No-grid fallback: with zero downbeats the strip stays enabled and forces Free ("Drag to set loop (free — no bar grid yet)") instead of showing "No bar grid available".
  • Mode-aware relock: regions remember how they were drawn. TempoMapCmd/TempoGridCmd exec+rollback now relock bar/grid loops onto the changed grid (with an end-edge probe so a bar span whose end sits exactly on a new downbeat doesn't grow by a bar), while freely drawn loops are absolute seconds and never move.
  • Grid/free regions label as a plain time range instead of a misleading "Bars X–Y"; the mode preference persists as an editor preference (localStorage, never the pack).
  • Loop-in-3D verified unaffected: the highway A–B hand-off is seconds-based end to end, so free loops pass through unchanged.

Verification

  • node --check screen.js clean
  • New tests/loop_snap_modes.test.js — 10 cases via the @pure:loop-region block: bar-span legacy behavior + mode stamping, bar-degrades-to-free with no downbeats, grid snap-fn routing, free normalization/clamping, zero-width create, edge adjust per mode (no edge-crossing), mid-drag mode switch restamps
  • All 26 JS test files pass (existing loop_region.test.js untouched and green)

🤖 Generated with Claude Code

https://claude.ai/code/session_01JEoFeTPSnz4NpwwCG52hnu

Summary by CodeRabbit

  • New Features

    • Added loop strip snap mode controls (Bar / Grid / Free) with a persistent editor preference.
    • Shift-drag temporarily enables Free, with live snapping behavior flipping while the gesture is in progress.
  • Bug Fixes

    • Improved handling when no bar grid/downbeats exist (loop strip remains available; “drag to set loop” stays forced to Free).
    • Mode-aware loop labels now show an accurate time range; tempo-map/tempo-grid edits re-lock Bar/Grid loops without moving Free loops.
  • Tests

    • Added coverage for snap/edge-adjustment behavior, mid-drag mode switching, and undo/redo + 3D handoff mode preservation.

@coderabbitai

coderabbitai Bot commented Jul 6, 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: 0934d3d6-309e-4bcd-86eb-d7b58700f2bc

📥 Commits

Reviewing files that changed from the base of the PR and between 8b95704 and 70a4291.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • screen.html
  • screen.js
  • tests/loop_snap_modes.test.js
  • tests/loop_undo_mode.test.js
  • tests/pending_view.test.js

📝 Walkthrough

Walkthrough

Adds three loop snap modes to the loop strip, with mode-aware loop math, persisted mode state, Shift-drag overrides, tempo-edit relocking, pending-view preservation, and test coverage.

Changes

Loop Snap Modes

Layer / File(s) Summary
Pure loop region and edge-adjust helpers
screen.js
Adds mode-aware _loopRegionForDragPure and _loopEdgeAdjustPure supporting bar, grid, and free snapping, storing region.mode.
Loop mode UI controls and rendering
screen.html, screen.js
Adds Bar/Grid/Free buttons to the loop strip and updates loop strip rendering and label formatting for free-mode and gridless states.
Snap preference, live mode, and handoff state
screen.js
Adds the persisted snap preference, Shift-driven live mode resolution, the global snap-mode setter, loop-mode button refresh, pending-view mode preservation, and loop region mode retention across the Loop-in-3D handoff.
Loop drag and resize wiring
screen.js
Updates loop-strip mouse down and mouse move handling to use the live-resolved snap mode and the new pure helpers, and filters interactions on the mode controls from starting loop drags.
Tempo command relocking
screen.js
Updates TempoGridCmd and TempoMapCmd to snapshot the prior loop selection, relock on exec after grid changes, and restore the exact saved selection on rollback.
Tests and changelog
tests/loop_snap_modes.test.js, tests/loop_undo_mode.test.js, tests/pending_view.test.js, CHANGELOG.md
Adds tests for loop region creation, edge adjustment, tempo undo symmetry, and pending-view mode preservation, and expands the changelog entry for the new loop snap modes.

Estimated code review effort: 4 (Complex) | ~45 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: new loop snap modes with a no-grid fallback.
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 feat/editor-loop-snap-modes

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.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
screen.html (1)

133-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider aria-pressed for the active mode button.

All three buttons share identical base classes with no built-in indicator of which mode is currently selected; this relies entirely on JS toggling classes at runtime (not shown in this batch). Using aria-pressed alongside the visual class toggle would make the active mode discoverable to assistive technology, independent of how the styling is implemented.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@screen.html` around lines 133 - 135, Add an accessible active-state indicator
to the loop snap mode controls in the button group that uses
editorSetLoopSnapMode; the current selection is only conveyed visually via
runtime class changes. Update the Bar, Grid, and Free buttons to expose their
selected state with aria-pressed, and ensure the value is kept in sync with the
active mode wherever the UI updates the classes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@screen.html`:
- Around line 133-135: Add an accessible active-state indicator to the loop snap
mode controls in the button group that uses editorSetLoopSnapMode; the current
selection is only conveyed visually via runtime class changes. Update the Bar,
Grid, and Free buttons to expose their selected state with aria-pressed, and
ensure the value is kept in sync with the active mode wherever the UI updates
the classes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ecce53f-eadb-491c-bb72-689530971c1c

📥 Commits

Reviewing files that changed from the base of the PR and between e067f25 and 43bc67b.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • screen.html
  • screen.js
  • tests/loop_snap_modes.test.js

ChrisBeWithYou and others added 2 commits July 6, 2026 21:41
The loop region was bar-snapped only (_barSpanForTimes on every drag),
and looping was disabled entirely on a chart with no downbeats — the
exact starting state of a song that wasn't recorded to a click.

- Three-way mode control in the loop strip: Bar (whole-downbeat spans,
  unchanged default), Grid (edges snap via snapTime at the current
  subdivision, honoring the snap on/off toggle), Free (no snapping).
- Shift-drag = temporary Free in any mode, resolved live mid-gesture.
- No-grid fallback: the strip stays enabled with zero downbeats and
  forces Free, so drifting-tempo songs can loop while their tempo map
  is being authored.
- Regions carry their mode; TempoMapCmd/TempoGridCmd exec+rollback now
  relock bar/grid loops onto the new grid (end-edge probe avoids
  growing a bar span whose end already sits on a downbeat) while freely
  drawn loops never move. Loop-in-3D is seconds-based and unaffected.
- Grid/free regions show a plain time range, not a "Bars X-Y" label.
- Mode preference persists as an editor pref (localStorage).

Tests: tests/loop_snap_modes.test.js (10 cases). node --check clean;
all 26 JS test files pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JEoFeTPSnz4NpwwCG52hnu
…gion.mode through 3D handoff

TempoMapCmd.rollback and TempoGridCmd.rollback previously re-ran
_loopRelockAfterGridChange(), which re-derives S.barSel from the
(already-relocked) grid. Relock is lossy / not self-inverse, so
exec->rollback was non-idempotent: a bar loop [2,4] resurfaced as [1,6]
after edit+undo. Both commands now snapshot S.barSel (deep copy incl.
mode) on first exec BEFORE mutating, and rollback restores that exact
snapshot. Forward relock stays on exec so a live tempo edit still
re-snaps the loop onto the new grid.

The editor<->3D-highway round-trip dropped region.mode:
_effectiveLoopRegion, _resolvePendingViewStatePure and editorLoopIn3D
rebuilt the region without mode, so a freely drawn loop returned as
mode===undefined (treated as 'bar' -> moved on the next tempo edit and
mislabeled). All three now carry mode through.

Tests: TempoMapCmd exec/rollback loop-symmetry (undo restores the exact
original barSel; redo re-applies) and mode preservation through the
pending-view handoff resolver.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos
byrongamatos force-pushed the feat/editor-loop-snap-modes branch from 8b95704 to 70a4291 Compare July 6, 2026 19:41
@byrongamatos
byrongamatos merged commit fc44d2f into main Jul 6, 2026
@byrongamatos
byrongamatos deleted the feat/editor-loop-snap-modes branch July 6, 2026 19:41
byrongamatos added a commit that referenced this pull request Jul 12, 2026
- Shift+click after a marquee anchors on the nearest multi-selected
  downbeat instead of falling through to the plain path (which cleared
  the selection and started a drag) — _tempoShiftAnchorPure.
- The context menu offers 'Delete N barlines' only when the clicked pole
  is itself in the selection; otherwise the bulk action would delete the
  OTHER barlines and leave the one under the cursor —
  _tempoPoleBulkCountPure.
- Undoing the bulk delete restores the multi-selection and the focus
  (#92: snapshot, restore selection state) — TempoGridCmd grows an
  optional oldMulti snapshot, restored on rollback only; exec (redo)
  still drops the set, and commands without a snapshot keep the
  drop-never-remap contract.

Co-Authored-By: Claude Fable 5 <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.

2 participants