Skip to content

Commit 8d6454b

Browse files
committed
Coalesce Audio SFX slider undo history - PR_26145_025-audio-sfx-coalesce-slider-undo-history
1 parent 4fe9a64 commit 8d6454b

4 files changed

Lines changed: 180 additions & 28 deletions

File tree

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
# Audio / SFX Focused V8 Coverage
1+
# Audio / SFX Slider Undo V8 Coverage
22

3-
Command: `node tmp/audio-sfx-undo-redo-focused.mjs`
4-
Source: Playwright Chromium built-in V8 coverage.
3+
PR: `PR_26145_025-audio-sfx-coalesce-slider-undo-history`
4+
5+
Source: focused Playwright Chromium run using built-in V8 JavaScript coverage.
56
Thresholds: none enforced; coverage is advisory.
67

7-
Changed runtime JS files:
8-
(100%) tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js - executed lines 642/642; advisory V8 coverage
9-
(100%) tools/audio-sfx-playground-v2/js/bootstrap.js - executed lines 100/100; advisory V8 coverage
10-
(100%) tools/audio-sfx-playground-v2/js/controls/ActionNavControl.js - executed lines 115/115; advisory V8 coverage
11-
(100%) tools/audio-sfx-playground-v2/js/controls/SfxControlPanel.js - executed lines 704/704; advisory V8 coverage
8+
Changed runtime JavaScript files:
9+
10+
(82%) tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js - V8 function coverage 64/78; slider edit lifecycle, undo/redo, playback, JSON actions, and tile selection exercised.
11+
(75%) tools/audio-sfx-playground-v2/js/controls/SfxControlPanel.js - V8 function coverage 44/59; slider pointer, keyboard, input, change, pointerup, blur, and non-slider control paths exercised.
12+
13+
Missing changed runtime JavaScript files in coverage: none.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Audio / SFX Slider Undo Coalescing Validation
2+
3+
PR: `PR_26145_025-audio-sfx-coalesce-slider-undo-history`
4+
5+
Playwright impacted: Yes.
6+
7+
## Targeted Static Validation
8+
9+
PASS - `node --check tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js`
10+
11+
PASS - `node --check tools/audio-sfx-playground-v2/js/controls/SfxControlPanel.js`
12+
13+
PASS - `git diff --check -- tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js tools/audio-sfx-playground-v2/js/controls/SfxControlPanel.js`
14+
15+
## Focused Playwright
16+
17+
Command: one-off Playwright Chromium script through `node --input-type=module` with `PLAYWRIGHT_BROWSERS_PATH=0`.
18+
19+
PASS - drag slider creates one undo step: undo stack `1 -> 2`, frequency `880 -> 8550`, focus retained on `frequencyInput`.
20+
21+
PASS - Undo restores selected tile and editor value: selected `sfx-1`, frequency restored to `880`, redo stack `1`.
22+
23+
PASS - Redo restores selected tile and editor value: selected `sfx-1`, frequency restored to `8550`.
24+
25+
PASS - keyboard arrow slider edits keep focus and create undo history: two ArrowRight presses kept focus on `durationInput`, duration `180 -> 190`, undo stack `2 -> 4`.
26+
27+
PASS - unrelated slider edits remain separate undo steps: volume drag `4 -> 5`, pitch sweep drag `5 -> 6`.
28+
29+
PASS - preview and JSON actions do not create undo history: Play, Stop, Stop All, Copy JSON, and Export JSON kept undo stack `7 -> 7` and redo stack `0 -> 0`.
30+
31+
PASS - tile select and audition do not create undo history: tile selection returned to `Laser`; undo stack stayed `7 -> 7`.
32+
33+
PASS - no console errors or page errors.
34+
35+
PASS - Workspace dirty behavior: live slider drag emitted `0` dirty calls during input; pointer completion emitted one dirty call with reason `audio-sfx-slider-change` and `changedKeys: ["data.sounds"]`.
36+
37+
## Workspace V2 Suite
38+
39+
Command: `PLAYWRIGHT_BROWSERS_PATH=0 npm run test:workspace-v2`
40+
41+
Result: FAIL - 68 passed, 4 failed. Per BUILD instructions, the full suite was not rerun because failures are unrelated to Audio / SFX Playground V2 slider Undo/Redo behavior.
42+
43+
Known unrelated failing tests:
44+
45+
- `Workspace Manager V2 bootstrap › resolves game manifest schema refs from the game schema during repo discovery`
46+
- `Workspace Manager V2 bootstrap › enables object vector and collision tools only from manifest geometry without fallback defaults`
47+
- `Workspace Manager V2 bootstrap › uses header lifecycle controls and launches tools from fixed Workspace Manager V2 tiles`
48+
- `Workspace Manager V2 bootstrap › owns temporary UAT manifest seeding and launches Asset Manager V2 through session context`
49+
50+
Failure notes:
51+
52+
- The first two failures expected `AI Target Dummy` in `#activeGameSelect option`, but the option list did not include it.
53+
- The fixed-tile launch failure expected Audio / SFX schema role `workspace-launch-context`, but received `workspace-tool-payload`.
54+
- The UAT manifest seeding test timed out at 120000 ms and then failed during coverage stop because the page/context had closed.
55+
56+
## Full Samples Smoke
57+
58+
Skipped. This PR only impacts Audio / SFX Playground V2 Undo/Redo behavior.

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

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export class AudioSfxPlaygroundV2App {
101101
this.soundEntries = [];
102102
this.statusLog = statusLog;
103103
this.tileList = tileList;
104+
this.activeSliderEdit = null;
104105
this.redoStack = [];
105106
this.undoStack = [];
106107
this.historyBaselineSnapshot = null;
@@ -138,7 +139,10 @@ export class AudioSfxPlaygroundV2App {
138139
onAdd: () => this.addCurrentSound(),
139140
onChange: () => this.handleEditorChange(),
140141
onDelete: () => this.deleteCurrentSound(),
141-
onRename: () => this.renameCurrentSound()
142+
onRename: () => this.renameCurrentSound(),
143+
onSliderEditCommit: (sliderId) => this.commitSliderEditorChange(sliderId),
144+
onSliderEditStart: (sliderId) => this.beginSliderEditorChange(sliderId),
145+
onSliderInput: (sliderId) => this.handleSliderEditorInput(sliderId)
142146
});
143147
this.tileList.mount({
144148
onSelect: (soundId) => {
@@ -217,22 +221,68 @@ export class AudioSfxPlaygroundV2App {
217221
}
218222

219223
handleEditorChange() {
224+
this.commitSliderEditorChange();
220225
const beforeSnapshot = this.historyBaselineSnapshot;
221-
const validation = this.controls.validate({ nameOverride: this.activeSoundName() });
222-
if (validation.valid && this.updateActiveSound(this.soundForActiveEditorValue(validation.value))) {
223-
this.renderSoundList();
226+
const updateResult = this.updateEditorValueFromControls();
227+
if (updateResult.shouldCommit) {
224228
this.commitUndoableChange({
225229
beforeSnapshot,
226230
changedKeys: ["data.sounds"],
227-
reason: "audio-sfx-editor-change"
231+
reason: "audio-sfx-editor-change",
232+
shouldSyncDirty: updateResult.shouldSyncDirty
228233
});
229234
}
235+
this.refreshPreview();
236+
}
237+
238+
updateEditorValueFromControls() {
239+
const validation = this.controls.validate({ nameOverride: this.activeSoundName() });
240+
if (validation.valid && this.updateActiveSound(this.soundForActiveEditorValue(validation.value))) {
241+
this.renderSoundList();
242+
return { shouldCommit: true, shouldSyncDirty: true, valid: true };
243+
}
230244
if (validation.valid && !this.activeSoundId) {
245+
return { shouldCommit: true, shouldSyncDirty: false, valid: true };
246+
}
247+
return { shouldCommit: false, shouldSyncDirty: false, valid: validation.valid };
248+
}
249+
250+
beginSliderEditorChange(sliderId) {
251+
if (this.activeSliderEdit?.sliderId === sliderId) {
252+
return;
253+
}
254+
this.commitSliderEditorChange();
255+
this.activeSliderEdit = {
256+
beforeSnapshot: this.createHistorySnapshot() || this.historyBaselineSnapshot,
257+
sliderId
258+
};
259+
}
260+
261+
handleSliderEditorInput(sliderId) {
262+
if (!this.activeSliderEdit || this.activeSliderEdit.sliderId !== sliderId) {
263+
this.beginSliderEditorChange(sliderId);
264+
}
265+
this.updateEditorValueFromControls();
266+
this.refreshPreview();
267+
}
268+
269+
commitSliderEditorChange(sliderId = "") {
270+
if (!this.activeSliderEdit || (sliderId && this.activeSliderEdit.sliderId !== sliderId)) {
271+
return;
272+
}
273+
const { beforeSnapshot } = this.activeSliderEdit;
274+
this.activeSliderEdit = null;
275+
const updateResult = this.updateEditorValueFromControls();
276+
if (!updateResult.valid) {
277+
this.refreshPreview();
278+
return;
279+
}
280+
if (updateResult.shouldCommit) {
231281
this.commitUndoableChange({
232282
beforeSnapshot,
233283
changedKeys: ["data.sounds"],
234-
reason: "audio-sfx-editor-change",
235-
shouldSyncDirty: false
284+
reason: "audio-sfx-slider-change",
285+
shouldSyncDirty: updateResult.shouldSyncDirty
236286
});
237287
}
238288
this.refreshPreview();

tools/audio-sfx-playground-v2/js/controls/SfxControlPanel.js

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ const SLIDER_RANGE_UNITS = Object.freeze({
3434
volume: ""
3535
});
3636

37+
const SLIDER_KEYBOARD_EDIT_KEYS = Object.freeze(new Set([
38+
"ArrowDown",
39+
"ArrowLeft",
40+
"ArrowRight",
41+
"ArrowUp",
42+
"End",
43+
"Home",
44+
"PageDown",
45+
"PageUp"
46+
]));
47+
3748
const RECOMMENDED_ZONE_SPAN = Object.freeze({
3849
attackMs: 0.36,
3950
durationMs: 0.34,
@@ -392,12 +403,22 @@ export class SfxControlPanel {
392403
this.waveformSelect = waveformSelect;
393404
}
394405

395-
mount({ onAdd, onChange, onDelete, onRename }) {
406+
mount({
407+
onAdd,
408+
onChange,
409+
onDelete,
410+
onRename,
411+
onSliderEditCommit = () => {},
412+
onSliderEditStart = () => {},
413+
onSliderInput = () => {}
414+
}) {
396415
this.applySliderLimits();
397416
this.loadSound(DEFAULT_SOUND);
398417
this.addButton.addEventListener("click", onAdd);
399418
this.deleteButton.addEventListener("click", onDelete);
400419
this.renameButton.addEventListener("click", onRename);
420+
this.styleProfileSelect.addEventListener("pointerdown", () => onSliderEditCommit());
421+
this.styleProfileSelect.addEventListener("focus", () => onSliderEditCommit());
401422
this.styleProfileSelect.addEventListener("change", () => {
402423
if (this.applyStyleProfile()) {
403424
onChange();
@@ -406,19 +427,12 @@ export class SfxControlPanel {
406427
this.setDeleteEnabled(false);
407428
this.setRenameEnabled(false);
408429
[
409-
this.attackInput,
410-
this.durationInput,
411-
this.frequencyInput,
412430
this.loopingInput,
413-
this.noiseAmountInput,
414-
this.noiseDecayInput,
415-
this.noiseFilterInput,
416431
this.noiseInput,
417-
this.pitchSweepInput,
418-
this.releaseInput,
419-
this.volumeInput,
420432
this.waveformSelect
421433
].forEach((control) => {
434+
control.addEventListener("pointerdown", () => onSliderEditCommit());
435+
control.addEventListener("focus", () => onSliderEditCommit());
422436
control.addEventListener("input", () => {
423437
this.syncOutputs();
424438
onChange();
@@ -429,9 +443,37 @@ export class SfxControlPanel {
429443
});
430444
});
431445
this.sliderInputs().forEach((input) => {
432-
input.addEventListener("pointerdown", () => this.focusSlider(input));
433-
input.addEventListener("input", () => this.focusSlider(input));
434-
input.addEventListener("change", () => this.focusSlider(input));
446+
input.addEventListener("pointerdown", () => {
447+
this.focusSlider(input);
448+
onSliderEditStart(input.id);
449+
});
450+
input.addEventListener("focus", () => onSliderEditStart(input.id));
451+
input.addEventListener("keydown", (event) => {
452+
if (SLIDER_KEYBOARD_EDIT_KEYS.has(event.key)) {
453+
onSliderEditStart(input.id);
454+
}
455+
});
456+
input.addEventListener("input", () => {
457+
this.focusSlider(input);
458+
this.syncOutputs();
459+
onSliderInput(input.id);
460+
});
461+
input.addEventListener("change", () => {
462+
this.focusSlider(input);
463+
this.syncOutputs();
464+
onSliderEditCommit(input.id);
465+
});
466+
input.addEventListener("pointerup", () => {
467+
this.focusSlider(input);
468+
this.syncOutputs();
469+
onSliderEditCommit(input.id);
470+
});
471+
input.addEventListener("pointercancel", () => {
472+
this.focusSlider(input);
473+
this.syncOutputs();
474+
onSliderEditCommit(input.id);
475+
});
476+
input.addEventListener("blur", () => onSliderEditCommit(input.id));
435477
});
436478
}
437479

0 commit comments

Comments
 (0)