@@ -880,8 +880,8 @@ async function visibleMidiStudioControlOwnership(page, activeTabId) {
880880 playSectionButton: { canonical: "timing preview playback state", kind: "workflow-state", owner: "Octave Timeline", wired: "wired" },
881881 playSequenceButton: { canonical: "timing preview playback state from Song Sequence order", kind: "workflow-state", owner: "Octave Timeline", wired: "wired" },
882882 regenerateArrangementButton: { canonical: "music.songs[].studioArrangement generated lanes from Song Sheet sequence", kind: "canonical-action", owner: "Song Setup", wired: "wired" },
883- renderedExportSaveButton: { canonical: "future rendered audio renderer ", kind: "unwired ", owner: "Export", wired: "unwired " },
884- renderedExportTargetTypeSelect: { canonical: "future rendered audio renderer ", kind: "unwired ", owner: "Export", wired: "unwired " },
883+ renderedExportSaveButton: { canonical: "declared rendered audio target download ", kind: "action ", owner: "Export", wired: "wired " },
884+ renderedExportTargetTypeSelect: { canonical: "rendered audio target format selection ", kind: "workflow-state ", owner: "Export", wired: "wired " },
885885 resetSongEditsButton: { canonical: "music.songs[] reset baseline", kind: "canonical-action", owner: "Global NAV", wired: "wired" },
886886 returnToWorkspaceButton: { canonical: "workspace navigation", kind: "action", owner: "Workspace NAV", wired: "wired" },
887887 saveInstrumentPresetButton: { canonical: "Instruments tab instrument preset library", kind: "workflow-state", owner: "Instruments", wired: "wired" },
@@ -7373,17 +7373,17 @@ test.describe("MIDI Studio V2", () => {
73737373 await expect(page.locator("#statusLog")).toHaveValue(/MIDI Studio V2 toolState/);
73747374
73757375 await selectMidiStudioTab(page, "export");
7376- await expect(page.locator("#exportRenderSource [data-export-field='selected- song'] dd")).toHaveText("Release Candidate Loop");
7376+ await expect(page.locator("#exportRenderSource [data-export-field='song-name '] dd")).toHaveText("Release Candidate Loop");
73777377 await expect(page.locator("#exportRenderSource [data-export-field='generated-id'] dd")).toHaveText("releaseCandidateLoop-Boss");
73787378 await expect(page.locator("#exportManifestReadinessDetails [data-export-field='game-usage-assignment'] dd")).toContainText("Boss: Release Candidate Loop");
73797379 for (const [format, label] of [["wav", "Save WAV"], ["mp3", "Save MP3"], ["ogg", "Save OGG"]]) {
73807380 await page.locator("#renderedExportTargetTypeSelect").selectOption(format);
73817381 await expect(page.locator("#renderedExportSaveButton")).toHaveText(label);
73827382 }
73837383 await expect(page.locator("#futureSoundFontSelect")).toHaveAttribute("data-midi-studio-unwired", "not-implemented");
7384- await expect(page.locator("#renderedExportSaveButton")).toHaveAttribute("data-midi-studio-unwired", "not-implemented" );
7384+ await expect(page.locator("#renderedExportSaveButton")).not. toHaveAttribute("data-midi-studio-unwired", /.+/ );
73857385 await page.locator("#renderedExportSaveButton").click();
7386- await expect(page.locator("#statusLog")).toHaveValue(/WARN Export rendering not implemented| FAIL Missing rendered/);
7386+ await expect(page.locator("#statusLog")).toHaveValue(/FAIL Missing rendered OGG export target /);
73877387 await expect(page.locator("#statusLog")).not.toHaveValue(/created .*\.ogg|wrote .*\.ogg|saved .*\.ogg/i);
73887388 await page.locator("#toolExportToolStateButton").click();
73897389 await expect(page.locator("#statusLog")).toHaveValue(/PASS Export JSON PASS/);
@@ -7513,6 +7513,49 @@ test.describe("MIDI Studio V2", () => {
75137513 }
75147514 });
75157515
7516+ test("validates PR281-340 PROD rendered export save wiring and duplicate export display cleanup", async ({ page }) => {
7517+ await page.route("**/assets/music/rendered/theme-main.ogg", async (route) => {
7518+ await route.fulfill({
7519+ body: Buffer.from("OggS MIDI Studio V2 PROD rendered target fixture"),
7520+ contentType: "audio/ogg"
7521+ });
7522+ });
7523+ const server = await openMidiStudio(page);
7524+ try {
7525+ await selectMidiStudioTab(page, "export");
7526+ await page.evaluate(() => {
7527+ window.__midiStudioProdDownloads = [];
7528+ window.__midiStudioV2App.triggerRenderedDownload = async (blob, filename) => {
7529+ window.__midiStudioProdDownloads.push({ filename, size: blob.size });
7530+ };
7531+ });
7532+ await expect(page.locator("#exportRenderSource [data-export-field='song-name'] dd")).toHaveText("Main Theme");
7533+ await expect(page.locator("#exportRenderSource [data-export-field='selected-song']")).toHaveCount(0);
7534+ await expect(page.locator("#renderedExportTargetTypeSelect")).not.toHaveAttribute("data-midi-studio-unwired", /.+/);
7535+ await expect(page.locator("#renderedExportSaveButton")).not.toHaveAttribute("data-midi-studio-unwired", /.+/);
7536+ await expect(page.locator("#futureSoundFontSelect")).toHaveAttribute("data-midi-studio-unwired", "not-implemented");
7537+
7538+ await page.locator("#renderedExportTargetTypeSelect").selectOption("ogg");
7539+ await expect(page.locator("#renderedExportSaveButton")).toHaveText("Save OGG");
7540+ await page.locator("#renderedExportSaveButton").click();
7541+ await expect.poll(() => page.evaluate(() => window.__midiStudioProdDownloads)).toEqual([
7542+ { filename: "theme-main.ogg", size: 48 }
7543+ ]);
7544+ await expect(page.locator("#statusLog")).toHaveValue(/PASS Rendered OGG export downloaded from assets\/music\/rendered\/theme-main\.ogg\./);
7545+ await expect(page.locator("#exportStatusDetails")).toContainText("PASS: Rendered OGG export downloaded from assets/music/rendered/theme-main.ogg.");
7546+
7547+ await page.evaluate(() => window.__midiStudioV2App.selectSong("source-only"));
7548+ await expect(page.locator("#exportRenderSource [data-export-field='song-name'] dd")).toHaveText("Source Only");
7549+ await page.locator("#renderedExportTargetTypeSelect").selectOption("wav");
7550+ await page.locator("#renderedExportSaveButton").click();
7551+ await expect(page.locator("#statusLog")).toHaveValue(/FAIL Missing rendered WAV export target for Source Only\. Add music\.songs\[\]\.rendered\.wav before exporting\./);
7552+ await expect(page.locator("#statusLog")).not.toHaveValue(/not implemented for WAV|saved .*\.wav|created .*\.wav/i);
7553+ } finally {
7554+ await workspaceV2CoverageReporter.stop(page);
7555+ await server.close();
7556+ }
7557+ });
7558+
75167559 test("derives primary song, instrument, grid, playback, and diagnostics views from the canonical selected song", async ({ page }) => {
75177560 const server = await openMidiStudioForImport(page);
75187561 try {
0 commit comments