← Back to README • Architecture
Comprehensive guide to selection mechanics, multi-selection patterns, and the 2D selection box model.
See also: Keyboard Navigation • Commands • Data Model • Coding Patterns • ADR-001: Vertical Selection
The Selection object tracks the current editing focus:
Selection {
staffIndex: number // Active staff (0 = treble, 1 = bass)
measureIndex: number | null // null when at ghost cursor
eventId: string | null // Selected event
noteId: string | null // Selected note within chord
selectedNotes: SelectedNote[] // Multi-selection array
anchor?: SelectedNote | null // Range selection anchor
verticalAnchors?: { // Vertical extension state
direction: 'up' | 'down'
sliceAnchors: Record<time, SelectedNote>
originSelection: SelectedNote[]
}
}| State | Detection | Meaning |
|---|---|---|
| Ghost Cursor | eventId === null (measureIndex === null OR previewNote !== null) |
Ready to insert new note |
| Single Selection | selectedNotes.length === 1 |
One note highlighted |
| Multi-Selection | selectedNotes.length > 1 |
Multiple notes selected |
| Range Active | anchor !== null |
Shift+navigation in progress |
| Action | Result |
|---|---|
| Click note | Select that note |
| Cmd+Click | Toggle note in multi-selection |
| Shift+Click | Select range from anchor |
| Click empty space | Place ghost cursor |
| Shortcut | Behavior |
|---|---|
← / → |
Move cursor/selection horizontally |
Shift + ←/→ |
Extend selection horizontally |
Cmd + Shift + ↑/↓ |
Extend selection vertically |
Cmd + A |
Progressive scope expansion |
Esc |
Clear selection |
Click and drag on empty space to create a rectangular selection area. All notes within the rectangle are selected.
Both horizontal and vertical selection use an anchor-based model:
Anchor ←————→ Cursor
^ ^
Fixed point Movable endpoint
- Anchor: Set on first Shift+navigation
- Cursor: Moves with each subsequent navigation
- Range: All notes between anchor and cursor are selected
Created by Shift+Left/Right. Selects events in timeline order.
Created by Cmd+Shift+Up/Down. Each time slice maintains an independent anchor:
Time 0: [Anchor: G4] ←→ [Cursor: C3]
Time 96: [Anchor: E4] ←→ [Cursor: G2]
This allows extending multiple chords simultaneously while maintaining predictable expand/contract behavior.
Vertical selection treats the score as a 2D grid:
- X (Time):
measureIndex × 100000 + quantPosition - Y (Vertical Metric):
(100 - staffIndex) × 1000 + midiPitch
| Note | Staff | MIDI | Metric |
|---|---|---|---|
| G4 (treble) | 0 | 67 | 100,067 |
| C4 (treble) | 0 | 60 | 100,060 |
| C3 (bass) | 1 | 48 | 99,048 |
| C2 (bass) | 1 | 36 | 99,036 |
This ensures all treble notes are "above" all bass notes, and within each staff, higher pitches have higher values.
| Command | Trigger | Purpose |
|---|---|---|
SelectEventCommand |
Click | Select single note/event |
RangeSelectCommand |
Shift+Click | Select range to clicked note |
ToggleNoteCommand |
Cmd+Click | Add/remove from multi-selection |
SelectAllInEventCommand |
Click chord body | Select all notes in chord |
ExtendSelectionVerticallyCommand |
Cmd+Shift+↑/↓ | Vertical expand/contract |
ExtendSelectionHorizontallyCommand |
Shift+←/→ | Horizontal expand/contract |
SelectAllCommand |
Cmd+A | Progressive scope expansion |
SelectMeasureCommand |
Double-click measure | Select all in measure |
LassoSelectCommand |
Drag | Select by rectangle |
ClearSelectionCommand |
Esc | Clear all selection |
Selection state is managed by a dedicated engine, separate from the ScoreEngine:
graph TD
A[User Action] --> B{Selection or Mutation?}
B -->|Selection| C[SelectionEngine.dispatch]
B -->|Mutation| D[ScoreEngine.dispatch]
C --> E[New Selection State]
D --> F[New Score State]
- No Undo for Selection: Cursor movement shouldn't pollute undo history
- Ephemeral State: Selection is transient, score is persistent
- Independent History: Enable future "selection undo" if needed
| File | Purpose |
|---|---|
| SelectionEngine.ts | State management |
| selection/ | All selection commands |
| verticalStack.ts | Vertical metrics & stacks |
| selection.ts | Selection utilities |