Skip to content

Commit bf7ceac

Browse files
committed
Add Audio SFX schema and fix copied JSON active state - PR_26144_010-add-audio-sfx-schema-and-fix-copy-json-state
1 parent a4cb107 commit bf7ceac

4 files changed

Lines changed: 347 additions & 50 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Audio / SFX Playground V2 Schema and Copy JSON Validation
2+
3+
PR: `PR_26144_010-add-audio-sfx-schema-and-fix-copy-json-state`
4+
5+
Playwright impacted: Yes.
6+
7+
## Scope
8+
9+
- Added an exported toolState schema for Audio / SFX Playground V2 under `tools/schemas/tool-states/`.
10+
- Updated Audio / SFX Playground V2 JSON export/import/copy behavior so `payload.sounds[]` is the single source of truth.
11+
- Removed duplicated active sound data from exported JSON. The selected sound is resolved by `payload.activeSoundId` pointing to an entry in `payload.sounds[]`.
12+
13+
## Targeted Validation
14+
15+
PASS: JavaScript syntax validation
16+
17+
Command:
18+
19+
```powershell
20+
Get-ChildItem -Recurse -File tools/audio-sfx-playground-v2/js -Filter *.js | ForEach-Object { node --check $_.FullName }
21+
```
22+
23+
PASS: HTML/CSS static validation
24+
25+
Checked Audio / SFX Playground V2 HTML/CSS for empty files, inline event handlers, `<style>` blocks, and inline `<script>` blocks.
26+
27+
PASS: Schema JSON parse validation
28+
29+
Command:
30+
31+
```powershell
32+
Get-Content -Raw tools/schemas/tool-states/audio-sfx-playground-v2.tool-state.schema.json | ConvertFrom-Json | Out-Null
33+
```
34+
35+
PASS: Targeted schema/export/import behavior validation
36+
37+
Validated with an inline Node module script:
38+
39+
- New schema requires the exported toolState wrapper path.
40+
- New schema requires nonblank `payload.activeSoundId`.
41+
- New schema requires at least one `payload.sounds[]` entry.
42+
- New schema does not define duplicated `payload.sound` or `payload.name`.
43+
- Serializer accepts generated export payloads.
44+
- Serializer rejects blank active sound ids.
45+
- Serializer rejects stale duplicated `payload.sound`.
46+
- Serializer rejects active ids missing from `payload.sounds[]`.
47+
- Copy JSON creates/selects a sound tile when needed.
48+
- Copy JSON output passes serializer validation.
49+
- Copy JSON activeSoundId matches an existing tile.
50+
- Import restores the selected active sound.
51+
- Invalid import shows a visible Status error and does not mutate the selected sound.
52+
53+
PASS: Diff whitespace validation
54+
55+
Command:
56+
57+
```powershell
58+
git diff --check -- tools/audio-sfx-playground-v2 tools/schemas
59+
```
60+
61+
## Workspace V2 Validation
62+
63+
BLOCKED: `npm run test:workspace-v2`
64+
65+
Result:
66+
67+
```text
68+
npm : File C:\Program Files\nodejs\npm.ps1 cannot be loaded because running scripts is disabled on this system.
69+
```
70+
71+
BLOCKED: `npm.cmd run test:workspace-v2`
72+
73+
Result:
74+
75+
```text
76+
'playwright' is not recognized as an internal or external command,
77+
operable program or batch file.
78+
```
79+
80+
Expected Playwright pass behavior when dependencies are available:
81+
82+
- Workspace V2 launches Audio / SFX Playground V2.
83+
- Copy JSON produces a schema-valid toolState wrapper.
84+
- Export JSON produces the same schema-valid wrapper shape.
85+
- `payload.activeSoundId` is nonblank and matches an existing `payload.sounds[]` entry.
86+
- Import restores the selected sound tile and editor state.
87+
- Invalid payloads are rejected with visible Status errors.
88+
- No console errors occur during launch, copy, export, import, or invalid import rejection.
89+
90+
Expected Playwright fail behavior:
91+
92+
- Fail if Copy JSON or Export JSON emits duplicated/stale active sound fields.
93+
- Fail if `activeSoundId` is blank while sounds exist.
94+
- Fail if `activeSoundId` points to a missing sound tile.
95+
- Fail if invalid JSON partially renders or mutates the selected sound.
96+
- Fail if console errors occur.
97+
98+
## Coverage
99+
100+
Playwright V8 coverage could not be collected because Playwright is not available in this environment.
101+
102+
WARN: `tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js` - changed runtime JavaScript; coverage unavailable.
103+
WARN: `tools/audio-sfx-playground-v2/js/services/ToolStateSerializer.js` - changed runtime JavaScript; coverage unavailable.
104+
105+
## Full Samples Smoke Test
106+
107+
Skipped. This PR only impacts Audio / SFX Playground V2 toolState schema and JSON behavior.

tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ function exportFileName(name) {
3131
return `${baseName || "audio-sfx"}-tool-state.json`;
3232
}
3333

34+
function activeSoundFromToolState(toolState) {
35+
return toolState.payload.sounds.find((entry) => entry.id === toolState.payload.activeSoundId)?.sound || null;
36+
}
37+
3438
export class AudioSfxPlaygroundV2App {
3539
constructor({ accordions, actionNav, audioEngine, controls, inspector, preview, serializer, shell, statusLog, tileList, windowRef = window }) {
3640
this.accordions = accordions;
@@ -89,7 +93,6 @@ export class AudioSfxPlaygroundV2App {
8993
}
9094
const toolState = this.serializer.createToolState({
9195
activeSoundId: this.activeSoundId,
92-
sound: validation.value,
9396
soundEntries: this.soundEntries
9497
});
9598
this.controls.showMessage("Ready to audition.", false);
@@ -104,19 +107,43 @@ export class AudioSfxPlaygroundV2App {
104107
this.actionNav.setToolActionsEnabled(false);
105108
return;
106109
}
107-
this.preview.render(toolState.payload.sound);
110+
this.preview.render(validation.value);
108111
this.inspector.showObject(toolState);
109112
this.actionNav.setToolActionsEnabled(true);
110113
}
111114

112115
handleEditorChange() {
113-
if (this.activeSoundId) {
114-
this.activeSoundId = "";
116+
const validation = this.controls.validate();
117+
if (validation.valid && this.updateActiveSound(validation.value)) {
115118
this.renderSoundList();
116119
}
117120
this.refreshPreview();
118121
}
119122

123+
createSoundEntry(sound) {
124+
const entry = {
125+
id: `sfx-${this.nextSoundNumber}`,
126+
sound: cloneSound(sound)
127+
};
128+
this.nextSoundNumber += 1;
129+
this.soundEntries.push(entry);
130+
this.activeSoundId = entry.id;
131+
return entry;
132+
}
133+
134+
updateActiveSound(sound) {
135+
const entry = this.soundEntries.find((candidate) => candidate.id === this.activeSoundId);
136+
if (!entry) {
137+
return null;
138+
}
139+
entry.sound = cloneSound(sound);
140+
return entry;
141+
}
142+
143+
ensureActiveSoundEntry(sound) {
144+
return this.updateActiveSound(sound) || this.createSoundEntry(sound);
145+
}
146+
120147
addCurrentSound() {
121148
const validation = this.controls.validate();
122149
if (!validation.valid) {
@@ -125,13 +152,7 @@ export class AudioSfxPlaygroundV2App {
125152
return;
126153
}
127154

128-
const entry = {
129-
id: `sfx-${this.nextSoundNumber}`,
130-
sound: cloneSound(validation.value)
131-
};
132-
this.nextSoundNumber += 1;
133-
this.soundEntries.push(entry);
134-
this.activeSoundId = entry.id;
155+
const entry = this.createSoundEntry(validation.value);
135156
this.renderSoundList();
136157
this.refreshPreview();
137158
this.statusLog.write(`Added ${entry.sound.name}.`);
@@ -159,15 +180,15 @@ export class AudioSfxPlaygroundV2App {
159180
}
160181

161182
async play() {
162-
const { toolState, validation } = this.currentToolState();
183+
const { validation } = this.currentToolState();
163184
if (!validation.valid) {
164185
this.statusLog.error(validation.message);
165186
this.refreshPreview();
166187
return;
167188
}
168189
try {
169-
await this.audioEngine.play(toolState.payload.sound);
170-
this.statusLog.write(`Played ${toolState.payload.name}.`);
190+
await this.audioEngine.play(validation.value);
191+
this.statusLog.write(`Played ${validation.value.name}.`);
171192
} catch (error) {
172193
this.statusLog.error(`Audio playback failed: ${error.message}`);
173194
}
@@ -182,17 +203,27 @@ export class AudioSfxPlaygroundV2App {
182203
return;
183204
}
184205
const [entry] = this.soundEntries.splice(entryIndex, 1);
185-
this.activeSoundId = "";
206+
const nextEntry = this.soundEntries[Math.min(entryIndex, this.soundEntries.length - 1)] || null;
207+
this.activeSoundId = nextEntry?.id || "";
208+
if (nextEntry) {
209+
this.controls.loadSound(nextEntry.sound);
210+
}
186211
this.renderSoundList();
187212
this.refreshPreview();
188213
this.statusLog.write(`Deleted ${entry.sound.name}.`);
189214
}
190215

191216
exportPayload() {
192-
const { toolState, validation } = this.currentToolState();
217+
const validation = this.controls.validate();
193218
if (!validation.valid) {
194219
return { valid: false, message: validation.message, toolState: null, json: "" };
195220
}
221+
this.ensureActiveSoundEntry(validation.value);
222+
this.renderSoundList();
223+
const toolState = this.serializer.createToolState({
224+
activeSoundId: this.activeSoundId,
225+
soundEntries: this.soundEntries
226+
});
196227

197228
const exportValidation = this.serializer.readToolState(toolState);
198229
if (!exportValidation.valid) {
@@ -217,7 +248,7 @@ export class AudioSfxPlaygroundV2App {
217248
try {
218249
this.downloadJson(exportResult);
219250
this.inspector.showObject(exportResult.toolState);
220-
this.statusLog.write(`Exported JSON for ${exportResult.toolState.payload.name}.`);
251+
this.statusLog.write(`Exported JSON for ${activeSoundFromToolState(exportResult.toolState)?.name || "audio-sfx"}.`);
221252
} catch (error) {
222253
this.statusLog.error(`Export JSON failed: ${error.message}`);
223254
}
@@ -282,7 +313,7 @@ export class AudioSfxPlaygroundV2App {
282313
const url = urlApi.createObjectURL(blob);
283314
const link = documentRef.createElement("a");
284315
link.href = url;
285-
link.download = exportFileName(exportResult.toolState.payload.name);
316+
link.download = exportFileName(activeSoundFromToolState(exportResult.toolState)?.name || "audio-sfx");
286317
try {
287318
documentRef.body.append(link);
288319
link.click();

0 commit comments

Comments
 (0)