Skip to content

feat(editor): barline multi-select + marquee + bulk delete (tempo PR 5a) - #226

Merged
byrongamatos merged 1 commit into
mainfrom
feat/editor-tempo-multiselect
Jul 12, 2026
Merged

feat(editor): barline multi-select + marquee + bulk delete (tempo PR 5a)#226
byrongamatos merged 1 commit into
mainfrom
feat/editor-tempo-multiselect

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Tempo-mapping PR 5a (charrette UX P5 / arch 1, first half). Select and delete multiple Tempo Map barlines at once — without disturbing the single-focus model (S.tempoSel, which inspector / tap / lock / modulate / suggest all key on).

Selection

  • New S.tempoSelMulti: Set<downbeatIdx> (the S.drumSel pattern), separate from tempoSel.
  • Shift+click a pole → the contiguous downbeat range.
  • Drag on empty grid → a tempo-marquee (the drum editor's deferred-3px moved idiom) that box-selects downbeats in its swept X range — plain replaces, Shift unions.
  • Ctrl+A → every downbeat.
  • The set is index-based, so it's cleared — never remapped — on any topology change (TempoGridCmd exec/rollback) and on Tempo Map exit.

Render

A light amber wash across the selected range; selected poles read amber (the focus keeps its unique halo). Rides the existing draw pass, no per-frame allocation.

Bulk delete

One TempoGridCmd: _tempoDeleteBarlinesPure demotes every selected interior downbeat (never the first/last — the existing guard generalized to a set) + one renumber. Via Del and right-click ▸ "Delete N barlines". Escape clears the selection, layered under #215's suggest-dismiss.

Tests & verification

  • tests/tempo_multiselect.test.mjs (6): marquee hit math, the delete transform (demote+renumber + first/last guard), the bulk-delete round-trip through the command, the exec/rollback set-clearing contract, and range selection.
  • Verified live on the testbed: Ctrl+A"105 barlines selected" (amber render, screenshot), Escape clears, a marquee box-selects, Ctrl+A+Del"Deleted 103 barlines" (first/last kept), no console errors.
  • npm test 115 green, npm run lint 0 errors (3 pre-existing warnings = main's ratchet).

Independent of the in-review queue (depends only on merged #215). Expect keep-both seams with #220 (_tempoMapDraw) and #218/#225 on the shared tempo.js. PR 5b (group drag) and PR 8 (range ops) stack on this.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Tempo Map barline multi-selection via Shift+click contiguous range, rubber-band marquee drag, or Ctrl+A.
    • Multi-selection is visually highlighted (amber wash + marquee).
    • Bulk delete selected interior barlines (including Delete/Backspace) runs as a single undoable step; first/last are preserved as map bounds, with a context menu action when applicable.
  • Bug Fixes
    • Escape now clears Tempo Map multi-selection, and selections correctly reset/deselect on mode changes, topology updates, and undo/redo.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Tempo Map now supports selecting multiple downbeat barlines with Shift-click, marquee selection, or Ctrl+A, displaying the selection in amber and deleting it as one undoable operation while preserving map bounds.

Changes

Tempo Map multiselect

Layer / File(s) Summary
Selection state and keyboard controls
src/state.js, src/input.js
Adds tempoSelMulti and supports Escape clearing, Ctrl+A selection, and bulk Delete/Backspace handling.
Mouse selection and visual feedback
src/mouse.js, src/tempo.js
Adds Shift-click ranges, grid marquee selection, drag lifecycle handling, amber selection rendering, and mode-scoped selection clearing.
Bulk deletion and undo lifecycle
src/tempo.js, CHANGELOG.md
Adds context-menu and helper flows for deleting interior barlines in one undoable command while preserving boundary barlines and clearing selection state across topology changes and undo.
Multiselect behavior tests
tests/tempo_multiselect.test.mjs
Tests marquee hit detection, range selection, deletion semantics, undo restoration, fallback deletion, and selection lifecycle behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Input
  participant TempoMap
  participant TempoGridCmd
  participant UndoRedo
  User->>Input: Shift-click, marquee, Ctrl+A, or Delete
  Input->>TempoMap: Update or delete tempoSelMulti
  TempoMap->>TempoGridCmd: Create one grid command
  TempoGridCmd->>UndoRedo: Record topology change
  UndoRedo-->>TempoMap: Clear selection on undo or redo
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 Tempo Map multi-select, marquee selection, and bulk delete changes.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-tempo-multiselect

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: 2

🧹 Nitpick comments (2)
tests/tempo_multiselect.test.mjs (2)

82-88: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert focus cleanup in the single-delete fallback.

This test should verify that S.tempoSel is reset after deletion; otherwise stale focus could survive while all current assertions still pass.

Suggested assertion
     _tempoDeleteSelection();
     assert.strictEqual(S.beats[8].measure, -1, 'the focused barline was demoted');
     assert.strictEqual(S.history.undo.length, 1);
+    assert.strictEqual(S.tempoSel, -1, 'single focus cleared after delete');
🤖 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/tempo_multiselect.test.mjs` around lines 82 - 88, Extend the test for
_tempoDeleteSelection() in the with-nothing-multi-selected case to assert that
S.tempoSel is reset after the focused beat is deleted. Keep the existing beat
demotion and undo-history assertions unchanged.

103-107: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover reverse endpoint order for range selection.

Add the (12, 4) case so the test protects the bidirectional Shift-click behavior.

Suggested test extension
     _tempoSelectDownbeatRange(4, 12);
     assert.deepStrictEqual([...S.tempoSelMulti].sort((a, b) => a - b), [4, 8, 12], 'downbeats 4,8,12 (not the sub-beats)');
+    S.tempoSelMulti.clear();
+    _tempoSelectDownbeatRange(12, 4);
+    assert.deepStrictEqual([...S.tempoSelMulti].sort((a, b) => a - b), [4, 8, 12], 'reverse range selects the same downbeats');
🤖 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/tempo_multiselect.test.mjs` around lines 103 - 107, Extend the
_tempoSelectDownbeatRange test to also invoke the range selection with reversed
endpoints (12, 4), and assert that S.tempoSelMulti contains the same contiguous
downbeats [4, 8, 12]. Preserve the existing forward-order assertion.
🤖 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/tempo.js`:
- Around line 787-789: Restrict the tempo-marquee initialization in the
empty-grid pointer handling branch to presses whose y-coordinate falls within
the grid’s vertical bounds. Preserve the existing waveform guard and
stationary-versus-drag behavior, while preventing blank areas such as the Tempo
HUD from creating or changing marquee selections.
- Around line 251-257: Update the barline styling in the rendering logic around
ctx.strokeStyle and ctx.fillStyle so poles included in S.tempoSelMulti receive
the amber selection treatment before the locked state is applied. Ensure
multi-selected locked poles visibly use the selection color and focus halo,
while preserving existing locked, hover, and unselected styling for other poles.

---

Nitpick comments:
In `@tests/tempo_multiselect.test.mjs`:
- Around line 82-88: Extend the test for _tempoDeleteSelection() in the
with-nothing-multi-selected case to assert that S.tempoSel is reset after the
focused beat is deleted. Keep the existing beat demotion and undo-history
assertions unchanged.
- Around line 103-107: Extend the _tempoSelectDownbeatRange test to also invoke
the range selection with reversed endpoints (12, 4), and assert that
S.tempoSelMulti contains the same contiguous downbeats [4, 8, 12]. Preserve the
existing forward-order assertion.
🪄 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: fae39895-5ec4-486b-8dca-75e824ef6f08

📥 Commits

Reviewing files that changed from the base of the PR and between 2a764a1 and 0bfe46d.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • src/input.js
  • src/mouse.js
  • src/state.js
  • src/tempo.js
  • tests/tempo_multiselect.test.mjs

Comment thread src/tempo.js
Comment thread src/tempo.js
Charrette UX P5 / arch 1 (first half). Select and delete multiple Tempo Map
barlines at once, without touching the single-focus model (S.tempoSel — which
inspector / tap / lock / modulate / suggest all key on).

- New S.tempoSelMulti: Set<downbeatIdx> (the S.drumSel pattern). Shift+click a
  pole extends the contiguous downbeat range; a drag on empty grid arms a
  tempo-marquee (the drum editor's deferred-3px `moved` idiom) that box-selects
  downbeats in its swept X range (plain replaces, Shift unions); Ctrl+A in mode
  selects every downbeat. The set is index-based, so it is CLEARED — never
  remapped — in TempoGridCmd exec/rollback and on Tempo Map exit.
- Render: a light amber wash across the selected range, and selected poles read
  amber (the focus keeps its unique halo). Rides the existing draw pass.
- Bulk delete = ONE TempoGridCmd: _tempoDeleteBarlinesPure demotes every selected
  INTERIOR downbeat (never the first/last — the existing guard, generalized to a
  set) + one renumber. Reachable via Del and right-click "Delete N barlines".
- Escape clears the selection, layered UNDER #215's suggest-dismiss (ghosts own
  Escape while showing).

tests/tempo_multiselect.test.mjs (6): marquee hit math, the delete transform
(demote+renumber, first/last guard), the bulk-delete round-trip through the
command, the exec/rollback set-clearing contract, and range selection. Verified
live: Ctrl+A → "105 barlines selected" (amber render), Escape clears, a marquee
box-selects, Ctrl+A+Del → "Deleted 103 barlines" (first/last kept), no errors.
npm test 115 green, lint 0 errors.

Independent of the in-review queue (deps only on merged #215); keep-both seams
with #220 (_tempoMapDraw) and #218/#225 on the shared tempo.js.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
@ChrisBeWithYou
ChrisBeWithYou force-pushed the feat/editor-tempo-multiselect branch from 393a2a0 to f342417 Compare July 12, 2026 20:06

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/tempo.js (1)

930-945: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Show the actual deletable count.

Ctrl+A includes the first and last downbeats, but _tempoDeleteSelection() filters them out. The menu can offer “Delete 5 barlines” while deleting only 3. Count valid interior selections for the label, and suppress/disable the bulk action when none are deletable.

🤖 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/tempo.js` around lines 930 - 945, Update the context-menu logic around
_tempoDeleteSelection and the nMulti count to derive the bulk-delete count from
valid interior selections, excluding the first and last downbeats. Use that
filtered count in the “Delete N barlines” label, and suppress or disable the
delete-multi action when the count is zero while preserving the single-delete
behavior.
🤖 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/tempo.js`:
- Around line 276-290: Update the tempoSelMulti rendering block to group
selected barlines into contiguous downbeat runs instead of using one global
minT/maxT range. Render a separate amber wash for each run, preserving the
existing timeToX bounds, fill styling, and canvas region so gaps between
disjoint selections remain unpainted.

---

Outside diff comments:
In `@src/tempo.js`:
- Around line 930-945: Update the context-menu logic around
_tempoDeleteSelection and the nMulti count to derive the bulk-delete count from
valid interior selections, excluding the first and last downbeats. Use that
filtered count in the “Delete N barlines” label, and suppress or disable the
delete-multi action when the count is zero while preserving the single-delete
behavior.
🪄 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: ba1e64eb-6a99-41ba-b480-77623317f9d4

📥 Commits

Reviewing files that changed from the base of the PR and between 6e8325d and f342417.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • src/input.js
  • src/mouse.js
  • src/state.js
  • src/tempo.js
  • tests/tempo_multiselect.test.mjs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/state.js
  • CHANGELOG.md
  • src/mouse.js

Comment thread src/tempo.js
Comment on lines +276 to +290
// Multi-selected barlines (PR 5a): a light amber wash spanning the range
// between the outermost selected downbeats (the existing halo grammar).
if (S.tempoSelMulti && S.tempoSelMulti.size) {
let minT = Infinity, maxT = -Infinity;
for (const i of S.tempoSelMulti) {
const b = S.beats[i];
if (b && b.measure > 0) { if (b.time < minT) minT = b.time; if (b.time > maxT) maxT = b.time; }
}
if (minT <= maxT) {
const xa = Math.max(LABEL_W, timeToX(minT)), xb = Math.min(w, timeToX(maxT));
if (xb > xa) {
ctx.fillStyle = 'rgba(251,191,36,0.10)';
ctx.fillRect(xa, (TIMELINE_TOP + WAVEFORM_H), xb - xa, gridBottom - (TIMELINE_TOP + WAVEFORM_H));
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Render disjoint selections as separate washes.

Shift-marquee unions can create disjoint selections, but this min/max fill paints the gaps as selected. Split the wash by contiguous downbeat runs so deletion feedback matches the actual selected barlines.

🤖 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/tempo.js` around lines 276 - 290, Update the tempoSelMulti rendering
block to group selected barlines into contiguous downbeat runs instead of using
one global minT/maxT range. Render a separate amber wash for each run,
preserving the existing timeToX bounds, fill styling, and canvas region so gaps
between disjoint selections remain unpainted.

@byrongamatos
byrongamatos merged commit 91620f9 into main Jul 12, 2026
4 checks passed
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