feat(editor): one consolidated ruler + whole-song minimap (workspace-shell B3) - #209
Conversation
…shell B3) The three time-surfaces (floating loop strip, waveform seek, bottom beat bar) consolidate into a canvas timeline header: a whole-song minimap (sections/loop/viewport/playhead, click-pans) over the one authoritative ruler owning bars + beats + sections + loop + playhead. Ruler upper half paints/resizes the mode-aware loop, lower half scrubs; loop snap + Clear move to Transport > Loop; Alt+arrows nudge edges. Loop state stays S.barSel end-to-end. Every band below shifts by the fixed 40px TIMELINE_TOP; the retired beat bar's BEAT_H is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThe editor replaces the standalone loop strip and beat bar with a consolidated minimap/ruler header. Loop editing, scrubbing, snapping, keyboard nudging, menu actions, canvas geometry, rendering, and related tests are updated for the new 40px timeline offset. ChangesTimeline editor
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant Canvas
participant Ruler
participant EditorState
participant Renderer
User->>Canvas: drag minimap, ruler, or scrub area
Canvas->>Ruler: route header pointer event
Ruler->>EditorState: update scroll, cursor, or loop selection
Ruler->>Renderer: request redraw
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/parts-view.js (1)
149-204: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winRender the shared timeline header in Parts View
src/parts-view.js:149-204—_partsViewDraw()only paintshost.drawWaveform(w)plus the part lanes, so the0..TIMELINE_TOPstrip stays empty. If Parts View should match the main editor, draw the minimap/ruler here too.🤖 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/parts-view.js` around lines 149 - 204, Update _partsViewDraw() to render the shared timeline header across the 0..TIMELINE_TOP region before drawing the part lanes, matching the main editor’s minimap/ruler rendering. Reuse the existing shared header-drawing routine and preserve the current waveform and lane rendering behavior.
🧹 Nitpick comments (2)
tests/ruler.test.mjs (2)
144-153: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMissing coverage for scrub-while-playing / resume.
seedGrid()setsplaying: falsefor every test, so no test exercises theresumepath inrulerOnMouseDown/rulerOnMouseUp(starting a scrub whileS.playingistrue). Given theresumeflag's correctness is in question (seesrc/ruler.jscomment), a test seedingS.playing = truebefore a scrub press/release would catch this.🤖 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/ruler.test.mjs` around lines 144 - 153, Extend the scrub test around rulerOnMouseDown and rulerOnMouseUp to set S.playing = true before the press, then verify scrubbing records the resume state and restores playback after release. Keep the existing seek, drag-type, movement, and cleanup assertions intact.
117-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDead/tautological assertion — test doesn't verify what its comment claims.
S.barSel = null; const region = { startTime: 4.5, endTime: 9.2 }; ... assert.strictEqual(rulerOnMouseUp(), false, 'barsel up belongs to mouse.js, not the ruler'); S.drag = null; assert.ok(region.endTime > region.startTime); —
regionis a locally-constructed literal never derived from ruler behavior; assertingregion.endTime > region.startTimeis always true regardless of what the ruler does. The "Drag right into bar 3 — the region snaps to whole bars 2–3" comment implies snapping is verified here, but it isn't — this only tests the mousedown seed and thatrulerOnMouseUpreturnsfalseforbarsel.Either drop the dead
regionlocal/comment, or actually exercise_loopRegionForDragPure(the same pure the mouse.js move path calls) to verify the claimed snap-to-bar behavior.🤖 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/ruler.test.mjs` around lines 117 - 129, Remove the tautological local region construction and assertion from the loop-half drag test, or replace them by invoking _loopRegionForDragPure with the drag coordinates to assert the expected whole-bar 2–3 region. Keep the existing mousedown barsel setup and rulerOnMouseUp ownership assertion, and ensure the test directly verifies the snapping behavior described by its comment.
🤖 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/input.js`:
- Around line 905-912: Update the tone-lane context-menu gate in onContextMenu
to accept y values from TIMELINE_TOP through TIMELINE_TOP + TONE_LANE_H,
replacing the zero-based range while preserving the existing tone-menu handling.
In `@src/ruler.js`:
- Around line 308-313: In the scrub handling flow, capture the current S.playing
value before calling stopPlayback(), then assign that saved value to
S.drag.resume. Preserve the existing scrubTo(x) behavior and ensure mouse-up can
resume playback when it was active before scrubbing.
---
Outside diff comments:
In `@src/parts-view.js`:
- Around line 149-204: Update _partsViewDraw() to render the shared timeline
header across the 0..TIMELINE_TOP region before drawing the part lanes, matching
the main editor’s minimap/ruler rendering. Reuse the existing shared
header-drawing routine and preserve the current waveform and lane rendering
behavior.
---
Nitpick comments:
In `@tests/ruler.test.mjs`:
- Around line 144-153: Extend the scrub test around rulerOnMouseDown and
rulerOnMouseUp to set S.playing = true before the press, then verify scrubbing
records the resume state and restores playback after release. Keep the existing
seek, drag-type, movement, and cleanup assertions intact.
- Around line 117-129: Remove the tautological local region construction and
assertion from the loop-half drag test, or replace them by invoking
_loopRegionForDragPure with the drag coordinates to assert the expected
whole-bar 2–3 region. Keep the existing mousedown barsel setup and
rulerOnMouseUp ownership assertion, and ensure the test directly verifies the
snapping behavior described by its comment.
🪄 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: 7362887b-2d78-4ff5-b29b-61f5c8a21675
📒 Files selected for processing (20)
CHANGELOG.mdscreen.htmlsrc/annotation-lanes.jssrc/draw.jssrc/drum.jssrc/geometry.jssrc/input.jssrc/keys.jssrc/loop.jssrc/main.jssrc/menu-bar.jssrc/mouse.jssrc/parts-view.jssrc/ruler.jssrc/tempo.jssrc/waveform.jstests/geometry.test.mjstests/keyboard_gutter_dblclick.test.mjstests/ruler.test.mjstests/waveform_render.test.js
…oop nudge Review fixes for the B3 ruler consolidation: 1. Drum-edit and Tempo-map draw paths painted only the waveform, leaving the new timeline header (minimap + ruler + loop region + playhead) blank while mouse.js still routed every press above TIMELINE_TOP to it — loops were editable but invisible in those two modes (the old DOM strip was visible in every mode). Add host.drawTimelineHeader(w) to _drumEditorDraw and _tempoMapDraw, mirroring parts-view. 2. Keyboard loop-edge nudge lost the coarse (±50 ms free) step when Shift was reassigned to pick the edge. Add Ctrl as the coarse modifier: Alt+←/→ = fine start, Alt+Shift = end, +Ctrl = coarse (loop_nudge.test already covers the ±0.05 branch this reaches). 3. Remove the dead `editorClearLoop` re-export from ruler.js (nothing imports it; menu-bar dispatches _clearBarSelection directly) and its now-unused import. Suite 102/102 + ruler 14/14; ESLint 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charrette arch 5a / rhythm G5 / trans P6-prep (design slice 2a). Sparse labeled chips on the ruler show where the tempo and meter change through the song. ZERO storage — markers are a PURE function of S.beats (the executable truth, never a second source): _tempoMarkersPure derives tempo-change markers where a measure's BPM leaves the current run beyond 0.01 (the same constant _tempoHasMultipleMeasureBpmsPure uses) and meter markers where the numerator / den changes, with a bar-1 baseline of each and no spurious marker on a trailing partial bar. Memoized on editGen (+ the S.beats ref). Painted in the ruler band via timeToX only (the D-T1 invariant); tempo chips (cyan) and meter chips (violet) stack when they share a barline. Marker fields are never smuggled into S.beats entries. Filed feedpak-spec#51 for the AUTHORED markers the grid can't express (tempo ramps, meter groupings, fermata holds, lock/provenance) — slice 2b, range-provenance and community maps gate on it; not built here. tests/tempo_markers.test.mjs (7): baseline-only on a constant map, tempo-change detection, within-tolerance run behaviour, mid-song meter change + trailing- partial suppression, denominator change, degenerate inputs, and purity (no mutation of the input beats). Verified live: the ruler shows per-bar BPM chips tracking AC/DC's imported tempo drift + a 4/4 meter chip, no errors. npm test 115 green, lint 0 errors. Independent of the in-review queue (deps only merged ruler #209/#211). Keep-both seam with #220 on tempo.js. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
…10) (#227) Charrette arch 5a / rhythm G5 / trans P6-prep (design slice 2a). Sparse labeled chips on the ruler show where the tempo and meter change through the song. ZERO storage — markers are a PURE function of S.beats (the executable truth, never a second source): _tempoMarkersPure derives tempo-change markers where a measure's BPM leaves the current run beyond 0.01 (the same constant _tempoHasMultipleMeasureBpmsPure uses) and meter markers where the numerator / den changes, with a bar-1 baseline of each and no spurious marker on a trailing partial bar. Memoized on editGen (+ the S.beats ref). Painted in the ruler band via timeToX only (the D-T1 invariant); tempo chips (cyan) and meter chips (violet) stack when they share a barline. Marker fields are never smuggled into S.beats entries. Filed feedpak-spec#51 for the AUTHORED markers the grid can't express (tempo ramps, meter groupings, fermata holds, lock/provenance) — slice 2b, range-provenance and community maps gate on it; not built here. tests/tempo_markers.test.mjs (7): baseline-only on a constant map, tempo-change detection, within-tolerance run behaviour, mid-song meter change + trailing- partial suppression, denominator change, degenerate inputs, and purity (no mutation of the input beats). Verified live: the ruler shows per-bar BPM chips tracking AC/DC's imported tempo drift + a 4/4 meter chip, no errors. npm test 115 green, lint 0 errors. Independent of the in-review queue (deps only merged ruler #209/#211). Keep-both seam with #220 on tempo.js. Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q Co-authored-by: ChrisBeWithYou <chris@rifflarr.local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: byrongamatos <xasiklas@gmail.com>
What
Workspace-shell B3 (charrette §2.5 / D-C5): the three time-surfaces — the floating HTML loop strip, the waveform band's seek role, and the bottom beat bar — consolidate into a canvas timeline header in the charrette's locked order (transport+LCD → ruler → waveform → lanes → sub-lanes):
_loopEdgeAdjustPurethe strip used). Lower half = scrub (press/drag the playhead; playback resumes after). Measure numbers skip in powers of two instead of colliding at zoom-out; sub-beat ticks appear once a beat clears 6px. Section names moved here from the lane area (the dashed boundary lines stay in the chart as guides).Everything the ruler paints is a pure function of
S.beatsthroughtimeToX— the D-T1 invariant as chrome.What moved where
S.barSel, same beat-anchored edges (A4), same commands, same undo, same Loop-in-3D handoff. Only the surface moved._renderLoopStripsurvives as the "loop surface changed → refresh mirrors + repaint" hook (tempo/grid ops call it), minus the DOM.Transport ▸ Loopmenu rows (newloopSnap/loopClearitem kinds resolve checkmarks at open time, like everything else in the B4 menu).Alt+←/→nudges the loop start,Alt+Shift+←/→the end (same_loopNudgeEdge, same mode resolution; direct input.js handling per the drum-velocity-nudge precedent).The layout shift
Every band below the header moves down by a fixed
TIMELINE_TOP(40px):laneToY/yToLane/midiToYcarry it, the retired beat bar'sBEAT_His deleted (lanes reclaim its height),_beatBarTopY()keeps its name as the lanes-bottom anchor every sub-lane and hit-test already uses. Hit-testing migrated: the header consumes every press aboveTIMELINE_TOPin every mode (the old strip was mode-independent DOM — drum/tempo modes keep their loop editing); drum/tempo/parts/keys-gutter/dblclick gates all shifted and suite-verified.Tests
tests/ruler.test.mjs(13, real-import ESM; fails on main —src/ruler.jsdoesn't exist): zone split, minimap map round-trip + clamps + degenerate durations, label-skip, edge-grab resolution, and interaction round-trips against the realS(loop create carries the mode, edge drag resolves to whole bars, scrub tracks + clamps, minimap pans, header consumption, Shift=free). Geometry/waveform/gutter suites updated where they pinned the old y-space (sliced suites injectTIMELINE_TOP=0so their fixtures stay exact);menu_modelpasses unchanged.Full suite 102/102; ESLint clean (0 errors, warn-ratchet unchanged).
Expected keep-both seams vs the other open PRs: CHANGELOG (all of #205–#208),
src/menu-bar.jsdispatch/menu rows (#205),screen.html(#205).🤖 Generated with Claude Code
https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Summary by CodeRabbit
New Features
Improvements
Tests