Skip to content

Commit 1ca6002

Browse files
author
DavidQ
committed
Add Text to Speech V2 workspace return nav without schema changes - PR_26130_018-text-to-speech-v2-workspace-nav-only
1 parent ea47fc7 commit 1ca6002

6 files changed

Lines changed: 117 additions & 46 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# PR_26130_018-text-to-speech-v2-workspace-nav-only
2+
3+
## Summary
4+
5+
Updated Text to Speech V2 workspace launch navigation only. Workspace launches now show a below-header Text to Speech V2 workspace nav with Return to Workspace, matching the Palette Manager V2 placement pattern while keeping speech playback controls inside the Named Sentences surface.
6+
7+
## Scope Guard
8+
9+
- Text to Speech V2 only for implementation files.
10+
- No schema files changed.
11+
- No Workspace Manager schema contract changes.
12+
- No Text to Speech V2 payload/schema shape changes.
13+
- No start_of_day changes.
14+
15+
## Files Changed
16+
17+
- tools/text2speach-V2/index.html
18+
- tools/text2speach-V2/styles/text2speach-V2.css
19+
- tools/text2speach-V2/js/bootstrap.js
20+
- tools/text2speach-V2/js/controls/ActionNavControl.js
21+
- tests/playwright/tools/WorkspaceManagerV2.spec.mjs
22+
23+
## Implementation Notes
24+
25+
- Moved the Text to Speech V2 workspace nav out of the Named Sentences accordion to the below-header position used by Palette Manager V2.
26+
- The below-header workspace nav uses Text to Speech V2 classes: text2speach-V2__menu, text2speach-V2__workspace-menu, and text2speach-V2__menu-actions.
27+
- The workspace nav contains Return to Workspace only.
28+
- The regular speech action buttons remain in the Named Sentences accordion and remain available during workspace launches.
29+
- Return to Workspace continues to preserve hostContextId in the Workspace Manager V2 return URL.
30+
31+
## Playwright Impacted
32+
33+
Yes.
34+
35+
Playwright validates:
36+
- Standalone Text to Speech V2 launch keeps the workspace nav hidden.
37+
- Workspace-launched Text to Speech V2 shows the below-header workspace nav.
38+
- The Return to Workspace button is visible in workspace launch mode.
39+
- The workspace nav is placed between the collapsible header and the main Text to Speech V2 app surface.
40+
- Return to Workspace preserves the active hostContextId in the Workspace Manager V2 URL.
41+
42+
Expected pass behavior:
43+
- Workspace nav is visible only for launch=workspace, fromTool=workspace-manager-v2, and hostContextId launches.
44+
- Standalone launches do not show the workspace nav.
45+
- Return navigation returns to Workspace Manager V2 without clearing or dirtying workspace/toolState data.
46+
47+
Expected fail behavior:
48+
- Missing or misplaced workspace nav, missing Return to Workspace button, or lost hostContextId fails the Playwright assertions.
49+
50+
## Validation
51+
52+
- `npm run test:workspace-v2` passed: 30 tests passed.
53+
- Initial `npm run test:workspace-v2` attempt timed out due the command timeout; rerun with a longer timeout passed.
54+
- `node --check tools/text2speach-V2/js/bootstrap.js` passed.
55+
- `node --check tools/text2speach-V2/js/controls/ActionNavControl.js` passed.
56+
- `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs` passed.
57+
- `git diff --check HEAD -- ...` passed with line-ending warnings only.
58+
59+
## Full Samples Smoke Test
60+
61+
Skipped. Reason: this PR is limited to Text to Speech V2 workspace nav placement and does not modify shared sample loading, broad runtime sample launch paths, or game/sample data contracts.
62+
63+
## Manual Test Steps
64+
65+
1. Open Text to Speech V2 directly from `tools/text2speach-V2/index.html`.
66+
2. Confirm no below-header Workspace actions nav is visible.
67+
3. Open Workspace Manager V2, select a repo and Asteroids, then launch Text to Speech V2.
68+
4. Confirm a below-header Workspace actions nav appears with Return to Workspace.
69+
5. Click Return to Workspace.
70+
6. Confirm the browser returns to Workspace Manager V2 with the same hostContextId and the active workspace/toolState remains open.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -981,8 +981,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
981981
await expect(page.locator('[aria-controls="text2speach-V2TextContent"] > span:first-child')).toHaveText("Text to Speak");
982982
await expect(page.locator("#text2speach-V2TextContent")).not.toContainText("Speech text");
983983
await expect(page.locator('[aria-controls="text2speach-V2QueueContent"] > span:first-child')).toHaveText("Named Sentences");
984-
await expect(page.locator('[data-launch-mode-nav="tool"]')).toBeVisible();
985-
await expect(page.locator('[data-launch-mode-nav="workspace"]')).toBeHidden();
984+
await expect(page.locator("#text2speach-V2SpeechActions")).toBeVisible();
985+
await expect(page.locator(".text2speach-V2__workspace-menu")).toBeHidden();
986986
await expect(page.locator("#text2speach-V2SpeechPreview")).toHaveCount(0);
987987
await expect(page.locator("#text2speach-V2SpeechText")).toHaveValue("Welcome to Toolbox Aid. This is the default Text to Speech V2 sample line for previewing narration, prompts, and menu feedback.");
988988
await expect(page.locator("#text2speach-V2SpeakButton")).toBeEnabled();
@@ -1009,17 +1009,17 @@ test.describe("Workspace Manager V2 bootstrap", () => {
10091009
"text2speach-V2PitchSlider",
10101010
"text2speach-V2SpeechItemName"
10111011
]);
1012-
expect(await page.locator("#text2speach-V2QueueContent [data-launch-mode-nav='tool'] button").evaluateAll((buttons) => buttons.map((button) => button.id))).toEqual([
1012+
expect(await page.locator("#text2speach-V2SpeechActions button").evaluateAll((buttons) => buttons.map((button) => button.id))).toEqual([
10131013
"text2speach-V2SpeakButton",
10141014
"text2speach-V2PauseButton",
10151015
"text2speach-V2ResumeButton",
10161016
"text2speach-V2StopButton"
10171017
]);
10181018
const namedSentenceTopControls = await page.locator("#text2speach-V2QueueContent").evaluate((content) => {
10191019
const firstElement = Array.from(content.children).find((child) => child.nodeType === Node.ELEMENT_NODE);
1020-
return firstElement?.getAttribute("data-launch-mode-nav") || "";
1020+
return firstElement?.id || "";
10211021
});
1022-
expect(namedSentenceTopControls).toBe("tool");
1022+
expect(namedSentenceTopControls).toBe("text2speach-V2SpeechActions");
10231023
expect(await page.locator("#text2speach-V2SpeechOptionsContent .text2speach-V2__item-actions button").evaluateAll((buttons) => buttons.map((button) => button.textContent.trim()))).toEqual(["Add", "Duplicate", "Delete"]);
10241024
const statusHeaderOrder = await page.locator(".text2speach-V2__status-accordion-header").evaluate((header) => Array.from(header.querySelectorAll(":scope > span, :scope > div > span, :scope > div > button"), (element) => element.textContent.trim()));
10251025
expect(statusHeaderOrder).toEqual(["Status", "+", "Clear"]);
@@ -1496,7 +1496,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14961496
await expect(page.locator("#returnToWorkspaceButton")).toBeVisible();
14971497
await expect(page.locator("#text2speach-V2QueueTiles [data-speech-item-id]")).toHaveCount(0);
14981498
await expect(page.locator("#text2speach-V2SpeechText")).toHaveValue("");
1499-
await expect(page.locator("#text2speach-V2WorkspaceSpeakButton")).toBeDisabled();
1499+
await expect(page.locator("#text2speach-V2SpeakButton")).toBeDisabled();
15001500
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/FAIL Text to Speech V2 payload from \/games\/Asteroids\/game\.manifest\.json failed tools\/schemas\/tools\/text2speach-V2\.schema\.json validation:/);
15011501
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/root\.queue\[0\]\.autoSpeak is not allowed/);
15021502
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/root\.queue\[0\]\.repeatCount is not allowed/);
@@ -3135,28 +3135,38 @@ test.describe("Workspace Manager V2 bootstrap", () => {
31353135
await expect(page).toHaveURL(/text2speach-V2\/index\.html.*launch=workspace/);
31363136
await expect(page).toHaveURL(/fromTool=workspace-manager-v2/);
31373137
await expect(page).toHaveURL(/hostContextId=workspace-manager-v2-/);
3138-
await expect(page.locator('[data-launch-mode-nav="tool"]')).toBeHidden();
3139-
await expect(page.locator('[data-launch-mode-nav="workspace"]')).toBeVisible();
3140-
await expect(page.locator('[data-launch-mode-nav="workspace"] button')).toHaveText(["Speak", "Pause", "Resume", "Stop", "Return to Workspace"]);
3138+
await expect(page.locator("#text2speach-V2SpeechActions")).toBeVisible();
3139+
await expect(page.locator(".text2speach-V2__workspace-menu")).toBeVisible();
3140+
await expect(page.locator(".text2speach-V2__workspace-menu")).toHaveAttribute("data-launch-mode-nav", "workspace");
3141+
await expect(page.locator(".text2speach-V2__workspace-menu button")).toHaveText(["Return to Workspace"]);
3142+
const textToSpeechWorkspaceNavPlacement = await page.locator("body").evaluate(() => {
3143+
const details = document.querySelector("body > details");
3144+
const workspaceNav = document.querySelector("body > .text2speach-V2__workspace-menu");
3145+
const main = document.querySelector("body > main.text2speach-V2");
3146+
return Boolean(details && workspaceNav && main
3147+
&& workspaceNav.previousElementSibling === details
3148+
&& workspaceNav.nextElementSibling === main);
3149+
});
3150+
expect(textToSpeechWorkspaceNavPlacement).toBe(true);
31413151
await expect(page.locator("#text2speach-V2QueueTiles [data-speech-item-id]")).toHaveCount(3);
31423152
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/OK Loaded Text to Speech V2 payload source: \/games\/Asteroids\/game\.manifest\.json\./);
31433153
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/OK Text to Speech V2 schema validation result: tools\/schemas\/tools\/text2speach-V2\.schema\.json valid; queue=3\./);
31443154
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/OK Text to Speech V2 dirty state: isDirty=false; reason=clean\./);
31453155
await expect(page.locator("#text2speach-V2SpeechText")).toHaveValue("Welcome to Toolbox Aid. This is the default Text to Speech V2 sample line for previewing narration, prompts, and menu feedback.");
31463156
await expect(page.locator("#text2speach-V2SpeechItemName")).toHaveValue("Narrator welcome");
3147-
await expect(page.locator("#text2speach-V2WorkspaceSpeakButton")).toBeEnabled();
3148-
await expect(page.locator("#text2speach-V2WorkspacePauseButton")).toBeEnabled();
3149-
await expect(page.locator("#text2speach-V2WorkspaceResumeButton")).toBeEnabled();
3150-
await expect(page.locator("#text2speach-V2WorkspaceStopButton")).toBeEnabled();
3157+
await expect(page.locator("#text2speach-V2SpeakButton")).toBeEnabled();
3158+
await expect(page.locator("#text2speach-V2PauseButton")).toBeEnabled();
3159+
await expect(page.locator("#text2speach-V2ResumeButton")).toBeEnabled();
3160+
await expect(page.locator("#text2speach-V2StopButton")).toBeEnabled();
31513161
await expect(page.locator("#text2speach-V2VoiceSelect option")).toHaveCount(4);
3152-
await page.locator("#text2speach-V2WorkspaceSpeakButton").click();
3162+
await page.locator("#text2speach-V2SpeakButton").click();
31533163
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/OK Speech queued: Narrator welcome; mode=replace; en-US; voice=Google US English; rate=1; pitch=1; volume=1; queuedItems=1\./);
31543164
const spoken = await page.evaluate(() => window["__text2speach-V2Spoken"]);
31553165
expect(spoken).toHaveLength(1);
31563166
expect(spoken[0].text).toBe("Welcome to Toolbox Aid. This is the default Text to Speech V2 sample line for previewing narration, prompts, and menu feedback.");
3157-
await page.locator("#text2speach-V2WorkspacePauseButton").click();
3158-
await page.locator("#text2speach-V2WorkspaceResumeButton").click();
3159-
await page.locator("#text2speach-V2WorkspaceStopButton").click();
3167+
await page.locator("#text2speach-V2PauseButton").click();
3168+
await page.locator("#text2speach-V2ResumeButton").click();
3169+
await page.locator("#text2speach-V2StopButton").click();
31603170
await expect(page.locator("#text2speach-V2StatusLog")).toHaveValue(/OK Speech queue stopped: 1 queued item\(s\) cleared\./);
31613171
expect(await page.evaluate(() => window["__text2speach-V2Paused"])).toBe(1);
31623172
expect(await page.evaluate(() => window["__text2speach-V2Resumed"])).toBe(1);

tools/text2speach-V2/index.html

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ <h2 class="tools-platform-frame__eyebrow">Browser speech synthesis</h2>
3636
</div>
3737
</details>
3838

39+
<nav class="text2speach-V2__menu text2speach-V2__workspace-menu" aria-label="Workspace actions" data-launch-mode-nav="workspace" hidden>
40+
<div class="text2speach-V2__menu-actions">
41+
<button id="returnToWorkspaceButton" type="button">Return to Workspace</button>
42+
</div>
43+
</nav>
44+
3945
<main class="text2speach-V2 app-shell" data-tool-id="text2speach-V2">
4046
<aside class="text2speach-V2__panel text2speach-V2__panel--left" aria-label="Speech input">
4147
<section class="accordion-v2 text2speach-V2__accordion is-open" data-accordion-v2-open="true">
@@ -118,19 +124,12 @@ <h2 class="tools-platform-frame__eyebrow">Browser speech synthesis</h2>
118124
<span class="accordion-v2__icon" aria-hidden="true">+</span>
119125
</button>
120126
<div id="text2speach-V2QueueContent" class="accordion-v2__content">
121-
<nav class="text2speach-V2__menu" aria-label="Tool actions" data-launch-mode-nav="tool">
127+
<nav id="text2speach-V2SpeechActions" class="text2speach-V2__menu text2speach-V2__speech-actions" aria-label="Speech actions">
122128
<button id="text2speach-V2SpeakButton" type="button" disabled>Speak</button>
123129
<button id="text2speach-V2PauseButton" type="button" disabled>Pause</button>
124130
<button id="text2speach-V2ResumeButton" type="button" disabled>Resume</button>
125131
<button id="text2speach-V2StopButton" type="button" disabled>Stop</button>
126132
</nav>
127-
<nav class="text2speach-V2__menu text2speach-V2__workspace-menu" aria-label="Workspace actions" data-launch-mode-nav="workspace" hidden>
128-
<button id="text2speach-V2WorkspaceSpeakButton" type="button" disabled>Speak</button>
129-
<button id="text2speach-V2WorkspacePauseButton" type="button" disabled>Pause</button>
130-
<button id="text2speach-V2WorkspaceResumeButton" type="button" disabled>Resume</button>
131-
<button id="text2speach-V2WorkspaceStopButton" type="button" disabled>Stop</button>
132-
<button id="returnToWorkspaceButton" type="button">Return to Workspace</button>
133-
</nav>
134133
<div id="text2speach-V2QueueTiles" class="text2speach-V2__queue-tiles" role="listbox" aria-label="Named Sentences"></div>
135134
</div>
136135
</section>

tools/text2speach-V2/js/bootstrap.js

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,11 @@ window.addEventListener("DOMContentLoaded", () => {
2020
mountAccordionV2(document);
2121
const app = new TextToSpeechToolApp({
2222
actionNav: new ActionNavControl({
23-
pauseButtons: [
24-
requireElement("#text2speach-V2PauseButton"),
25-
requireElement("#text2speach-V2WorkspacePauseButton")
26-
],
23+
pauseButtons: [requireElement("#text2speach-V2PauseButton")],
2724
returnToWorkspaceButton: requireElement("#returnToWorkspaceButton"),
28-
resumeButtons: [
29-
requireElement("#text2speach-V2ResumeButton"),
30-
requireElement("#text2speach-V2WorkspaceResumeButton")
31-
],
32-
speakButtons: [
33-
requireElement("#text2speach-V2SpeakButton"),
34-
requireElement("#text2speach-V2WorkspaceSpeakButton")
35-
],
36-
stopButtons: [
37-
requireElement("#text2speach-V2StopButton"),
38-
requireElement("#text2speach-V2WorkspaceStopButton")
39-
],
40-
toolNav: requireElement('[data-launch-mode-nav="tool"]'),
25+
resumeButtons: [requireElement("#text2speach-V2ResumeButton")],
26+
speakButtons: [requireElement("#text2speach-V2SpeakButton")],
27+
stopButtons: [requireElement("#text2speach-V2StopButton")],
4128
workspaceNav: requireElement('[data-launch-mode-nav="workspace"]')
4229
}),
4330
engine: new TextToSpeechEngine(),

tools/text2speach-V2/js/controls/ActionNavControl.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export class ActionNavControl {
66
resumeButtons = [],
77
speakButtons = [],
88
stopButtons = [],
9-
toolNav,
109
windowRef = window,
1110
workspaceNav
1211
}) {
@@ -16,7 +15,6 @@ export class ActionNavControl {
1615
this.resumeButtons = resumeButtons;
1716
this.speakButtons = speakButtons;
1817
this.stopButtons = stopButtons;
19-
this.toolNav = toolNav;
2018
this.window = windowRef;
2119
this.workspaceNav = workspaceNav;
2220
}
@@ -45,7 +43,6 @@ export class ActionNavControl {
4543
const isWorkspaceManagerLaunch = params.get("launch") === "workspace"
4644
&& params.get("fromTool") === "workspace-manager-v2"
4745
&& Boolean(params.get("hostContextId"));
48-
this.toolNav.hidden = isWorkspaceManagerLaunch;
4946
this.workspaceNav.hidden = !isWorkspaceManagerLaunch;
5047
}
5148

tools/text2speach-V2/styles/text2speach-V2.css

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,16 @@
8080
display: none;
8181
}
8282

83+
.text2speach-V2__menu-actions {
84+
display: flex;
85+
flex-wrap: wrap;
86+
justify-content: center;
87+
gap: 10px;
88+
}
89+
8390
.text2speach-V2__item-actions,
84-
.text2speach-V2__options .text2speach-V2__menu {
91+
.text2speach-V2__options .text2speach-V2__menu,
92+
.text2speach-V2__speech-actions {
8593
flex-wrap: wrap;
8694
justify-content: flex-start;
8795
width: 100%;

0 commit comments

Comments
 (0)