Skip to content

Commit 7e7a12e

Browse files
committed
Remove duplicate MIDI Studio V2 octave sheets and polish piano key indentation - PR_26146_042-midi-studio-v2-single-studio-octave-editor
1 parent 6cd6c1a commit 7e7a12e

4 files changed

Lines changed: 141 additions & 6 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# PR_26146_042 MIDI Studio V2 Single Studio Octave Editor Validation
2+
3+
Status: PASS
4+
5+
## Scope
6+
7+
- Kept a single Octave Timeline editor DOM instance and made it visible only on the Studio tab.
8+
- Removed the visible editor from Instruments and Auto-Create Parts while preserving Auto-Create setup controls.
9+
- Preserved the Studio tab grid as the source of truth for playback, editing, zoom, vertical scrolling, section feedback, and selected instrument highlighting.
10+
- Preserved PR041 piano colors and Bar/Beat `#3600af` header color.
11+
- Changed piano-key shape only: natural keys remain full width and sharp/flat keys are visually indented/narrower while labels stay left-aligned.
12+
13+
## Files Changed
14+
15+
- `tools/midi-studio-v2/index.html`
16+
- `tools/midi-studio-v2/styles/midiStudioV2.css`
17+
- `tests/playwright/tools/MidiStudioV2.spec.mjs`
18+
- `docs/dev/reports/PR_26146_042-midi-studio-v2-single-studio-octave-editor_validation.md`
19+
- `docs/dev/reports/coverage_changed_js_guardrail.txt`
20+
- `docs/dev/codex_commands.md`
21+
- `docs/dev/commit_comment.txt`
22+
23+
Review artifacts are generated separately:
24+
25+
- `docs/dev/reports/codex_review.diff`
26+
- `docs/dev/reports/codex_changed_files.txt`
27+
- `docs/dev/reports/playwright_v8_coverage_report.txt`
28+
29+
## Validation Commands
30+
31+
PASS:
32+
33+
```powershell
34+
node --check tests/playwright/tools/MidiStudioV2.spec.mjs
35+
node -e "const fs=require('fs'); const p='tools/midi-studio-v2/index.html'; const html=fs.readFileSync(p,'utf8'); if(/<script(?![^>]*\ssrc=)[^>]*>/i.test(html)) throw new Error('Inline script block found'); if(/<style\b/i.test(html)) throw new Error('Inline style block found'); if(/\son[a-z]+\s*=/i.test(html)) throw new Error('Inline event handler found'); console.log('HTML external-only guard passed:', p);"
36+
node -e "const fs=require('fs'); for (const file of ['tools/midi-studio-v2/styles/midiStudioV2.css']) { const text=fs.readFileSync(file,'utf8'); let depth=0; for (const ch of text) { if (ch==='{') depth++; if (ch==='}') depth--; if (depth<0) throw new Error(file+': unexpected }'); } if (depth!==0) throw new Error(file+': unbalanced braces'); console.log(file+': CSS brace check OK'); }"
37+
npx.cmd playwright test tests/playwright/tools/MidiStudioV2.spec.mjs -g "octave timeline freezes compact headers and note labels while active cells stay textless|fast octave note editing supports drag painting keyboard shortcuts selection and timeline scroll sync|renders timing ruler, section navigation, and loop region visualization" --config=codex_playwright_system_chrome.config.cjs --reporter=list --workers=1 --timeout=60000
38+
git diff --check
39+
```
40+
41+
Playwright result:
42+
43+
```text
44+
Running 3 tests using 1 worker
45+
46+
ok 1 MIDI Studio V2 - fast octave note editing supports drag painting keyboard shortcuts selection and timeline scroll sync
47+
ok 2 MIDI Studio V2 - octave timeline freezes compact headers and note labels while active cells stay textless
48+
ok 3 MIDI Studio V2 - renders timing ruler, section navigation, and loop region visualization
49+
50+
3 passed
51+
```
52+
53+
Notes:
54+
55+
- An initial targeted run failed because the piano-label CSS accidentally overrode sticky positioning. The CSS was corrected, and the final targeted run passed.
56+
- `git diff --check` exited 0. Git emitted line-ending warnings for touched files; no whitespace errors were reported.
57+
- Full samples smoke test was not run.
58+
59+
## Test Evidence
60+
61+
- Only one editor exists: Playwright verifies one `#instrumentGridOutput`, one `#instrumentGridHeading`, and one `.midi-studio-v2__octave-timeline`.
62+
- Remaining editor is Studio-only: Playwright verifies `#instrumentGridOutput` is tagged for the Studio tab, visible on Studio, and hidden on Auto-Create Parts and Instruments.
63+
- Playback/editing uses Studio grid: Playwright performs note editing and Play/Stop on the Studio grid after tab switching.
64+
- Sharp/flat keys are indented/narrower: Playwright compares black-key `::before` left offset and width against the natural key.
65+
- Natural keys are full width: Playwright verifies the white-key shape width matches the label column width.
66+
- Piano key text remains left-aligned: Playwright verifies text alignment and grid justification for white and black keys.
67+
- Grid row alignment is preserved: Playwright verifies label/cell top and height deltas remain within 1px and frozen labels remain visible during horizontal scroll.
68+
- Play and Stop still work: Playwright verifies existing keyboard/button Play/Stop behavior through the targeted MIDI Studio V2 tests.
69+
70+
## Samples
71+
72+
Full samples smoke test: SKIP.
73+
74+
Reason: PR scope is limited to MIDI Studio V2 tool UI/runtime and targeted Playwright coverage; sample JSON alignment and full samples smoke are explicitly out of scope.
75+
76+
## Manual UAT
77+
78+
1. Open MIDI Studio V2 and import the UAT manifest.
79+
2. Confirm the Octave Timeline editor is visible on Studio.
80+
3. Switch to Instruments and Auto-Create Parts and confirm no duplicate octave editor is visible.
81+
4. Return to Studio, edit a note, use zoom, and scroll vertically/horizontally.
82+
5. Confirm black piano keys are visibly indented/narrower and text remains readable.
83+
6. Press Play and Stop and confirm playback still uses the Studio grid.

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,22 @@ test.describe("MIDI Studio V2", () => {
977977
await expect(page.locator("#instrumentGridSnapIndicator")).toContainText("Snap:");
978978
await expect(page.locator("#instrumentGridZoomOutButton")).toHaveAttribute("aria-label", "Zoom out octave grid");
979979
await expect(page.locator("#instrumentGridZoomInButton")).toHaveAttribute("aria-label", "Zoom in octave grid");
980+
await expect(page.locator("#instrumentGridHeading")).toHaveCount(1);
981+
await expect(page.locator("#instrumentGridOutput")).toHaveCount(1);
982+
await expect(page.locator(".midi-studio-v2__octave-timeline")).toHaveCount(1);
983+
await expect(page.locator("#instrumentGridOutput")).toBeVisible();
984+
await expect(page.locator(".midi-studio-v2__octave-timeline")).toBeVisible();
985+
expect(await page.locator("#instrumentGridOutput").evaluate((element) => element.dataset.midiStudioTabPanel)).toBe("studio");
986+
await selectMidiStudioTab(page, "auto-create-parts");
987+
await expect(page.locator("#instrumentGridOutput")).toBeHidden();
988+
await expect(page.locator(".midi-studio-v2__octave-timeline")).toBeHidden();
989+
await expect(page.locator(".midi-studio-v2__timeline-title")).toBeHidden();
990+
await selectMidiStudioTab(page, "instruments");
991+
await expect(page.locator("#instrumentGridOutput")).toBeHidden();
992+
await expect(page.locator(".midi-studio-v2__octave-timeline")).toBeHidden();
993+
await selectMidiStudioTab(page, "studio");
994+
await expect(page.locator("#instrumentGridOutput")).toBeVisible();
995+
await expect(page.locator(".midi-studio-v2__octave-timeline")).toBeVisible();
980996
const zoomControlsLayout = await page.locator(".midi-studio-v2__timeline-title").evaluate((header) => {
981997
const snap = header.querySelector("#instrumentGridSnapIndicator").getBoundingClientRect();
982998
const zoomOut = header.querySelector("#instrumentGridZoomOutButton").getBoundingClientRect();
@@ -1015,6 +1031,8 @@ test.describe("MIDI Studio V2", () => {
10151031
const beatHeaderStyle = getComputedStyle(element.querySelector('.midi-studio-v2__timing-header-row-2.midi-studio-v2__note-table-column-header[data-step-index="1"]'));
10161032
const barAxisStyle = getComputedStyle(element.querySelector(".midi-studio-v2__timing-header-row-1.midi-studio-v2__timing-axis-header"));
10171033
const beatAxisStyle = getComputedStyle(element.querySelector(".midi-studio-v2__timing-header-row-2.midi-studio-v2__timing-axis-header"));
1034+
const whiteKeyStyle = getComputedStyle(whiteLabel, "::before");
1035+
const blackKeyStyle = getComputedStyle(blackLabel, "::before");
10181036
const whiteLabelRect = whiteLabel.getBoundingClientRect();
10191037
const blackLabelRect = blackLabel.getBoundingClientRect();
10201038
const whiteCellRect = whiteCell.getBoundingClientRect();
@@ -1025,6 +1043,8 @@ test.describe("MIDI Studio V2", () => {
10251043
blackClass: blackLabel.className,
10261044
blackColor: blackLabelStyle.color,
10271045
blackJustifyItems: blackLabelStyle.justifyItems,
1046+
blackKeyLeft: Number.parseFloat(blackKeyStyle.left),
1047+
blackKeyWidth: Number.parseFloat(blackKeyStyle.width),
10281048
blackKind: blackLabel.dataset.keyKind,
10291049
blackLabelHeight: blackLabelRect.height,
10301050
blackLabelText: blackLabel.textContent,
@@ -1049,6 +1069,8 @@ test.describe("MIDI Studio V2", () => {
10491069
whiteClass: whiteLabel.className,
10501070
whiteColor: whiteLabelStyle.color,
10511071
whiteJustifyItems: whiteLabelStyle.justifyItems,
1072+
whiteKeyLeft: Number.parseFloat(whiteKeyStyle.left),
1073+
whiteKeyWidth: Number.parseFloat(whiteKeyStyle.width),
10521074
whiteKind: whiteLabel.dataset.keyKind,
10531075
whiteLabelHeight: whiteLabelRect.height,
10541076
whiteLabelText: whiteLabel.textContent,
@@ -1092,6 +1114,10 @@ test.describe("MIDI Studio V2", () => {
10921114
expect(gridLayout.blackTextAlign).toBe("left");
10931115
expect(gridLayout.whiteJustifyItems).toBe("start");
10941116
expect(gridLayout.blackJustifyItems).toBe("start");
1117+
expect(gridLayout.whiteKeyLeft).toBe(0);
1118+
expect(Math.abs(gridLayout.whiteKeyWidth - gridLayout.labelWidth)).toBeLessThanOrEqual(1);
1119+
expect(gridLayout.blackKeyLeft).toBeGreaterThan(0);
1120+
expect(gridLayout.blackKeyWidth).toBeLessThan(gridLayout.whiteKeyWidth);
10951121
expect(gridLayout.whiteRowDelta).toBeLessThanOrEqual(1);
10961122
expect(gridLayout.blackRowDelta).toBeLessThanOrEqual(1);
10971123
expect(gridLayout.whiteRowHeightDelta).toBeLessThanOrEqual(1);

tools/midi-studio-v2/index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,9 @@ <h3>Song Sheet</h3>
192192
</div>
193193
</section>
194194

195-
<section class="midi-studio-v2__timeline-editor-panel" data-midi-studio-tab-panel="studio instruments auto-create-parts" aria-labelledby="instrumentGridHeading">
195+
<section class="midi-studio-v2__timeline-editor-panel" data-midi-studio-tab-panel="studio auto-create-parts" aria-labelledby="instrumentGridHeading">
196196
<div id="instrumentGridContent" class="midi-studio-v2__stack">
197-
<div class="midi-studio-v2__timeline-title">
197+
<div class="midi-studio-v2__timeline-title" data-midi-studio-tab-panel="studio">
198198
<h2 id="instrumentGridHeading">Octave Timeline</h2>
199199
<p>Edit Notes Here</p>
200200
<div class="midi-studio-v2__timeline-title-actions">
@@ -233,7 +233,7 @@ <h2 id="instrumentGridHeading">Octave Timeline</h2>
233233
</select>
234234
</label>
235235
</div>
236-
<div id="instrumentGridOutput" class="midi-studio-v2__instrument-grid-output" aria-label="Octave timeline note editor"></div>
236+
<div id="instrumentGridOutput" class="midi-studio-v2__instrument-grid-output" aria-label="Octave timeline note editor" data-midi-studio-tab-panel="studio"></div>
237237
<details class="midi-studio-v2__advanced-lane-source" data-midi-studio-tab-panel="auto-create-parts">
238238
<summary>Advanced lane source text</summary>
239239
<div class="midi-studio-v2__grid-lane-inputs midi-studio-v2__grid-lane-inputs--source" aria-label="Advanced pipe-delimited lane source inputs">
@@ -266,7 +266,7 @@ <h2 id="instrumentGridHeading">Octave Timeline</h2>
266266
<button id="generateBasicDrumsButton" type="button">Generate Basic Drums</button>
267267
<button id="normalizeInstrumentGridButton" type="button">Normalize Grid</button>
268268
</div>
269-
<div class="midi-studio-v2__transport-panel" aria-label="Instrument grid Preview Synth and timing preview" data-midi-studio-tab-panel="studio instruments auto-create-parts">
269+
<div class="midi-studio-v2__transport-panel" aria-label="Instrument grid Preview Synth and timing preview" data-midi-studio-tab-panel="studio auto-create-parts">
270270
<div class="midi-studio-v2__section-presets" aria-label="Section shortcuts">
271271
<button class="midi-studio-v2__section-preset" type="button" data-section-preset="intro">Intro</button>
272272
<button class="midi-studio-v2__section-preset" type="button" data-section-preset="loop">Loop</button>

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,16 +439,29 @@ body[data-tool-id="midi-studio-v2"] #instrumentGridContent {
439439

440440
.midi-studio-v2__octave-row-label {
441441
align-content: center;
442+
background: var(--tool-starter-surface);
442443
height: var(--midi-studio-v2-octave-cell-size);
443444
font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
444445
font-size: 0.7rem;
446+
isolation: isolate;
445447
justify-items: start;
446448
min-height: var(--midi-studio-v2-octave-cell-size);
449+
overflow: hidden;
447450
padding: 0 0.45rem;
448451
text-align: left;
449452
text-transform: none;
450453
}
451454

455+
.midi-studio-v2__octave-row-label::before {
456+
content: "";
457+
inset-block: 0;
458+
left: 0;
459+
pointer-events: none;
460+
position: absolute;
461+
width: 100%;
462+
z-index: 0;
463+
}
464+
452465
.midi-studio-v2__timeline-scroll-proxy {
453466
background: var(--tool-starter-surface);
454467
border-bottom: 1px solid var(--tool-starter-line);
@@ -758,13 +771,26 @@ body[data-tool-id="midi-studio-v2"] #instrumentGridContent {
758771
box-shadow: inset -0.28rem 0 0 rgba(148, 163, 184, 0.28), inset 0 -1px 0 rgba(15, 23, 42, 0.16);
759772
}
760773

774+
.midi-studio-v2__octave-row-label--white-key::before {
775+
background: linear-gradient(90deg, #ffffff 0%, #f8fafc 72%, #dbeafe 100%);
776+
box-shadow: inset -0.28rem 0 0 rgba(148, 163, 184, 0.28), inset 0 -1px 0 rgba(15, 23, 42, 0.16);
777+
width: 100%;
778+
}
779+
761780
.midi-studio-v2__octave-row-label--black-key {
762-
background: linear-gradient(90deg, #020617 0%, #020617 68%, #1e293b 100%);
781+
background: color-mix(in srgb, var(--tool-starter-surface) 78%, #020617 22%);
763782
border-right-color: #020617;
764783
color: #f8fafc;
765784
justify-items: start;
766-
padding-left: 0.45rem;
785+
padding-left: 0.95rem;
786+
}
787+
788+
.midi-studio-v2__octave-row-label--black-key::before {
789+
background: linear-gradient(90deg, #020617 0%, #020617 68%, #1e293b 100%);
790+
border-radius: 0 0 4px 0;
767791
box-shadow: inset -1.15rem 0 0 rgba(2, 6, 23, 0.96), inset 0 -1px 0 rgba(248, 250, 252, 0.18);
792+
left: 0.65rem;
793+
width: 68%;
768794
}
769795

770796
.midi-studio-v2__octave-row-label--percussion-key {

0 commit comments

Comments
 (0)