Skip to content

feat(editor): the overview strip becomes a real horizontal scrollbar - #325

Merged
ChrisBeWithYou merged 2 commits into
mainfrom
feat/editor-canvas-scrollbars
Jul 19, 2026
Merged

feat(editor): the overview strip becomes a real horizontal scrollbar#325
ChrisBeWithYou merged 2 commits into
mainfrom
feat/editor-canvas-scrollbars

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

What

The minimap already painted a viewport window, but it was a one-pixel outline nobody could grab, and mousedown always re-centred the view on the click. So there was no way to drag the timeline precisely, and the strip read as decoration rather than a control.

The window is now a proper scrollbar thumb:

Gesture Result
Grab the thumb body View tracks the pointer 1:1, no jump on press
Drag either edge Zooms, opposite edge anchored
Double-click the strip Fits the whole song
Click the empty track Jumps there (unchanged)

It paints as a filled block with edge grips, the cursor spells out which gesture you'll get (grab on the body, ew-resize on a grip), and a very short thumb floors at a grabbable width instead of thinning to a hairline. The grip half-width is capped at a third of the thumb, so a floored thumb still keeps a draggable body between its two grips.

Where it comes from

Ableton Live's Arrangement Overview — Live 12 manual p.151: "click and drag horizontally to scroll left or right, or click and drag vertically to zoom in or out. To zoom out to the full Arrangement, double-click anywhere within" — plus its resizable Clip View Selector (p.210). Logic Pro splits the same job across a horizontal scroll bar and a Horizontal Zoom slider (Logic Pro user guide, p.297); one strip doing both is the tighter fit for a canvas with 14px of chrome to spend.

The zoom floor had to move

Zoom-to-fit forced a second fix. The floor was 20 px/s, inlined at each zoom writer, which capped the widest possible view at ~50 seconds — so fit-to-song would have been a no-op for any real song, and "zoom out to see the arrangement" was simply unreachable.

ZOOM_MIN / ZOOM_MAX / clampZoom() now live in src/geometry.js at a 2px/s floor (~8 minutes on a typical canvas), shared by the wheel, the zoom buttons and the scrollbar. The painters already degrade gracefully down there — bar labels thin out via _rulerBarLabelSkipPure, note bodies hold at MIN_NOTE_W.

Verification

Unit tests cover the geometry, but the interesting part was checked in a real browser against a 3:35 song, because the pure functions can't prove the wiring:

  • Grabbing 25% into the thumb (off-centre, where the old centre-on-click model would have leapt ~100px) moves it 0px on press, then tracks +199px over a +200px drag.
  • Double-click drops zoom 100 → 5 with the thumb spanning the full strip — note 5 is below the old floor, so this gesture was impossible before.
  • Dragging the end grip takes the thumb 403 → 652px and zoom 15 → 9.
  • Cursors report grab / ew-resize. No page errors.

tests/minimap_scrollbar.test.mjs pins the geometry (thumb extent, min-width flooring and end-of-song containment, hit-test precedence, anchored resize incl. the clamped case, fit, and degenerate/NaN inputs). tests/keyboard_gutter_dblclick.test.mjs gains a case asserting onDblClick consults the minimap before the parts/drum/tempo mode guards — that one fails on main ("onDblClick must consult the minimap first").

231/231 JS tests pass; lint clean (the 3 remaining warnings are pre-existing in create.js/main.js).

Notes for review

  • View state stays out of history, as required — scrolling and zooming are not undoable.
  • No new listeners or timers, so nothing to add to the teardown registry; the strip is painted inside the existing rAF draw-coalesce.
  • Vertical scrolling is deliberately not in this PR. The drum grid and piano roll have a genuine vertical reachability problem (_drumLaneIdxToY and midiToY have no scroll term, so on a short canvas the kick and snare lanes are unreachable) and that wants its own change.

🤖 Generated with Claude Code

https://claude.ai/code/session_017xGPjDBF8NTwTK7VQvizix

Summary by CodeRabbit

  • New Features

    • The overview strip now works as a draggable horizontal scrollbar with a visible minimap thumb.
    • Drag the thumb to navigate, or use its edges to adjust the visible time range.
    • Double-click the minimap to fit the entire song in view.
  • Improvements

    • Zooming out now reaches a wider view, allowing an entire song to be displayed.
    • Zoom behavior is consistent across controls, mouse gestures, and the minimap.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 24 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: 618a81b2-486c-4588-b1c1-f02e32c35101

📥 Commits

Reviewing files that changed from the base of the PR and between 81409fd and 360c2cb.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • src/geometry.js
  • src/main.js
  • src/mouse.js
  • src/ruler.js
  • tests/keyboard_gutter_dblclick.test.mjs
  • tests/minimap_scrollbar.test.mjs
📝 Walkthrough

Walkthrough

The PR centralizes zoom bounds and adds a draggable minimap scrollbar with thumb dragging, grip-based zoom resizing, zoom-to-fit double-click behavior, updated cursor and event routing, and regression coverage for geometry and interaction edge cases.

Changes

Minimap scrollbar and zoom behavior

Layer / File(s) Summary
Shared zoom bounds and entry points
src/geometry.js, src/main.js, src/mouse.js, CHANGELOG.md
Zoom bounds are defined in shared geometry utilities and applied to editor zoom and Ctrl+wheel controls.
Minimap geometry and rendering
src/geometry.js, src/ruler.js, tests/minimap_scrollbar.test.mjs
Minimap thumb, grip, hit-testing, resize, and fit-to-viewport calculations are implemented, rendered, and tested against invalid and boundary inputs.
Minimap interaction routing
src/mouse.js, src/ruler.js, tests/keyboard_gutter_dblclick.test.mjs
Hover states, double-click routing, thumb dragging, grip zooming, track recentering, and drag completion use the new minimap interaction modes.

Estimated code review effort: 4 (Complex) | ~45 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 accurately summarizes the main change: the overview strip is turned into an interactive horizontal scrollbar.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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-canvas-scrollbars

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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@src/mouse.js`:
- Around line 436-449: Move the minimap hover handling in the ruler hover path
ahead of the drum-edit and tempo-map hover guards so _minimapHitPure and the
corresponding cursor assignment always run when the pointer is over the minimap.
Preserve the existing mode-specific guards and mousedown behavior after this
early minimap handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 56a83c3b-93bf-4889-a937-a03986d29f1e

📥 Commits

Reviewing files that changed from the base of the PR and between 5e6c34e and 81409fd.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • src/geometry.js
  • src/main.js
  • src/mouse.js
  • src/ruler.js
  • tests/keyboard_gutter_dblclick.test.mjs
  • tests/minimap_scrollbar.test.mjs

Comment thread src/mouse.js Outdated
ChrisBeWithYou and others added 2 commits July 19, 2026 16:22
The minimap already painted a viewport window, but it was a one-pixel
outline nobody could grab, and mousedown always re-centred the view on the
click — so there was no way to drag the timeline precisely, and the strip
read as decoration. Christian's words on seeing it: "is this the scroll
bar? because it doesn't work currently."

The window is now a thumb:

  - grab the body and the view tracks the pointer 1:1, no jump on press
  - drag either edge to zoom, opposite edge anchored
  - double-click the strip to fit the whole song
  - clicking the empty track still jumps there (unchanged)

Painted as a filled block with edge grips, with cursor feedback (grab on
the body, ew-resize on a grip) and a minimum thumb width so a deeply
zoomed-in view still leaves something to hold. The grip half-width is
capped at a third of the thumb so a floored thumb keeps a draggable body
between its two grips.

Modelled on Ableton Live's Arrangement Overview (Live 12 manual p.151:
"click and drag horizontally to scroll left or right, or click and drag
vertically to zoom in or out. To zoom out to the full Arrangement,
double-click anywhere within") and its resizable Clip View Selector
(p.210). Logic Pro splits the same job across a horizontal scroll bar and
a Horizontal Zoom slider (Logic Pro user guide, p.297); one strip doing
both is the tighter fit for a canvas with 14px of chrome to spend.

Zoom-to-fit forced a second fix. The zoom floor was 20px/s inlined at each
writer, which capped the widest view at ~50 seconds — fit-to-song would
have been a no-op for any real song. ZOOM_MIN/ZOOM_MAX/clampZoom() now
live in geometry.js at a 2px/s floor (~8 minutes), shared by the wheel,
the zoom buttons and the scrollbar.

Verified in a browser against a 3:35 song, not just in unit tests: grabbing
25% into the thumb moves it 0px on press (the old model would have leapt
~100px) and tracks +199px over a +200px drag; double-click drops zoom
100→5 with the thumb spanning the full strip; dragging the end grip takes
the thumb 403→652px and zoom 15→9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xGPjDBF8NTwTK7VQvizix
@ChrisBeWithYou
ChrisBeWithYou force-pushed the feat/editor-canvas-scrollbars branch from 169b474 to 360c2cb Compare July 19, 2026 21:22
@ChrisBeWithYou
ChrisBeWithYou merged commit fb8a1a4 into main Jul 19, 2026
@ChrisBeWithYou
ChrisBeWithYou deleted the feat/editor-canvas-scrollbars branch July 19, 2026 21:31
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.

1 participant