fix(editor): a MIDI file is a project — MIDI-only create + real multitrack unpack - #284
Conversation
…track unpack
Christian's report (dkcjungle-2.mid, format 1, 20 tracks): 'why can't
the editor upload this file and unpack it?' Diagnosis: the backend
parsed it fine — the CREATE MODAL dropped the staged file on the floor
(_stageMidi kept only the name), never opened the gate for MIDI-only
(_createGateOpen: gp/eof/roster+title only), and even the promised
'+Keys later' path made the user re-pick the file — where multi-select
silently imported ONLY THE FIRST track ('MIDI keys import is
single-track').
- _stageMidi keeps the File objects, pre-fills the title from the
filename (_midiDefaultTitlePure — the blank-create backend requires
one), and says what Create will do.
- _createGateOpen: a staged MIDI alone opens the gate, like a GP file.
- editorDoCreate routes MIDI-only to _editorDoMidiCreate: a blank
(draft) create seeded with a flagged Keys placeholder, then the
staged file feeds straight into the existing Add-Keys track picker
(window._editorKeysHandleFile — the factored File-object form of
editorKeysFileSelected). No re-pick.
- REAL unpack: the MIDI import loops EVERY selected track (one POST +
one arrangement each). Names are keys-safe by construction
(_midiKeysArrNamePure: 'Keys — <track name>' — kind inference is
name-driven and the notes use keys packing; a bare 'Bass, Baby.'
would render the packing as fretted lanes). The tempo-map offer
still fires once (same file, same map).
- The placeholder is removed only when provably untouched
(_midiSeedRemovablePure: flagged index, seeded name, zero
notes/chords, never the last part) — user work always stays.
tests/midi_create.test.mjs (3, fails on main) + create_gate.test.mjs
gains the MIDI-stage case.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
…th the flagged Keys seed
…m/Bass rosters only
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughMIDI uploads can now open the Create flow, derive a project title, and enter the Keys track picker. Selected MIDI tracks are imported in batches as separate arrangements, with temporary seed cleanup and supporting tests. ChangesMIDI creation and multitrack import
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CreateModal
participant KeysPicker
participant ImportEndpoint
participant EditorState
CreateModal->>KeysPicker: open with staged MIDI file
KeysPicker->>ImportEndpoint: submit selected track indexes and filters
ImportEndpoint-->>KeysPicker: return arrangements and tempo_map
KeysPicker->>EditorState: append named arrangements and remove seed placeholder
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
routes.py (1)
5792-5847: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy liftBatch conversion is all-or-nothing — one bad track fails the whole selection.
_convert()runs every selected track in a single worker call; if any one_convert_one()raises (bad track index, corrupt data, etc.), the exception aborts the entire batch and none of the already-converted tracks are returned — the user sees one generic 500 and has to retry the whole selection. Given this endpoint now accepts up to 64 tracks in one request, a per-track failure isolation (catch inside the loop, return partial results + afailedlist) would give a much better multitrack-import experience, at the cost of a response-shape change on both ends.🤖 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 `@routes.py` around lines 5792 - 5847, Update _convert and its response handling so _convert_one failures are isolated per selected track: catch each track’s exception inside the batch loop, retain successful conversions, and collect failed track identifiers and error details in a failed list. Return partial results with the new failed field while preserving tempo_map generation and successful track output; update the consuming frontend response handling to accept and surface the changed response shape instead of returning a single 500 for one failed track.
🤖 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/create.js`:
- Around line 1864-1889: Update the MIDI import flow around _editorDoBlankCreate
to track whether the roster is untouched boilerplate separately from the actual
roster value, so an intentional Lead-only selection is preserved and only the
default placeholder is marked for cleanup. Process all staged files in
createState.midiFiles rather than only index 0, passing each through the
existing editorShowAddKeysModal/_editorKeysHandleFile flow while preserving the
seeded arrangement handling.
- Around line 1878-1888: The _editorDoMidiCreate flow only forwards
createState.midiFiles[0], dropping additional staged MIDI files while the UI
claims they will be imported. Update _editorDoMidiCreate and the existing
keys-picker handoff to process every staged MIDI file, or constrain
editorContentImportSelected to stage only one file and keep the label consistent
with that limit.
In `@src/import.js`:
- Around line 300-308: Update the arrangement-renaming logic after the
arrangements normalization to run unconditionally, removing the
pickedList.length > 1 guard. Ensure every valid arrangement is paired with its
corresponding pickedList entry and renamed via _midiKeysArrNamePure, including
single-track imports.
---
Nitpick comments:
In `@routes.py`:
- Around line 5792-5847: Update _convert and its response handling so
_convert_one failures are isolated per selected track: catch each track’s
exception inside the batch loop, retain successful conversions, and collect
failed track identifiers and error details in a failed list. Return partial
results with the new failed field while preserving tempo_map generation and
successful track output; update the consuming frontend response handling to
accept and surface the changed response shape instead of returning a single 500
for one failed track.
🪄 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: 0e087663-c0f3-4310-b6c3-04beec267c47
📒 Files selected for processing (7)
CHANGELOG.mdroutes.pysrc/create.jssrc/import.jssrc/main.jstests/create_gate.test.mjstests/midi_create.test.mjs
…ter provenance (review #284 items 19-21) Item 19: _stageMidi silently kept every staged file while Create sent only midiFiles[0]. The stage is now ONE honest slot — the last pick wins, replaces anything staged earlier, and the status names both the kept and dropped files (_midiStageSlotPure). Multi-file import stays a Create-window-redesign follow-up. Item 20: the keys-safe source name (_midiKeysArrNamePure) applied only when more than one track was selected; a single selection now gets 'Keys — <track>' too instead of the generic server-side 'Keys'. Item 21: the seed-placeholder decision compared the roster VALUE to ['Lead'], so an explicitly-chosen Lead was removed after import. createState.rosterTouched now tracks provenance (set on any roster add/remove), and _midiSeedRosterPure seeds only for an untouched or emptied roster — an explicit rebuild of the default is kept. All three regressions fail on the pre-fix code (2-files-staged, generic 'Keys', rosterTouched undefined). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
_editorDoMidiCreate set S._midiSeedArrIdx on shared state but only _maybeRemoveMidiSeed cleared it. If the user cancelled the auto-opened track picker (no import) the flag survived — loadCDLC/editorApplyCreateResult never clear it — so a later Add-Keys import on a DIFFERENT song would delete that song's arrangement 0 whenever it was an untouched empty 'Lead' (a common fresh-blank-project state): silent cross-session arrangement loss. Root cause: the seed flag is session-lifetime state with no session identity. Bind it to the session that created it (S._midiSeedSession) and refuse the cleanup — deleting both flags either way — when the current session no longer matches. Regression test drives editorDoAddKeys on song B carrying song A's stale flag; the empty Lead now survives. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
What
A MIDI file is a project now. Reported with
dkcjungle-2.mid(format 1, 20 tracks): "why can't the editor upload this file and unpack it?" The backend parsed it fine all along — the create modal was the dead end, three ways deep:_stageMidirecorded only the name; the staged File was dropped on the floor._createGateOpennever opened for MIDI-only — Create stayed disabled forever.The flow now
Stage a
.mid→ the title pre-fills from the filename → Create enables → Create runs a draft create and opens straight into the track picker with the file already parsed (no re-pick) → check any number of tracks → every one imports as its own chart track, named after its MIDI track under a keys-safe prefix (Keys — Rumble in da jungle) → the temporary placeholder the draft create needed is removed automatically once real tracks land → the MIDI's own tempo map is offered as the project grid, as before.Load-bearing details
Bass, Baby.would render that packing as fretted lanes (garbage)._midiKeysArrNamePurepins the contract.import-keys-midirmtrees its temp dir after responding, so a request loop found no file on track two. The endpoint now acceptstracks: [{index, channel_filter}]and converts everything before the one cleanup; the legacy single-track body is unchanged._midiSeedRemovablePure: flagged index, seeded name, zero notes/chords, never the last part) — user work always stays. The seed isLeadbecause the draft-create backend accepts Lead/Rhythm/Bass rosters only today.['Lead']boilerplate is replaced.Testing
tests/midi_create.test.mjs(3 cases, fails on main) + the create-gate suite gains the MIDI-stage case. Full gates green: 159 JS files / 0 fail, pytest 256, lint at baseline. Live-verified with the reported file itself: staged → Create → picker auto-opened with all 15 note-bearing tracks → 3 selected →Keys — Rumble in da jungle(257 notes),Keys — Cheap Xylophone(208),Keys — Diddlydiddlydiddlydiddladeedade(17) — placeholder gone, zero page errors.🤖 Generated with Claude Code
https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Summary by CodeRabbit