Skip to content

Commit fbb1f4e

Browse files
committed
Remove Audio SFX dirty bridge and use Workspace dirty contract - PR_26145_019-remove-audio-sfx-dirty-bridge-and-use-workspace-contract
1 parent 0ab7f1c commit fbb1f4e

5 files changed

Lines changed: 215 additions & 136 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# PR_26145_019 Audio / SFX Shared Dirty Contract
2+
3+
Date: 2026-05-25
4+
5+
## Targeted validation
6+
7+
- `node --check` passed for changed JavaScript:
8+
- `tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js`
9+
- `tools/audio-sfx-playground-v2/js/bootstrap.js`
10+
- `tools/workspace-manager-v2/js/services/WorkspaceManagerV2ContextService.js`
11+
- JSON parse validation passed for:
12+
- `tools/schemas/game.manifest.schema.json`
13+
- `tools/schemas/tools/audio-sfx-playground-v2.schema.json`
14+
- HTML external asset guard passed for `tools/audio-sfx-playground-v2/index.html`.
15+
- Static grep verified no `WorkspaceDirtyBridge`, `workspaceDirtyBridge`, or `sessionStorage` usage remains under `tools/audio-sfx-playground-v2/js`.
16+
- Node contract validation passed for `WorkspaceManagerV2ContextService.writeWorkspaceToolPayload`, including schema validation, dirty marking, and persisted context validation.
17+
18+
## npm run test:workspace-v2
19+
20+
Blocked by local Playwright browser installation:
21+
22+
```text
23+
browserType.launch: Executable doesn't exist at C:\Users\DavidQ\AppData\Local\ms-playwright\chromium-1217\chrome-win64\chrome.exe
24+
```
25+
26+
The exact command `npm.cmd run test:workspace-v2` was attempted and timed out after repeating the missing-browser failure. A one-failure run confirmed the root cause.
27+
28+
## Focused Playwright validation
29+
30+
Ran a focused Playwright script with installed Microsoft Edge.
31+
32+
Validated:
33+
34+
- `WorkspaceDirtyBridge.js` is removed from Audio / SFX runtime usage.
35+
- Audio / SFX uses `WorkspaceManagerV2ContextService` as the shared Workspace V2 dirty path.
36+
- Add/edit marks `workspace.tools.audio-sfx-playground-v2.dirty.isDirty=true`.
37+
- Valid Import JSON marks dirty.
38+
- Copy JSON, Export JSON, and Play do not mark dirty.
39+
- Copy JSON produces parseable exported JSON.
40+
- Workspace dirty gate used by Save/Cancel observes the Audio / SFX dirty session.
41+
- Workspace refresh includes the Audio / SFX payload for Save context.
42+
- Audio / SFX payload schema fragment validates.
43+
- Game manifest schema validates `root.tools.audio-sfx-playground-v2`.
44+
- No console errors on launch or validation actions.
45+
46+
Result: focused impacted validation passed.

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

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ function activeSoundFromToolState(toolState) {
4646
return toolState.payload.sounds.find((entry) => entry.id === toolState.payload.activeSoundId)?.sound || null;
4747
}
4848

49+
const TOOL_ID = "audio-sfx-playground-v2";
50+
const TOOL_SCHEMA_PATH = "tools/schemas/tools/audio-sfx-playground-v2.schema.json";
51+
52+
function toolStateFromPayload(payload) {
53+
return {
54+
$schema: TOOL_SCHEMA_PATH,
55+
schema: "html-js-gaming.tool-state",
56+
version: 1,
57+
toolId: TOOL_ID,
58+
payload
59+
};
60+
}
61+
4962
export class AudioSfxPlaygroundV2App {
5063
constructor({
5164
accordions,
@@ -59,7 +72,7 @@ export class AudioSfxPlaygroundV2App {
5972
statusLog,
6073
tileList,
6174
windowRef = window,
62-
workspaceDirtyBridge = null
75+
workspaceContextService = null
6376
}) {
6477
this.accordions = accordions;
6578
this.actionNav = actionNav;
@@ -75,7 +88,7 @@ export class AudioSfxPlaygroundV2App {
7588
this.statusLog = statusLog;
7689
this.tileList = tileList;
7790
this.window = windowRef;
78-
this.workspaceDirtyBridge = workspaceDirtyBridge;
91+
this.workspaceContextService = workspaceContextService;
7992
}
8093

8194
start() {
@@ -108,33 +121,42 @@ export class AudioSfxPlaygroundV2App {
108121
}
109122
});
110123
this.statusLog.mount();
111-
this.loadWorkspacePayload();
124+
void this.finishStartup();
125+
}
126+
127+
async finishStartup() {
128+
await this.loadWorkspacePayload();
112129
this.renderSoundList();
113130
this.refreshPreview();
114131
this.statusLog.write("Audio / SFX Playground V2 ready.");
115132
}
116133

117-
loadWorkspacePayload() {
118-
if (!this.workspaceDirtyBridge) {
134+
async loadWorkspacePayload() {
135+
if (!this.workspaceContextService) {
119136
return;
120137
}
121-
const result = this.workspaceDirtyBridge.readPayload();
122-
if (result.skipped) {
138+
const result = await this.workspaceContextService.readWorkspaceToolPayload(TOOL_ID);
139+
if (result.skipped || result.payload === null) {
123140
return;
124141
}
125142
if (!result.ok) {
126143
this.statusLog.error(`Workspace payload load failed: ${result.message}`);
127144
return;
128145
}
129-
const nextSoundEntries = result.value.soundEntries.map((entry) => ({
146+
const validation = this.serializer.readToolState(toolStateFromPayload(result.payload));
147+
if (!validation.valid) {
148+
this.statusLog.error(`Workspace payload load failed: ${validation.message}`);
149+
return;
150+
}
151+
const nextSoundEntries = validation.value.soundEntries.map((entry) => ({
130152
id: entry.id,
131153
sound: cloneSound(entry.sound)
132154
}));
133-
this.activeSoundId = result.value.activeSoundId;
155+
this.activeSoundId = validation.value.activeSoundId;
134156
this.soundEntries = nextSoundEntries;
135157
this.nextSoundNumber = nextSoundNumberAfter(nextSoundEntries);
136-
this.controls.loadSound(result.value.sound);
137-
this.statusLog.write(`Loaded ${result.value.sound.name} from Workspace V2.`);
158+
this.controls.loadSound(validation.value.sound);
159+
this.statusLog.write(`Loaded ${validation.value.sound.name} from Workspace V2.`);
138160
}
139161

140162
currentToolState() {
@@ -168,21 +190,21 @@ export class AudioSfxPlaygroundV2App {
168190
const validation = this.controls.validate({ nameOverride: this.activeSoundName() });
169191
if (validation.valid && this.updateActiveSound(this.soundForActiveEditorValue(validation.value))) {
170192
this.renderSoundList();
171-
this.syncWorkspaceDirty("audio-sfx-editor-change", ["data.sounds"]);
193+
void this.syncWorkspaceDirty("audio-sfx-editor-change", ["data.sounds"]);
172194
}
173195
this.refreshPreview();
174196
}
175197

176-
syncWorkspaceDirty(reason, changedKeys) {
177-
if (!this.workspaceDirtyBridge) {
198+
async syncWorkspaceDirty(reason, changedKeys) {
199+
if (!this.workspaceContextService) {
178200
return;
179201
}
180202
const { toolState, validation } = this.currentToolState();
181203
if (!validation.valid || !toolState) {
182204
this.statusLog.error(`Workspace dirty sync skipped: ${validation.message}`);
183205
return;
184206
}
185-
const result = this.workspaceDirtyBridge.syncToolState(toolState, { reason, changedKeys });
207+
const result = await this.workspaceContextService.writeWorkspaceToolPayload(TOOL_ID, toolState.payload, { reason, changedKeys });
186208
if (result.skipped) {
187209
return;
188210
}
@@ -266,7 +288,7 @@ export class AudioSfxPlaygroundV2App {
266288
const entry = this.createSoundEntry(validation.value);
267289
this.renderSoundList();
268290
this.refreshPreview();
269-
this.syncWorkspaceDirty("audio-sfx-sound-added", ["data.sounds", "data.activeSoundId"]);
291+
void this.syncWorkspaceDirty("audio-sfx-sound-added", ["data.sounds", "data.activeSoundId"]);
270292
this.statusLog.write(`Added ${entry.sound.name}.`);
271293
}
272294

@@ -291,7 +313,7 @@ export class AudioSfxPlaygroundV2App {
291313
entry.sound.name = nextName;
292314
this.renderSoundList();
293315
this.refreshPreview();
294-
this.syncWorkspaceDirty("audio-sfx-sound-renamed", ["data.sounds"]);
316+
void this.syncWorkspaceDirty("audio-sfx-sound-renamed", ["data.sounds"]);
295317
this.statusLog.write(`Renamed SFX to ${entry.sound.name}.`);
296318
}
297319

@@ -355,7 +377,7 @@ export class AudioSfxPlaygroundV2App {
355377
}
356378
this.renderSoundList();
357379
this.refreshPreview();
358-
this.syncWorkspaceDirty("audio-sfx-sound-deleted", ["data.sounds", "data.activeSoundId"]);
380+
void this.syncWorkspaceDirty("audio-sfx-sound-deleted", ["data.sounds", "data.activeSoundId"]);
359381
this.statusLog.write(`Deleted ${entry.sound.name}.`);
360382
}
361383

@@ -422,7 +444,7 @@ export class AudioSfxPlaygroundV2App {
422444
this.controls.loadSound(result.value.sound);
423445
this.renderSoundList();
424446
this.refreshPreview();
425-
this.syncWorkspaceDirty("audio-sfx-json-imported", ["data.sounds", "data.activeSoundId"]);
447+
await this.syncWorkspaceDirty("audio-sfx-json-imported", ["data.sounds", "data.activeSoundId"]);
426448
this.statusLog.write(`Imported JSON from ${file.name}.`);
427449
} catch (error) {
428450
this.statusLog.error(`Import JSON failed: ${error.message}`);
@@ -449,8 +471,12 @@ export class AudioSfxPlaygroundV2App {
449471

450472
async writeClipboardText(text) {
451473
if (typeof this.window.navigator?.clipboard?.writeText === "function") {
452-
await this.window.navigator.clipboard.writeText(text);
453-
return;
474+
try {
475+
await this.window.navigator.clipboard.writeText(text);
476+
return;
477+
} catch {
478+
// Fall back to the legacy copy command below when browser permission blocks the async Clipboard API.
479+
}
454480
}
455481

456482
const documentRef = this.window.document;

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { StatusLogControl } from "./controls/StatusLogControl.js";
99
import { ToolShellControl } from "./controls/ToolShellControl.js";
1010
import { AudioSfxEngine } from "./services/AudioSfxEngine.js";
1111
import { ToolStateSerializer } from "./services/ToolStateSerializer.js";
12-
import { WorkspaceDirtyBridge } from "./services/WorkspaceDirtyBridge.js";
12+
import { WorkspaceManagerV2ContextService } from "../../workspace-manager-v2/js/services/WorkspaceManagerV2ContextService.js";
1313

1414
function requireElement(selector) {
1515
const element = document.querySelector(selector);
@@ -81,10 +81,7 @@ window.addEventListener("DOMContentLoaded", () => {
8181
tileList: new SfxTileListControl({
8282
list: requireElement("#sfxTileList")
8383
}),
84-
workspaceDirtyBridge: new WorkspaceDirtyBridge({
85-
serializer,
86-
windowRef: window
87-
}),
84+
workspaceContextService: new WorkspaceManagerV2ContextService(),
8885
windowRef: window
8986
});
9087

tools/audio-sfx-playground-v2/js/services/WorkspaceDirtyBridge.js

Lines changed: 0 additions & 110 deletions
This file was deleted.

0 commit comments

Comments
 (0)