Skip to content

Commit 25112e4

Browse files
committed
Restore MIDI Studio V2 Song Setup editable details and organize controls into tab buckets - PR_26146_053-midi-studio-v2-song-setup-buckets-restore
1 parent c0e9691 commit 25112e4

9 files changed

Lines changed: 375 additions & 80 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# PR_26146_053 MIDI Studio V2 Song Setup Buckets Restore Validation
2+
3+
## Summary
4+
5+
Status: PASS
6+
7+
Playwright impacted: Yes
8+
9+
This PR restores Song Setup as the editable song metadata surface while preserving the PR052 tab order and the PR051 canvas-backed Octave Timeline. Song Setup now has the common buckets: Songs, Song Details, Song Sheet, Sections / Loop, and Status.
10+
11+
## Tabs Inventory Note
12+
13+
The requested uploaded `Tabs.txt` inventory was not present in the repository, Downloads, or temp upload locations during execution. The current `tools/midi-studio-v2/index.html` DOM inventory was used as the concrete fallback, and the resulting tab/bucket map is documented in `docs/dev/reports/PR_26146_053-midi-studio-v2-tab-bucket-map.md`.
14+
15+
## Files Changed
16+
17+
- `tools/midi-studio-v2/index.html`
18+
- `tools/midi-studio-v2/js/MidiStudioV2App.js`
19+
- `tools/midi-studio-v2/js/bootstrap.js`
20+
- `tools/midi-studio-v2/js/controls/SongDetailsControl.js`
21+
- `tools/midi-studio-v2/js/controls/SongSheetControl.js`
22+
- `tools/midi-studio-v2/styles/midiStudioV2.css`
23+
- `tests/playwright/tools/MidiStudioV2.spec.mjs`
24+
- `docs/dev/reports/PR_26146_053-midi-studio-v2-song-setup-buckets-restore_validation.md`
25+
- `docs/dev/reports/PR_26146_053-midi-studio-v2-tab-bucket-map.md`
26+
- `docs/dev/codex_commands.md`
27+
- `docs/dev/commit_comment.txt`
28+
29+
## Validation Commands
30+
31+
- PASS: changed-file JavaScript syntax checks with `node --check`.
32+
- PASS: HTML external-only check for `<script>` usage.
33+
- PASS: HTML no-inline-style/no-inline-handler check for `<style| on[a-z]+=`.
34+
- PASS: `npx playwright test tests/playwright/tools/MidiStudioV2.spec.mjs --grep "restores Song Setup editable buckets|canvas octave timeline edits canonical data"`.
35+
- PASS: `git diff --check` with line-ending warnings only.
36+
37+
## Playwright Coverage
38+
39+
The targeted MIDI Studio V2 tests prove:
40+
41+
- Song Setup contains Songs, Song Details, Song Sheet, Sections / Loop, and Status buckets.
42+
- Song Details contains editable Name, Id, Tempo/BPM, Key, Style, Tags, Usage, and Notes when present.
43+
- Editing Song Details updates the canonical selected song model.
44+
- Switching songs refreshes Song Details and clears stale values.
45+
- Song Sheet remains visible immediately after Song Details.
46+
- No separate duplicate Song Sheet Summary bucket exists; Song Sheet status/summary lives inside the Song Sheet bucket.
47+
- MIDI Import controls are hidden from Song Setup and remain owned by the MIDI Import tab.
48+
- Diagnostics-only buckets are hidden from Song Setup.
49+
- Octave Timeline still renders the canvas-backed editor and edits canonical notes.
50+
- Play and Stop still work.
51+
52+
## Coverage Artifacts
53+
54+
- `docs/dev/reports/playwright_v8_coverage_report.txt` was refreshed by the targeted Playwright run.
55+
- `docs/dev/reports/coverage_changed_js_guardrail.txt` was refreshed by the targeted Playwright run.
56+
- Changed runtime JS coverage guardrail reports no warnings.
57+
58+
## Samples Decision
59+
60+
Full samples smoke test: SKIP.
61+
62+
Reason: explicitly out of scope for this PR and not requested; this change is limited to MIDI Studio V2 UI/runtime behavior.
63+
64+
## Manual Validation Notes
65+
66+
1. Open MIDI Studio V2 and import the UAT manifest.
67+
2. Confirm Song Setup is first and Octave Timeline is second.
68+
3. Confirm Song Setup shows Songs, Song Details, Song Sheet, Sections / Loop, and Status.
69+
4. Edit Name, Id, Tempo/BPM, Key, Style, Tags, Usage, and Notes, then verify the JSON Details output after switching to Diagnostics.
70+
5. Switch songs and confirm Song Details refresh to the newly selected song.
71+
6. Open Octave Timeline, click a note cell, then Play and Stop.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# PR_26146_053 MIDI Studio V2 Tab Bucket Map
2+
3+
## Inventory Source
4+
5+
Requested source: uploaded `Tabs.txt`.
6+
7+
Resolution: `Tabs.txt` was not present in the repository, Downloads, or temp upload locations. This map uses the current `tools/midi-studio-v2/index.html` DOM inventory after PR053 edits.
8+
9+
## Tab Order
10+
11+
1. Song Setup
12+
2. Octave Timeline
13+
3. Instruments
14+
4. Auto-Create Parts
15+
5. MIDI Import
16+
6. Diagnostics
17+
18+
## Song Setup
19+
20+
- Songs: `songListContent`
21+
- Song Details: `songDetailsContent`
22+
- Editable Name
23+
- Editable Id
24+
- Editable Tempo/BPM
25+
- Editable Key dropdown
26+
- Editable Style dropdown
27+
- Editable Tags
28+
- Editable Usage when present
29+
- Editable Notes when present
30+
- Song Sheet: `songSheetContent`
31+
- Intro chord progression
32+
- Loop chord progression
33+
- Parse Guided Song Sheet
34+
- Song Sheet summary/status inside the same bucket
35+
- Sections / Loop: `songSectionsLoopContent`
36+
- Sections
37+
- Loop enabled
38+
- Loop start
39+
- Loop end
40+
- Status: `statusLogContent`
41+
42+
## Octave Timeline
43+
44+
- Songs: `songListContent`
45+
- Canvas/grid editor and playback editing surface: `instrumentGridOutput`, timeline title, zoom controls, and transport controls scoped to the timeline.
46+
- Selection inspector: `timelineSelectionContent`
47+
- Status: `statusLogContent`
48+
49+
## Instruments
50+
51+
- Instrument row management and GM type/instrument controls: `instrumentListContent`
52+
- Selection inspector: `timelineSelectionContent`
53+
54+
## Auto-Create Parts
55+
56+
- Generation and normalization helpers:
57+
- Grid settings
58+
- Advanced lane source text
59+
- Generate Bass/Pad/Arpeggio/Drums
60+
- Normalize Grid
61+
- Section preview controls
62+
63+
## MIDI Import
64+
65+
- Songs: `songListContent`
66+
- MIDI source/import/inspect controls: `midiImportContent`
67+
68+
## Diagnostics
69+
70+
- Rendered Preview: `playbackContent`
71+
- Game Music Director diagnostic metadata: `directorContent`
72+
- Rendered Export Targets: `renderedTargetsContent`
73+
- JSON Details: `inspectorContent`
74+
- Timeline Diagnostics: `instrumentGridSummaryContent`
75+
- Audio Diagnostics: `audioDiagnosticsContent`
76+
- Status: `statusLogContent`

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 72 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -686,12 +686,15 @@ test.describe("MIDI Studio V2", () => {
686686
await expect(octaveTimelineCanvas(page)).toBeVisible();
687687
await expect(page.locator(".midi-studio-v2__octave-note-cell")).toHaveCount(0);
688688
await expect(page.locator(".midi-studio-v2__grid-cell--timing-header")).toHaveCount(0);
689+
await expect(page.locator(".midi-studio-v2__instrument-list-panel")).toBeHidden();
690+
await selectMidiStudioTab(page, "instruments");
689691
await expect(instrumentTypeSelect(page, "lead")).toHaveJSProperty("tagName", "SELECT");
690692
await expect(instrumentSelect(page, "lead")).toHaveJSProperty("tagName", "SELECT");
691693
await expect(page.locator("#playButton")).toHaveJSProperty("tagName", "BUTTON");
692694
await expect(page.locator("#toolImportManifestInput")).toHaveJSProperty("tagName", "INPUT");
693695

694696
await selectInstrumentRow(page, "lead");
697+
await selectMidiStudioTab(page, "studio");
695698
await waitForCanvasRender(page);
696699
const initialCanvasState = await canvasTimelineState(page);
697700
expect(initialCanvasState.rows.some((row) => row.value === "C6")).toBe(true);
@@ -1883,7 +1886,7 @@ test.describe("MIDI Studio V2", () => {
18831886
}
18841887
});
18851888

1886-
test("cleans up Song Setup tab and keeps song changes canonical", async ({ page }) => {
1889+
test("restores Song Setup editable buckets without duplicating diagnostics", async ({ page }) => {
18871890
const server = await openMidiStudioForImport(page);
18881891
try {
18891892
await page.locator("#toolImportManifestInput").setInputFiles(uatManifestPath);
@@ -1897,73 +1900,99 @@ test.describe("MIDI Studio V2", () => {
18971900
]);
18981901
expect(tabs.map((tab) => tab.text)).not.toContain("Studio");
18991902
await expect(page.locator('[data-midi-studio-tab="song-setup"]')).toHaveAttribute("aria-selected", "true");
1903+
await expect(page.locator('.accordion-v2__header[aria-controls="songListContent"]')).toContainText("Songs");
1904+
await expect(page.locator('.accordion-v2__header[aria-controls="songDetailsContent"]')).toContainText("Song Details");
1905+
await expect(page.locator('.accordion-v2__header[aria-controls="songSheetContent"]')).toContainText("Song Sheet");
1906+
await expect(page.locator('.accordion-v2__header[aria-controls="songSectionsLoopContent"]')).toContainText("Sections / Loop");
1907+
await expect(page.locator('.accordion-v2__header[aria-controls="statusLogContent"]')).toContainText("Status");
19001908
await expect(page.locator("#songDetailsContent")).toBeVisible();
1901-
await expect(page.locator("#songDetailsContent #songSheetContent")).toHaveCount(1);
1902-
await expect(page.locator("#songDetailsContent #songSheetSummary")).toHaveCount(0);
1909+
await expect(page.locator("#songSheetContent")).toBeVisible();
1910+
await expect(page.locator("#songSectionsLoopContent")).toBeVisible();
19031911
await expect(page.locator("#songSheetSummaryContent")).toHaveCount(0);
1904-
await expect(page.locator("#songDetails input[data-song-detail-field]")).toHaveCount(0);
1905-
await expect(page.locator("#songDetails")).toBeHidden();
1906-
await expect(page.locator("#directorContent")).toBeHidden();
1907-
await expect(page.locator("#renderedTargetsContent")).toBeHidden();
1908-
await expect(page.locator("#songSheetKeyInput")).toBeVisible();
1912+
await expect(page.locator("#songSheetContent #songSheetSummary")).toHaveCount(1);
1913+
expect(await page.locator("#songDetailsContent").evaluate((details) => {
1914+
const sheet = document.querySelector("#songSheetContent");
1915+
return Boolean(sheet && details.compareDocumentPosition(sheet) & Node.DOCUMENT_POSITION_FOLLOWING);
1916+
})).toBe(true);
1917+
await expect(page.locator("#songDetails [data-song-detail-field='name']")).toHaveValue("Camptown Races UAT Reel");
1918+
await expect(page.locator("#songDetails [data-song-detail-field='id']")).toHaveValue("camptown-races-uat-reel");
1919+
await expect(page.locator("#songSheetTempoInput")).toHaveValue("144");
19091920
await expect(page.locator("#songSheetKeyInput")).toHaveJSProperty("tagName", "SELECT");
19101921
await expect(page.locator("#songSheetKeyInput")).toHaveValue("G major");
1911-
await expect(page.locator("#songSheetStyleInput")).toBeVisible();
19121922
await expect(page.locator("#songSheetStyleInput")).toHaveJSProperty("tagName", "SELECT");
19131923
await expect(page.locator("#songSheetStyleInput")).toHaveValue("public-domain-reel");
1914-
const trailingSpace = await page.locator("#songDetailsContent").evaluate((content) => {
1915-
const sheet = content.querySelector("#songSheetContent").getBoundingClientRect();
1916-
const bounds = content.getBoundingClientRect();
1917-
return Math.round(bounds.bottom - sheet.bottom);
1918-
});
1919-
expect(trailingSpace).toBeLessThanOrEqual(16);
1924+
await expect(page.locator("#songDetails [data-song-detail-field='tags']")).toHaveValue("uat, upbeat, traditional, public-domain");
1925+
await expect(page.locator("#songDetails [data-song-detail-field='usage']")).toHaveValue("uat, public-domain, preview-synth");
1926+
await expect(page.locator("#songDetails [data-song-detail-field='notes']")).toHaveValue("Traditional Camptown Races style public-domain test arrangement with lead, bass, chords/pad, and drums.");
1927+
await expect(page.locator("#songSectionsLoopDetails [data-song-detail-field='sections']")).toHaveValue("verse:2, chorus:2");
1928+
await expect(page.locator("#songSectionsLoopDetails [data-song-detail-field='loopEnabled']")).toBeChecked();
1929+
await expect(page.locator("#directorContent")).toBeHidden();
1930+
await expect(page.locator("#renderedTargetsContent")).toBeHidden();
1931+
await expect(page.locator("#playbackContent")).toBeHidden();
1932+
await expect(page.locator("#audioDiagnosticsContent")).toBeHidden();
1933+
await expect(page.locator("#instrumentGridSummaryContent")).toBeHidden();
1934+
await expect(page.locator("#midiImportContent")).toBeHidden();
1935+
await expect(page.locator("#songDetailsContent #midiImportContent")).toHaveCount(0);
1936+
await expect(page.locator("#songDetails [data-song-detail-field='sourceMidi']")).toHaveCount(0);
1937+
await expect(page.locator("#songDetails [data-song-detail-field='instrumentSet']")).toHaveCount(0);
19201938

1921-
const initialSongCount = await page.locator("#songList [data-song-id]").count();
1922-
await page.locator("#addSongButton").click();
1923-
await expect(page.locator("#songList [data-song-id]")).toHaveCount(initialSongCount + 1);
1924-
await expect(page.locator("#songList")).toContainText("New Song 4");
1939+
await page.locator("#songDetails [data-song-detail-field='name']").fill("Edited UAT Reel");
1940+
await page.locator("#songDetails [data-song-detail-field='id']").fill("edited-uat-reel");
1941+
await page.locator("#songSheetTempoInput").fill("156");
1942+
await page.locator("#songSheetKeyInput").selectOption("C major");
1943+
await page.locator("#songSheetStyleInput").selectOption("chip");
1944+
await page.locator("#songDetails [data-song-detail-field='tags']").fill("uat, edited");
1945+
await page.locator("#songDetails [data-song-detail-field='usage']").fill("title, test");
1946+
await page.locator("#songDetails [data-song-detail-field='notes']").fill("Edited notes from Song Setup.");
1947+
await page.locator("#songSectionsLoopDetails [data-song-detail-field='loopEnabled']").setChecked(false);
19251948
expect(await page.evaluate(() => {
1926-
const app = window.__midiStudioV2App;
1927-
const song = app.selectedSong();
1949+
const song = window.__midiStudioV2App.selectedSong();
19281950
return {
1929-
activeSongId: app.payload.activeSongId,
1930-
hasArrangement: Boolean(song.studioArrangement),
1931-
songCount: app.payload.songs.length
1951+
activeSongId: window.__midiStudioV2App.payload.activeSongId,
1952+
id: song.id,
1953+
key: song.studioArrangement.key,
1954+
loopEnabled: song.loop.enabled,
1955+
name: song.name,
1956+
notes: song.director.notes,
1957+
style: song.studioArrangement.style,
1958+
tags: song.tags,
1959+
tempo: song.studioArrangement.tempo,
1960+
usage: song.director.usage
19321961
};
19331962
})).toEqual({
1934-
activeSongId: "new-song-4",
1935-
hasArrangement: true,
1936-
songCount: 4
1963+
activeSongId: "edited-uat-reel",
1964+
id: "edited-uat-reel",
1965+
key: "C major",
1966+
loopEnabled: false,
1967+
name: "Edited UAT Reel",
1968+
notes: "Edited notes from Song Setup.",
1969+
style: "chip",
1970+
tags: ["uat", "edited"],
1971+
tempo: "156",
1972+
usage: ["title", "test"]
19371973
});
19381974

1939-
await page.locator("#songSheetKeyInput").selectOption("C major");
1975+
await page.locator('[data-song-id="frog-hop-nursery-rhyme"]').click();
1976+
await expect(page.locator("#songDetails [data-song-detail-field='name']")).toHaveValue("Frog Hop Nursery Rhyme UAT");
1977+
await expect(page.locator("#songDetails [data-song-detail-field='id']")).toHaveValue("frog-hop-nursery-rhyme");
1978+
await expect(page.locator("#songSheetTempoInput")).toHaveValue("132");
19401979
await expect(page.locator("#songSheetKeyInput")).toHaveValue("C major");
1941-
await expect.poll(() => page.evaluate(() => window.__midiStudioV2App.selectedSong().studioArrangement.key)).toBe("C major");
1942-
await page.locator("#songSheetStyleInput").selectOption("chip");
19431980
await expect(page.locator("#songSheetStyleInput")).toHaveValue("chip");
1944-
await expect.poll(() => page.evaluate(() => window.__midiStudioV2App.selectedSong().studioArrangement.style)).toBe("chip");
1981+
await expect(page.locator("#songDetails [data-song-detail-field='usage']")).toHaveValue("uat, frog-hop, nursery-rhyme");
19451982

1983+
await selectMidiStudioTab(page, "instruments");
1984+
await selectInstrumentRow(page, "lead");
19461985
await selectMidiStudioTab(page, "studio");
19471986
await waitForCanvasRender(page);
19481987
await expect(octaveTimelineCanvas(page)).toBeVisible();
1988+
await clickCanvasCell(page, "C6", 2);
1989+
expect(await page.evaluate(() => window.__midiStudioV2App.selectedSong().studioArrangement.lanes.lead)).toContain("C6");
19491990
await page.locator("#playButton").click();
19501991
await expect(page.locator("#playButton")).toBeDisabled();
19511992
await expect(page.locator("#stopButton")).toBeEnabled();
19521993
await page.locator("#stopButton").click();
19531994
await expect(page.locator("#stopButton")).toBeDisabled();
19541995
await expect(page.locator("#playButton")).toBeEnabled();
1955-
expect(await page.evaluate(() => {
1956-
const song = window.__midiStudioV2App.selectedSong();
1957-
return {
1958-
key: song.studioArrangement.key,
1959-
name: song.name,
1960-
style: song.studioArrangement.style
1961-
};
1962-
})).toEqual({
1963-
key: "C major",
1964-
name: "New Song 4",
1965-
style: "chip"
1966-
});
19671996
} finally {
19681997
await workspaceV2CoverageReporter.stop(page);
19691998
await server.close();

0 commit comments

Comments
 (0)