- Removed aiPrompt/aiLoading state and handleAIPrompt from PianoRollTabEditor
- Removed AI prompt input UI from piano roll toolbar
- handleAISend now detects active piano roll tab and routes
prompts to MIDI note generation, appends notes to the tab
- Initial click note pitch now snaps when snapToScale is on
- Added snapToScaleRef and selectedScaleRef for stale-closure safety
- All brush-draw snap paths (mousedown + mousemove) use refs
- Added snapToScale state + toggle switch in piano roll toolbar
- snapPitchToScale only applies when snapToScale is true
- Removed Select/Pen/Eraser tool buttons
- Right-click drag erase sweep sets swallowContextMenuRef=true
so context menu won't appear during sweep
- Separate swallowContextMenuRef (not tied to rightClickDragRef) so
menu is skipped on right-click delete
- Initial click draw uses raw pitch, not snapPitchToScale (scale only
applies during brush-drag in mousemove)
Right-click on note = delete.
Right-click empty space (no drag) = context menu only (no erase sweep).
Right-click empty space + drag >5px = erase sweep (starts on mousemove).
Uses rightClickDragRef to track pending right-click state.
ActiveRollTool default changed from 'select' to 'pen' so clicking
empty space draws notes immediately. Users who switch tool must
switch back to 'pen' to draw.
Sub-menu items now render as a standalone fixed-position panel
at origin.x+150, not mixed into the main menu. Main menu stays
at its initial position. Hover to open, click to select scale.
- Context menu (Scales) appears at right-click position on grid canvas
- Removed erase sweep on right-click (blocked brush after menu close)
- Removed keybed onContextMenu (was wrong position)
- Store menu origin ref so sub-menus don't drift on hover
- Brush (pen tool only) draws notes snapped to selected scale
- Scale sub-menus open on hover instead of click
- Keybed: swipe/drag across keys plays notes (mouseenter+mousedown)
- Global mouseup resets keybed drag state
When section duration is resized shorter, sub-track clips and MIDI
notes were still drawn outside the section's visual rectangle.
Added ctx.save/clip/restore around sub-track rendering.
6 render positions (clip, marker, section, MIDI, note, selection) were
using React prop scrollLeft instead of DOM scrollLeftVal, causing
grid-vs-item desync during fast auto-scroll.
- PADDING_LEFT=0 in all 3 components (grid aligns with clip origin)
- TempoTrackLane/WaveformLane tEnd uses CLIP_BUFFER/zoom so bars render past right edge
- TimelineRuler tEnd uses CLIP_BUFFER/zoom instead of PADDING_LEFT
- Removed Math.max(beatSec, ...) clamp from clip drag, section drag, section resize handlers
- Replaced with Math.max(0, ...) so items can snap to time 0
- leadInMargin=0, time 0 at left edge, only 8px PADDING_LEFT
- TimelineRuler simplified: time-only labels, 40px height
- TempoTrackLane re-added to layout below time ruler (40px)
- WaveformLane PADDING_LEFT=8/zoom, leadIn=0
- Added leadInMargin prop to TempoTrackLane so time-0 aligns with ruler
- Added CLIP_BUFFER to TempoTrackLane grid rendering
- Moved TimelineRuler time labels from bottom (y=40) to top (y=11)
- Add 1-bar lead-in margin at timeline start (bar 0, time 0s at margin edge)
- Playhead position includes lead-in offset
- Bar numbers now 0-indexed (0, 1, 2...) in ruler and track grid
- Fix bar number clipping: CLIP_BUFFER increased from 200px to
max(400px, barWidth+200px) so bars past viewport edge render
- All mouse/time handlers account for lead-in margin (ruler, clip
drag, section drag/resize, context menu, double-click)
- formatBeat returns 0-indexed bar
- Sub-tab ruler has leadInMargin=0 (no lead-in in clip editor)