diff --git a/docs_build/dev/reports/PR_26171_069-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_069-manual-validation-notes.md new file mode 100644 index 000000000..c55aa9daf --- /dev/null +++ b/docs_build/dev/reports/PR_26171_069-manual-validation-notes.md @@ -0,0 +1,7 @@ +# PR_26171_069 Manual Validation Notes + +- Confirmed Message Studio center work surface now presents the Messages parent table without detached Emotion Profile or Available TTS Profile tables. +- Confirmed Message Parts remain directly under the expanded Message row. +- Confirmed Message Studio exposes Play Message, Play Part, and Stop. +- Confirmed TTS Studio default profiles render the expected parent/child TTS Profiles to Emotion Settings model. +- Confirmed the scoped ZIP must be generated under `tmp/` and not staged. diff --git a/docs_build/dev/reports/PR_26171_069-message-tts-ownership-checklist.md b/docs_build/dev/reports/PR_26171_069-message-tts-ownership-checklist.md new file mode 100644 index 000000000..fce5a6afd --- /dev/null +++ b/docs_build/dev/reports/PR_26171_069-message-tts-ownership-checklist.md @@ -0,0 +1,11 @@ +# PR_26171_069 Message/TTS Ownership Checklist + +- PASS: Message Studio owns text entered for Messages. +- PASS: Message Studio owns ordered Message Parts. +- PASS: Message Studio does not expose reusable Emotion Profile authoring. +- PASS: Message Studio does not expose reusable TTS Profile authoring. +- PASS: TTS Studio owns reusable TTS Profiles. +- PASS: TTS Studio owns per-profile Emotion Settings. +- PASS: `src/engine/audio/TextToSpeechEngine.js` remains the playback owner. +- PASS: Message playback and Stop are routed through the Message Studio TTS service registry. +- PASS: Audio output remains generated/played by the shared audio engine path. diff --git a/docs_build/dev/reports/PR_26171_069-message-tts-profile-contract-alignment.md b/docs_build/dev/reports/PR_26171_069-message-tts-profile-contract-alignment.md new file mode 100644 index 000000000..a31357ce7 --- /dev/null +++ b/docs_build/dev/reports/PR_26171_069-message-tts-profile-contract-alignment.md @@ -0,0 +1,29 @@ +# PR_26171_069 Message/TTS Profile Contract Alignment + +## Scope +- Align Message Studio and TTS Studio around the parent/child table model. +- Keep Message Studio as the owner of game text and ordered Message Parts. +- Keep TTS Studio as the owner of reusable TTS Profiles and per-profile Emotion Settings. +- Keep playback routed through `src/engine/audio/TextToSpeechEngine`. + +## Requirement Evidence +- PASS: Message Studio parent table remains `Messages` in `toolbox/messages/index.html`. +- PASS: Clicking a Message row opens the `Message Parts` child table in `toolbox/messages/messages.js`. +- PASS: Message Parts expose Text, Emotion, TTS Profile, and row actions. +- PASS: Message Studio exposes Play Part, Play Message, and Stop. +- PASS: Stop routes through `toolbox/messages/message-tts-service-registry.js` to `TextToSpeechEngine.stop()`. +- PASS: Message Studio no longer displays Message-owned Emotion Profile or Available TTS Profile side tables. +- PASS: TTS Studio parent table remains `TTS Profiles`. +- PASS: Clicking a TTS Profile row opens the `Emotion Settings` child table. +- PASS: `Man Profile 1` and `Woman Profile 2` seed Neutral, Happy, Angry, and Scared Emotion Settings. +- PASS: No separate Emotion Studio was created. +- PASS: No database schema changes were added. +- PASS: Theme V2 and external JavaScript structure remain in use; no inline styles, style blocks, or inline handlers were added. + +## Git Workflow +- Current branch: `codex/pr-26171-069-message-tts-profile-contract-alignment` +- Created branch: `codex/pr-26171-069-message-tts-profile-contract-alignment` +- Push result: pending at report creation +- PR URL: pending at report creation +- Merge result: pending at report creation +- Final main commit: pending at report creation diff --git a/docs_build/dev/reports/PR_26171_069-parent-child-table-checklist.md b/docs_build/dev/reports/PR_26171_069-parent-child-table-checklist.md new file mode 100644 index 000000000..52d25d2a7 --- /dev/null +++ b/docs_build/dev/reports/PR_26171_069-parent-child-table-checklist.md @@ -0,0 +1,17 @@ +# PR_26171_069 Parent/Child Table Checklist + +## Message Studio +- PASS: Parent table is `Messages`. +- PASS: Message row click expands the child `Message Parts` table directly under the parent row. +- PASS: Message Parts include Text, Emotion, and TTS Profile controls. +- PASS: Message Parts provide row-level Play Part and inline edit behavior. +- PASS: Message rows provide Play Message and inline edit behavior. +- PASS: Stop is available from Message Studio playback controls. +- PASS: Message-owned detached Emotion Profile and Available TTS Profile tables were removed. + +## TTS Studio +- PASS: Parent table is `TTS Profiles`. +- PASS: TTS Profile row click expands the child `Emotion Settings` table. +- PASS: Emotion Settings belong to the selected TTS Profile. +- PASS: Default profile data includes Neutral, Happy, Angry, and Scared settings for the example profiles. +- PASS: No separate Emotion Studio was introduced. diff --git a/docs_build/dev/reports/PR_26171_069-validation.md b/docs_build/dev/reports/PR_26171_069-validation.md new file mode 100644 index 000000000..2fa239af9 --- /dev/null +++ b/docs_build/dev/reports/PR_26171_069-validation.md @@ -0,0 +1,15 @@ +# PR_26171_069 Validation + +- PASS: `node --check toolbox/messages/messages.js` +- PASS: `node --check toolbox/messages/message-tts-service-registry.js` +- PASS: `node --check toolbox/text-to-speech/text2speech.js` +- PASS: `node --check tests/playwright/tools/MessagesTool.spec.mjs` +- PASS: `node --check tests/playwright/tools/TextToSpeechFunctional.spec.mjs` +- PASS: `node --check tests/tools/Text2SpeechShell.test.mjs` +- PASS: `node --test tests/tools/Text2SpeechShell.test.mjs` +- PASS: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs tests/playwright/tools/TextToSpeechFunctional.spec.mjs --project=playwright --workers=1 --reporter=list` +- PASS: `npm run test:workspace-v2` + +## Notes +- `npm run test:workspace-v2` is the legacy command name; this report treats it as Project Workspace validation. +- No full samples smoke was run. diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 973f685f2..2f0436ffd 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,10 +1,17 @@ -docs_build/dev/reports/codex_review.diff -docs_build/dev/reports/codex_changed_files.txt -docs_build/dev/reports/PR_26171_071-merge-conflict-recovery-report.md -docs_build/dev/reports/PR_26171_071-preserved-local-commit-report.md -docs_build/dev/reports/PR_26171_071-discarded-unrelated-work-report.md -docs_build/dev/reports/PR_26171_071-manual-validation-notes.md -docs_build/dev/reports/PR_26171_071-recovery-precleanup.patch.md -docs_build/dev/reports/PR_26171_071-status-before-cleanup.txt -docs_build/dev/reports/PR_26171_071-conflicted-files-before-cleanup.txt -docs_build/dev/reports/PR_26171_071-untracked-files-before-cleanup.txt +A docs_build/dev/reports/PR_26171_069-manual-validation-notes.md +A docs_build/dev/reports/PR_26171_069-message-tts-ownership-checklist.md +A docs_build/dev/reports/PR_26171_069-message-tts-profile-contract-alignment.md +A docs_build/dev/reports/PR_26171_069-parent-child-table-checklist.md +A docs_build/dev/reports/PR_26171_069-validation.md +A docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/APPLY_PR.md +A docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/BUILD_PR.md +A docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/PLAN_PR.md +M tests/playwright/tools/MessagesTool.spec.mjs +M tests/playwright/tools/TextToSpeechFunctional.spec.mjs +M tests/tools/Text2SpeechShell.test.mjs +M toolbox/messages/index.html +M toolbox/messages/message-tts-service-registry.js +M toolbox/messages/messages.js +M toolbox/text-to-speech/text2speech.js +M docs_build/dev/reports/codex_review.diff +M docs_build/dev/reports/codex_changed_files.txt diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 6c94cd1a4..0cad4fde4 100644 Binary files a/docs_build/dev/reports/codex_review.diff and b/docs_build/dev/reports/codex_review.diff differ diff --git a/docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/APPLY_PR.md b/docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/APPLY_PR.md new file mode 100644 index 000000000..033db7128 --- /dev/null +++ b/docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/APPLY_PR.md @@ -0,0 +1,18 @@ +# PR_26171_069-message-tts-profile-contract-alignment Apply + +## Apply Steps + +1. Start from clean latest `main`. +2. Create `codex/pr-26171-069-message-tts-profile-contract-alignment`. +3. Implement only the BUILD scope. +4. Run targeted Message Studio validation. +5. Run targeted TTS Studio validation. +6. Run `npm run test:workspace-v2`. +7. Stage only scoped files. +8. Commit. +9. Push branch. +10. Create PR. +11. Resolve merge conflicts if encountered and rerun validation. +12. Merge after validation passes. +13. Return to `main` and pull latest. +14. Produce final code file change list only, excluding MD, TXT, reports, and other non-code files. diff --git a/docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/BUILD_PR.md b/docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/BUILD_PR.md new file mode 100644 index 000000000..de77298b6 --- /dev/null +++ b/docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/BUILD_PR.md @@ -0,0 +1,61 @@ +# PR_26171_069-message-tts-profile-contract-alignment Build + +## Source Of Truth + +Use this BUILD doc, the original PR request, `docs_build/dev/PROJECT_INSTRUCTIONS.md`, and `docs_build/dev/PROJECT_MULTI_PC.txt`. + +## Singular Purpose + +Align Message Studio and TTS Studio around reusable TTS Profiles, per-profile Emotion Settings, and Message Parts playback. + +## Exact Targets + +- `toolbox/messages/index.html` +- `toolbox/messages/messages.js` +- `toolbox/messages/message-tts-service-registry.js` +- `toolbox/text-to-speech/index.html` +- `toolbox/text-to-speech/text2speech.js` +- `src/engine/audio/TextToSpeechEngine.js` +- Targeted Message Studio Playwright/unit tests. +- Targeted TTS Studio Playwright/unit tests. +- `docs_build/dev/reports/codex_review.diff` +- `docs_build/dev/reports/codex_changed_files.txt` +- PR-specific report. +- Message/TTS ownership checklist. +- Parent-child table checklist. +- Validation report. +- Manual validation notes. + +## Requirements + +- Message Studio parent table is Messages. +- Clicking a Message row opens the Message Parts child table. +- Message Parts select Text, Emotion, and TTS Profile. +- Message Studio exposes Play Part, Play Message, and Stop. +- Message playback uses `src/engine/audio/TextToSpeechEngine`. +- Default TTS Profile exists until real profiles are available. +- TTS Studio parent table is TTS Profiles. +- Clicking a TTS Profile row opens Emotion Settings child table. +- Emotion settings belong to the selected TTS Profile. +- Do not create separate Emotion Studio. +- Include example profiles with Neutral, Happy, Angry, and Scared settings. +- Message Studio owns text and ordered message parts. +- TTS Studio owns reusable profiles and per-profile emotion settings. +- `src/engine/audio` owns playback. +- Audio owns generated/played output. +- Use Theme V2 and external JavaScript only. +- Do not add inline styles, style blocks, inline event handlers, page-local CSS, or tool-local CSS. +- Do not add database changes unless an existing Local API contract already supports them. +- Do not use browser-owned product data as source of truth. +- Do not add silent fallbacks. + +## Validation + +- Run targeted Message Studio validation. +- Run targeted TTS Studio validation. +- Run `npm run test:workspace-v2`. +- Do not run full samples smoke. + +## ZIP + +Produce `tmp/PR_26171_069-message-tts-profile-contract-alignment_delta.zip` with repo-structured changed files only. diff --git a/docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/PLAN_PR.md b/docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/PLAN_PR.md new file mode 100644 index 000000000..3b9fedb0e --- /dev/null +++ b/docs_build/pr/PR_26171_069-message-tts-profile-contract-alignment/PLAN_PR.md @@ -0,0 +1,32 @@ +# PR_26171_069-message-tts-profile-contract-alignment Plan + +## Purpose + +Align Message Studio and TTS Studio around the parent/child table model and shared TTS profile contract. + +## Scope + +- Message Studio owns Messages and ordered Message Parts. +- Message Parts select Text, Emotion, and TTS Profile. +- Message Studio provides Play Part, Play Message, and Stop using `src/engine/audio/TextToSpeechEngine`. +- TTS Studio owns TTS Profiles and per-profile Emotion Settings. +- TTS Profile rows expand to Emotion Settings child rows. +- Keep Theme V2 only, external JavaScript only, no page-local or tool-local CSS. +- Add targeted validation and required PR reports. + +## Non-Goals + +- Do not add database schema or persistence changes. +- Do not create a separate Emotion Studio. +- Do not introduce browser storage as product source of truth. +- Do not change unrelated tools or shared runtime behavior beyond the audio playback contract needed by Message Studio. + +## Validation + +- Targeted Message Studio validation. +- Targeted TTS Studio validation. +- `npm run test:workspace-v2` with note that the command name is legacy and the user-facing language is Project Workspace. + +## Delivery + +- Commit, push, create PR, resolve conflicts if encountered, merge after validation passes, return to `main`, pull latest, and produce `tmp/PR_26171_069-message-tts-profile-contract-alignment_delta.zip`. diff --git a/tests/playwright/tools/MessagesTool.spec.mjs b/tests/playwright/tools/MessagesTool.spec.mjs index 451c4439f..0b7f357b4 100644 --- a/tests/playwright/tools/MessagesTool.spec.mjs +++ b/tests/playwright/tools/MessagesTool.spec.mjs @@ -162,10 +162,12 @@ test("Message Studio renders Messages with child Message Parts and plays ordered await expect(page.locator("style, [style], script:not([src])")).toHaveCount(0); await expect(page.locator("[data-messages-category-name]")).toHaveCount(0); await expect(page.locator("[data-messages-tts-add-row]")).toHaveCount(0); + await expect(page.locator("[data-messages-emotions]")).toHaveCount(0); + await expect(page.locator("[data-messages-tts-profiles]")).toHaveCount(0); await expect(page.getByRole("columnheader", { name: "Message Name" })).toBeVisible(); await expect(page.getByRole("columnheader", { name: "Default TTS Profile" })).toBeVisible(); - await expect(page.locator("[data-messages-tts-profiles]")).toContainText("Browser Speech Default"); - await expect(page.locator("[data-messages-tts-profiles]")).toContainText("Owned by TTS Studio"); + await expect(page.locator("[data-messages-segment-count]")).toHaveText("0"); + await expect(page.getByRole("button", { name: "Stop" })).toBeEnabled(); await page.getByRole("button", { name: "Add Message" }).click(); await page.locator("[data-messages-commit='__new__']").click(); @@ -230,6 +232,10 @@ test("Message Studio renders Messages with child Message Parts and plays ordered type: "speak", voiceName: "Test Voice", })); + await page.getByRole("button", { name: "Stop" }).click(); + await expect(page.locator("[data-messages-log]")).toHaveText("Message Studio playback stopped. Cleared 2 queued items."); + speechCalls = await page.evaluate(() => window.__messagesSpeechCalls); + expect(speechCalls.at(-1)).toEqual({ type: "cancel" }); await page.locator("[data-messages-segment-row]").filter({ hasText: "Keep your torch high." }).getByRole("button", { name: "Play Part" }).click(); await expect(page.locator("[data-messages-log]")).toHaveText("Play Part queued Part 2 using Browser Speech Default."); diff --git a/tests/playwright/tools/TextToSpeechFunctional.spec.mjs b/tests/playwright/tools/TextToSpeechFunctional.spec.mjs index 8c964fecc..984835da8 100644 --- a/tests/playwright/tools/TextToSpeechFunctional.spec.mjs +++ b/tests/playwright/tools/TextToSpeechFunctional.spec.mjs @@ -107,7 +107,7 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as await expect(page.locator("[data-tts-voice-select]")).toContainText("Arcade Voice"); await expect(page.locator("[data-tts-voice-count]")).toHaveText("2"); await expect(page.locator("[data-tts-profile-count]")).toHaveText("3"); - await expect(page.locator("[data-tts-emotion-count]")).toHaveText("3"); + await expect(page.locator("[data-tts-emotion-count]")).toHaveText("12"); await expect(page.locator("[data-tts-profile-table]")).toContainText("Default Balanced Profile"); await expect(page.locator("[data-tts-profile-table]")).toContainText("Man Profile 1"); await expect(page.locator("[data-tts-profile-table]")).toContainText("Woman Profile 2"); @@ -116,7 +116,23 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as await expect(page.getByRole("heading", { name: "Emotion Settings" })).toBeVisible(); await expect(page.getByRole("columnheader", { name: "Emotion", exact: true })).toBeVisible(); await expect(page.getByRole("columnheader", { name: "SSML-like Preset" })).toBeVisible(); + await expect(page.locator("[data-tts-emotion-row]")).toHaveCount(4); await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Neutral" }).getByRole("button", { name: "Delete" })).toBeDisabled(); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Happy" })).toBeVisible(); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Angry" })).toBeVisible(); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Scared" })).toBeVisible(); + await page.locator("[data-tts-profile-row]").filter({ hasText: "Man Profile 1" }).click(); + await expect(page.locator("[data-tts-emotion-row]")).toHaveCount(4); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Neutral" })).toBeVisible(); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Happy" })).toBeVisible(); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Angry" })).toBeVisible(); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Scared" })).toBeVisible(); + await page.locator("[data-tts-profile-row]").filter({ hasText: "Woman Profile 2" }).click(); + await expect(page.locator("[data-tts-emotion-row]")).toHaveCount(4); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Neutral" })).toBeVisible(); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Happy" })).toBeVisible(); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Angry" })).toBeVisible(); + await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Scared" })).toBeVisible(); await page.getByRole("button", { name: "Add Profile" }).click(); await expect(page.locator("[data-tts-profile-editor='__new__']")).toBeVisible(); @@ -212,6 +228,7 @@ test("Text To Speech shows actionable error when browser speech synthesis is una try { await expect(page.getByRole("heading", { level: 1, name: "Text To Speech" })).toBeVisible(); await expect(page.locator("[data-tts-profile-count]")).toHaveText("3"); + await expect(page.locator("[data-tts-emotion-count]")).toHaveText("12"); await expect(page.locator("[data-tts-engine-status]")).toContainText("SpeechSynthesis is unavailable"); await expect(page.locator("[data-tts-status]")).toContainText("Use a browser with Web Speech API support"); await expect(page.locator("[data-tts-voice-select]")).toContainText("No browser voices available"); diff --git a/tests/tools/Text2SpeechShell.test.mjs b/tests/tools/Text2SpeechShell.test.mjs index 51e4f019f..719243bff 100644 --- a/tests/tools/Text2SpeechShell.test.mjs +++ b/tests/tools/Text2SpeechShell.test.mjs @@ -88,8 +88,10 @@ test("Text2Speech profile contract exposes Message Studio compatible profile opt assert.equal(TTS_PROFILE_CONTRACT_VERSION, "tts-profile-emotion-v1"); assert.equal(defaults[0].name, "Default Balanced Profile"); assert.equal(defaults[0].messageStudioUsageCount, 1); - assert.equal(defaults[0].emotions[0].emotionLabel, "Neutral"); - assert.equal(defaults[0].emotions[0].messagePartsUsageCount, 1); + assert.deepEqual(defaults[0].emotions.map((emotion) => emotion.emotionLabel), ["Neutral", "Happy", "Angry", "Scared"]); + assert.deepEqual(defaults[1].emotions.map((emotion) => emotion.emotionLabel), ["Neutral", "Happy", "Angry", "Scared"]); + assert.deepEqual(defaults[2].emotions.map((emotion) => emotion.emotionLabel), ["Neutral", "Happy", "Angry", "Scared"]); + assert.equal(defaults[0].emotions.find((emotion) => emotion.emotion === "neutral").messagePartsUsageCount, 1); assert.deepEqual(options, [{ active: true, emotionSettings: [{ diff --git a/toolbox/messages/index.html b/toolbox/messages/index.html index 5df9a0290..74a54356d 100644 --- a/toolbox/messages/index.html +++ b/toolbox/messages/index.html @@ -51,7 +51,7 @@
Messages own game text and ordered parts. Audio playback is delegated to the audio engine.
| Name | -Volume | -Pitch | -Rate | -Status | -Actions | -
|---|---|---|---|---|---|
| Loading emotion profiles. | |||||
| Name | -Provider | -Voice | -Language | -Status | -Actions | -
|---|---|---|---|---|---|
| Loading TTS profiles. | |||||