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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 modifieddocs_build/dev/reports/codex_review.diff
Binary file not shown.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
PR_26171_069 message TTS profile contract alignment by ToolboxAid · Pull Request #23 · ToolboxAid/HTML-JavaScript-Gaming · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 modifieddocs_build/dev/reports/codex_review.diff
Binary file not shown.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' PR_26171_069 message TTS profile contract alignment by ToolboxAid · Pull Request #23 · ToolboxAid/HTML-JavaScript-Gaming · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 modifieddocs_build/dev/reports/codex_review.diff
Binary file not shown.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' PR_26171_069 message TTS profile contract alignment by ToolboxAid · Pull Request #23 · ToolboxAid/HTML-JavaScript-Gaming · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 modifieddocs_build/dev/reports/codex_review.diff
Binary file not shown.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' PR_26171_069 message TTS profile contract alignment by ToolboxAid · Pull Request #23 · ToolboxAid/HTML-JavaScript-Gaming · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 modifieddocs_build/dev/reports/codex_review.diff
Binary file not shown.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' PR_26171_069 message TTS profile contract alignment by ToolboxAid · Pull Request #23 · ToolboxAid/HTML-JavaScript-Gaming · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 modifieddocs_build/dev/reports/codex_review.diff
Binary file not shown.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' PR_26171_069 message TTS profile contract alignment by ToolboxAid · Pull Request #23 · ToolboxAid/HTML-JavaScript-Gaming · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 modifieddocs_build/dev/reports/codex_review.diff
Binary file not shown.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); PR_26171_069 message TTS profile contract alignment by ToolboxAid · Pull Request #23 · ToolboxAid/HTML-JavaScript-Gaming · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 modifieddocs_build/dev/reports/codex_review.diff
Binary file not shown.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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