feat(editor): bulk barline locks + band-aware Tempo Map selection - #291
Merged
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
This was referenced Jul 15, 2026
ChrisBeWithYou
force-pushed
the
feat/editor-metronome-guide
branch
from
July 16, 2026 03:54
cce7ca2 to
95b0219
Compare
ChrisBeWithYou
force-pushed
the
feat/editor-tempo-bulk-locks
branch
from
July 16, 2026 03:54
a8235fa to
e0c109c
Compare
ChrisBeWithYou
pushed a commit
that referenced
this pull request
Jul 16, 2026
…t tree Makes the track-session tree (from the foundation PR) a first-class UI: a resizable header column beside the timeline, one header cell per lane the canvas draws — both surfaces read the SAME pure geometry (_trackSessionLaneLayoutPure / fitted heights), so they can never drift. Per row: inline rename, drag reorder + folders, lane-height resize, mute/solo/volume (the SAME canonical S.partMix the mixer panel owns — a new mixerTogglePart/mixerSetPart seam keeps that panel the sole owner), pair a transcription with its studio stem (writes the one pairing store, S.stemLinks, and syncs via /stem-op op 'links'), double-click to open the native editor, and a stem row-menu to lock the metronome guide. Audio removal is non-destructive (a removedSourceIds tombstone). parts-view.js is rewritten to draw unified lanes matching the header rows (shared layout, shared hit-test _partsTrackRowAtYPure); the in-canvas gutter shrinks to LABEL_W since the DOM column now owns names/controls. partsViewMode becomes the one-way landing surface; leaving happens by OPENING a transcription (host.openTrackSessionTarget). A vertical wheel over the area scrolls the shared lane stack; horizontal still pans. Scoped deliberately: no per-lane stem waveforms or meters and no audio strips on audio rows — stem playback is the engine slice (next PR). The host hooks (trackWaveform, scrollTrackArea) land inert-but-honest: the master lane draws from S.waveformPeaks today, stems light up with the engine. Tests: new track_session_ui.test.mjs (lane geometry/density/drop/rename pures), a unified-row hit-test case in parts_view.test.js (its @pure slice now strips `export`), two responsive/rename CSS-contract cases in theme_chrome.test.mjs; the shared history env's fake element gained a faithful DOM-method surface. 182 JS suites, 291 pytest, lint 0 errors. Stacked on feat/editor-tempo-bulk-locks (#291). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
ChrisBeWithYou
pushed a commit
that referenced
this pull request
Jul 16, 2026
_beatLockStorageKeyPure returned the shared key `editorBeatLocks:` for a project with no filename, so locks set while charting one new import leaked into the NEXT new import (both keyed by ''). It now returns null for an empty filename; _saveBeatLocks / _restoreBeatLocks treat null as "nowhere to persist" and scrub any legacy blank-key residue, so a fresh project always starts with a clean lock set. A straggler from #291 (bulk barline locks) — same beat-lock code, missed in the re-cut. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
ChrisBeWithYou
force-pushed
the
feat/editor-metronome-guide
branch
from
July 16, 2026 03:57
95b0219 to
31cc404
Compare
ChrisBeWithYou
force-pushed
the
feat/editor-tempo-bulk-locks
branch
from
July 16, 2026 03:57
e0c109c to
4f00d59
Compare
ChrisBeWithYou
pushed a commit
that referenced
this pull request
Jul 16, 2026
…t tree Makes the track-session tree (from the foundation PR) a first-class UI: a resizable header column beside the timeline, one header cell per lane the canvas draws — both surfaces read the SAME pure geometry (_trackSessionLaneLayoutPure / fitted heights), so they can never drift. Per row: inline rename, drag reorder + folders, lane-height resize, mute/solo/volume (the SAME canonical S.partMix the mixer panel owns — a new mixerTogglePart/mixerSetPart seam keeps that panel the sole owner), pair a transcription with its studio stem (writes the one pairing store, S.stemLinks, and syncs via /stem-op op 'links'), double-click to open the native editor, and a stem row-menu to lock the metronome guide. Audio removal is non-destructive (a removedSourceIds tombstone). parts-view.js is rewritten to draw unified lanes matching the header rows (shared layout, shared hit-test _partsTrackRowAtYPure); the in-canvas gutter shrinks to LABEL_W since the DOM column now owns names/controls. partsViewMode becomes the one-way landing surface; leaving happens by OPENING a transcription (host.openTrackSessionTarget). A vertical wheel over the area scrolls the shared lane stack; horizontal still pans. Scoped deliberately: no per-lane stem waveforms or meters and no audio strips on audio rows — stem playback is the engine slice (next PR). The host hooks (trackWaveform, scrollTrackArea) land inert-but-honest: the master lane draws from S.waveformPeaks today, stems light up with the engine. Tests: new track_session_ui.test.mjs (lane geometry/density/drop/rename pures), a unified-row hit-test case in parts_view.test.js (its @pure slice now strips `export`), two responsive/rename CSS-contract cases in theme_chrome.test.mjs; the shared history env's fake element gained a faithful DOM-method surface. 182 JS suites, 291 pytest, lint 0 errors. Stacked on feat/editor-tempo-bulk-locks (#291). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
ChrisBeWithYou
pushed a commit
that referenced
this pull request
Jul 16, 2026
_beatLockStorageKeyPure returned the shared key `editorBeatLocks:` for a project with no filename, so locks set while charting one new import leaked into the NEXT new import (both keyed by ''). It now returns null for an empty filename; _saveBeatLocks / _restoreBeatLocks treat null as "nowhere to persist" and scrub any legacy blank-key residue, so a fresh project always starts with a clean lock set. A straggler from #291 (bulk barline locks) — same beat-lock code, missed in the re-cut. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
ChrisBeWithYou
pushed a commit
that referenced
this pull request
Jul 16, 2026
_beatLockStorageKeyPure returned the shared key `editorBeatLocks:` for a project with no filename, so locks set while charting one new import leaked into the NEXT new import (both keyed by ''). It now returns null for an empty filename; _saveBeatLocks / _restoreBeatLocks treat null as "nowhere to persist" and scrub any legacy blank-key residue, so a fresh project always starts with a clean lock set. A straggler from #291 (bulk barline locks) — same beat-lock code, missed in the re-cut. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Lock/unlock a whole barline selection as ONE undoable edit. The new
TempoLockCmd is a first-class exec/rollback command (replacing the old
toggle-plus-checkpoint): entries re-resolve by time (±0.02s) so undo
survives a grid rebuild, every apply re-persists the editor-pref lock
list, and the new `sessionNeutral` history flag keeps lock toggles out
of markSessionDirty — undoable, but never a false Save prompt.
The plan is uniform and uncapped: multi-selection wins over the single
focus, target state = lock-all unless every selected barline is already
locked, no size limit (Ctrl/Cmd+A then S locks the whole song). The
tempo toolbar gains a lock button whose face derives from the same plan
the verb executes ("Lock 12 barlines"), the right-click menu's lock item
is group-aware (right-click outside the group collapses to the clicked
barline), and Ctrl/Cmd-click toggles individual poles.
Selection ergonomics: the marker lane is split into a top HANDLE band
(direct edits — forgiving pole grabs, sub-beat rubato) and the lane body
below (marquee territory) — while the thin full-height barline keeps a
precise 2.5px drag target anywhere and a held selection modifier
restores the forgiving width. Marquee finalize now routes through a pure
selector that focuses the drag end. Hover mirrors the same band-aware
tolerance so the cursor never promises a grab the click would route to
the marquee.
Stacked on feat/editor-metronome-guide (#290).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
byrongamatos
force-pushed
the
feat/editor-tempo-bulk-locks
branch
from
July 16, 2026 18:30
4f00d59 to
6d351f9
Compare
byrongamatos
pushed a commit
that referenced
this pull request
Jul 16, 2026
…t tree Makes the track-session tree (from the foundation PR) a first-class UI: a resizable header column beside the timeline, one header cell per lane the canvas draws — both surfaces read the SAME pure geometry (_trackSessionLaneLayoutPure / fitted heights), so they can never drift. Per row: inline rename, drag reorder + folders, lane-height resize, mute/solo/volume (the SAME canonical S.partMix the mixer panel owns — a new mixerTogglePart/mixerSetPart seam keeps that panel the sole owner), pair a transcription with its studio stem (writes the one pairing store, S.stemLinks, and syncs via /stem-op op 'links'), double-click to open the native editor, and a stem row-menu to lock the metronome guide. Audio removal is non-destructive (a removedSourceIds tombstone). parts-view.js is rewritten to draw unified lanes matching the header rows (shared layout, shared hit-test _partsTrackRowAtYPure); the in-canvas gutter shrinks to LABEL_W since the DOM column now owns names/controls. partsViewMode becomes the one-way landing surface; leaving happens by OPENING a transcription (host.openTrackSessionTarget). A vertical wheel over the area scrolls the shared lane stack; horizontal still pans. Scoped deliberately: no per-lane stem waveforms or meters and no audio strips on audio rows — stem playback is the engine slice (next PR). The host hooks (trackWaveform, scrollTrackArea) land inert-but-honest: the master lane draws from S.waveformPeaks today, stems light up with the engine. Tests: new track_session_ui.test.mjs (lane geometry/density/drop/rename pures), a unified-row hit-test case in parts_view.test.js (its @pure slice now strips `export`), two responsive/rename CSS-contract cases in theme_chrome.test.mjs; the shared history env's fake element gained a faithful DOM-method surface. 182 JS suites, 291 pytest, lint 0 errors. Stacked on feat/editor-tempo-bulk-locks (#291). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
byrongamatos
added a commit
that referenced
this pull request
Jul 16, 2026
…t tree (#292) * feat(editor): the Tracks area — a DAW track column over the persistent tree Makes the track-session tree (from the foundation PR) a first-class UI: a resizable header column beside the timeline, one header cell per lane the canvas draws — both surfaces read the SAME pure geometry (_trackSessionLaneLayoutPure / fitted heights), so they can never drift. Per row: inline rename, drag reorder + folders, lane-height resize, mute/solo/volume (the SAME canonical S.partMix the mixer panel owns — a new mixerTogglePart/mixerSetPart seam keeps that panel the sole owner), pair a transcription with its studio stem (writes the one pairing store, S.stemLinks, and syncs via /stem-op op 'links'), double-click to open the native editor, and a stem row-menu to lock the metronome guide. Audio removal is non-destructive (a removedSourceIds tombstone). parts-view.js is rewritten to draw unified lanes matching the header rows (shared layout, shared hit-test _partsTrackRowAtYPure); the in-canvas gutter shrinks to LABEL_W since the DOM column now owns names/controls. partsViewMode becomes the one-way landing surface; leaving happens by OPENING a transcription (host.openTrackSessionTarget). A vertical wheel over the area scrolls the shared lane stack; horizontal still pans. Scoped deliberately: no per-lane stem waveforms or meters and no audio strips on audio rows — stem playback is the engine slice (next PR). The host hooks (trackWaveform, scrollTrackArea) land inert-but-honest: the master lane draws from S.waveformPeaks today, stems light up with the engine. Tests: new track_session_ui.test.mjs (lane geometry/density/drop/rename pures), a unified-row hit-test case in parts_view.test.js (its @pure slice now strips `export`), two responsive/rename CSS-contract cases in theme_chrome.test.mjs; the shared history env's fake element gained a faithful DOM-method surface. 182 JS suites, 291 pytest, lint 0 errors. Stacked on feat/editor-tempo-bulk-locks (#291). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q * Preserve transcription identity across track edits * Guard inline transcription renames * Restore tombstoned audio tracks * fix(editor): keep Tracks-column fader draggable and invalidate render on audioUrl Two bugs in the new Tracks header column: - Dragging a per-track fader rebuilt the panel's innerHTML on every `input` (via host.partMixChanged → refreshTrackSession, whose memo key includes S.partMix), destroying the <input type=range> under the pointer and aborting the native drag. Suppress the rebuild for the synchronous span of a header-column fader input — the fader already reflects its own value, the same way the mixer panel avoids re-rendering itself from its fader input. - The refresh memo key omitted S.audioUrl even though render() derives the Master Mix source from it (_liveSources). A late audio load that set S.audioUrl without touching S.stems did not invalidate the memo, leaving the column missing the Master Mix until an unrelated refresh. Add S.audioUrl to the key. (Reported by Codex first pass, P2.) Regression tests in tests/track_session_fader_drag.test.mjs cover both: the fader input must not rebuild innerHTML; an S.audioUrl-only change must. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(editor): address CodeRabbit findings on the Tracks column (PR #292) Data integrity: - _trackSessionNormalizePure keeps every persisted removed-source tombstone even when its source is not yet loaded, so a session installed before its audio arrives (S.audioUrl set late) no longer loses a `master` tombstone and resurrects the Master Mix row once the audio loads. - deleteTrack shifts the surviving `arr:<n>` mix strips down one slot on a transcription delete (new _partMixDropArrangementPure) instead of wiping S.partMix, preserving every other track's mute/solo/volume. - _syncPairing stamps each full-snapshot write and ignores responses a later pairing has superseded, so a slow earlier response can't clobber the newer S.stemLinks selection. Functional correctness: - mixerSetPart refreshes the mixer panel so its companion fader tracks a Tracks-column fader change (symmetry with mixerTogglePart). - _editorTogglePartsView gates entry on the unified rows, letting an audio-only session (audio before any arrangement/drum) into Tracks. - initTrackSession resizes the canvas after applying a restored non-default header width so hit geometry isn't left stale. Docs: resolve the contradictory Unreleased changelog notes. Regression tests (fail pre-fix, pass after): - tests/track_session.test.mjs: late-audio tombstone; partMix key shift. - tests/track_session_pairing_race.test.mjs: stale pairing response ignored. Skipped (not valid): canvas lane selection already arms the arrangement target inline (host.selectTrackSessionTarget is a no-op for drums and redundant for arrangements). Skipped (out of scope): full keyboard resize/reorder is a net-new a11y interaction model, not a correctness fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: ChrisBeWithYou <chris@rifflarr.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: byrongamatos <xasiklas@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
PR 3 of the #285 re-cut (stacked on #290 → #289 → main; retarget as parents merge; CI fires on retarget to main — local gates green). The marquee-reliability and bulk-lock features, rebuilt on main's existing multi-select infrastructure.
The features
TempoLockCmdmakes lock/unlock a first-class exec/rollback command: select any number of barlines (marquee, Shift-click, Ctrl/Cmd-click, Ctrl/Cmd+A — uncapped) and theSkey / toolbar button / right-click menu applies one uniform state (lock all unless every one is already locked) as one undoable edit. Entries re-resolve by time so undo survives a grid rebuild.sessionNeutralhistory flag (new, inhistory.js): lock toggles belong in Undo but are an editor preference, not pack data — they now skipmarkSessionDirty, so locking barlines never creates a false Save prompt._afterEdit(cmd)threads the command through exec/undo/redo.What was already on main (not re-ported)
The marquee drag machinery, Shift-click range extend, Ctrl/Cmd+A select-all, Escape-clears, and group drag all landed in the earlier multi-select wave — this PR layers planners + the undo command + the hit-band refinement on top.
Tests
8 new cases in
tests/tempo_multiselect.test.mjs: the handle band, the dual grab tolerance, marquee replace/extend + drag-end focus, non-mutating Ctrl-toggle, the uniform lock plan (mixed → lock-all; all → unlock-all; single-focus fallback), the no-cap contract (512 barlines), the button-face pure, and the full exec → one-undo → redo round-trip assertingS.sessionDirty === false. (The shared test env's fake elements gained no-opclassList— the signpost cue path needed it.)Gates: 182 JS suites green, pytest 291/2 skipped, lint 0 errors / 3 baseline warnings.
Remaining from the salvage queue
Tracks lanes UI → engine/meters (evaluate).
🤖 Generated with Claude Code
https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q