Skip to content

Commit fede39b

Browse files
committed
Simplify MIDI Studio V2 export controls to Type dropdown and Save button - PR_26146_034-midi-studio-v2-export-ui-simplification
1 parent 07dd798 commit fede39b

6 files changed

Lines changed: 158 additions & 40 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# PR_26146_034-midi-studio-v2-export-ui-simplification Validation
2+
3+
Status: PASS
4+
5+
## Scope
6+
7+
- Continued from `PR_26146_033-midi-studio-v2-implementation-audit-and-left-column-fit`.
8+
- Simplified MIDI Studio V2 rendered export controls to one `Type` dropdown plus one `Save` button.
9+
- Removed duplicate `Export WAV`, `Export MP3`, and `Export OGG` NAV actions.
10+
- Preserved dropdown values: `WAV`, `MP3`, and `OGG`.
11+
- Kept export behavior centralized through the selected dropdown type and Save action.
12+
- Preserved manifest import, octave timeline editing, playback, Play/Stop, instrument controls, 350px left column sizing, roadmap files, and audit files.
13+
14+
## Changed Files
15+
16+
- `tools/midi-studio-v2/index.html`
17+
- `tools/midi-studio-v2/js/bootstrap.js`
18+
- `tools/midi-studio-v2/js/controls/RenderedExportActionsControl.js`
19+
- `tools/midi-studio-v2/styles/midiStudioV2.css`
20+
- `tests/playwright/tools/MidiStudioV2.spec.mjs`
21+
- `docs/dev/reports/PR_26146_034-midi-studio-v2-export-ui-simplification_validation.md`
22+
- `docs/dev/reports/codex_review.diff`
23+
- `docs/dev/reports/codex_changed_files.txt`
24+
- `docs/dev/reports/playwright_v8_coverage_report.txt`
25+
- `docs/dev/reports/coverage_changed_js_guardrail.txt`
26+
- `docs/dev/codex_commands.md`
27+
- `docs/dev/commit_comment.txt`
28+
29+
## Validation Commands
30+
31+
```powershell
32+
node --check tools/midi-studio-v2/js/bootstrap.js
33+
node --check tools/midi-studio-v2/js/controls/RenderedExportActionsControl.js
34+
node --check tests/playwright/tools/MidiStudioV2.spec.mjs
35+
node -e "const fs=require('fs'); const p='tools/midi-studio-v2/styles/midiStudioV2.css'; const css=fs.readFileSync(p,'utf8'); if(/\/\*[^]*$/.test(css.replace(/\/\*[^]*?\*\//g,''))) throw new Error('Unclosed CSS comment'); let depth=0; for (const ch of css.replace(/\/\*[^]*?\*\//g,'')) { if (ch==='{') depth++; if (ch==='}') depth--; if (depth<0) throw new Error('Unexpected }'); } if (depth!==0) throw new Error('Unbalanced CSS braces'); console.log('CSS syntax guard passed:', p);"
36+
node -e "const fs=require('fs'); const p='tools/midi-studio-v2/index.html'; const html=fs.readFileSync(p,'utf8'); if(/<script(?![^>]*\ssrc=)[^>]*>/i.test(html)) throw new Error('Inline script block found'); if(/<style\b/i.test(html)) throw new Error('Inline style block found'); if(/\son[a-z]+\s*=/i.test(html)) throw new Error('Inline event handler found'); console.log('HTML external-only guard passed:', p);"
37+
npx.cmd playwright test tests/playwright/tools/MidiStudioV2.spec.mjs -g "launches and renders a valid multi-song manifest payload|exports through Type dropdown and Save without claiming files were written|fast octave note editing supports drag painting keyboard shortcuts selection and timeline scroll sync" --config=codex_playwright_system_chrome.config.cjs --reporter=list --workers=1 --timeout=60000
38+
git diff --check
39+
```
40+
41+
## Results
42+
43+
- PASS: changed JavaScript files passed `node --check`.
44+
- PASS: changed CSS passed the CSS brace/comment syntax guard.
45+
- PASS: changed HTML passed the external-only script/style/event-handler guard.
46+
- PASS: targeted MIDI Studio V2 Playwright run passed, `3 passed`.
47+
- PASS: `git diff --check` exited successfully. Git reported only line-ending notices for tracked files.
48+
- PASS: Playwright V8 coverage artifacts were refreshed:
49+
- `docs/dev/reports/playwright_v8_coverage_report.txt`
50+
- `docs/dev/reports/coverage_changed_js_guardrail.txt`
51+
52+
## Playwright Proof
53+
54+
The targeted MIDI Studio V2 Playwright run validates:
55+
56+
- duplicate `Export WAV`, `Export MP3`, and `Export OGG` buttons are removed.
57+
- `Type` dropdown exists in the tool NAV.
58+
- `Type` dropdown contains `WAV`, `MP3`, and `OGG`.
59+
- `Save` button exists.
60+
- `Type` dropdown and `Save` button fit together in the existing toolbar layout.
61+
- `Save` uses the currently selected dropdown type for `WAV`, `MP3`, and `OGG`.
62+
- unavailable export rendering logs honest WARN messages with planned target paths.
63+
- missing selected rendered target still fails visibly.
64+
- manifest import and initial MIDI Studio rendering still work.
65+
- fast octave editing, keyboard shortcuts, selection highlight, timeline scroll sync, and Play/Stop behavior still work.
66+
67+
## Lanes
68+
69+
- runtime: executed because this PR changes MIDI Studio V2 UI controls and export click behavior.
70+
- contract/docs: executed through required validation/report artifacts; roadmap/audit files were preserved and not modified.
71+
- integration: skipped because Workspace Manager handoff contracts did not change.
72+
- engine: skipped because no engine/shared runtime files changed.
73+
- samples: skipped by explicit PR instruction. Full samples smoke test was not run.
74+
- recovery/UAT: covered only for the MIDI Studio V2 export UI simplification slice named by this PR.
75+
76+
## Manual Validation Notes
77+
78+
1. Open `tools/midi-studio-v2/index.html`.
79+
2. Import `tests/fixtures/midi-studio-v2/uat-midi-studio-v2.game.manifest.json`.
80+
3. Confirm the top NAV shows `Type` with `WAV`, `MP3`, and `OGG`, plus one `Save` button.
81+
4. Confirm there are no separate `Export WAV`, `Export MP3`, or `Export OGG` buttons.
82+
5. Select each Type and click Save; expected result is a WARN that export rendering is not implemented and identifies the selected planned target.
83+
6. Verify Play and Stop still work and octave timeline note editing still responds.
84+
85+
## Out Of Scope
86+
87+
- Full samples smoke test.
88+
- SoundFont playback.
89+
- Implementing rendered WAV/MP3/OGG export rendering.
90+
- DAW mixer complexity.
91+
- Automation lanes.
92+
- Any roadmap or implementation-audit status changes.

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,12 +1226,15 @@ test.describe("MIDI Studio V2", () => {
12261226
const renderedHeader = page.locator('.accordion-v2__header[aria-controls="renderedTargetsContent"]');
12271227
await expect(renderedHeader).toContainText("Rendered Export Targets");
12281228
await expect(renderedHeader.locator("#exportWavButton")).toHaveCount(0);
1229+
await expect(page.locator("#exportWavButton")).toHaveCount(0);
1230+
await expect(page.locator("#exportMp3Button")).toHaveCount(0);
1231+
await expect(page.locator("#exportOggButton")).toHaveCount(0);
12291232
await expect(page.locator(".midi-studio-v2__tool-menu #toolImportManifestButton")).toBeVisible();
12301233
await expect(page.locator(".midi-studio-v2__tool-menu #loadExampleAndPlayButton")).toHaveCount(0);
12311234
await expect(page.locator(".midi-studio-v2__tool-menu #stopAllAudioButton")).toBeVisible();
1232-
await expect(page.locator(".midi-studio-v2__tool-menu #exportWavButton")).toBeVisible();
1233-
await expect(page.locator(".midi-studio-v2__tool-menu #exportMp3Button")).toBeVisible();
1234-
await expect(page.locator(".midi-studio-v2__tool-menu #exportOggButton")).toBeVisible();
1235+
await expect(page.locator('.midi-studio-v2__tool-menu label[for="renderedExportTargetTypeSelect"]')).toContainText("Type");
1236+
await expect(page.locator(".midi-studio-v2__tool-menu #renderedExportTargetTypeSelect")).toBeVisible();
1237+
await expect(page.locator(".midi-studio-v2__tool-menu #renderedExportSaveButton")).toBeVisible();
12351238
await expect(page.locator("#midiSourceDetails")).toContainText("No MIDI source inspected.");
12361239
await expect(page.locator("#audioDiagnosticsContent")).toBeHidden();
12371240
await expect(page.locator("#playbackState")).toContainText("Audible preview ready: Main Theme.");
@@ -1297,19 +1300,39 @@ test.describe("MIDI Studio V2", () => {
12971300
}
12981301
});
12991302

1300-
test("reports rendered export nav action status without claiming files were written", async ({ page }) => {
1303+
test("exports through Type dropdown and Save without claiming files were written", async ({ page }) => {
13011304
const server = await openMidiStudio(page);
13021305
try {
1303-
await expect(page.locator('.accordion-v2__header[aria-controls="renderedTargetsContent"] #exportWavButton')).toHaveCount(0);
1304-
await expect(page.locator(".midi-studio-v2__tool-menu #exportWavButton")).toBeVisible();
1305-
await page.locator("#exportWavButton").click();
1306-
await page.locator("#exportMp3Button").click();
1307-
await page.locator("#exportOggButton").click();
1306+
await expect(page.locator("#exportWavButton")).toHaveCount(0);
1307+
await expect(page.locator("#exportMp3Button")).toHaveCount(0);
1308+
await expect(page.locator("#exportOggButton")).toHaveCount(0);
1309+
await expect(page.locator("#renderedExportTargetTypeSelect")).toBeVisible();
1310+
await expect(page.locator("#renderedExportTargetTypeSelect option")).toContainText(["WAV", "MP3", "OGG"]);
1311+
await expect(page.locator("#renderedExportSaveButton")).toBeVisible();
1312+
const exportControlsFit = await page.locator(".midi-studio-v2__tool-menu").evaluate((menu) => {
1313+
const label = menu.querySelector('label[for="renderedExportTargetTypeSelect"]').getBoundingClientRect();
1314+
const typeSelect = menu.querySelector("#renderedExportTargetTypeSelect").getBoundingClientRect();
1315+
const saveButton = menu.querySelector("#renderedExportSaveButton").getBoundingClientRect();
1316+
const menuRect = menu.getBoundingClientRect();
1317+
return {
1318+
fit: typeSelect.left >= menuRect.left - 1 && saveButton.right <= menuRect.right + 1,
1319+
sameRow: label.right <= saveButton.left && label.bottom >= saveButton.top && saveButton.bottom >= label.top
1320+
};
1321+
});
1322+
expect(exportControlsFit.fit).toBe(true);
1323+
expect(exportControlsFit.sameRow).toBe(true);
1324+
await page.locator("#renderedExportTargetTypeSelect").selectOption("wav");
1325+
await page.locator("#renderedExportSaveButton").click();
1326+
await page.locator("#renderedExportTargetTypeSelect").selectOption("mp3");
1327+
await page.locator("#renderedExportSaveButton").click();
1328+
await page.locator("#renderedExportTargetTypeSelect").selectOption("ogg");
1329+
await page.locator("#renderedExportSaveButton").click();
13081330
await expect(page.locator("#statusLog")).toHaveValue(/WARN Export rendering not implemented for WAV\. Planned target: assets\/music\/rendered\/theme-main\.wav\./);
13091331
await expect(page.locator("#statusLog")).toHaveValue(/WARN Export rendering not implemented for MP3\. Planned target: assets\/music\/rendered\/theme-main\.mp3\./);
13101332
await expect(page.locator("#statusLog")).toHaveValue(/WARN Export rendering not implemented for OGG\. Planned target: assets\/music\/rendered\/theme-main\.ogg\./);
13111333
await page.locator('[data-song-id="source-only"]').click();
1312-
await page.locator("#exportWavButton").click();
1334+
await page.locator("#renderedExportTargetTypeSelect").selectOption("wav");
1335+
await page.locator("#renderedExportSaveButton").click();
13131336
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Missing rendered WAV export target for Source Only\. Add music\.songs\[\]\.rendered\.wav before exporting\./);
13141337
} finally {
13151338
await workspaceV2CoverageReporter.stop(page);
@@ -2230,7 +2253,8 @@ test.describe("MIDI Studio V2", () => {
22302253
await expect(page.locator("#songSourceField")).toHaveValue("No song selected");
22312254
await expect(page.locator("#renderedTargets")).toContainText("No rendered WAV target selected.");
22322255
await expect(page.locator("#statusLog")).toHaveValue(/FAIL MIDI Studio V2 payload rejected before render .* music\.songs\[0\]\.id is required\./);
2233-
await page.locator("#exportWavButton").click();
2256+
await page.locator("#renderedExportTargetTypeSelect").selectOption("wav");
2257+
await page.locator("#renderedExportSaveButton").click();
22342258
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Missing MIDI song for WAV export\. Load or select a song before exporting\./);
22352259
} finally {
22362260
await workspaceV2CoverageReporter.stop(page);

tools/midi-studio-v2/index.html

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,17 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
4444
<button id="playButton" type="button" disabled>Play</button>
4545
<button id="stopButton" type="button" disabled>Stop</button>
4646
<button id="stopAllAudioButton" type="button">Stop All Audio</button>
47-
<label class="midi-studio-v2__compact-field" for="renderedExportTargetTypeSelect">
48-
<span>Export target type</span>
49-
<select id="renderedExportTargetTypeSelect">
50-
<option value="wav">WAV</option>
51-
<option value="mp3">MP3</option>
52-
<option value="ogg">OGG</option>
53-
</select>
54-
</label>
55-
<button id="exportWavButton" type="button">Export WAV</button>
56-
<button id="exportMp3Button" type="button">Export MP3</button>
57-
<button id="exportOggButton" type="button">Export OGG</button>
47+
<div class="midi-studio-v2__export-save-controls">
48+
<label class="midi-studio-v2__compact-field" for="renderedExportTargetTypeSelect">
49+
<span>Type</span>
50+
<select id="renderedExportTargetTypeSelect">
51+
<option value="wav">WAV</option>
52+
<option value="mp3">MP3</option>
53+
<option value="ogg">OGG</option>
54+
</select>
55+
</label>
56+
<button id="renderedExportSaveButton" type="button">Save</button>
57+
</div>
5858
<button id="toolCopyJsonButton" type="button" disabled>Copy JSON</button>
5959
<button id="toolExportToolStateButton" type="button" disabled>Export JSON</button>
6060
<input id="toolImportManifestInput" type="file" accept="application/json,.json" hidden>

tools/midi-studio-v2/js/bootstrap.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,7 @@ window.addEventListener("DOMContentLoaded", () => {
109109
previewSynth: new PreviewSynthEngine({ windowRef: window }),
110110
renderedExportActions: new RenderedExportActionsControl({
111111
exportTargetTypeSelect: requireElement("#renderedExportTargetTypeSelect"),
112-
mp3Button: requireElement("#exportMp3Button"),
113-
oggButton: requireElement("#exportOggButton"),
114-
wavButton: requireElement("#exportWavButton")
112+
saveButton: requireElement("#renderedExportSaveButton")
115113
}),
116114
serializer: new MidiStudioStateSerializer(),
117115
shell: new ToolShellControl(),
Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,13 @@
11
export class RenderedExportActionsControl {
2-
constructor({ exportTargetTypeSelect, mp3Button, oggButton, wavButton }) {
2+
constructor({ exportTargetTypeSelect, saveButton }) {
33
this.exportTargetTypeSelect = exportTargetTypeSelect;
4-
this.mp3Button = mp3Button;
5-
this.oggButton = oggButton;
6-
this.wavButton = wavButton;
4+
this.saveButton = saveButton;
75
}
86

97
mount({ onExport }) {
10-
this.wavButton.addEventListener("click", (event) => {
8+
this.saveButton.addEventListener("click", (event) => {
119
event.stopPropagation();
12-
onExport("wav");
13-
});
14-
this.mp3Button.addEventListener("click", (event) => {
15-
event.stopPropagation();
16-
onExport("mp3");
17-
});
18-
this.oggButton.addEventListener("click", (event) => {
19-
event.stopPropagation();
20-
onExport("ogg");
10+
onExport(this.exportTargetTypeSelect.value);
2111
});
2212
}
2313
}

tools/midi-studio-v2/styles/midiStudioV2.css

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,8 +775,9 @@ body[data-tool-id="midi-studio-v2"] #instrumentGridContent {
775775
.midi-studio-v2__compact-field {
776776
align-items: center;
777777
display: flex;
778-
flex-wrap: wrap;
778+
flex-wrap: nowrap;
779779
gap: 0.35rem;
780+
min-width: 0;
780781
}
781782

782783
.midi-studio-v2__compact-field span {
@@ -785,6 +786,19 @@ body[data-tool-id="midi-studio-v2"] #instrumentGridContent {
785786
text-transform: uppercase;
786787
}
787788

789+
.midi-studio-v2__compact-field select {
790+
min-width: 4.75rem;
791+
}
792+
793+
.midi-studio-v2__export-save-controls {
794+
align-items: center;
795+
display: flex;
796+
flex: 0 1 auto;
797+
flex-wrap: nowrap;
798+
gap: 0.35rem;
799+
min-width: 0;
800+
}
801+
788802
.midi-studio-v2__primary-action {
789803
font-weight: 800;
790804
}

0 commit comments

Comments
 (0)