Skip to content

feat(editor): edge-drag note-duration resize in the read-only fretted roll - #141

Merged
byrongamatos merged 2 commits into
mainfrom
feat/editor-roll-edge-resize
Jul 9, 2026
Merged

feat(editor): edge-drag note-duration resize in the read-only fretted roll#141
byrongamatos merged 2 commits into
mainfrom
feat/editor-roll-edge-resize

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What & why

A fretted part shown in the piano roll is edit-locked (V4) — no silent
string/fret writes until the suggest-position writer lands. But a sustain
edit is a duration change
: it never changes what a note sounds like, only
how long it rings. So it should apply directly, exactly like the VA.5
position-cycle's pitchPreserving carve-out already does.

This lets you tighten an imported fretted note's duration by dragging its
right edge
, without switching to String view.

How

  • onMouseDown no longer suppresses the sustain edge grab in the read-only
    roll: edgeIdx = hitNoteEdge(x, y) (was _rollReadOnly() ? -1 : …).
  • ResizeSustainCmd and ResizeSustainGroupCmd carry pitchPreserving = true,
    so the EditHistory edit lock passes them on exec / undo / redo. The
    lock still blocks every non-pitchPreserving (pitch/position) write.

Tests

tests/roll_edge_resize.test.js (4) — both commands declare pitchPreserving;
a single and a group resize apply in a read-only roll and round-trip through
undo/redo; an ordinary (unflagged) command is still blocked and the user is
told why. All fail on main. Full JS suite green (72/72).


Part of the fretted-roll authoring pile. Companion to the suggest-position PR
(which adds notes at a sensible default length); this is the duration-editing
half.

Summary by CodeRabbit

  • New Features
    • Read-only fretted piano rolls now allow edge-drag resizing for note duration only, keeping pitch and position unchanged.
    • Group edge-drag sustain resizing now preserves each note’s individual duration behavior (including independent clamping).
  • Bug Fixes
    • Improved edge-grab handling so duration-only resizing can start directly from note edges even when the roll is read-only.
    • Sustain duration resize actions behave more consistently in locked views, including multi-note edits (with proper undo/redo behavior).

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@byrongamatos, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 86974dd5-b85a-4c7b-ac16-d9ccd5a1928b

📥 Commits

Reviewing files that changed from the base of the PR and between 5b44df6 and c5e64fa.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • screen.js
  • tests/chord_resize.test.js
  • tests/roll_edge_resize.test.js
📝 Walkthrough

Walkthrough

This PR enables read-only fretted piano-roll sustain edge resizing, marks sustain resize commands as pitch-preserving, changes the sustain resize helper to clamp each member independently, and updates tests plus the changelog to match.

Changes

Read-only sustain edge-drag resizing

Layer / File(s) Summary
Per-member sustain resize logic
screen.js
_maxSustainBeforeCollisionPure now supports per-member collision checks, and _resizeSustainsForDeltaPure applies each note’s own original sustain with independent clamping.
Pitch-preserving sustain commands
screen.js
Sustain duration edit command paths set pitchPreserving = true so duration-only edits pass the read-only lock.
Read-only edge-drag handling
screen.js
The edge-grab path always evaluates hitNoteEdge(x, y), stores origSustains, and updates drag-time sustain resizing from those per-note values.
Tests and changelog
tests/roll_edge_resize.test.js, tests/chord_resize.test.js, CHANGELOG.md
Adds harness coverage for locked read-only sustain edits and per-member resize behavior, updates the chord-resize expectation, and documents the feature in the changelog.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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: enabling edge-drag note-duration resizing in the read-only fretted roll.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-roll-edge-resize

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)
tests/roll_edge_resize.test.js (1)

24-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer throwing over process.exit(1) in extractBlock for consistent failure reporting.

extractClass reports missing symbols via assert.ok (caught by the t() harness, counted in fail), but extractBlock calls process.exit(1) directly. If the @pure:edit-history marker is ever renamed/removed in screen.js, this silently kills the whole process before the t() catch block or the final pass/fail summary runs — masking which specific test failed and skipping any tests after the first makeEnv() call.

♻️ Suggested fix
 function extractBlock(name) {
     const re = new RegExp('/\\* `@pure`:' + name + ':start[\\s\\S]*?`@pure`:' + name + ':end \\*/');
     const m = src.match(re);
-    if (!m) { console.error(`FAIL: `@pure`:${name} block not found in screen.js`); process.exit(1); }
+    if (!m) { throw new Error(`@pure:${name} block not found in screen.js`); }
     return m[0];
 }
🤖 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 `@tests/roll_edge_resize.test.js` around lines 24 - 40, `extractBlock`
currently terminates the process directly, unlike `extractClass`, which lets the
`t()` harness report failures cleanly. Update `extractBlock` to raise an
assertion or throw an error instead of calling `process.exit(1)`, so missing
`@pure:${name}` markers in screen.js are reported through the normal test
failure path and don’t abort the remaining tests or summary output.
🤖 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 `@tests/roll_edge_resize.test.js`:
- Around line 24-40: `extractBlock` currently terminates the process directly,
unlike `extractClass`, which lets the `t()` harness report failures cleanly.
Update `extractBlock` to raise an assertion or throw an error instead of calling
`process.exit(1)`, so missing `@pure:${name}` markers in screen.js are reported
through the normal test failure path and don’t abort the remaining tests or
summary output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 95b3ea3b-6190-4830-9e49-f671b98c5e69

📥 Commits

Reviewing files that changed from the base of the PR and between b367c23 and b261888.

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

@byrongamatos

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

ChrisBeWithYou and others added 2 commits July 9, 2026 09:49
… roll

A fretted part shown in the piano roll is edit-locked (no silent string/fret
writes until the suggest-position writer lands). But a sustain edit is a
DURATION change - it never changes what a note SOUNDS like, only how long it
rings - so it should apply directly, exactly like the VA.5 position-cycle's
pitchPreserving carve-out.

- onMouseDown no longer suppresses the sustain edge grab in the read-only
  roll (edgeIdx = hitNoteEdge(x, y), not `_rollReadOnly() ? -1 : ...`).
- ResizeSustainCmd + ResizeSustainGroupCmd carry pitchPreserving = true, so
  the EditHistory edit lock passes them (exec + undo + redo). The lock still
  blocks every non-pitchPreserving (pitch/position) write.

Now you can tighten an imported fretted note's duration by dragging its right
edge without switching to String view.

Tests: tests/roll_edge_resize.test.js (4) - both commands declare
pitchPreserving; a single and a group resize apply in a read-only roll and
round-trip through undo/redo; an ordinary (unflagged) command is still blocked
and the user is told why. 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>
…iew)

A group sustain edge-drag computed one `anchorOrig + delta` and assigned it
to every chord member, flattening members that had different original
ring-outs (e.g. imported GP data: bass 1.0, top 0.5) to a single value —
silent duration-data loss, first exposed by the read-only fretted-roll
resize. Now the drag delta is applied to each member's OWN original sustain
(`sustain_i = clamp(origSustain_i + delta)`), and each member clamps
independently to >=0 and to its own next same-string onset: a member that
would collide stops at its limit while the others keep extending. Single-note
resize is unchanged (scalar orig broadcast).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos
byrongamatos force-pushed the feat/editor-roll-edge-resize branch from 5b44df6 to c5e64fa Compare July 9, 2026 07:49
@byrongamatos
byrongamatos merged commit 95f9076 into main Jul 9, 2026
2 checks passed
@byrongamatos
byrongamatos deleted the feat/editor-roll-edge-resize branch July 9, 2026 07:49
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