Skip to content

Commit 03d3e93

Browse files
committed
Polish MIDI Studio V2 launch-specific NAV bars and tab styling - PR_26146_049-midi-studio-v2-nav-and-tab-mode-polish
1 parent 70d4ed8 commit 03d3e93

6 files changed

Lines changed: 162 additions & 42 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# PR_26146_049 MIDI Studio V2 NAV and Tab Mode Polish Validation
2+
3+
Status: PASS
4+
5+
## Scope
6+
7+
- Removed the visible launch-mode badge text from MIDI Studio V2 while preserving internal launch-mode state on `body[data-midi-studio-launch-mode]`.
8+
- Preserved launch-specific NAV behavior so workspace launch shows only Workspace NAV actions and tool launch shows only tool-owned Import/Save/Export actions.
9+
- Normalized `Import JSON Manifest` to use the same NAV button styling as neighboring action buttons.
10+
- Restyled MIDI Studio section controls as tabs with matching NAV font, clickable inactive tabs, and a distinct active tab.
11+
12+
## Changed Files
13+
14+
- `tools/midi-studio-v2/index.html`
15+
- `tools/midi-studio-v2/js/bootstrap.js`
16+
- `tools/midi-studio-v2/js/controls/ActionNavControl.js`
17+
- `tools/midi-studio-v2/styles/midiStudioV2.css`
18+
- `tests/playwright/tools/MidiStudioV2.spec.mjs`
19+
20+
## Validation Commands
21+
22+
- PASS: `node --check tools/midi-studio-v2/js/bootstrap.js`
23+
- PASS: `node --check tools/midi-studio-v2/js/controls/ActionNavControl.js`
24+
- PASS: `node --check tests/playwright/tools/MidiStudioV2.spec.mjs`
25+
- PASS: HTML external-only guard for `tools/midi-studio-v2/index.html`
26+
- PASS: CSS brace check for `tools/midi-studio-v2/styles/midiStudioV2.css`
27+
- PASS: `npx.cmd playwright test tests/playwright/tools/MidiStudioV2.spec.mjs -g "separates Workspace launch save ownership from Tool Mode standalone save|launches and renders a valid multi-song manifest payload|exports output through Type dropdown and Save Output without claiming project save|fast octave note editing supports drag painting keyboard shortcuts selection and timeline scroll sync" --config=codex_playwright_system_chrome.config.cjs --reporter=list --workers=1 --timeout=60000`
28+
- PASS: `git diff --check`
29+
30+
## Playwright Coverage
31+
32+
The targeted MIDI Studio V2 Playwright run proved:
33+
34+
- Workspace launch shows Workspace NAV and hides the tool-only NAV.
35+
- Workspace launch exposes `Return to Workspace` without standalone Save/Import/Export output controls.
36+
- Tool launch shows tool-only Import JSON Manifest, Save Project, and output Export Type/Save controls.
37+
- Workspace-only return controls are hidden in tool launch.
38+
- Visible `Tool Mode` and `Workspace Mode` labels are absent.
39+
- `Import JSON Manifest` computed font, weight, padding, and button shape match the other NAV buttons.
40+
- Studio, Song Setup, Instruments, Auto-Create Parts, MIDI Import, and Diagnostics render as tabs.
41+
- Tab font matches NAV button font.
42+
- Active tab is visually distinct and inactive tabs remain clickable.
43+
- Tab switching still shows the MIDI Import and Studio panels.
44+
- Play and Stop still work through the existing fast octave editing coverage.
45+
46+
## Samples Decision
47+
48+
Full samples smoke test: SKIP. This PR only changes MIDI Studio V2 launch NAV presentation, tab styling, and targeted Playwright coverage. No sample JSON, shared runtime, or broad sample loading behavior changed.
49+
50+
## Notes
51+
52+
- Initial targeted Playwright run exposed a brittle exact subpixel border assertion for tabs. The implementation was kept scoped, and the test was corrected to prove visible tab shape and active/inactive distinction instead of an environment-specific fractional border value.

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,10 +1076,14 @@ test.describe("MIDI Studio V2", () => {
10761076
const manifest = JSON.parse(await fs.readFile(uatManifestPath, "utf8"));
10771077
const server = await openMidiStudioFromWorkspace(page, manifest);
10781078
try {
1079-
await expect(page.locator("#launchModeIndicator")).toHaveText("Workspace Mode");
1079+
await expect(page.locator("#launchModeIndicator")).toHaveCount(0);
1080+
await expect(page.getByText("Tool Mode", { exact: true })).toHaveCount(0);
1081+
await expect(page.getByText("Workspace Mode", { exact: true })).toHaveCount(0);
10801082
await expect(page.locator("body")).toHaveAttribute("data-midi-studio-launch-mode", "workspace");
10811083
await expect(page.locator('[data-launch-mode-nav="workspace"]')).toBeVisible();
1084+
await expect(page.locator('[data-launch-mode-nav="tool"]')).toBeHidden();
10821085
await expect(page.locator("#returnToWorkspaceButton")).toBeVisible();
1086+
await expect(page.locator('[data-launch-mode-nav="workspace"] button:not([hidden])')).toHaveText(["Return to Workspace"]);
10831087
await expect(page.locator("#saveProjectButton")).toBeHidden();
10841088
await expect(page.locator("#toolImportManifestButton")).toBeHidden();
10851089
await expect(page.locator("#renderedExportSaveButton")).toBeHidden();
@@ -1871,7 +1875,11 @@ test.describe("MIDI Studio V2", () => {
18711875
try {
18721876
await expect(page.locator("body")).toHaveAttribute("data-tool-id", "midi-studio-v2");
18731877
await expect(page.locator("body")).toHaveAttribute("data-midi-studio-launch-mode", "tool");
1874-
await expect(page.locator("#launchModeIndicator")).toHaveText("Tool Mode");
1878+
await expect(page.locator("#launchModeIndicator")).toHaveCount(0);
1879+
await expect(page.getByText("Tool Mode", { exact: true })).toHaveCount(0);
1880+
await expect(page.locator('[data-launch-mode-nav="tool"]')).toBeVisible();
1881+
await expect(page.locator('[data-launch-mode-nav="workspace"]')).toBeHidden();
1882+
await expect(page.locator("#returnToWorkspaceButton")).toBeHidden();
18751883
await expect(page.locator("[data-midi-studio-header]")).toContainText("MIDI Studio V2");
18761884
await expect(page.locator("#songList [data-song-id]")).toHaveCount(3);
18771885
await expect(page.locator('[data-song-id="theme-main"]')).toHaveAttribute("aria-pressed", "true");
@@ -1895,6 +1903,74 @@ test.describe("MIDI Studio V2", () => {
18951903
await expect(page.locator(".midi-studio-v2__tool-menu #renderedExportTargetTypeSelect")).toBeVisible();
18961904
await expect(page.locator(".midi-studio-v2__tool-menu #renderedExportSaveButton")).toBeVisible();
18971905
await expect(page.locator(".midi-studio-v2__tool-menu #renderedExportSaveButton")).toHaveText("Save Output");
1906+
const navAndTabPresentation = await page.evaluate(() => {
1907+
const readActionButton = (selector) => {
1908+
const style = getComputedStyle(document.querySelector(selector));
1909+
return {
1910+
borderRadius: style.borderRadius,
1911+
fontFamily: style.fontFamily,
1912+
fontSize: style.fontSize,
1913+
fontWeight: style.fontWeight,
1914+
paddingBottom: style.paddingBottom,
1915+
paddingLeft: style.paddingLeft,
1916+
paddingRight: style.paddingRight,
1917+
paddingTop: style.paddingTop
1918+
};
1919+
};
1920+
const readTab = (tab) => {
1921+
const style = getComputedStyle(tab);
1922+
return {
1923+
backgroundColor: style.backgroundColor,
1924+
borderTopLeftRadius: style.borderTopLeftRadius,
1925+
borderTopRightRadius: style.borderTopRightRadius,
1926+
color: style.color,
1927+
cursor: style.cursor,
1928+
fontFamily: style.fontFamily,
1929+
fontSize: style.fontSize,
1930+
fontWeight: style.fontWeight
1931+
};
1932+
};
1933+
const tabs = Array.from(document.querySelectorAll(".midi-studio-v2__tabs [role='tab']"));
1934+
const activeTab = tabs.find((tab) => tab.classList.contains("is-active"));
1935+
const inactiveTab = tabs.find((tab) => !tab.classList.contains("is-active"));
1936+
return {
1937+
activeTab: readTab(activeTab),
1938+
importButton: readActionButton("#toolImportManifestButton"),
1939+
inactiveTab: readTab(inactiveTab),
1940+
stopAllButton: readActionButton("#stopAllAudioButton"),
1941+
tabLabels: tabs.map((tab) => tab.textContent.trim())
1942+
};
1943+
});
1944+
expect(navAndTabPresentation.importButton).toEqual(navAndTabPresentation.stopAllButton);
1945+
expect(navAndTabPresentation.activeTab).toMatchObject({
1946+
cursor: "pointer",
1947+
fontFamily: navAndTabPresentation.stopAllButton.fontFamily,
1948+
fontSize: navAndTabPresentation.stopAllButton.fontSize,
1949+
fontWeight: navAndTabPresentation.stopAllButton.fontWeight
1950+
});
1951+
expect(navAndTabPresentation.inactiveTab).toMatchObject({
1952+
cursor: "pointer",
1953+
fontFamily: navAndTabPresentation.stopAllButton.fontFamily,
1954+
fontSize: navAndTabPresentation.stopAllButton.fontSize,
1955+
fontWeight: navAndTabPresentation.stopAllButton.fontWeight
1956+
});
1957+
expect(navAndTabPresentation.activeTab.backgroundColor).not.toBe(navAndTabPresentation.inactiveTab.backgroundColor);
1958+
expect(navAndTabPresentation.activeTab.borderTopLeftRadius).not.toBe("0px");
1959+
expect(navAndTabPresentation.activeTab.borderTopRightRadius).not.toBe("0px");
1960+
expect(navAndTabPresentation.tabLabels).toEqual([
1961+
"Studio",
1962+
"Song Setup",
1963+
"Instruments",
1964+
"Auto-Create Parts",
1965+
"MIDI Import",
1966+
"Diagnostics"
1967+
]);
1968+
await selectMidiStudioTab(page, "midi-import");
1969+
await expect(page.locator('[data-midi-studio-tab="midi-import"]')).toHaveAttribute("aria-selected", "true");
1970+
await expect(page.locator("#midiImportContent")).toBeVisible();
1971+
await selectMidiStudioTab(page, "studio");
1972+
await expect(page.locator('[data-midi-studio-tab="studio"]')).toHaveAttribute("aria-selected", "true");
1973+
await expect(page.locator("#instrumentGridOutput")).toBeVisible();
18981974
await expect(page.locator("#midiSourceDetails")).toContainText("No MIDI source inspected.");
18991975
await expect(page.locator("#audioDiagnosticsContent")).toBeHidden();
19001976
await expect(page.locator("#playbackState")).toContainText("Audible preview ready: Main Theme.");

tools/midi-studio-v2/index.html

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
3939
</details>
4040

4141
<nav class="tool-starter__menu tool-starter__tool__menu midi-studio-v2__tool-menu" aria-label="Tool actions" data-launch-mode-nav="tool">
42-
<button id="toolImportManifestButton" class="midi-studio-v2__primary-action" type="button">Import JSON Manifest</button>
42+
<button id="toolImportManifestButton" type="button">Import JSON Manifest</button>
4343
<span id="nowPlayingLabel" class="midi-studio-v2__now-playing-label">No song selected</span>
4444
<button id="playButton" type="button" disabled>Play</button>
4545
<button id="stopButton" type="button" disabled>Stop</button>
@@ -69,10 +69,6 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
6969
<button id="returnToWorkspaceButton" type="button" hidden>Return to Workspace</button>
7070
</nav>
7171

72-
<div class="midi-studio-v2__mode-bar">
73-
<span id="launchModeIndicator" class="midi-studio-v2__mode-indicator">Tool Mode</span>
74-
</div>
75-
7672
<nav class="midi-studio-v2__tabs" aria-label="MIDI Studio sections" role="tablist">
7773
<button class="is-active" type="button" role="tab" aria-selected="true" data-midi-studio-tab="studio">Studio</button>
7874
<button type="button" role="tab" aria-selected="false" data-midi-studio-tab="song-setup">Song Setup</button>

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ window.addEventListener("DOMContentLoaded", () => {
4141
const app = new MidiStudioV2App({
4242
accordions: Array.from(document.querySelectorAll(".accordion-v2"), (section) => new AccordionSection(section)),
4343
actionNav: new ActionNavControl({
44-
launchModeIndicator: requireElement("#launchModeIndicator"),
4544
nowPlayingLabel: requireElement("#nowPlayingLabel"),
4645
projectDirtyState: requireElement("#projectDirtyState"),
4746
returnToWorkspaceButton: requireElement("#returnToWorkspaceButton"),

tools/midi-studio-v2/js/controls/ActionNavControl.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
export class ActionNavControl {
22
constructor({
33
locationRef = window.location,
4-
launchModeIndicator,
54
nowPlayingLabel,
65
projectDirtyState,
76
returnToWorkspaceButton,
@@ -20,7 +19,6 @@ export class ActionNavControl {
2019
workspaceNav
2120
}) {
2221
this.location = locationRef;
23-
this.launchModeIndicator = launchModeIndicator;
2422
this.nowPlayingLabel = nowPlayingLabel;
2523
this.projectDirtyState = projectDirtyState;
2624
this.returnToWorkspaceButton = returnToWorkspaceButton;
@@ -76,10 +74,6 @@ export class ActionNavControl {
7674
this.workspaceCopyManifestButton.hidden = isWorkspaceManagerLaunch;
7775
this.workspaceExportManifestButton.hidden = isWorkspaceManagerLaunch;
7876
this.returnToWorkspaceButton.hidden = !isWorkspaceManagerLaunch;
79-
if (this.launchModeIndicator) {
80-
this.launchModeIndicator.textContent = isWorkspace ? "Workspace Mode" : "Tool Mode";
81-
this.launchModeIndicator.dataset.midiStudioLaunchMode = isWorkspace ? "workspace" : "tool";
82-
}
8377
this.window.document.body.dataset.midiStudioLaunchMode = isWorkspace ? "workspace" : "tool";
8478
}
8579

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

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -54,33 +54,55 @@
5454
gap: 0.75rem;
5555
}
5656

57+
body[data-tool-id="midi-studio-v2"] {
58+
--midi-studio-v2-nav-font-size: 0.82rem;
59+
--midi-studio-v2-nav-font-weight: 800;
60+
}
61+
5762
.midi-studio-v2__tool-menu {
5863
align-items: center;
5964
position: sticky;
6065
top: 0;
6166
z-index: 5;
6267
}
6368

69+
.midi-studio-v2__tool-menu button,
70+
.midi-studio-v2__workspace-menu button {
71+
font-family: inherit;
72+
font-size: var(--midi-studio-v2-nav-font-size);
73+
font-weight: var(--midi-studio-v2-nav-font-weight);
74+
}
75+
6476
.midi-studio-v2__tabs {
65-
align-items: center;
77+
align-items: flex-end;
6678
border-bottom: 1px solid var(--tool-starter-line);
6779
display: flex;
6880
flex-wrap: wrap;
69-
gap: 0.4rem;
70-
padding: 0.55rem 1rem 0.65rem;
81+
gap: 0.2rem;
82+
padding: 0.55rem 1rem 0;
7183
}
7284

73-
.midi-studio-v2__tabs button {
74-
border-radius: 6px;
75-
font-size: 0.82rem;
76-
font-weight: 800;
77-
min-height: 2.25rem;
85+
body[data-tool-id="midi-studio-v2"] .midi-studio-v2__tabs button {
86+
background: var(--tool-starter-surface);
87+
border: 1px solid var(--tool-starter-line);
88+
border-bottom: 0;
89+
border-radius: 8px 8px 0 0;
90+
cursor: pointer;
91+
font-family: inherit;
92+
font-size: var(--midi-studio-v2-nav-font-size);
93+
font-weight: var(--midi-studio-v2-nav-font-weight);
94+
margin-bottom: -1px;
95+
min-height: 2.35rem;
96+
opacity: 0.86;
97+
padding: 0.45rem 0.75rem;
7898
}
7999

80-
.midi-studio-v2__tabs button.is-active {
100+
body[data-tool-id="midi-studio-v2"] .midi-studio-v2__tabs button.is-active {
81101
background: #0ea5e9;
82102
border-color: #7dd3fc;
103+
border-bottom-color: #0ea5e9;
83104
color: #08111f;
105+
opacity: 1;
84106
}
85107

86108
.midi-studio-v2__now-playing-label {
@@ -106,21 +128,6 @@
106128
border-color: rgba(245, 158, 11, 0.72);
107129
}
108130

109-
.midi-studio-v2__mode-bar {
110-
display: flex;
111-
justify-content: flex-end;
112-
padding: 0.4rem 1rem 0;
113-
}
114-
115-
.midi-studio-v2__mode-indicator {
116-
border: 1px solid var(--tool-starter-line);
117-
border-radius: 999px;
118-
font-size: 0.78rem;
119-
font-weight: 800;
120-
padding: 0.22rem 0.6rem;
121-
text-transform: uppercase;
122-
}
123-
124131
.midi-studio-v2.app-shell {
125132
grid-template-columns: 350px minmax(0, 1fr) minmax(14rem, 18rem);
126133
}
@@ -1074,10 +1081,6 @@ body[data-tool-id="midi-studio-v2"] #instrumentGridContent {
10741081
min-width: 0;
10751082
}
10761083

1077-
.midi-studio-v2__primary-action {
1078-
font-weight: 800;
1079-
}
1080-
10811084
.midi-studio-v2__header-actions .accordion-v2__icon {
10821085
margin-left: auto;
10831086
}

0 commit comments

Comments
 (0)