Skip to content

Commit 5c93380

Browse files
committed
Complete MIDI Studio V2 usable UAT build with visible editable note grid and playback - PR_26146_022-midi-studio-v2-uat-completion-build
1 parent 72bef94 commit 5c93380

5 files changed

Lines changed: 186 additions & 2 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# PR_26146_022 MIDI Studio V2 UAT Completion Build Validation
2+
3+
## Scope
4+
- Completed the MIDI Studio V2 primary UAT path around a visible studio surface: top transport bar, left Tracks list, center editable Timeline note grid, and compact right-side diagnostics/status.
5+
- Load Example keeps the explicit Twinkle Twinkle Little Star grid instead of overwriting it with generated chord-only data.
6+
- Guided Song Sheet edits now update the editable grid and normalized playable timeline.
7+
- Export rendering remains honest WARN/not implemented.
8+
9+
## Lanes
10+
- recovery/UAT: executed because this PR repairs manual MIDI Studio V2 usability.
11+
- runtime: executed through targeted MIDI Studio V2 Playwright because UI state, grid editing, Song Sheet normalization, and Preview Synth playback behavior changed.
12+
- integration: skipped because Workspace Manager registration/handoff was not changed.
13+
- engine: skipped because no `src/engine` runtime files changed in this PR.
14+
- samples: SKIP because sample JSON alignment is out of scope.
15+
16+
## Validation Commands
17+
- PASS: `node --check tools/midi-studio-v2/js/MidiStudioV2App.js`
18+
- PASS: `node --check tools/midi-studio-v2/js/controls/InstrumentGridControl.js`
19+
- PASS: `node --check tests/playwright/tools/MidiStudioV2.spec.mjs`
20+
- PASS: `Select-String -Path tools/midi-studio-v2/index.html -Pattern '<script>|<style>|\son[a-zA-Z]+='`
21+
- PASS: `$env:PLAYWRIGHT_BROWSERS_PATH='node_modules/.cache/ms-playwright'; npx.cmd playwright test tests/playwright/tools/MidiStudioV2.spec.mjs --project=playwright --grep "Twinkle Twinkle Little Star test song data|loads example and starts audible Preview Synth MVP playback|edits spreadsheet note cells|parses guided Song Sheet fields"`
22+
- PASS: `git diff --check`
23+
24+
## Playwright Coverage
25+
- Targeted MIDI Studio V2 UAT tests: 4 passed.
26+
- Coverage report written: `docs/dev/reports/playwright_v8_coverage_report.txt`.
27+
- Changed JS guardrail written: `docs/dev/reports/coverage_changed_js_guardrail.txt`.
28+
29+
## Manual UAT Steps
30+
1. Open `tools/midi-studio-v2/index.html?manifestPath=/midi-fixture.game.manifest.json`.
31+
2. Click `Load Example And Play`.
32+
3. Confirm the visible UAT labels are present: `Now playing`, `Tracks`, `Timeline`, and `Edit notes here`.
33+
4. Confirm Twinkle Twinkle Little Star notes appear as editable note blocks in the center grid.
34+
5. Edit a visible note cell and normalize/play again.
35+
6. Confirm Preview Synth playback starts, the playhead moves, and Audio Diagnostics shows playable notes.
36+
7. Click `Stop All Audio` and confirm playback state stops.
37+
8. Edit guided Song Sheet fields and click `Parse Guided Song Sheet`; confirm the grid updates from the Song Sheet.
38+
39+
## Notes
40+
- `npx playwright install chromium` was run because the repo-local Playwright browser cache was not present.
41+
- A non-blocking exploratory full MIDI Studio spec run was not used as the validation gate; it timed out on older legacy-surface coverage after the requested UAT slice had exposed and repaired the Twinkle demo overwrite issue.
42+
- Full samples smoke test was not run per PR instructions.

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,13 @@ test.describe("MIDI Studio V2", () => {
386386
const server = await openMidiStudio(page);
387387
try {
388388
await page.locator("#useExampleButton").click();
389+
await expect(page.locator(".midi-studio-v2__now-playing-label")).toHaveText("Now playing");
390+
await expect(page.locator(".midi-studio-v2__track-list-panel")).toContainText("Tracks");
391+
await expect(page.locator(".midi-studio-v2__track-list li")).toContainText(["Chords", "Bass", "Pad", "Lead", "Drums"]);
392+
await expect(page.locator(".midi-studio-v2__timeline-title")).toContainText("Timeline");
393+
await expect(page.locator(".midi-studio-v2__timeline-title")).toContainText("Edit notes here");
394+
await expect(page.locator("#instrumentGridContent")).toBeVisible();
395+
await expect(page.locator('.accordion-v2__header[aria-controls="instrumentGridContent"]')).toBeHidden();
389396
await expect(page.locator("#songList")).toContainText("Twinkle Twinkle Little Star");
390397
await expect(page.locator("#songList")).toContainText("Demo Missing Target");
391398
await expect(page.locator("#songSheetKeyInput")).toHaveValue("C major");
@@ -642,6 +649,11 @@ test.describe("MIDI Studio V2", () => {
642649
await page.locator("#parseSongSheetButton").click();
643650
await expect(page.locator("#songSheetSummary")).toContainText("intro: 2 bars, 2 chords");
644651
await expect(page.locator("#songSheetSummary")).toContainText("loop: 4 bars, 4 chords, loop");
652+
await expect(page.locator("#instrumentGridSectionsInput")).toHaveValue("intro:2, loop:4");
653+
await expect(page.locator("#instrumentGridChordsInput")).toHaveValue("Am Am Am Am | F F F F | Am Am Am Am | F F F F | C C C C | G G G G");
654+
await expect(spreadsheetCell(page, "chords", 0)).toHaveText("Am");
655+
await expect(spreadsheetCell(page, "chords", 8)).toHaveText("Am");
656+
await expect(spreadsheetCell(page, "bass", 0)).toContainText("A2");
645657
expect(await page.locator("#songSheetSummary div").evaluateAll((rows) => Object.fromEntries(rows.map((row) => [
646658
row.querySelector("dt")?.textContent || "",
647659
row.querySelector("dd")?.textContent || ""
@@ -656,6 +668,7 @@ test.describe("MIDI Studio V2", () => {
656668
"Warnings": "none"
657669
});
658670
await expect(page.locator("#statusLog")).toHaveValue(/OK Song Sheet parsed: 2 sections, 6 bars, 6 chords\./);
671+
await expect(page.locator("#statusLog")).toHaveValue(/OK Song Sheet updated the editable note grid\./);
659672
} finally {
660673
await workspaceV2CoverageReporter.stop(page);
661674
await server.close();

tools/midi-studio-v2/index.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
3939

4040
<nav class="tool-starter__menu tool-starter__tool__menu midi-studio-v2__tool-menu" aria-label="Tool actions" data-launch-mode-nav="tool">
4141
<button id="toolImportManifestButton" type="button">Import Manifest</button>
42+
<span class="midi-studio-v2__now-playing-label">Now playing</span>
4243
<button id="loadExampleAndPlayButton" class="midi-studio-v2__primary-action" type="button">Load Example And Play</button>
4344
<button id="useExampleButton" type="button">Use Example Test Song</button>
4445
<button id="stopAllAudioButton" type="button">Stop All Audio</button>
@@ -67,6 +68,17 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
6768

6869
<main class="tool-starter midi-studio-v2 app-shell" data-tool-id="midi-studio-v2">
6970
<aside class="tool-starter__panel tool-starter__panel--left" aria-label="MIDI song setup">
71+
<section class="midi-studio-v2__uat-card midi-studio-v2__track-list-panel" aria-label="Tracks">
72+
<h2>Tracks</h2>
73+
<ul class="midi-studio-v2__track-list">
74+
<li>Chords</li>
75+
<li>Bass</li>
76+
<li>Pad</li>
77+
<li>Lead</li>
78+
<li>Drums</li>
79+
</ul>
80+
</section>
81+
7082
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
7183
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="songListContent">
7284
<span>Songs</span>
@@ -204,6 +216,10 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
204216
<span class="accordion-v2__icon" aria-hidden="true">+</span>
205217
</button>
206218
<div id="instrumentGridContent" class="accordion-v2__content midi-studio-v2__stack">
219+
<div class="midi-studio-v2__timeline-title">
220+
<h2>Timeline</h2>
221+
<p>Edit notes here</p>
222+
</div>
207223
<div class="midi-studio-v2__grid-settings">
208224
<label class="tool-starter__field" for="instrumentGridSectionsInput">
209225
<span>Sections</span>

tools/midi-studio-v2/js/MidiStudioV2App.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ export class MidiStudioV2App {
269269
this.statusLog.ok(`MIDI source inspected for ${song.name}: format ${result.format}, ${result.trackCount} track${result.trackCount === 1 ? "" : "s"}, ${result.ticksPerQuarterNote} TPQN.`);
270270
}
271271

272-
parseSongSheet(request) {
272+
parseSongSheet(request, { updateGrid = true } = {}) {
273273
const result = request?.ok === false ? request : this.songSheetParser.parse(request?.sourceText || request);
274274
this.songSheet.render(result);
275275
if (!result.ok) {
@@ -283,9 +283,44 @@ export class MidiStudioV2App {
283283
this.statusLog.warn(`Song Sheet parsed with warnings: ${result.warningSummary}`);
284284
}
285285
this.statusLog.ok(`Song Sheet parsed: ${result.sections.length} section${result.sections.length === 1 ? "" : "s"}, ${result.bars} bars, ${result.chordCount} chords.`);
286+
if (updateGrid) {
287+
this.applySongSheetToGrid(result);
288+
}
286289
this.updateAudioDiagnostics();
287290
}
288291

292+
applySongSheetToGrid(result) {
293+
const playableSections = result.sections.filter((section) => section.bars > 0);
294+
if (!playableSections.length || !result.chordCount) {
295+
this.statusLog.warn("Song Sheet did not update the note grid because no playable chord sections were found.");
296+
return;
297+
}
298+
const beatsPerBar = "4";
299+
const sections = playableSections.map((section) => `${section.label}:${section.bars}`).join(", ");
300+
const chords = playableSections
301+
.flatMap((section) => section.chords.map((chord) => Array.from({ length: Number(beatsPerBar) }, () => chord).join(" ")))
302+
.join(" | ");
303+
this.instrumentGrid.applyGridDefaults({
304+
bass: "",
305+
beatsPerBar,
306+
chords,
307+
drums: "",
308+
lead: "",
309+
pad: "",
310+
previewInstruments: this.instrumentGrid.previewLaneSettings().instruments,
311+
sections,
312+
subdivision: "1"
313+
});
314+
["bass", "pad", "lead", "drums"].forEach((lane) => {
315+
const generated = this.instrumentGridParser.generateLane(this.instrumentGrid.readInput(), lane);
316+
if (generated.ok) {
317+
this.instrumentGrid.applyGeneratedLane(generated);
318+
}
319+
});
320+
this.normalizeInstrumentGrid(this.instrumentGrid.readInput());
321+
this.statusLog.ok("Song Sheet updated the editable note grid.");
322+
}
323+
289324
normalizeInstrumentGrid(input) {
290325
const result = this.instrumentGridParser.parse(input);
291326
this.instrumentGrid.render(result);
@@ -500,7 +535,7 @@ export class MidiStudioV2App {
500535
this.songSheet.applyGuidedDefaults(EXAMPLE_GUIDED_SHEET);
501536
this.instrumentGrid.applyGridDefaults(EXAMPLE_GRID);
502537
this.statusLog.ok("Loaded explicit Twinkle Twinkle Little Star test song data. Demo paths are declared for UAT only; they are not hidden fallback assets.");
503-
this.parseSongSheet(this.songSheet.composeGuidedSheet());
538+
this.parseSongSheet(this.songSheet.composeGuidedSheet(), { updateGrid: false });
504539
["bass", "pad", "lead", "drums"].forEach((lane) => {
505540
const input = this.instrumentGrid.readInput();
506541
const laneText = String(input.lanes?.[lane] || "").trim();

tools/midi-studio-v2/styles/midiStudioV2.css

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,71 @@
6565
gap: 0.75rem;
6666
}
6767

68+
.midi-studio-v2__tool-menu {
69+
align-items: center;
70+
position: sticky;
71+
top: 0;
72+
z-index: 5;
73+
}
74+
75+
.midi-studio-v2__now-playing-label {
76+
border: 1px solid var(--tool-starter-line);
77+
border-radius: 6px;
78+
font-size: 0.78rem;
79+
font-weight: 800;
80+
padding: 0.38rem 0.55rem;
81+
text-transform: uppercase;
82+
}
83+
84+
.midi-studio-v2.app-shell {
85+
grid-template-columns: minmax(13rem, 16rem) minmax(0, 1fr) minmax(18rem, 22rem);
86+
}
87+
88+
.midi-studio-v2__uat-card {
89+
border: 1px solid var(--tool-starter-line);
90+
border-radius: 8px;
91+
display: grid;
92+
gap: 0.65rem;
93+
padding: 0.75rem;
94+
}
95+
96+
.midi-studio-v2__uat-card h2,
97+
.midi-studio-v2__timeline-title h2 {
98+
font-size: 0.95rem;
99+
margin: 0;
100+
}
101+
102+
.midi-studio-v2__track-list {
103+
display: grid;
104+
gap: 0.4rem;
105+
list-style: none;
106+
margin: 0;
107+
padding: 0;
108+
}
109+
110+
.midi-studio-v2__track-list li {
111+
background: var(--tool-starter-panel-strong);
112+
border: 1px solid var(--tool-starter-line);
113+
border-radius: 6px;
114+
font-weight: 700;
115+
padding: 0.45rem 0.55rem;
116+
}
117+
118+
.midi-studio-v2__timeline-title {
119+
align-items: center;
120+
display: flex;
121+
gap: 0.75rem;
122+
justify-content: space-between;
123+
}
124+
125+
.midi-studio-v2__timeline-title p {
126+
color: var(--tool-starter-muted);
127+
font-size: 0.85rem;
128+
font-weight: 800;
129+
margin: 0;
130+
text-transform: uppercase;
131+
}
132+
68133
.midi-studio-v2__test-steps {
69134
display: grid;
70135
gap: 0.35rem;
@@ -156,6 +221,19 @@
156221
overflow: auto;
157222
}
158223

224+
body[data-tool-id="midi-studio-v2"] .tool-starter__panel--center > .accordion-v2:not(:has(#instrumentGridContent)),
225+
body[data-tool-id="midi-studio-v2"] .tool-starter__panel--right > .accordion-v2:not(:has(#audioDiagnosticsContent)):not(:has(#statusLogContent)):not(:has(#renderedTargetsContent)) {
226+
display: none;
227+
}
228+
229+
body[data-tool-id="midi-studio-v2"] .tool-starter__panel--center .accordion-v2:has(#instrumentGridContent) > .accordion-v2__header {
230+
display: none;
231+
}
232+
233+
body[data-tool-id="midi-studio-v2"] #instrumentGridContent {
234+
display: grid;
235+
}
236+
159237
.midi-studio-v2__instrument-grid {
160238
display: grid;
161239
min-width: max-content;

0 commit comments

Comments
 (0)