Skip to content

fix(editor): horizontal scroll works before the first Play in compose/MIDI-only sessions - #307

Merged
byrongamatos merged 2 commits into
mainfrom
fix/editor-compose-scroll-clamp
Jul 17, 2026
Merged

fix(editor): horizontal scroll works before the first Play in compose/MIDI-only sessions#307
byrongamatos merged 2 commits into
mainfrom
fix/editor-compose-scroll-clamp

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

The bug (tester report: "I couldn''t scroll left/right on the note view")

A session with no decoded audio only derives S.duration from the grid inside startPlayback() (audio.js:800). Before the first Play, _editorClampScrollX therefore saw a 0-length song, _editorMaxScrollXPure returned 0 (geometry.js:81), and every horizontal-scroll input — wheel, middle-drag pan, and the minimap (whose own songDur() knows the grid length but funnels through the same clamp at ruler.js:457) — silently pinned the view to t = 0. Pressing Play once set the duration and "cured" it, which is exactly why the report came in as "scroll issues" rather than a repro.

The fix

_editorClampScrollX falls back to _composeSongDuration() — the same grid/authored-content rule playback itself uses (charrette §1.7) — whenever the audio-derived duration is not positive. The fallback is guarded so audio-bounded sessions never pay for the content walk and keep their exact old clamp.

Tests

tests/compose_scroll_clamp.test.mjs — the un-played MIDI-only leg fails on main (clamps to 0); authored content past the grid extends the extent; a genuinely empty session still clamps to 0; the audio-bounded clamp is byte-identical with the fallback provably dormant (a longer grid cannot extend it). Full suite: 205 JS green, lint 0 errors.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed horizontal scrolling in MIDI-only sessions before playback starts.
    • Scrolling now follows the authored content and grid length instead of being restricted to the starting position.
    • Preserved existing scrolling behavior for audio-based sessions.
    • Empty sessions continue to remain correctly bounded.

…/MIDI-only sessions

Tester report: "I couldn''t scroll left/right on the note view" — then it
mysteriously fixed itself. Root cause: a session with no decoded audio
only derives S.duration from the grid inside startPlayback(), so before
the first Play _editorClampScrollX saw duration 0, maxScroll clamped to
0, and every horizontal-scroll input (wheel, middle-drag pan, and the
minimap — whose own songDur() knows the grid length but funnels through
the same clamp) silently pinned the view to t = 0.

The clamp now falls back to _composeSongDuration() — the same
grid/content rule playback itself uses (charrette §1.7) — whenever the
audio-derived duration is not positive. Guarded so audio-bounded
sessions never pay for the content walk and keep their exact old clamp.

Tests: tests/compose_scroll_clamp.test.mjs — the un-played MIDI-only leg
fails on main (clamps to 0); content-past-grid extent; empty session
still clamps; audio-bounded clamp byte-identical (fallback dormant).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
@coderabbitai

coderabbitai Bot commented Jul 17, 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: 59 minutes

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: 65abda1b-6f0a-4c3c-a0a4-168353e1be52

📥 Commits

Reviewing files that changed from the base of the PR and between 43e86c0 and 0d35b11.

📒 Files selected for processing (1)
  • CHANGELOG.md
📝 Walkthrough

Walkthrough

Horizontal scroll clamping now uses composed song/grid duration when audio duration is unavailable, enabling pre-play scrolling in MIDI-only sessions. Audio-bounded behavior remains unchanged, with new tests covering the affected scenarios.

Changes

MIDI scroll clamping

Layer / File(s) Summary
Scroll clamp duration fallback
src/loop.js
_editorClampScrollX falls back to _composeSongDuration() when the audio-derived duration is non-positive.
Scroll clamp scenarios and release note
tests/compose_scroll_clamp.test.mjs, CHANGELOG.md
Tests cover MIDI-only, extended-content, empty, and audio-bounded sessions; the changelog documents the fix.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: byrongamatos

🚥 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 and accurately summarizes the main fix for horizontal scrolling in compose/MIDI-only sessions before first playback.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/editor-compose-scroll-clamp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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)
src/loop.js (1)

42-52: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Avoid rescanning authored content on every scroll clamp.

When the audio duration is non-positive, _editorClampScrollX calls _composeSongDuration(), which walks authored content. Because this function serves wheel, drag, and minimap paths, large MIDI sessions may perform an O(content) scan per input/frame. Cache the composed extent and invalidate it when beats, notes, guides, or composeLength change.

🤖 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 `@src/loop.js` around lines 42 - 52, The fallback in _editorClampScrollX must
not rescan authored content on every scroll event. Cache the value returned by
_composeSongDuration(), reuse it while beats, notes, guides, and composeLength
are unchanged, and invalidate or recompute that cache whenever any of those
inputs changes; preserve the existing audio-duration path without invoking the
content scan.
🤖 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 `@src/loop.js`:
- Around line 42-52: The fallback in _editorClampScrollX must not rescan
authored content on every scroll event. Cache the value returned by
_composeSongDuration(), reuse it while beats, notes, guides, and composeLength
are unchanged, and invalidate or recompute that cache whenever any of those
inputs changes; preserve the existing audio-duration path without invoking the
content scan.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f52f14b9-24a9-40de-8d3a-587a33ff6071

📥 Commits

Reviewing files that changed from the base of the PR and between a5bdae7 and 43e86c0.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/loop.js
  • tests/compose_scroll_clamp.test.mjs

@byrongamatos
byrongamatos merged commit 9c159cc into main Jul 17, 2026
@byrongamatos
byrongamatos deleted the fix/editor-compose-scroll-clamp branch July 17, 2026 21:50
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