Skip to content

Commit ec7c1d3

Browse files
committed
Clarify MIDI Studio V2 JSON export wording and add unwired editing history placeholders - PR_26146_059-midi-studio-v2-export-wording-and-history-placeholders
1 parent d770123 commit ec7c1d3

5 files changed

Lines changed: 194 additions & 7 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# PR_26146_059 MIDI Studio V2 Export Wording And History Placeholders Validation
2+
3+
Status: PASS
4+
5+
## Scope
6+
7+
- Kept JSON controls using JSON wording.
8+
- Changed rendered audio action wording to Save WAV, Save MP3, and Save OGG through the existing Type dropdown.
9+
- Added future Editing History controls under Song Setup only:
10+
- Undo
11+
- Redo
12+
- Snapshots
13+
- Revision History
14+
- Revert To Saved
15+
- Autosave
16+
- Kept Editing History controls disabled, red/unwired, and inert.
17+
- Preserved Export ownership of rendered output controls and Song Setup ownership of metadata/history placeholders.
18+
19+
## Validation Commands
20+
21+
```powershell
22+
node --check tools/midi-studio-v2/js/controls/RenderedExportActionsControl.js; node --check tests/playwright/tools/MidiStudioV2.spec.mjs
23+
rg -n -P '<script(?![^>]*\bsrc=)' tools/midi-studio-v2/index.html; if ($LASTEXITCODE -eq 1) { exit 0 } else { exit $LASTEXITCODE }
24+
rg -n '<style| on[a-z]+=' tools/midi-studio-v2/index.html; if ($LASTEXITCODE -eq 1) { exit 0 } else { exit $LASTEXITCODE }
25+
npx playwright test tests/playwright/tools/MidiStudioV2.spec.mjs --grep "keeps JSON wording|keeps Export tab usable|enforces SSoT export ownership|canvas note editing flow supports hover click drag paint erase and playback|canvas octave timeline edits canonical data"
26+
git diff --check
27+
```
28+
29+
## Results
30+
31+
- Changed-file JavaScript syntax checks: PASS.
32+
- MIDI Studio V2 HTML inline script/style/event-handler guard: PASS.
33+
- Targeted Playwright: PASS, 5 tests passed.
34+
- `git diff --check`: PASS. Git reported line-ending normalization warnings only.
35+
- Full samples smoke test: not run per PR instruction.
36+
37+
## Tested Behavior
38+
39+
- Import JSON Manifest wording remains present.
40+
- Export JSON wording remains present for JSON tool-state preview.
41+
- Rendered audio action changes to Save WAV, Save MP3, and Save OGG when the Type dropdown changes.
42+
- Export WAV, Export MP3, and Export OGG wording is absent from rendered audio actions.
43+
- Editing History appears only under Song Setup.
44+
- Undo, Redo, Snapshots, Revision History, Revert To Saved, and Autosave are disabled red/unwired controls with tooltip/status text.
45+
- Editing History placeholders do not modify canonical selected song data.
46+
- Play and Stop still work.
47+
48+
## Coverage Artifacts
49+
50+
- `docs/dev/reports/playwright_v8_coverage_report.txt`
51+
- `docs/dev/reports/coverage_changed_js_guardrail.txt`
52+
53+
Coverage is advisory only. The changed runtime JS guardrail reports no warnings.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# PR_26146_059 MIDI Studio V2 Wording And SSoT Notes
2+
3+
Status: PASS
4+
5+
## Wording
6+
7+
| Workflow | Wording | Reason |
8+
| --- | --- | --- |
9+
| JSON manifest/tool-state import | Import JSON Manifest | JSON is editable and round-trippable. |
10+
| JSON tool-state preview | Export JSON | JSON is editable and round-trippable. |
11+
| Rendered audio WAV | Save WAV | Rendered audio generation is a future file-save workflow, not a JSON export. |
12+
| Rendered audio MP3 | Save MP3 | Rendered audio generation is a future file-save workflow, not a JSON export. |
13+
| Rendered audio OGG | Save OGG | Rendered audio generation is a future file-save workflow, not a JSON export. |
14+
15+
The Export tab keeps the Type dropdown. The rendered audio action label updates from that selection.
16+
17+
## Ownership
18+
19+
| Value/control | Owner |
20+
| --- | --- |
21+
| Song metadata | Song Setup |
22+
| Editing History placeholders | Song Setup |
23+
| Rendered output Type dropdown | Export |
24+
| Rendered audio save action | Export |
25+
| Rendered target paths | Export |
26+
| JSON Details / derived data | Diagnostics |
27+
28+
## Editing History Placeholders
29+
30+
These controls are future placeholders only and are disabled, red/unwired, and inert:
31+
32+
- Undo
33+
- Redo
34+
- Snapshots
35+
- Revision History
36+
- Revert To Saved
37+
- Autosave
38+
39+
They do not mutate canonical song data and are not duplicated in other tabs.
40+
41+
## Preserved Behavior
42+
43+
- Canvas-backed Octave Timeline remains editable.
44+
- Play and Stop remain functional.
45+
- Manifest import and multiple songs remain supported.
46+
- GM instrument controls remain under Instruments.
47+
- Launch-specific NAV behavior remains unchanged.
48+
- Shared red unwired control behavior remains centralized through `FutureControlsControl` and `setUnwiredControlState`.

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2374,7 +2374,7 @@ test.describe("MIDI Studio V2", () => {
23742374
await expect(page.locator("#exportWorkflowContent")).toBeVisible();
23752375
await expect(page.locator("#renderedTargetsContent")).toBeVisible();
23762376
await expect(page.locator("#renderedExportTargetTypeSelect option")).toContainText(["WAV", "MP3", "OGG"]);
2377-
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save/Export");
2377+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save WAV");
23782378
await expect(page.locator("#renderedExportTargetTypeSelect")).toHaveAttribute("data-midi-studio-unwired", "not-implemented");
23792379
await expect(page.locator("#renderedExportSaveButton")).toHaveAttribute("data-midi-studio-unwired", "not-implemented");
23802380
await expect(page.locator("#renderedExportSaveButton")).toHaveAttribute("title", /Not implemented: Rendered audio export generation is not implemented yet/);
@@ -2478,7 +2478,7 @@ test.describe("MIDI Studio V2", () => {
24782478
await expect(page.locator("#futureExportOptionsSection")).toContainText("Future Rendering Options");
24792479
await expect(page.locator("#exportStatusSection")).toContainText("Export Status");
24802480
await expect(page.locator("#renderedExportTargetTypeSelect option")).toContainText(["WAV", "MP3", "OGG"]);
2481-
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save/Export");
2481+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save WAV");
24822482
const exportControlOwners = await page.locator("#renderedExportTargetTypeSelect, #renderedExportSaveButton").evaluateAll((controls) => controls.map((control) => control.closest("[data-midi-studio-tab-panel]")?.dataset.midiStudioTabPanel));
24832483
expect(exportControlOwners).toEqual(["export", "export"]);
24842484

@@ -2527,6 +2527,7 @@ test.describe("MIDI Studio V2", () => {
25272527
]));
25282528

25292529
await page.locator("#renderedExportTargetTypeSelect").selectOption("mp3");
2530+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save MP3");
25302531
await page.locator("#renderedExportSaveButton").click();
25312532
await expect(page.locator("#statusLog")).toHaveValue(/WARN Export rendering not implemented for MP3\. Planned target: assets\/music\/rendered\/custom-main\.mp3\./);
25322533
await expect(page.locator("#statusLog")).not.toHaveValue(/created .*custom-main\.mp3|wrote .*custom-main\.mp3|saved .*custom-main\.mp3/i);
@@ -2566,6 +2567,59 @@ test.describe("MIDI Studio V2", () => {
25662567
}
25672568
});
25682569

2570+
test("keeps JSON wording and Song Setup editing history placeholders honest", async ({ page }) => {
2571+
const server = await openMidiStudioForImport(page);
2572+
try {
2573+
await expect(page.locator("#toolImportManifestButton")).toHaveText("Import JSON Manifest");
2574+
await page.locator("#toolImportManifestInput").setInputFiles(uatManifestPath);
2575+
2576+
await selectMidiStudioTab(page, "export");
2577+
await expect(page.locator("#toolExportToolStateButton")).toHaveText("Export JSON");
2578+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save WAV");
2579+
await page.locator("#renderedExportTargetTypeSelect").selectOption("mp3");
2580+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save MP3");
2581+
await page.locator("#renderedExportTargetTypeSelect").selectOption("ogg");
2582+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save OGG");
2583+
await expect(page.locator("body")).not.toContainText(/\bExport WAV\b|\bExport MP3\b|\bExport OGG\b/);
2584+
2585+
await selectMidiStudioTab(page, "song-setup");
2586+
await expect(page.locator("#editingHistoryContent")).toBeVisible();
2587+
await expect(page.locator('[data-midi-studio-tab-panel="song-setup"] #editingHistoryContent')).toHaveCount(1);
2588+
await expect(page.locator('[data-midi-studio-tab-panel="export"] #editingHistoryContent, [data-midi-studio-tab-panel="diagnostics"] #editingHistoryContent, [data-midi-studio-tab-panel="instruments"] #editingHistoryContent, [data-midi-studio-tab-panel="midi-import"] #editingHistoryContent')).toHaveCount(0);
2589+
const songBeforeHistoryClicks = await page.evaluate(() => JSON.stringify(window.__midiStudioV2App.selectedSong()));
2590+
const historyControls = await page.locator("#editingHistoryContent [data-midi-studio-future-control]").evaluateAll((controls) => controls.map((control) => ({
2591+
disabled: control.disabled,
2592+
status: control.dataset.midiStudioUnwired,
2593+
text: control.textContent.trim(),
2594+
title: control.title
2595+
})));
2596+
expect(historyControls).toEqual([
2597+
expect.objectContaining({ disabled: true, status: "not-implemented", text: "Undo" }),
2598+
expect.objectContaining({ disabled: true, status: "not-implemented", text: "Redo" }),
2599+
expect.objectContaining({ disabled: true, status: "not-implemented", text: "Snapshots" }),
2600+
expect.objectContaining({ disabled: true, status: "not-implemented", text: "Revision History" }),
2601+
expect.objectContaining({ disabled: true, status: "not-implemented", text: "Revert To Saved" }),
2602+
expect.objectContaining({ disabled: true, status: "not-implemented", text: "Autosave" })
2603+
]);
2604+
expect(historyControls.every((control) => control.title.includes("Not implemented:"))).toBe(true);
2605+
await page.locator("#editingHistoryContent [data-midi-studio-future-control]").evaluateAll((controls) => {
2606+
controls.forEach((control) => control.dispatchEvent(new MouseEvent("click", { bubbles: true })));
2607+
});
2608+
expect(await page.evaluate(() => JSON.stringify(window.__midiStudioV2App.selectedSong()))).toBe(songBeforeHistoryClicks);
2609+
2610+
await selectMidiStudioTab(page, "studio");
2611+
await page.locator("#playButton").click();
2612+
await expect(page.locator("#playButton")).toBeDisabled();
2613+
await expect(page.locator("#stopButton")).toBeEnabled();
2614+
await page.locator("#stopButton").click();
2615+
await expect(page.locator("#stopButton")).toBeDisabled();
2616+
await expect(page.locator("#playButton")).toBeEnabled();
2617+
} finally {
2618+
await workspaceV2CoverageReporter.stop(page);
2619+
await server.close();
2620+
}
2621+
});
2622+
25692623
test("derives primary song, instrument, grid, playback, and diagnostics views from the canonical selected song", async ({ page }) => {
25702624
const server = await openMidiStudioForImport(page);
25712625
try {
@@ -2726,7 +2780,7 @@ test.describe("MIDI Studio V2", () => {
27262780
await expect(page.locator('label[for="renderedExportTargetTypeSelect"]')).toContainText("Output Type");
27272781
await expect(page.locator("#renderedExportTargetTypeSelect")).toBeVisible();
27282782
await expect(page.locator("#renderedExportSaveButton")).toBeVisible();
2729-
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save/Export");
2783+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save WAV");
27302784
await selectMidiStudioTab(page, "song-setup");
27312785
const navAndTabPresentation = await page.evaluate(() => {
27322786
const readActionButton = (selector) => {
@@ -2920,7 +2974,7 @@ test.describe("MIDI Studio V2", () => {
29202974
}
29212975
});
29222976

2923-
test("exports output through Export tab Type dropdown and Save/Export without claiming project save", async ({ page }) => {
2977+
test("saves output through Export tab Type dropdown without claiming project save", async ({ page }) => {
29242978
const server = await openMidiStudio(page);
29252979
try {
29262980
await expect(page.locator("#exportWavButton")).toHaveCount(0);
@@ -2932,7 +2986,7 @@ test.describe("MIDI Studio V2", () => {
29322986
await expect(page.locator("#renderedExportTargetTypeSelect")).toBeVisible();
29332987
await expect(page.locator("#renderedExportTargetTypeSelect option")).toContainText(["WAV", "MP3", "OGG"]);
29342988
await expect(page.locator("#renderedExportSaveButton")).toBeVisible();
2935-
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save/Export");
2989+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save WAV");
29362990
const exportControlsFit = await page.locator("#exportWorkflowContent").evaluate((panel) => {
29372991
const label = panel.querySelector('label[for="renderedExportTargetTypeSelect"]').getBoundingClientRect();
29382992
const typeSelect = panel.querySelector("#renderedExportTargetTypeSelect").getBoundingClientRect();
@@ -2946,10 +3000,13 @@ test.describe("MIDI Studio V2", () => {
29463000
expect(exportControlsFit.fit).toBe(true);
29473001
expect(exportControlsFit.sameRow).toBe(true);
29483002
await page.locator("#renderedExportTargetTypeSelect").selectOption("wav");
3003+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save WAV");
29493004
await page.locator("#renderedExportSaveButton").click();
29503005
await page.locator("#renderedExportTargetTypeSelect").selectOption("mp3");
3006+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save MP3");
29513007
await page.locator("#renderedExportSaveButton").click();
29523008
await page.locator("#renderedExportTargetTypeSelect").selectOption("ogg");
3009+
await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save OGG");
29533010
await page.locator("#renderedExportSaveButton").click();
29543011
await expect(page.locator("#statusLog")).toHaveValue(/WARN Export rendering not implemented for WAV\. Planned target: assets\/music\/rendered\/theme-main\.wav\./);
29553012
await expect(page.locator("#statusLog")).toHaveValue(/WARN Export rendering not implemented for MP3\. Planned target: assets\/music\/rendered\/theme-main\.mp3\./);
@@ -2980,7 +3037,7 @@ test.describe("MIDI Studio V2", () => {
29803037
await expect(outputTypeSelect).toHaveAttribute("data-midi-studio-unwired", "not-implemented");
29813038
await expect(saveOutputButton).toHaveAttribute("data-midi-studio-unwired", "not-implemented");
29823039
await expect(saveOutputButton).toHaveAttribute("title", /Not implemented: Rendered audio export generation is not implemented yet/);
2983-
await expect(saveOutputButton).toHaveAttribute("aria-label", /Save\/Export \(Not implemented\)/);
3040+
await expect(saveOutputButton).toHaveAttribute("aria-label", /Save WAV \(Not implemented\)/);
29843041
await expect(outputTypeSelect).toHaveAttribute("title", /Not implemented: Rendered audio export generation is not implemented yet/);
29853042
expect(await controlColors(page, "#renderedExportSaveButton")).toMatchObject({
29863043
borderTopColor: "rgb(248, 113, 113)",

tools/midi-studio-v2/index.html

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ <h3 id="exportOutputTypeHeading">Output Type</h3>
228228
<option value="ogg">OGG</option>
229229
</select>
230230
</label>
231-
<button id="renderedExportSaveButton" type="button">Save/Export</button>
231+
<button id="renderedExportSaveButton" type="button">Save WAV</button>
232232
</div>
233233
</section>
234234
<section id="exportRenderSourceSection" class="midi-studio-v2__export-section" aria-labelledby="exportRenderSourceHeading">
@@ -293,6 +293,24 @@ <h3 id="exportStatusHeading">Export Status</h3>
293293
</div>
294294
</section>
295295

296+
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true" data-midi-studio-tab-panel="song-setup">
297+
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="editingHistoryContent">
298+
<span>Editing History</span>
299+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
300+
</button>
301+
<div id="editingHistoryContent" class="accordion-v2__content midi-studio-v2__future-panel" aria-label="Future editing history controls">
302+
<h3>Editing History</h3>
303+
<div class="midi-studio-v2__future-controls">
304+
<button id="futureUndoButton" type="button" disabled data-midi-studio-future-control data-midi-studio-future-detail="Undo editing history is planned but not implemented yet.">Undo</button>
305+
<button id="futureRedoButton" type="button" disabled data-midi-studio-future-control data-midi-studio-future-detail="Redo editing history is planned but not implemented yet.">Redo</button>
306+
<button id="futureSnapshotsButton" type="button" disabled data-midi-studio-future-control data-midi-studio-future-detail="Snapshots are planned but not implemented yet.">Snapshots</button>
307+
<button id="futureRevisionHistoryButton" type="button" disabled data-midi-studio-future-control data-midi-studio-future-detail="Revision History is planned but not implemented yet.">Revision History</button>
308+
<button id="futureRevertToSavedButton" type="button" disabled data-midi-studio-future-control data-midi-studio-future-detail="Revert To Saved is planned but not implemented yet.">Revert To Saved</button>
309+
<button id="futureAutosaveButton" type="button" disabled data-midi-studio-future-control data-midi-studio-future-detail="Autosave is planned but not implemented yet.">Autosave</button>
310+
</div>
311+
</div>
312+
</section>
313+
296314
<section class="midi-studio-v2__timeline-editor-panel" data-midi-studio-tab-panel="studio auto-create-parts" aria-labelledby="instrumentGridHeading">
297315
<div id="instrumentGridContent" class="midi-studio-v2__stack">
298316
<div class="midi-studio-v2__timeline-title" data-midi-studio-tab-panel="studio">

tools/midi-studio-v2/js/controls/RenderedExportActionsControl.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,24 @@ export class RenderedExportActionsControl {
1010
}
1111

1212
mount({ onExport }) {
13+
this.updateSaveButtonLabel();
1314
this.markRenderedExportControlsUnwired();
15+
this.exportTargetTypeSelect.addEventListener("change", () => this.updateSaveButtonLabel());
1416
this.saveButton.addEventListener("click", (event) => {
1517
event.stopPropagation();
1618
onExport(this.exportTargetTypeSelect.value);
1719
});
1820
}
1921

22+
updateSaveButtonLabel() {
23+
const format = String(this.exportTargetTypeSelect.value || "wav").trim().toUpperCase() || "WAV";
24+
const label = `Save ${format}`;
25+
this.saveButton.textContent = label;
26+
if (this.saveButton.dataset.midiStudioUnwired) {
27+
this.saveButton.setAttribute("aria-label", `${label} (${this.saveButton.dataset.midiStudioUnwiredStatus || "Not implemented"})`);
28+
}
29+
}
30+
2031
markRenderedExportControlsUnwired() {
2132
[
2233
this.exportTargetTypeLabel,

0 commit comments

Comments
 (0)