Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 15 additions & 0 deletions docs_build/dev/reports/PR_26171_069-validation.md
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 17 additions & 10 deletions docs_build/dev/reports/codex_changed_files.txt
Original file line number Diff line number Diff line change
@@ -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
Binary file modified docs_build/dev/reports/codex_review.diff
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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`.
10 changes: 8 additions & 2 deletions tests/playwright/tools/MessagesTool.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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.");
Expand Down
19 changes: 18 additions & 1 deletion tests/playwright/tools/TextToSpeechFunctional.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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();
Expand Down Expand Up @@ -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");
Expand Down
6 changes: 4 additions & 2 deletions tests/tools/Text2SpeechShell.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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: [{
Expand Down
Loading
Loading