Skip to content

feat(editor): the Tracks area — a DAW track column over the persistent tree - #292

Merged
byrongamatos merged 6 commits into
mainfrom
feat/editor-tracks-ui
Jul 16, 2026
Merged

feat(editor): the Tracks area — a DAW track column over the persistent tree#292
byrongamatos merged 6 commits into
mainfrom
feat/editor-tracks-ui

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

What this is

PR 4 of the #285 re-cut (stacked on #291#290#289 → main; retarget as parents merge). The unified Tracks UI — the persistent track tree from the foundation PR, now a surface you can see and arrange.

The surface

A resizable header column beside the timeline lists every track (master mix, studio stems, each transcription part, folders), and the canvas draws a matching lane for each row. Both read the same pure geometry (_trackSessionLaneLayoutPure + fitted heights), so header cells and canvas lanes always line up.

Per row: inline rename (double-click / menu), drag reorder + folders, lane-height resize, mute/solo/volume, pair a transcription with its studio stem, double-click to open the native editor, and a stem's right-click menu locks it as the metronome guide. Non-destructive audio removal (a removedSourceIds tombstone — media stays in the pack). A vertical wheel scrolls the shared lane stack; horizontal still pans the timeline.

Design decisions

  • One mixer-state owner. M/S/volume write the same canonical S.partMix the mixer panel owns, through a new thin mixerTogglePart/mixerSetPart seam — the panel stays the sole owner, so the DAW solo rule can't fork.
  • One pairing store. The row dropdown writes S.stemLinks (the pairing truth from the foundation PR) and syncs via /stem-op op links — the same atomic-snapshot contract the stem manager uses. Inlined (not imported from stem-tracks) to keep seams, not cycles.
  • partsViewMode is now the one-way landing surface (the DAW arrangement-view idiom): you enter the Tracks area for any loaded song and leave it by opening a transcription (host.openTrackSessionTarget), not by re-toggling. parts-view.js is rewritten to draw unified lanes matching the header rows; the in-canvas gutter shrinks to LABEL_W since the DOM column owns names/controls now.
  • Responsive: drag the divider to resize; as it narrows, the pairing select → the fader → the guide chrome fold away, but every row keeps its name and type badge. The inline-rename input is marked -webkit-app-region: no-drag so text selection isn't stolen by the desktop window or the draggable row.

Scoped deliberately (→ the engine PR)

No per-lane stem waveforms or meters, and no audio strips on audio rows — stem playback is the engine slice. The new host hooks (trackWaveform, scrollTrackArea) land inert-but-honest: the master lane draws from S.waveformPeaks today; stems light up when the engine caches theirs.

Tests

  • tests/track_session_ui.test.mjs (new, 6): lane height clamp/default, density breakpoints, modest auto-fit (never shrinks below authored), the shared lane layout, folder-aware drop placement, and the escaped/drag-safe rename markup.
  • tests/parts_view.test.js: a unified-row hit-test case (_partsTrackRowAtYPure); the @pure slice now strips export so the moved-to-exported pures still evaluate in new Function.
  • tests/theme_chrome.test.mjs: two CSS-contract cases (responsive fold-away preserves identity controls; rename text-selection excluded from window dragging).
  • The shared _history_env fake element gained a faithful DOM-method surface (querySelector/classList/etc.) so headless suites don't trip on the new header refresh.

Gates: 182 JS suites, pytest 291 / 2 skipped, lint 0 errors / 3 baseline warnings. Module-load smoke confirms no import cycle.

⚠️ Interactive desktop verification pending — like the prior re-cut PRs, the DOM interaction (drag reorder, inline rename focus, resize) isn't exercised by unit tests; a desktop pass with a multi-stem session is the honest close condition (tracked in the handoff).

Remaining from the salvage queue

Stem playback engine + meters (the final PR).

🤖 Generated with Claude Code

https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q

Summary by CodeRabbit

  • New Features
    • Added a persistent Tracks area beside the timeline with resizable, scrollable lanes.
    • Support for inline rename, drag reordering, folder grouping, lane density modes, and transcription↔stem pairing (mute/solo/level), including metronome locking.
    • Double-click opens tracks in native editors; removing audio tracks is non-destructive and can be restored.
    • Improved Parts view navigation and shared track-area scrolling.
  • Documentation
    • Updated the user guide’s Tracks section and documented the Shift+A shortcut.
  • Bug Fixes
    • Wheel/track scrolling and panning behavior improved; stale stem-pairing results no longer override newer selections.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a persistent Tracks sidebar with resizable lanes, unified Parts view rendering, track selection and editing, mixer controls, transcription-stem pairing, host callbacks, responsive styling, documentation, and regression tests.

Changes

Tracks Session

Layer / File(s) Summary
Track session contracts and editor shell
screen.html, src/state.js, src/host.js, src/arrangement.js, src/track-session.js, src/mixer-panel.js
Adds the Tracks layout, UI state, host callbacks, persistence normalization, exported pure helpers, and mixer mutation APIs.
Tracks sidebar UI and mutations
src/track-session.js, assets/v3-theme.css, tests/track_session_*.mjs, tests/theme_chrome.test.mjs, docs/USER-GUIDE.md, CHANGELOG.md
Implements track rendering and interactions for selection, renaming, reordering, folders, resizing, pairing, restoration, deletion, mixer controls, and responsive styling, with documentation and regression coverage.
Unified Parts view and editor wiring
src/main.js, src/parts-view.js, src/mouse.js, tests/parts_view.test.js, tests/_history_env.mjs
Connects Track Session initialization and refreshes to the editor, renders unified rows and waveforms, routes scrolling, updates Parts view hit testing and mode entry, and expands the test DOM stub.

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

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant MainEditor
  participant TrackSession
  participant PartsView
  participant Host
  participant StemAPI
  MainEditor->>TrackSession: initialize and refresh Tracks area
  PartsView->>TrackSession: derive unified rows and lane geometry
  PartsView->>Host: request waveform data
  Host-->>PartsView: return waveform peaks and duration
  TrackSession->>StemAPI: synchronize transcription-stem pairing
  TrackSession-->>MainEditor: update selection, layout, and rendered rows
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a new Tracks area/DAW-style track column built over the persistent tree.
✨ 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-tracks-ui

Comment @coderabbitai help to get the list of available commands.

ChrisBeWithYou pushed a commit that referenced this pull request Jul 16, 2026
…ack order

Three Tracks/mixer fixes surfaced by dogfooding:

1. The Master row and tempo guide could vanish ("No guide"): sources were
   derived from S.audioUrl, which (a) still points at the previous song at
   install time and (b) active-source switching reassigns to a focused
   stem. Derive the master from the stable S.masterAudioUrl instead, pinned
   at install.

2. "Master Mix" naming: the master track now defaults to the SONG name (its
   recording), and the guide button follows the track's DISPLAY name so an
   inline rename wins instead of reverting to a generic label.

3. Mixer strips now reorder to match a drag-reorder of the Tracks column
   (and rename with it): the mixer reads host.mixerTrackOrder ->
   trackSessionOrderedMixKeys() and reorders via _mixerOrderedPartsPure; the
   tree commit refreshes the mixer.

Tests: _mixerOrderedPartsPure ordering (tree order, tail-stable, empty);
master-name default/override. 184 JS suites, lint 0 errors.

Fixes behaviour in the guide (#290), Tracks UI (#292), and mixer (#295)
features; carried on the stack tip. Verified live in the desktop build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
@ChrisBeWithYou
ChrisBeWithYou force-pushed the feat/editor-tempo-bulk-locks branch from a8235fa to e0c109c Compare July 16, 2026 03:54
@ChrisBeWithYou
ChrisBeWithYou force-pushed the feat/editor-tracks-ui branch from 76bf4ae to 2243b39 Compare July 16, 2026 03:54
ChrisBeWithYou pushed a commit that referenced this pull request Jul 16, 2026
Stems (S.stems) now sound alongside the master, sample-aligned. The
engine is ADDITIVE: the master keeps its own path (S.audioSource →
_refGain, the audition MediaElement, A/B), and stems ride a parallel
scheduler hooked into the same start/stop/restart points, so a stem
fault can never take the recording down.

- decode: stemAudioCache (per-source {url,buffer,peaks}), syncStemAudio
  (parallel, generation-guarded, one failure never blocks the rest),
  resetStemAudioCache on song boundary; triggered from loadCDLC, create,
  and every /stem-op adopt.
- schedule: _startStemSources creates a BufferSource per cached stem at
  the SAME preRoll-shifted anchor as the master, each placed from its own
  S.audioShift + source.offset via the shared _audioBufferStartPure — so
  they stay aligned through seeks (_restartPlaybackAt), loops, and shift.
  _stopStemSources wired into restart/stop/teardown.
- mix: per-stem gain → _refGain (transparent, never the guide limiter),
  ramped by applyStemMix off host.partStripState('audio:<id>') — the SAME
  S.partMix store and whole-map solo rule as the synth parts. One mixer
  drives both bands. _mixerPartsPure now lists 'audio:<id>' stem strips.
- capability: host.stemMixChanged is wired (its presence flips
  stemMixerAvailable() true), lighting up Solo-my-source — reconciled to
  write S.partMix['audio:<id>'] (exclusive isolate over the audio band;
  the recording stays audible) instead of the dead S.stemMix stub — plus
  the PR-5 audio-row strips and per-stem lane waveforms.

Scoped deliberately: unity fader ceiling (no +6 dB) and no live meters —
both additive polish for a follow-up. Known limit: at audition speed < 1
the master reroutes to a pitch-preserving MediaElement and the
sample-accurate BufferSource path (stems included) is silent; stems
resume at 100%.

Tests: stem_engine.test.mjs (mixer band + solo rule + placement
alignment); stem_tracks.test.mjs reconciled to the partMix solo model;
audition_clock.test.mjs injects the new stem-scheduler stubs. 183 JS
suites, 291 pytest, lint 0 errors.

Stacked on feat/editor-tracks-ui (#292) — the final salvage slice.

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
…ack order

Three Tracks/mixer fixes surfaced by dogfooding:

1. The Master row and tempo guide could vanish ("No guide"): sources were
   derived from S.audioUrl, which (a) still points at the previous song at
   install time and (b) active-source switching reassigns to a focused
   stem. Derive the master from the stable S.masterAudioUrl instead, pinned
   at install.

2. "Master Mix" naming: the master track now defaults to the SONG name (its
   recording), and the guide button follows the track's DISPLAY name so an
   inline rename wins instead of reverting to a generic label.

3. Mixer strips now reorder to match a drag-reorder of the Tracks column
   (and rename with it): the mixer reads host.mixerTrackOrder ->
   trackSessionOrderedMixKeys() and reorders via _mixerOrderedPartsPure; the
   tree commit refreshes the mixer.

Tests: _mixerOrderedPartsPure ordering (tree order, tail-stable, empty);
master-name default/override. 184 JS suites, lint 0 errors.

Fixes behaviour in the guide (#290), Tracks UI (#292), and mixer (#295)
features; carried on the stack tip. Verified live in the desktop build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
@ChrisBeWithYou
ChrisBeWithYou force-pushed the feat/editor-tempo-bulk-locks branch from e0c109c to 4f00d59 Compare July 16, 2026 03:57
@ChrisBeWithYou
ChrisBeWithYou force-pushed the feat/editor-tracks-ui branch from 2243b39 to 977386b Compare July 16, 2026 03:57
ChrisBeWithYou pushed a commit that referenced this pull request Jul 16, 2026
Stems (S.stems) now sound alongside the master, sample-aligned. The
engine is ADDITIVE: the master keeps its own path (S.audioSource →
_refGain, the audition MediaElement, A/B), and stems ride a parallel
scheduler hooked into the same start/stop/restart points, so a stem
fault can never take the recording down.

- decode: stemAudioCache (per-source {url,buffer,peaks}), syncStemAudio
  (parallel, generation-guarded, one failure never blocks the rest),
  resetStemAudioCache on song boundary; triggered from loadCDLC, create,
  and every /stem-op adopt.
- schedule: _startStemSources creates a BufferSource per cached stem at
  the SAME preRoll-shifted anchor as the master, each placed from its own
  S.audioShift + source.offset via the shared _audioBufferStartPure — so
  they stay aligned through seeks (_restartPlaybackAt), loops, and shift.
  _stopStemSources wired into restart/stop/teardown.
- mix: per-stem gain → _refGain (transparent, never the guide limiter),
  ramped by applyStemMix off host.partStripState('audio:<id>') — the SAME
  S.partMix store and whole-map solo rule as the synth parts. One mixer
  drives both bands. _mixerPartsPure now lists 'audio:<id>' stem strips.
- capability: host.stemMixChanged is wired (its presence flips
  stemMixerAvailable() true), lighting up Solo-my-source — reconciled to
  write S.partMix['audio:<id>'] (exclusive isolate over the audio band;
  the recording stays audible) instead of the dead S.stemMix stub — plus
  the PR-5 audio-row strips and per-stem lane waveforms.

Scoped deliberately: unity fader ceiling (no +6 dB) and no live meters —
both additive polish for a follow-up. Known limit: at audition speed < 1
the master reroutes to a pitch-preserving MediaElement and the
sample-accurate BufferSource path (stems included) is silent; stems
resume at 100%.

Tests: stem_engine.test.mjs (mixer band + solo rule + placement
alignment); stem_tracks.test.mjs reconciled to the partMix solo model;
audition_clock.test.mjs injects the new stem-scheduler stubs. 183 JS
suites, 291 pytest, lint 0 errors.

Stacked on feat/editor-tracks-ui (#292) — the final salvage slice.

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
…ack order

Three Tracks/mixer fixes surfaced by dogfooding:

1. The Master row and tempo guide could vanish ("No guide"): sources were
   derived from S.audioUrl, which (a) still points at the previous song at
   install time and (b) active-source switching reassigns to a focused
   stem. Derive the master from the stable S.masterAudioUrl instead, pinned
   at install.

2. "Master Mix" naming: the master track now defaults to the SONG name (its
   recording), and the guide button follows the track's DISPLAY name so an
   inline rename wins instead of reverting to a generic label.

3. Mixer strips now reorder to match a drag-reorder of the Tracks column
   (and rename with it): the mixer reads host.mixerTrackOrder ->
   trackSessionOrderedMixKeys() and reorders via _mixerOrderedPartsPure; the
   tree commit refreshes the mixer.

Tests: _mixerOrderedPartsPure ordering (tree order, tail-stable, empty);
master-name default/override. 184 JS suites, lint 0 errors.

Fixes behaviour in the guide (#290), Tracks UI (#292), and mixer (#295)
features; carried on the stack tip. Verified live in the desktop build.

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
Stems (S.stems) now sound alongside the master, sample-aligned. The
engine is ADDITIVE: the master keeps its own path (S.audioSource →
_refGain, the audition MediaElement, A/B), and stems ride a parallel
scheduler hooked into the same start/stop/restart points, so a stem
fault can never take the recording down.

- decode: stemAudioCache (per-source {url,buffer,peaks}), syncStemAudio
  (parallel, generation-guarded, one failure never blocks the rest),
  resetStemAudioCache on song boundary; triggered from loadCDLC, create,
  and every /stem-op adopt.
- schedule: _startStemSources creates a BufferSource per cached stem at
  the SAME preRoll-shifted anchor as the master, each placed from its own
  S.audioShift + source.offset via the shared _audioBufferStartPure — so
  they stay aligned through seeks (_restartPlaybackAt), loops, and shift.
  _stopStemSources wired into restart/stop/teardown.
- mix: per-stem gain → _refGain (transparent, never the guide limiter),
  ramped by applyStemMix off host.partStripState('audio:<id>') — the SAME
  S.partMix store and whole-map solo rule as the synth parts. One mixer
  drives both bands. _mixerPartsPure now lists 'audio:<id>' stem strips.
- capability: host.stemMixChanged is wired (its presence flips
  stemMixerAvailable() true), lighting up Solo-my-source — reconciled to
  write S.partMix['audio:<id>'] (exclusive isolate over the audio band;
  the recording stays audible) instead of the dead S.stemMix stub — plus
  the PR-5 audio-row strips and per-stem lane waveforms.

Scoped deliberately: unity fader ceiling (no +6 dB) and no live meters —
both additive polish for a follow-up. Known limit: at audition speed < 1
the master reroutes to a pitch-preserving MediaElement and the
sample-accurate BufferSource path (stems included) is silent; stems
resume at 100%.

Tests: stem_engine.test.mjs (mixer band + solo rule + placement
alignment); stem_tracks.test.mjs reconciled to the partMix solo model;
audition_clock.test.mjs injects the new stem-scheduler stubs. 183 JS
suites, 291 pytest, lint 0 errors.

Stacked on feat/editor-tracks-ui (#292) — the final salvage slice.

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
…ack order

Three Tracks/mixer fixes surfaced by dogfooding:

1. The Master row and tempo guide could vanish ("No guide"): sources were
   derived from S.audioUrl, which (a) still points at the previous song at
   install time and (b) active-source switching reassigns to a focused
   stem. Derive the master from the stable S.masterAudioUrl instead, pinned
   at install.

2. "Master Mix" naming: the master track now defaults to the SONG name (its
   recording), and the guide button follows the track's DISPLAY name so an
   inline rename wins instead of reverting to a generic label.

3. Mixer strips now reorder to match a drag-reorder of the Tracks column
   (and rename with it): the mixer reads host.mixerTrackOrder ->
   trackSessionOrderedMixKeys() and reorders via _mixerOrderedPartsPure; the
   tree commit refreshes the mixer.

Tests: _mixerOrderedPartsPure ordering (tree order, tail-stable, empty);
master-name default/override. 184 JS suites, lint 0 errors.

Fixes behaviour in the guide (#290), Tracks UI (#292), and mixer (#295)
features; carried on the stack tip. Verified live in the desktop build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
@byrongamatos

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 9

🤖 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 `@CHANGELOG.md`:
- Around line 12-22: Resolve the contradictory changelog entries by updating the
older Lines 64-66 note to describe the Tracks data-model foundation rather than
claiming the unified Tracks surface ships separately. Preserve the new unified
Tracks surface entry as the current shipped behavior and ensure both notes
present a consistent release history.

In `@src/mixer-panel.js`:
- Around line 188-190: Update mixerSetPart to refresh the visible mixer panel
after applying the patch through _setPart, ensuring companion-fader changes made
via the track-session path are immediately reflected while preserving the
existing update behavior.

In `@src/parts-view.js`:
- Around line 280-282: Update the Tracks entry guard around _unifiedRows() so
audio-only sessions with unified audio lanes are allowed through. Use the
unified-row result to determine whether content exists, while retaining
partCount solely for the existing “Load a song first” status text.
- Around line 257-264: Update the canvas lane-selection handler before the
drums/arrangement branch to call host.selectTrackSessionTarget(row.targetId) for
every transcription row, matching the header-selection behavior in the existing
track-session flow. Preserve the current drum status handling and arrangement
selection logic after arming the selected target.

In `@src/track-session.js`:
- Around line 717-722: Update the deletion flow around S.partMix in the
arrangement-removal logic to preserve mixer settings: remove only the deleted
arr:<index> entry, shift subsequent arrangement keys down to their new indices,
and retain unaffected arrangement and drum mixer state. Do not reset S.partMix
to an empty object before normalizing S.trackSession.
- Around line 736-754: Update _syncPairing to serialize stem-op snapshot writes
in user-action order, ensuring each request waits for the previous write before
starting. Track the latest pairing operation or request generation and ignore
responses from older operations so stale data cannot overwrite S.stemLinks or
S.stems, while preserving the existing dirty-state and refresh behavior.
- Around line 777-780: After applyTrackHeaderWidth in the track-session
initialization flow, call host.resizeCanvas() so canvas dimensions and hit
geometry are recalculated using the restored header width. Keep the
persisted-width fallback behavior unchanged.
- Around line 425-432: Update the track-session installation and normalization
flow around S.trackSession and S.audioUrl so sanitized removed-source
tombstones, including the master tombstone, are retained when audio sources are
not yet available and reconciled only once those sources arrive. Ensure late
audio loading cannot resurrect the Master Mix row, and add a regression test
covering installation before S.audioUrl becomes available followed by late audio
loading.
- Around line 780-800: Extend the track splitter setup around the pointerdown
handler and the lane-grip/row ordering flow near the referenced area with
keyboard-accessible controls. Make resize controls focusable, expose the current
width through aria-valuenow, and handle Arrow keys to resize; add keyboard
commands to move rows before, after, or into valid targets while preserving the
existing pointer and HTML-drag 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: be49465e-503f-4584-b838-8d73379e03f9

📥 Commits

Reviewing files that changed from the base of the PR and between 4f00d59 and 841e04c.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • assets/v3-theme.css
  • docs/USER-GUIDE.md
  • screen.html
  • src/arrangement.js
  • src/host.js
  • src/main.js
  • src/mixer-panel.js
  • src/mouse.js
  • src/parts-view.js
  • src/state.js
  • src/track-session.js
  • tests/_history_env.mjs
  • tests/parts_view.test.js
  • tests/theme_chrome.test.mjs
  • tests/track_session_fader_drag.test.mjs
  • tests/track_session_ui.test.mjs

Comment thread CHANGELOG.md
Comment thread src/mixer-panel.js
Comment thread src/parts-view.js
Comment thread src/parts-view.js Outdated
Comment thread src/track-session.js
Comment thread src/track-session.js
Comment thread src/track-session.js Outdated
Comment thread src/track-session.js
Comment thread src/track-session.js
byrongamatos added a commit that referenced this pull request Jul 16, 2026
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>
@byrongamatos

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@byrongamatos
byrongamatos changed the base branch from feat/editor-tempo-bulk-locks to main July 16, 2026 18:28
ChrisBeWithYou and others added 6 commits July 16, 2026 20:32
…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
… 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>
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>
@byrongamatos
byrongamatos force-pushed the feat/editor-tracks-ui branch from dc54d83 to 3418dca Compare July 16, 2026 18:33
@byrongamatos
byrongamatos merged commit e94e492 into main Jul 16, 2026
3 checks passed
@byrongamatos
byrongamatos deleted the feat/editor-tracks-ui branch July 16, 2026 18:33
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