Skip to content

Commit b01045a

Browse files
committed
# Conflicts: # docs_build/dev/reports/codex_changed_files.txt # docs_build/dev/reports/codex_review.diff
2 parents 9944e06 + 27e5cc3 commit b01045a

9 files changed

Lines changed: 455 additions & 25 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
PR_26171_016-message-studio-speech-test-and-tts-service-picker manual validation notes
2+
3+
Reviewed Message Studio speech test behavior:
4+
- Speech Test accordion now includes TTS Service and TTS Profile selectors.
5+
- TTS Service is sourced from the existing TextToSpeechEngine browser path.
6+
- Test Speech is disabled before a message or segment target is selected.
7+
- Test Speech uses the selected message row text when a message row is selected.
8+
- Test Speech uses selected segment text when a segment row is selected.
9+
- Selected item readout updates between message row and segment row targets.
10+
- Browser speech synthesis unavailable state is visible and disables the service/test action.
11+
12+
Reviewed prior Text to Speech locations:
13+
- Active engine remains in src/engine/audio/TextToSpeechEngine.js.
14+
- toolbox/text-to-speech/index.html is a placeholder Voice Output page.
15+
- archive/v1-v2/tools/old_text2speech-V2 contains the older full tool implementation.
16+
- No active toolbox/tts or toolbox/text2speech-V2 folder is present.
17+
18+
No external providers, provider keys, AI voices, server audio generation, or audio persistence were manually exercised because they are out of scope.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
PR_26171_016-message-studio-speech-test-and-tts-service-picker validation report
2+
3+
Branch:
4+
PASS - Implemented on pr/26171-016-message-studio-speech-test-and-tts-service-picker.
5+
6+
Static checks:
7+
PASS - node --check toolbox/messages/message-tts-service-registry.js
8+
PASS - node --check toolbox/messages/messages.js
9+
PASS - node --check tests/playwright/tools/MessagesTool.spec.mjs
10+
PASS - node --check src/engine/audio/TextToSpeechEngine.js
11+
PASS - git diff --check
12+
13+
Targeted scans:
14+
PASS - Message Studio contains no external provider/API-key references.
15+
PASS - No new SQLite persistence was added. Existing SQLite references are limited to the pre-existing Messages Playwright harness/dev adapter path.
16+
17+
Targeted Playwright:
18+
PASS - npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --workers=1 --reporter=list
19+
Validated:
20+
- TTS service selector renders existing Browser Speech Synthesis service.
21+
- Test Speech button is disabled before required selections exist.
22+
- Message row speech test calls the existing browser-safe TextToSpeechEngine path.
23+
- Segment row speech test calls the existing browser-safe TextToSpeechEngine path.
24+
- No old Preview Message, Preview Segments, or Stop Preview controls are present.
25+
26+
Requested workspace lane:
27+
FAIL - npm run test:workspace-v2
28+
Failure scope:
29+
- RootToolsFutureState.spec.mjs existing root/tool/navigation/API failures outside Message Studio.
30+
31+
Samples:
32+
SKIPPED - Full samples smoke was out of scope.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# PR_26171_016-message-studio-speech-test-and-tts-service-picker
2+
3+
## Branch Validation
4+
5+
PASS - Work was implemented on `pr/26171-016-message-studio-speech-test-and-tts-service-picker` after starting from `main`.
6+
7+
## Summary
8+
9+
Message Studio now exposes a TTS Service picker and guarded Test Speech action. The picker is sourced from the existing browser-safe `TextToSpeechEngine` path, and speech tests run only through browser `SpeechSynthesis` when that engine is available. No external provider calls, API keys, AI voice generation, audio generation, or new database persistence were added.
10+
11+
## Prior Text To Speech Search
12+
13+
- PASS - `src/engine/audio/TextToSpeechEngine.js` exists and still provides the browser-safe speech engine path.
14+
- PASS - `src/engine/audio/TextToSpeechDefaults.js` exists and still defines Text to Speech V2 defaults.
15+
- PASS - `toolbox/text-to-speech/index.html` exists as a public Voice Output placeholder/wireframe.
16+
- PASS - `toolbox/tts/` does not exist.
17+
- PASS - Active `toolbox/text2speech-V2/` is not present.
18+
- PASS - The older full Text to Speech V2 implementation is archived under `archive/v1-v2/tools/old_text2speech-V2/`.
19+
- PASS - Shared Text to Speech contracts/schemas still exist under `src/shared/contracts/tools/textToSpeechContract.js` and `src/shared/schemas/tools/text2speech-V2.schema.json`.
20+
21+
## Requirement Checklist
22+
23+
- PASS - Searched the requested TTS locations and broader speech/TTS references.
24+
- PASS - Added a TTS Service dropdown to Message Studio.
25+
- PASS - Service options come from `TextToSpeechEngine` through `toolbox/messages/message-tts-service-registry.js`.
26+
- PASS - Added unavailable/disabled service state when browser speech synthesis is unavailable.
27+
- PASS - Added a Test Speech button for the selected Message Row or selected Segment Row.
28+
- PASS - Button remains disabled until a service, message or segment, emotion profile, active TTS profile, and voice are available.
29+
- PASS - Test Speech uses browser-local speech only through the existing engine.
30+
- PASS - No external provider calls, AI voice generation, API keys, or generated audio were introduced.
31+
- PASS - No new SQLite persistence, SQLite DDL, SQLite seed data, or SQLite service was introduced.
32+
- PASS - Existing SQLite references remain test/dev technical debt only; the report documents Postgres as authoritative direction.
33+
34+
## Impacted Lane
35+
36+
Message Studio Theme V2 tool, existing browser-safe TTS engine integration, and targeted Message Studio Playwright coverage.
37+
38+
## Validation Summary
39+
40+
- PASS - `node --check toolbox/messages/message-tts-service-registry.js`
41+
- PASS - `node --check toolbox/messages/messages.js`
42+
- PASS - `node --check tests/playwright/tools/MessagesTool.spec.mjs`
43+
- PASS - `node --check src/engine/audio/TextToSpeechEngine.js`
44+
- PASS - `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --workers=1 --reporter=list`
45+
- PASS - External provider/API-key scan returned no Message Studio matches.
46+
- PASS - `git diff --check`
47+
- FAIL - `npm run test:workspace-v2` failed in existing `RootToolsFutureState.spec.mjs` coverage outside this PR scope.
48+
49+
## Workspace V2 Failure Details
50+
51+
The required `npm run test:workspace-v2` command failed with five failures:
52+
53+
- `root tools surface links current tool pages without old_* routes`: expected toolbox accordion cards, received zero.
54+
- `common header renders primary navigation order across active pages`: existing Game Journey/Game Hub alphabetical expectation mismatch.
55+
- `learn wireframe pages load with shared Theme V2 structure`: unrelated failed requests for `/api/session/current` and `/api/platform-settings/banner`.
56+
- `tool template future-state page loads from root Theme V2 paths`: unrelated failed requests for `/api/session/current`, `/api/toolbox/registry/snapshot`, and `/api/platform-settings/banner`.
57+
- `representative active tool pages align center cleanup and registry group colors`: unrelated failed requests for toolbox constants, registry snapshot, session, and banner endpoints.
58+
59+
These failures were not introduced by the Message Studio speech test picker changes and were not fixed because they are outside this PR scope.
60+
61+
## Database Direction
62+
63+
Postgres remains authoritative. This PR did not add SQLite persistence or new SQLite runtime behavior. The existing Message Studio Playwright harness still uses the legacy dev SQLite adapter and is documented as technical debt only.
64+
65+
## Samples Decision
66+
67+
SKIPPED - Full samples validation was not run because samples are outside the Message Studio speech test and TTS service picker scope.

docs_build/dev/reports/coverage_changed_js_guardrail.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Source: Playwright/Chromium built-in V8 coverage from the active Playwright run.
88
Changed runtime JS files considered:
99
(0%) src/dev-runtime/messages/messages-sqlite-service.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
1010
(79%) toolbox/messages/messages-api-client.js - executed lines 64/64; executed functions 15/19
11-
(100%) toolbox/messages/messages.js - executed lines 990/990; executed functions 88/88
11+
(97%) toolbox/messages/messages.js - executed lines 1183/1183; executed functions 108/111
12+
(100%) toolbox/messages/message-tts-service-registry.js - executed lines 36/36; executed functions 6/6
1213

1314
Guardrail warnings:
1415
(0%) src/dev-runtime/messages/messages-sqlite-service.mjs - WARNING: changed runtime JS file missing from coverage; advisory only

docs_build/dev/reports/playwright_v8_coverage_report.txt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,27 @@ Note: entry percentages use function coverage when available, otherwise line cov
1212
Note: coverage entries are aggregated across every page/tool where coverageReporter.start(page) and coverageReporter.stop(page) ran.
1313

1414
Exercised tool entry points detected:
15-
(92%) Toolbox Index - exercised 3 runtime JS files
15+
(92%) Toolbox Index - exercised 4 runtime JS files
1616
(0%) Tool Template V2 - not exercised by this Playwright run
1717
(56%) Theme V2 Shared JS - exercised 2 runtime JS files
1818

1919
Changed runtime JS files covered:
2020
(0%) src/dev-runtime/messages/messages-sqlite-service.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
2121
(79%) toolbox/messages/messages-api-client.js - executed lines 64/64; executed functions 15/19
22-
(100%) toolbox/messages/messages.js - executed lines 990/990; executed functions 88/88
22+
(97%) toolbox/messages/messages.js - executed lines 1183/1183; executed functions 108/111
23+
(100%) toolbox/messages/message-tts-service-registry.js - executed lines 36/36; executed functions 6/6
2324

2425
Files with executed line/function counts where available:
2526
(36%) src/api/server-api-client.js - executed lines 167/167; executed functions 5/14
2627
(38%) src/api/public-config-client.js - executed lines 209/209; executed functions 10/26
28+
(50%) src/engine/audio/TextToSpeechEngine.js - executed lines 229/229; executed functions 13/26
2729
(54%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 977/977; executed functions 46/85
2830
(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 9/14
2931
(76%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 22/29
3032
(79%) toolbox/messages/messages-api-client.js - executed lines 64/64; executed functions 15/19
31-
(100%) toolbox/messages/messages.js - executed lines 990/990; executed functions 88/88
33+
(97%) toolbox/messages/messages.js - executed lines 1183/1183; executed functions 108/111
34+
(100%) src/engine/audio/TextToSpeechDefaults.js - executed lines 108/108; executed functions 1/1
35+
(100%) toolbox/messages/message-tts-service-registry.js - executed lines 36/36; executed functions 6/6
3236

3337
Uncovered or low-coverage changed JS files:
3438
(0%) src/dev-runtime/messages/messages-sqlite-service.mjs - WARNING: uncovered changed runtime JS file; advisory only
@@ -37,4 +41,5 @@ Changed JS files considered:
3741
(0%) src/dev-runtime/messages/messages-sqlite-service.mjs - changed JS file not collected as browser runtime coverage
3842
(0%) tests/playwright/tools/MessagesTool.spec.mjs - changed JS file not collected as browser runtime coverage
3943
(79%) toolbox/messages/messages-api-client.js - changed JS file with browser V8 coverage
40-
(100%) toolbox/messages/messages.js - changed JS file with browser V8 coverage
44+
(97%) toolbox/messages/messages.js - changed JS file with browser V8 coverage
45+
(100%) toolbox/messages/message-tts-service-registry.js - changed JS file with browser V8 coverage

tests/playwright/tools/MessagesTool.spec.mjs

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ test.afterAll(async () => {
4343
await workspaceV2CoverageReporter.writeReport();
4444
});
4545

46-
async function openMessagesPage(page, sqlitePath) {
46+
async function openMessagesPage(page, sqlitePath, options = {}) {
4747
const previousMessagesSqlitePath = process.env.GAMEFOUNDRY_MESSAGES_SQLITE_PATH;
4848
process.env.GAMEFOUNDRY_MESSAGES_SQLITE_PATH = sqlitePath;
4949
const server = await startRepoServer();
@@ -73,13 +73,18 @@ async function openMessagesPage(page, sqlitePath) {
7373
failures.failedRequests.push(`FAILED ${request.url()}`);
7474
});
7575

76-
await page.addInitScript(({ apiUrl }) => {
76+
await page.addInitScript(({ apiUrl, speechAvailable }) => {
7777
window.GameFoundryPublicConfig = {
7878
apiUrl,
7979
environmentLabel: "Development Environment",
8080
siteUrl: window.location.origin,
8181
};
8282
window.__messagesSpeechCalls = [];
83+
if (!speechAvailable) {
84+
Object.defineProperty(window, "SpeechSynthesisUtterance", { configurable: true, value: undefined });
85+
Object.defineProperty(window, "speechSynthesis", { configurable: true, value: undefined });
86+
return;
87+
}
8388
Object.defineProperty(window, "SpeechSynthesisUtterance", {
8489
configurable: true,
8590
value: class SpeechSynthesisUtterance {
@@ -95,15 +100,24 @@ async function openMessagesPage(page, sqlitePath) {
95100
window.__messagesSpeechCalls.push({ type: "cancel" });
96101
},
97102
getVoices() {
98-
return [{ lang: "en-US", name: "Test Voice" }];
103+
return [{ lang: "en-US", name: "Test Voice", voiceURI: "test-voice-uri" }];
99104
},
100105
speak(utterance) {
101-
window.__messagesSpeechCalls.push({ text: utterance.text, type: "speak" });
106+
window.__messagesSpeechCalls.push({
107+
lang: utterance.lang,
108+
pitch: utterance.pitch,
109+
rate: utterance.rate,
110+
text: utterance.text,
111+
type: "speak",
112+
voiceName: utterance.voice?.name || "",
113+
volume: utterance.volume,
114+
});
102115
},
103116
},
104117
});
105118
}, {
106119
apiUrl: `${server.baseUrl}/api`,
120+
speechAvailable: options.speechAvailable !== false,
107121
});
108122
await workspaceV2CoverageReporter.start(page);
109123
await page.goto(`${server.baseUrl}/tools/messages/index.html`, { waitUntil: "networkidle" });
@@ -168,7 +182,9 @@ test("Message Studio uses table governance, validates rows, and persists through
168182
await expect(page.locator("[data-messages-category]")).toHaveCount(0);
169183
await expect(page.getByRole("button", { name: /delete/i })).toHaveCount(0);
170184
await expect(page.locator("[data-messages-persistence-engine]")).toHaveText("Postgres target");
171-
await expect(page.locator("[data-messages-preview-status]")).toHaveText("No playback, provider calls, or audio generation run from Message Studio.");
185+
await expect(page.locator("[data-messages-tts-service]")).toHaveValue("browser-speech-synthesis");
186+
await expect(page.locator("[data-messages-preview-status]")).toHaveText("Select a message row or segment row before testing speech.");
187+
await expect(page.locator("[data-messages-test-speech]")).toBeDisabled();
172188
await expect(page.locator("[data-messages-preview-message], [data-messages-preview-segments], [data-messages-preview-stop]")).toHaveCount(0);
173189

174190
await expect(page.locator("[data-messages-emotions]")).toContainText("Calm");
@@ -191,15 +207,14 @@ test("Message Studio uses table governance, validates rows, and persists through
191207
await page.locator("[data-messages-tts-row]").filter({ hasText: "Arcade Browser Voice" }).getByRole("button", { name: "Edit" }).click();
192208
await page.locator("[data-messages-tts-editor] [data-tts-language]").fill("en-GB");
193209
await page.locator("[data-messages-tts-editor] [data-messages-tts-commit]").click();
194-
await page.locator("[data-messages-tts-row]").filter({ hasText: "Arcade Browser Voice" }).getByRole("button", { name: "Disable" }).click();
195-
await expect(page.locator("[data-messages-tts-row]").filter({ hasText: "Arcade Browser Voice" })).toContainText("Inactive");
210+
await expect(page.locator("[data-messages-tts-row]").filter({ hasText: "Arcade Browser Voice" })).toContainText("Active");
196211

197212
const ttsProfilesResult = await jsonRequest(`${failures.server.baseUrl}/api/messages/tts-profiles`);
198213
expect(ttsProfilesResult.response.ok).toBe(true);
199214
expect(ttsProfilesResult.payload.ok).toBe(true);
200215
const createdTtsProfile = ttsProfilesResult.payload.data.ttsProfiles.find((profile) => profile.name === "Arcade Browser Voice");
201216
expect(createdTtsProfile).toEqual(expect.objectContaining({
202-
active: false,
217+
active: true,
203218
language: "en-GB",
204219
providerKey: "browser-speech",
205220
voiceName: "Test Voice",
@@ -266,9 +281,41 @@ test("Message Studio uses table governance, validates rows, and persists through
266281
await expect(page.locator("[data-messages-log]")).toHaveText("Updated segment row 2.");
267282
await expect(page.locator("[data-messages-segment-row]")).toHaveCount(2);
268283

269-
const speechCalls = await page.evaluate(() => window.__messagesSpeechCalls);
284+
let speechCalls = await page.evaluate(() => window.__messagesSpeechCalls);
270285
expect(speechCalls).toEqual([]);
271286

287+
await page.locator("[data-messages-preview-tts-profile]").selectOption({ label: "Arcade Browser Voice" });
288+
await page.locator("[data-messages-row]").filter({ hasText: "Forest Warning" }).click();
289+
await expect(page.locator("[data-messages-speech-test-target]")).toHaveText("Message Row: Forest Warning");
290+
await expect(page.locator("[data-messages-test-speech]")).toBeEnabled();
291+
await page.locator("[data-messages-test-speech]").click();
292+
await expect(page.locator("[data-messages-preview-status]")).toHaveText("Speech test started for Message Row: Forest Warning using Browser Speech Synthesis.");
293+
speechCalls = await page.evaluate(() => window.__messagesSpeechCalls);
294+
expect(speechCalls.at(-1)).toEqual(expect.objectContaining({
295+
lang: "en-GB",
296+
pitch: 1.08,
297+
rate: 1.15,
298+
text: "The forest gets darker beyond this point.\nWe are being attacked by bats.",
299+
type: "speak",
300+
voiceName: "Test Voice",
301+
volume: 1,
302+
}));
303+
304+
await page.locator("[data-messages-segment-row]").filter({ hasText: "The forest gets darker beyond this point." }).click();
305+
await expect(page.locator("[data-messages-speech-test-target]")).toHaveText("Segment 1");
306+
await page.locator("[data-messages-test-speech]").click();
307+
await expect(page.locator("[data-messages-preview-status]")).toHaveText("Speech test started for Segment 1 using Browser Speech Synthesis.");
308+
speechCalls = await page.evaluate(() => window.__messagesSpeechCalls);
309+
expect(speechCalls.at(-1)).toEqual(expect.objectContaining({
310+
lang: "en-GB",
311+
pitch: 1,
312+
rate: 1,
313+
text: "The forest gets darker beyond this point.",
314+
type: "speak",
315+
voiceName: "Test Voice",
316+
volume: 1,
317+
}));
318+
272319
const segmentsResult = await jsonRequest(`${failures.server.baseUrl}/api/messages/segments`);
273320
expect(segmentsResult.response.ok).toBe(true);
274321
expect(segmentsResult.payload.ok).toBe(true);
@@ -325,6 +372,10 @@ test("Message Studio uses table governance, validates rows, and persists through
325372
name: "Forest Warning Updated",
326373
}));
327374

375+
await page.locator("[data-messages-tts-row]").filter({ hasText: "Arcade Browser Voice" }).getByRole("button", { name: "Disable" }).click();
376+
await expect(page.locator("[data-messages-log]")).toHaveText("Disabled TTS profile Arcade Browser Voice.");
377+
await expect(page.locator("[data-messages-tts-row]").filter({ hasText: "Arcade Browser Voice" })).toContainText("Inactive");
378+
328379
for (const url of [
329380
`${failures.server.baseUrl}/api/messages/messages/${createdMessage.key}`,
330381
`${failures.server.baseUrl}/api/messages/segments/${disabledSegment.key}`,

0 commit comments

Comments
 (0)