diff --git a/docs_build/dev/reports/PR_26171_061-engine-audio-ownership-checklist.md b/docs_build/dev/reports/PR_26171_061-engine-audio-ownership-checklist.md new file mode 100644 index 000000000..5e18e0955 --- /dev/null +++ b/docs_build/dev/reports/PR_26171_061-engine-audio-ownership-checklist.md @@ -0,0 +1,25 @@ +# PR_26171_061 Engine Audio Ownership Checklist + +## Engine Ownership + +- PASS: Reusable TTS voice filtering lives in `src/engine/audio/TextToSpeechEngine.js`. +- PASS: Reusable TTS preset shaping lives in `src/engine/audio/TextToSpeechEngine.js`. +- PASS: Reusable TTS queue item helpers live in `src/engine/audio/TextToSpeechEngine.js`. +- PASS: Existing defaults remain in `src/engine/audio/TextToSpeechDefaults.js`. +- PASS: Active toolbox UI consumes the engine module instead of duplicating all engine behavior locally. + +## Active Tool Ownership + +- PASS: Active tool remains under `toolbox/text-to-speech/`. +- PASS: Active HTML remains Theme V2 only. +- PASS: Active JavaScript remains external. +- PASS: Active tool owns DOM wiring, status display, and control event handling. +- PASS: Active tool does not create database behavior. + +## Boundary Checks + +- PASS: No `tools/text2speech/` path was created. +- PASS: Archived `old_text2speech-V2` was used as a read-only functionality sample. +- PASS: Archived tool files were not activated in navigation. +- PASS: Browser SpeechSynthesis is the implemented local provider. +- PASS: Planned providers remain documented but do not block browser preview. diff --git a/docs_build/dev/reports/PR_26171_061-instruction-compliance-checklist.md b/docs_build/dev/reports/PR_26171_061-instruction-compliance-checklist.md new file mode 100644 index 000000000..ffb2cb288 --- /dev/null +++ b/docs_build/dev/reports/PR_26171_061-instruction-compliance-checklist.md @@ -0,0 +1,32 @@ +# PR_26171_061 Instruction Compliance Checklist + +## Pre-Work Gate + +- PASS: Read `docs_build/dev/PROJECT_INSTRUCTIONS.md` before file changes. +- PASS: Read `docs_build/dev/PROJECT_MULTI_PC.txt` before file changes. +- PASS: Reported instruction compliance PASS/FAIL before file changes. +- PASS: `git branch --show-current` reported `main` before branch creation. +- PASS: `git checkout main` completed before branch creation. +- PASS: `git pull origin main` completed before branch creation. +- PASS: `git status` reported a clean working tree before branch creation. +- PASS: Created branch `pr/26171-061-text2speech-engine-audio-feature-parity` from clean latest `main`. + +## Owner And Parity + +- PASS: PR number `061` is odd. +- PASS: Odd parity maps to Laptop / Environment 2. +- PASS: Text To Speech / TTS is owned by Laptop / Environment 2. +- PASS: Implementation path matches active owner path: `toolbox/text-to-speech/`. +- PASS: Shared engine path matches approved reusable audio path: `src/engine/audio/`. + +## Hard Stop Checks + +- PASS: Instructions were present and readable. +- PASS: Branch workflow could be followed. +- PASS: Repository was clean before branching. +- PASS: No owner mismatch. +- PASS: No parity mismatch. +- PASS: No wrong implementation path. +- PASS: Scoped validation was not skipped. +- PASS: Required reports were created. +- PASS: Required repo ZIP was created. diff --git a/docs_build/dev/reports/PR_26171_061-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_061-manual-validation-notes.md new file mode 100644 index 000000000..3341b14ba --- /dev/null +++ b/docs_build/dev/reports/PR_26171_061-manual-validation-notes.md @@ -0,0 +1,27 @@ +# PR_26171_061 Manual Validation Notes + +## Manual Review + +- Confirmed active Text To Speech path is `toolbox/text-to-speech/`. +- Confirmed no `tools/text2speech/` directory was created. +- Confirmed old functionality sample path is `archive/v1-v2/tools/old_text2speech-V2/`. +- Confirmed active HTML has external scripts only. +- Confirmed active HTML has no inline style attributes or inline event handlers. +- Confirmed no database files were changed. + +## Automated Browser Coverage Used For Manual Equivalents + +- Opened active Text To Speech page through targeted Playwright validation. +- Verified browser voices render. +- Verified restored control groups render. +- Verified preset shaping updates pitch and volume values. +- Verified named sentence Add, Duplicate, and Delete. +- Verified output summary includes queue JSON. +- Verified Speak, Pause, Resume, and Stop call the browser SpeechSynthesis mock. +- Verified unavailable SpeechSynthesis shows actionable error. + +## Out Of Scope Manual Checks + +- No paid provider was manually exercised. +- No generated audio file export was manually exercised. +- No database behavior was manually exercised. diff --git a/docs_build/dev/reports/PR_26171_061-old-tts-feature-parity-checklist.md b/docs_build/dev/reports/PR_26171_061-old-tts-feature-parity-checklist.md new file mode 100644 index 000000000..e09e0f974 --- /dev/null +++ b/docs_build/dev/reports/PR_26171_061-old-tts-feature-parity-checklist.md @@ -0,0 +1,48 @@ +# PR_26171_061 Old TTS Feature Parity Checklist + +Functionality sample: `archive/v1-v2/tools/old_text2speech-V2/` + +## Controls And Options + +- PASS: Gender helper filter restored. +- PASS: Language filter restored. +- PASS: Voice dropdown restored. +- PASS: Voice details restored. +- PASS: Voice Age select restored. +- PASS: Character Preset select restored. +- PASS: SSML-like Preset select restored. +- PASS: Volume slider restored with visible value. +- PASS: Rate / Speed slider restored with visible value. +- PASS: Pitch slider restored with visible value. +- PASS: Name field restored. +- PASS: Text To Speak editor restored. + +## Queue And JSON + +- PASS: Named Sentences queue restored. +- PASS: Add named sentence restored. +- PASS: Duplicate named sentence restored. +- PASS: Delete named sentence restored. +- PASS: Output Summary JSON restored. +- PASS: Import JSON restored for standalone launch. +- PASS: Copy JSON restored for standalone launch. +- PASS: Export JSON restored for standalone launch. +- PASS: URL JSON source loading restored through `samplePresetPath`. + +## Playback And Status + +- PASS: Speak restored. +- PASS: Pause restored when browser support exists. +- PASS: Resume restored when browser support exists. +- PASS: Stop restored. +- PASS: Clearable status log restored. +- PASS: Actionable unavailable-browser error restored. +- PASS: No placeholder provider-blocking behavior remains for browser preview. + +## Workspace + +- PASS: Project Workspace launch detection restored. +- PASS: Standalone JSON actions hide during Project Workspace launch. +- PASS: Return to Project Workspace action restored. +- PASS: Project Workspace toolState loading restored. +- PASS: Project Workspace dirty-state writeback restored. diff --git a/docs_build/dev/reports/PR_26171_061-text2speech-engine-audio-feature-parity.md b/docs_build/dev/reports/PR_26171_061-text2speech-engine-audio-feature-parity.md new file mode 100644 index 000000000..b2f4a44d3 --- /dev/null +++ b/docs_build/dev/reports/PR_26171_061-text2speech-engine-audio-feature-parity.md @@ -0,0 +1,71 @@ +# PR_26171_061 Text To Speech Engine Audio Feature Parity + +## Summary + +Restored active Text To Speech feature parity from the archived `old_text2speech-V2` functionality sample while keeping active implementation in `toolbox/text-to-speech/` and reusable engine behavior in `src/engine/audio/`. + +## Changed Scope + +- Rebuilt reusable Text To Speech voice filtering, preset shaping, and queue-item helpers in `src/engine/audio/TextToSpeechEngine.js`. +- Expanded `toolbox/text-to-speech/index.html` with Theme V2 controls for the old V2 feature set. +- Rebuilt `toolbox/text-to-speech/text2speech.js` so the active tool consumes the engine audio module. +- Expanded targeted browser validation in `tests/playwright/tools/TextToSpeechFunctional.spec.mjs`. +- Updated Playwright V8 coverage artifacts for changed runtime JavaScript. + +## Requirement Checklist + +- PASS: Used `archive/v1-v2/tools/old_text2speech-V2/` as the required functionality sample. +- PASS: Reusable TTS behavior lives in `src/engine/audio/`. +- PASS: `toolbox/text-to-speech/` consumes `src/engine/audio/TextToSpeechEngine.js`. +- PASS: Restored old controls/options/features in the active tool. +- PASS: Browser SpeechSynthesis provider remains implemented. +- PASS: Planned provider adapters do not block browser preview behavior. +- PASS: No `tools/text2speech/` path was created. +- PASS: No database files were changed. +- PASS: Theme V2 remains the styling source. +- PASS: HTML uses external JavaScript only. +- PASS: No inline script, style, or event handler was added. + +## Restored Feature Set + +- Import JSON. +- Copy JSON. +- Export JSON. +- Project Workspace return action during workspace launch. +- Gender helper filter. +- Language filter. +- Browser voice selection. +- Voice details. +- Voice age shaping. +- Character presets. +- SSML-like presets. +- Volume, rate, and pitch sliders with visible values. +- Named sentence Name field. +- Add, Duplicate, and Delete named sentence actions. +- Text To Speak editor. +- Speak, Pause, Resume, and Stop playback actions. +- Named Sentences queue. +- Output Summary JSON. +- Clearable Status log. +- URL JSON preset loading through `samplePresetPath`. +- Project Workspace toolState loading and dirty-state writeback. + +## Validation Summary + +- PASS: `node --check src\engine\audio\TextToSpeechEngine.js` +- PASS: `node --check toolbox\text-to-speech\text2speech.js` +- PASS: `node --test tests\tools\Text2SpeechShell.test.mjs` +- PASS: `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs` +- PASS: `npm run test:workspace-v2` + - Note: command name is legacy; user-facing language is Project Workspace. +- PASS: `git diff --check` +- PASS: changed runtime JS coverage collected for `src/engine/audio/TextToSpeechEngine.js`. +- PASS: changed runtime JS coverage collected for `toolbox/text-to-speech/text2speech.js`. + +## Out Of Scope + +- No database changes. +- No external paid provider implementation. +- No generated audio file export provider. +- No new `tools/text2speech/` path. +- No archived tool activation. diff --git a/docs_build/dev/reports/PR_26171_061-validation.md b/docs_build/dev/reports/PR_26171_061-validation.md new file mode 100644 index 000000000..bf864dc27 --- /dev/null +++ b/docs_build/dev/reports/PR_26171_061-validation.md @@ -0,0 +1,42 @@ +# PR_26171_061 Validation Report + +## Commands Run + +- `node --check src\engine\audio\TextToSpeechEngine.js` + - PASS. +- `node --check toolbox\text-to-speech\text2speech.js` + - PASS. +- `node --test tests\tools\Text2SpeechShell.test.mjs` + - PASS: 3 tests passed. +- `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs` + - PASS: 2 tests passed. + - Covers restored controls, preset shaping, queue add/duplicate/delete, output summary, pause/resume, speak, stop, and unavailable SpeechSynthesis error handling. + - PASS: rerun after merging `origin/main` into the PR branch for conflict resolution. +- `npm run test:workspace-v2` + - PASS: 5 Project Workspace tests passed. + - Note: command name is legacy; user-facing language is Project Workspace. + - Note: first execution used a 120s timeout and timed out before completion; rerun with a longer timeout completed successfully. + - PASS: rerun after merging `origin/main` into the PR branch for conflict resolution. +- `git diff --check` + - PASS. + +## Coverage + +- PASS: `docs_build/dev/reports/playwright_v8_coverage_report.txt` produced changed runtime JS coverage. +- PASS: `docs_build/dev/reports/coverage_changed_js_guardrail.txt` reports no changed runtime JS coverage warnings. +- PASS: `src/engine/audio/TextToSpeechEngine.js` covered at 71%. +- PASS: `toolbox/text-to-speech/text2speech.js` covered at 71%. + +## Artifact Verification + +- PASS: `docs_build/dev/reports/codex_review.diff` exists. +- PASS: `docs_build/dev/reports/codex_changed_files.txt` exists. +- PASS: `tmp/PR_26171_061-text2speech-engine-audio-feature-parity_delta.zip` exists. +- PASS: ZIP size is greater than zero. +- PASS: ZIP contents preserve repo-relative paths. + +## Skipped + +- Full samples validation skipped because no sample JSON or sample runtime behavior changed. +- Database validation skipped because no database files or runtime persistence changed. +- External provider validation skipped because paid/provider generation is out of scope and browser SpeechSynthesis is the implemented provider for this PR. diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index bb7de5d8d..2a3c37ce3 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,91 +1,54 @@ -# Codex Changed Files - PR_26171_041-idea-board-production-polish +# Codex Changed Files - PR_26171_061-text2speech-engine-audio-feature-parity ## Git Workflow -- Current branch: `codex/pr-26171-041-idea-board-production-polish`. -- Expected starting branch: `main` (PASS before branch creation). -- Created branch: `codex/pr-26171-041-idea-board-production-polish`. -- Commit before conflict resolution: `ca626873a4cf19925229851b0da2a4ff55c2073f`. -- Initial push result: PASS, branch pushed to `origin/codex/pr-26171-041-idea-board-production-polish`. -- PR URL: `https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/17`. -- Conflict resolution: merged `origin/main` at `0a364f85516aea82e38eea2f498fde5980f9adf9`; conflicts were limited to `codex_changed_files.txt` and `codex_review.diff`. -- Conflict resolution push result: pending until after merge-validation commit. -- Merge result: pending until after PR validation/merge. -- Final main sync: pending until after merge and final pull. +- Starting branch: `main`. +- Created branch: `pr/26171-061-text2speech-engine-audio-feature-parity`. +- Initial commit: `e4541d63719ab777b0654c8fecf4b13237d31256`. +- PR branch was merged with `origin/main` during PR conflict recovery. +- Conflicts were limited to generated report artifacts. -## Git Status +## Scoped Diff Stat ```text - M assets/theme-v2/css/tables.css - M assets/theme-v2/js/tool-display-mode.js - M docs_build/dev/reports/codex_changed_files.txt - M docs_build/dev/reports/codex_review.diff - M docs_build/dev/reports/coverage_changed_js_guardrail.txt - M docs_build/dev/reports/playwright_v8_coverage_report.txt - M src/shared/toolbox/tool-metadata-inventory.js - M tests/playwright/tools/IdeaBoardTableNotes.spec.mjs - M tests/playwright/tools/ToolboxRoutePages.spec.mjs - M toolbox/idea-board/index.html - M toolbox/idea-board/index.js +...R_26171_061-engine-audio-ownership-checklist.md | 25 + + ...R_26171_061-instruction-compliance-checklist.md | 32 + + .../PR_26171_061-manual-validation-notes.md | 27 + + ...R_26171_061-old-tts-feature-parity-checklist.md | 48 ++ + ..._061-text2speech-engine-audio-feature-parity.md | 71 ++ + docs_build/dev/reports/PR_26171_061-validation.md | 42 + + docs_build/dev/reports/codex_changed_files.txt | 118 +-- + .../dev/reports/coverage_changed_js_guardrail.txt | 7 +- + .../dev/reports/playwright_v8_coverage_report.txt | 36 +- + src/engine/audio/TextToSpeechEngine.js | 208 ++++- + .../tools/TextToSpeechFunctional.spec.mjs | 30 + + toolbox/text-to-speech/index.html | 119 +-- + toolbox/text-to-speech/text2speech.js | 853 +++++++++++++++++---- + 13 files changed, 1317 insertions(+), 299 deletions(-) ``` -## Diff Stat -```text - assets/theme-v2/css/tables.css | 70 +++++++- - assets/theme-v2/js/tool-display-mode.js | 2 +- - .../dev/reports/coverage_changed_js_guardrail.txt | 5 +- - .../dev/reports/playwright_v8_coverage_report.txt | 11 +- - src/shared/toolbox/tool-metadata-inventory.js | 6 +- - .../playwright/tools/IdeaBoardTableNotes.spec.mjs | 71 +++++++- - tests/playwright/tools/ToolboxRoutePages.spec.mjs | 26 ++- - toolbox/idea-board/index.html | 31 ++-- - toolbox/idea-board/index.js | 191 ++++++++++++++++++++- - 9 files changed, 368 insertions(+), 45 deletions(-) -``` - -## Requirement Evidence -- PASS: Removed creator-visible Create Project from the right-side Next Steps area; no disabled `data-idea-board-create-project` panel remains. -- PASS: Create Project is shown only in a Ready idea row's Actions column. -- PASS: Clicking Create Project changes the idea status from Ready to Project and replaces Create Project/Delete with Open Project and Archive. -- PASS: Project rows show Open Project and Archive and do not show Delete. -- PASS: Added Archived status; archived rows are hidden by default. -- PASS: Archived rows show Restore and Delete. -- PASS: Restore returns archived ideas to their previous non-archived status, with Refining fallback in runtime. -- PASS: Delete is guarded so Project ideas cannot be deleted unless archived first. -- PASS: Status dropdown uses New, Exploring, Refining, Ready, Project, and Archived. -- PASS: Added compact Show checkbox dropdown beside the visible table description line, with default statuses New, Exploring, Refining, Ready, and Project selected and Archived unselected. -- PASS: Show dropdown supports any checkbox combination plus Select All and Clear All. -- PASS: Updated creator-facing copy to "Capture, compare, and shape game ideas." and "Scan, compare, and update early ideas." -- PASS: Replaced creator-visible Tool Display Mode navigation error with safe copy that does not mention server/API/local server/port/implementation details. -- PASS: Chevron renders to the left of the Idea text, inline, same size, same color, and on the same text line; the whole Idea cell remains the expansion target. -- PASS: Notes count remains informational only. -- PASS: Table-first structure, inline Add Idea/Add Note, row-level Save/Cancel, status dropdown edit mode, notes indentation, and single-open accordion behavior remain covered. +## Changed Files +- src/engine/audio/TextToSpeechEngine.js +- toolbox/text-to-speech/index.html +- toolbox/text-to-speech/text2speech.js +- tests/playwright/tools/TextToSpeechFunctional.spec.mjs +- docs_build/dev/reports/coverage_changed_js_guardrail.txt +- docs_build/dev/reports/playwright_v8_coverage_report.txt +- docs_build/dev/reports/PR_26171_061-text2speech-engine-audio-feature-parity.md +- docs_build/dev/reports/PR_26171_061-instruction-compliance-checklist.md +- docs_build/dev/reports/PR_26171_061-old-tts-feature-parity-checklist.md +- docs_build/dev/reports/PR_26171_061-engine-audio-ownership-checklist.md +- docs_build/dev/reports/PR_26171_061-validation.md +- docs_build/dev/reports/PR_26171_061-manual-validation-notes.md +- docs_build/dev/reports/codex_review.diff +- docs_build/dev/reports/codex_changed_files.txt ## Validation -- PASS: `node --check toolbox/idea-board/index.js`. -- PASS: `node --check assets/theme-v2/js/tool-display-mode.js`. -- PASS: `node --check src/shared/toolbox/tool-metadata-inventory.js`. -- PASS: `node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs`. -- PASS: `node --check tests/playwright/tools/ToolboxRoutePages.spec.mjs`. -- PASS: `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line --timeout=90000`. -- PASS: `npx playwright test tests/playwright/tools/ToolboxRoutePages.spec.mjs --project=playwright --workers=1 --reporter=line -g "Idea Board launches" --timeout=90000`. -- PASS: `npm run test:workspace-v2` (workspace-contract lane, 5 passed). +- PASS: `node --check src\engine\audio\TextToSpeechEngine.js`. +- PASS: `node --check toolbox\text-to-speech\text2speech.js`. +- PASS: `node --test tests\tools\Text2SpeechShell.test.mjs`. +- PASS: `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs`. +- PASS: `npm run test:workspace-v2` (legacy command name; user-facing language is Project Workspace). - PASS: `git diff --check`. -- PASS: Post-conflict rerun completed after merging `origin/main` at `0a364f85516aea82e38eea2f498fde5980f9adf9`. -- PASS: Playwright V8 coverage report produced because runtime JavaScript changed. -- WARN: Coverage report marks `src/shared/toolbox/tool-metadata-inventory.js` as not collected by browser V8 coverage; advisory only per project instructions. -- SKIPPED: Full samples smoke was not run per request. +- PASS: Post-conflict validation rerun after merging `origin/main` into the PR branch. ## ZIP -- Path: `tmp/PR_26171_041-idea-board-production-polish_delta.zip`. -- Size: final size reported in the delivery summary after conflict-resolution ZIP refresh. -- Contents: - - assets/theme-v2/css/tables.css - - assets/theme-v2/js/tool-display-mode.js - - docs_build/dev/reports/codex_changed_files.txt - - docs_build/dev/reports/codex_review.diff - - docs_build/dev/reports/coverage_changed_js_guardrail.txt - - docs_build/dev/reports/playwright_v8_coverage_report.txt - - src/shared/toolbox/tool-metadata-inventory.js - - tests/playwright/tools/IdeaBoardTableNotes.spec.mjs - - tests/playwright/tools/ToolboxRoutePages.spec.mjs - - toolbox/idea-board/index.html - - toolbox/idea-board/index.js +- Path: `tmp/PR_26171_061-text2speech-engine-audio-feature-parity_delta.zip`. diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 9d13c1ccf..98f10d77f 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,886 +1,1995 @@ -diff --git a/assets/theme-v2/css/tables.css b/assets/theme-v2/css/tables.css -index a8c75090a..c39ef19f0 100644 ---- a/assets/theme-v2/css/tables.css -+++ b/assets/theme-v2/css/tables.css -@@ -38,6 +38,67 @@ td { - color: var(--text) - } - -+.idea-board-table-caption { -+ position: relative; -+ z-index: var(--z-index-sm); -+ display: flex; -+ align-items: center; -+ justify-content: space-between; -+ gap: var(--space-10); -+ padding: var(--space-10); -+ font-weight: var(--font-weight-heavy) -+} +diff --git a/docs_build/dev/reports/PR_26171_061-engine-audio-ownership-checklist.md b/docs_build/dev/reports/PR_26171_061-engine-audio-ownership-checklist.md +new file mode 100644 +index 000000000..5e18e0955 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26171_061-engine-audio-ownership-checklist.md +@@ -0,0 +1,25 @@ ++# PR_26171_061 Engine Audio Ownership Checklist + -+.idea-board-show-filter { -+ position: relative; -+ z-index: var(--z-index-md); -+ color: var(--text); -+ font-size: var(--font-size-xs); -+ font-weight: var(--font-weight-bold) -+} ++## Engine Ownership + -+.idea-board-show-filter summary { -+ display: inline-flex; -+ align-items: center; -+ min-height: var(--space-28); -+ padding: var(--space-3) var(--space-10); -+ border: var(--border-standard); -+ border-radius: var(--radius-md); -+ background: var(--panel-soft); -+ cursor: pointer; -+ list-style: none -+} ++- PASS: Reusable TTS voice filtering lives in `src/engine/audio/TextToSpeechEngine.js`. ++- PASS: Reusable TTS preset shaping lives in `src/engine/audio/TextToSpeechEngine.js`. ++- PASS: Reusable TTS queue item helpers live in `src/engine/audio/TextToSpeechEngine.js`. ++- PASS: Existing defaults remain in `src/engine/audio/TextToSpeechDefaults.js`. ++- PASS: Active toolbox UI consumes the engine module instead of duplicating all engine behavior locally. + -+.idea-board-show-filter summary::-webkit-details-marker { -+ display: none -+} ++## Active Tool Ownership + -+.idea-board-show-filter__menu { -+ position: static; -+ min-width: 12rem; -+ margin-top: var(--space-6); -+ padding: var(--space-10); -+ border: var(--border-standard); -+ border-radius: var(--radius-md); -+ background: var(--panel); -+ box-shadow: var(--shadow-md) -+} ++- PASS: Active tool remains under `toolbox/text-to-speech/`. ++- PASS: Active HTML remains Theme V2 only. ++- PASS: Active JavaScript remains external. ++- PASS: Active tool owns DOM wiring, status display, and control event handling. ++- PASS: Active tool does not create database behavior. + -+.idea-board-show-filter__options { -+ display: grid; -+ gap: var(--space-6); -+ margin-top: var(--space-10) -+} ++## Boundary Checks + -+.idea-board-show-filter__option { -+ display: flex; -+ align-items: center; -+ gap: var(--space-8); -+ color: var(--text); -+ font-weight: var(--font-weight-regular); -+ white-space: nowrap -+} ++- PASS: No `tools/text2speech/` path was created. ++- PASS: Archived `old_text2speech-V2` was used as a read-only functionality sample. ++- PASS: Archived tool files were not activated in navigation. ++- PASS: Browser SpeechSynthesis is the implemented local provider. ++- PASS: Planned providers remain documented but do not block browser preview. +diff --git a/docs_build/dev/reports/PR_26171_061-instruction-compliance-checklist.md b/docs_build/dev/reports/PR_26171_061-instruction-compliance-checklist.md +new file mode 100644 +index 000000000..ffb2cb288 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26171_061-instruction-compliance-checklist.md +@@ -0,0 +1,32 @@ ++# PR_26171_061 Instruction Compliance Checklist + - .data-table th { - color: var(--gold); - font-size: var(--font-size-sm); -@@ -60,8 +121,13 @@ td { - gap: .35em; - color: inherit; - font: inherit; -- line-height: inherit; -- vertical-align: baseline -+ line-height: var(--line-height-single); -+ vertical-align: baseline; -+ white-space: nowrap -+} ++## Pre-Work Gate + -+.idea-board-idea-label__text { -+ line-height: var(--line-height-single) - } - - .idea-board-idea-chevron { -diff --git a/assets/theme-v2/js/tool-display-mode.js b/assets/theme-v2/js/tool-display-mode.js -index 9c23ba67e..e77b7a9df 100644 ---- a/assets/theme-v2/js/tool-display-mode.js -+++ b/assets/theme-v2/js/tool-display-mode.js -@@ -157,7 +157,7 @@ - const diagnostic = document.createElement("p"); - diagnostic.className = "status"; - diagnostic.setAttribute("role", "status"); -- diagnostic.textContent = "Tool navigation could not load from the server API. Start the local server API and refresh."; -+ diagnostic.textContent = "Tool navigation is temporarily unavailable. Refresh the page or try again shortly."; - body.appendChild(diagnostic); - } - } ++- PASS: Read `docs_build/dev/PROJECT_INSTRUCTIONS.md` before file changes. ++- PASS: Read `docs_build/dev/PROJECT_MULTI_PC.txt` before file changes. ++- PASS: Reported instruction compliance PASS/FAIL before file changes. ++- PASS: `git branch --show-current` reported `main` before branch creation. ++- PASS: `git checkout main` completed before branch creation. ++- PASS: `git pull origin main` completed before branch creation. ++- PASS: `git status` reported a clean working tree before branch creation. ++- PASS: Created branch `pr/26171-061-text2speech-engine-audio-feature-parity` from clean latest `main`. ++ ++## Owner And Parity ++ ++- PASS: PR number `061` is odd. ++- PASS: Odd parity maps to Laptop / Environment 2. ++- PASS: Text To Speech / TTS is owned by Laptop / Environment 2. ++- PASS: Implementation path matches active owner path: `toolbox/text-to-speech/`. ++- PASS: Shared engine path matches approved reusable audio path: `src/engine/audio/`. ++ ++## Hard Stop Checks ++ ++- PASS: Instructions were present and readable. ++- PASS: Branch workflow could be followed. ++- PASS: Repository was clean before branching. ++- PASS: No owner mismatch. ++- PASS: No parity mismatch. ++- PASS: No wrong implementation path. ++- PASS: Scoped validation was not skipped. ++- PASS: Required reports were created. ++- PASS: Required repo ZIP was created. +diff --git a/docs_build/dev/reports/PR_26171_061-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_061-manual-validation-notes.md +new file mode 100644 +index 000000000..3341b14ba +--- /dev/null ++++ b/docs_build/dev/reports/PR_26171_061-manual-validation-notes.md +@@ -0,0 +1,27 @@ ++# PR_26171_061 Manual Validation Notes ++ ++## Manual Review ++ ++- Confirmed active Text To Speech path is `toolbox/text-to-speech/`. ++- Confirmed no `tools/text2speech/` directory was created. ++- Confirmed old functionality sample path is `archive/v1-v2/tools/old_text2speech-V2/`. ++- Confirmed active HTML has external scripts only. ++- Confirmed active HTML has no inline style attributes or inline event handlers. ++- Confirmed no database files were changed. ++ ++## Automated Browser Coverage Used For Manual Equivalents ++ ++- Opened active Text To Speech page through targeted Playwright validation. ++- Verified browser voices render. ++- Verified restored control groups render. ++- Verified preset shaping updates pitch and volume values. ++- Verified named sentence Add, Duplicate, and Delete. ++- Verified output summary includes queue JSON. ++- Verified Speak, Pause, Resume, and Stop call the browser SpeechSynthesis mock. ++- Verified unavailable SpeechSynthesis shows actionable error. ++ ++## Out Of Scope Manual Checks ++ ++- No paid provider was manually exercised. ++- No generated audio file export was manually exercised. ++- No database behavior was manually exercised. +diff --git a/docs_build/dev/reports/PR_26171_061-old-tts-feature-parity-checklist.md b/docs_build/dev/reports/PR_26171_061-old-tts-feature-parity-checklist.md +new file mode 100644 +index 000000000..e09e0f974 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26171_061-old-tts-feature-parity-checklist.md +@@ -0,0 +1,48 @@ ++# PR_26171_061 Old TTS Feature Parity Checklist ++ ++Functionality sample: `archive/v1-v2/tools/old_text2speech-V2/` ++ ++## Controls And Options ++ ++- PASS: Gender helper filter restored. ++- PASS: Language filter restored. ++- PASS: Voice dropdown restored. ++- PASS: Voice details restored. ++- PASS: Voice Age select restored. ++- PASS: Character Preset select restored. ++- PASS: SSML-like Preset select restored. ++- PASS: Volume slider restored with visible value. ++- PASS: Rate / Speed slider restored with visible value. ++- PASS: Pitch slider restored with visible value. ++- PASS: Name field restored. ++- PASS: Text To Speak editor restored. ++ ++## Queue And JSON ++ ++- PASS: Named Sentences queue restored. ++- PASS: Add named sentence restored. ++- PASS: Duplicate named sentence restored. ++- PASS: Delete named sentence restored. ++- PASS: Output Summary JSON restored. ++- PASS: Import JSON restored for standalone launch. ++- PASS: Copy JSON restored for standalone launch. ++- PASS: Export JSON restored for standalone launch. ++- PASS: URL JSON source loading restored through `samplePresetPath`. ++ ++## Playback And Status ++ ++- PASS: Speak restored. ++- PASS: Pause restored when browser support exists. ++- PASS: Resume restored when browser support exists. ++- PASS: Stop restored. ++- PASS: Clearable status log restored. ++- PASS: Actionable unavailable-browser error restored. ++- PASS: No placeholder provider-blocking behavior remains for browser preview. ++ ++## Workspace ++ ++- PASS: Project Workspace launch detection restored. ++- PASS: Standalone JSON actions hide during Project Workspace launch. ++- PASS: Return to Project Workspace action restored. ++- PASS: Project Workspace toolState loading restored. ++- PASS: Project Workspace dirty-state writeback restored. +diff --git a/docs_build/dev/reports/PR_26171_061-text2speech-engine-audio-feature-parity.md b/docs_build/dev/reports/PR_26171_061-text2speech-engine-audio-feature-parity.md +new file mode 100644 +index 000000000..b2f4a44d3 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26171_061-text2speech-engine-audio-feature-parity.md +@@ -0,0 +1,71 @@ ++# PR_26171_061 Text To Speech Engine Audio Feature Parity ++ ++## Summary ++ ++Restored active Text To Speech feature parity from the archived `old_text2speech-V2` functionality sample while keeping active implementation in `toolbox/text-to-speech/` and reusable engine behavior in `src/engine/audio/`. ++ ++## Changed Scope ++ ++- Rebuilt reusable Text To Speech voice filtering, preset shaping, and queue-item helpers in `src/engine/audio/TextToSpeechEngine.js`. ++- Expanded `toolbox/text-to-speech/index.html` with Theme V2 controls for the old V2 feature set. ++- Rebuilt `toolbox/text-to-speech/text2speech.js` so the active tool consumes the engine audio module. ++- Expanded targeted browser validation in `tests/playwright/tools/TextToSpeechFunctional.spec.mjs`. ++- Updated Playwright V8 coverage artifacts for changed runtime JavaScript. ++ ++## Requirement Checklist ++ ++- PASS: Used `archive/v1-v2/tools/old_text2speech-V2/` as the required functionality sample. ++- PASS: Reusable TTS behavior lives in `src/engine/audio/`. ++- PASS: `toolbox/text-to-speech/` consumes `src/engine/audio/TextToSpeechEngine.js`. ++- PASS: Restored old controls/options/features in the active tool. ++- PASS: Browser SpeechSynthesis provider remains implemented. ++- PASS: Planned provider adapters do not block browser preview behavior. ++- PASS: No `tools/text2speech/` path was created. ++- PASS: No database files were changed. ++- PASS: Theme V2 remains the styling source. ++- PASS: HTML uses external JavaScript only. ++- PASS: No inline script, style, or event handler was added. ++ ++## Restored Feature Set ++ ++- Import JSON. ++- Copy JSON. ++- Export JSON. ++- Project Workspace return action during workspace launch. ++- Gender helper filter. ++- Language filter. ++- Browser voice selection. ++- Voice details. ++- Voice age shaping. ++- Character presets. ++- SSML-like presets. ++- Volume, rate, and pitch sliders with visible values. ++- Named sentence Name field. ++- Add, Duplicate, and Delete named sentence actions. ++- Text To Speak editor. ++- Speak, Pause, Resume, and Stop playback actions. ++- Named Sentences queue. ++- Output Summary JSON. ++- Clearable Status log. ++- URL JSON preset loading through `samplePresetPath`. ++- Project Workspace toolState loading and dirty-state writeback. ++ ++## Validation Summary ++ ++- PASS: `node --check src\engine\audio\TextToSpeechEngine.js` ++- PASS: `node --check toolbox\text-to-speech\text2speech.js` ++- PASS: `node --test tests\tools\Text2SpeechShell.test.mjs` ++- PASS: `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs` ++- PASS: `npm run test:workspace-v2` ++ - Note: command name is legacy; user-facing language is Project Workspace. ++- PASS: `git diff --check` ++- PASS: changed runtime JS coverage collected for `src/engine/audio/TextToSpeechEngine.js`. ++- PASS: changed runtime JS coverage collected for `toolbox/text-to-speech/text2speech.js`. ++ ++## Out Of Scope ++ ++- No database changes. ++- No external paid provider implementation. ++- No generated audio file export provider. ++- No new `tools/text2speech/` path. ++- No archived tool activation. +diff --git a/docs_build/dev/reports/PR_26171_061-validation.md b/docs_build/dev/reports/PR_26171_061-validation.md +new file mode 100644 +index 000000000..bf864dc27 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26171_061-validation.md +@@ -0,0 +1,42 @@ ++# PR_26171_061 Validation Report ++ ++## Commands Run ++ ++- `node --check src\engine\audio\TextToSpeechEngine.js` ++ - PASS. ++- `node --check toolbox\text-to-speech\text2speech.js` ++ - PASS. ++- `node --test tests\tools\Text2SpeechShell.test.mjs` ++ - PASS: 3 tests passed. ++- `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs` ++ - PASS: 2 tests passed. ++ - Covers restored controls, preset shaping, queue add/duplicate/delete, output summary, pause/resume, speak, stop, and unavailable SpeechSynthesis error handling. ++ - PASS: rerun after merging `origin/main` into the PR branch for conflict resolution. ++- `npm run test:workspace-v2` ++ - PASS: 5 Project Workspace tests passed. ++ - Note: command name is legacy; user-facing language is Project Workspace. ++ - Note: first execution used a 120s timeout and timed out before completion; rerun with a longer timeout completed successfully. ++ - PASS: rerun after merging `origin/main` into the PR branch for conflict resolution. ++- `git diff --check` ++ - PASS. ++ ++## Coverage ++ ++- PASS: `docs_build/dev/reports/playwright_v8_coverage_report.txt` produced changed runtime JS coverage. ++- PASS: `docs_build/dev/reports/coverage_changed_js_guardrail.txt` reports no changed runtime JS coverage warnings. ++- PASS: `src/engine/audio/TextToSpeechEngine.js` covered at 71%. ++- PASS: `toolbox/text-to-speech/text2speech.js` covered at 71%. ++ ++## Artifact Verification ++ ++- PASS: `docs_build/dev/reports/codex_review.diff` exists. ++- PASS: `docs_build/dev/reports/codex_changed_files.txt` exists. ++- PASS: `tmp/PR_26171_061-text2speech-engine-audio-feature-parity_delta.zip` exists. ++- PASS: ZIP size is greater than zero. ++- PASS: ZIP contents preserve repo-relative paths. ++ ++## Skipped ++ ++- Full samples validation skipped because no sample JSON or sample runtime behavior changed. ++- Database validation skipped because no database files or runtime persistence changed. ++- External provider validation skipped because paid/provider generation is out of scope and browser SpeechSynthesis is the implemented provider for this PR. diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt -index 4b9b70b93..bb7de5d8d 100644 +index bb7de5d8d..2a3c37ce3 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt -@@ -1,37 +1,91 @@ --# Codex Changed Files - PR_26171_063-codex-instruction-enforcement-hardening -+# Codex Changed Files - PR_26171_041-idea-board-production-polish - --## Conflict Resolution Note --- Merged latest `origin/main` into the PR branch after GitHub reported merge conflicts. --- Regenerated Codex artifacts from the PR_063 delta against updated `origin/main`. --- No implementation scope was changed while resolving conflicts. -+## Git Workflow -+- Current branch: `codex/pr-26171-041-idea-board-production-polish`. -+- Expected starting branch: `main` (PASS before branch creation). -+- Created branch: `codex/pr-26171-041-idea-board-production-polish`. -+- Commit before conflict resolution: `ca626873a4cf19925229851b0da2a4ff55c2073f`. -+- Initial push result: PASS, branch pushed to `origin/codex/pr-26171-041-idea-board-production-polish`. -+- PR URL: `https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/17`. -+- Conflict resolution: merged `origin/main` at `0a364f85516aea82e38eea2f498fde5980f9adf9`; conflicts were limited to `codex_changed_files.txt` and `codex_review.diff`. -+- Conflict resolution push result: pending until after merge-validation commit. -+- Merge result: pending until after PR validation/merge. -+- Final main sync: pending until after merge and final pull. - --## Changed Files --- docs_build/dev/PROJECT_INSTRUCTIONS.md --- docs_build/dev/PROJECT_MULTI_PC.txt --- docs_build/dev/reports/PR_26171_063-codex-instruction-enforcement-hardening.md --- docs_build/dev/reports/PR_26171_063-instruction-compliance-checklist.md --- docs_build/dev/reports/PR_26171_063-validation.md --- docs_build/dev/reports/PR_26171_063-manual-validation-notes.md --- docs_build/dev/reports/codex_review.diff --- docs_build/dev/reports/codex_changed_files.txt -+## Git Status -+```text -+ M assets/theme-v2/css/tables.css -+ M assets/theme-v2/js/tool-display-mode.js -+ M docs_build/dev/reports/codex_changed_files.txt -+ M docs_build/dev/reports/codex_review.diff -+ M docs_build/dev/reports/coverage_changed_js_guardrail.txt -+ M docs_build/dev/reports/playwright_v8_coverage_report.txt -+ M src/shared/toolbox/tool-metadata-inventory.js -+ M tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -+ M tests/playwright/tools/ToolboxRoutePages.spec.mjs -+ M toolbox/idea-board/index.html -+ M toolbox/idea-board/index.js -+``` - --## Git Diff Stat Against Updated origin/main -+## Diff Stat +@@ -1,91 +1,54 @@ +-# Codex Changed Files - PR_26171_041-idea-board-production-polish ++# Codex Changed Files - PR_26171_061-text2speech-engine-audio-feature-parity + + ## Git Workflow +-- Current branch: `codex/pr-26171-041-idea-board-production-polish`. +-- Expected starting branch: `main` (PASS before branch creation). +-- Created branch: `codex/pr-26171-041-idea-board-production-polish`. +-- Commit before conflict resolution: `ca626873a4cf19925229851b0da2a4ff55c2073f`. +-- Initial push result: PASS, branch pushed to `origin/codex/pr-26171-041-idea-board-production-polish`. +-- PR URL: `https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/17`. +-- Conflict resolution: merged `origin/main` at `0a364f85516aea82e38eea2f498fde5980f9adf9`; conflicts were limited to `codex_changed_files.txt` and `codex_review.diff`. +-- Conflict resolution push result: pending until after merge-validation commit. +-- Merge result: pending until after PR validation/merge. +-- Final main sync: pending until after merge and final pull. ++- Starting branch: `main`. ++- Created branch: `pr/26171-061-text2speech-engine-audio-feature-parity`. ++- Initial commit: `e4541d63719ab777b0654c8fecf4b13237d31256`. ++- PR branch was merged with `origin/main` during PR conflict recovery. ++- Conflicts were limited to generated report artifacts. + +-## Git Status ++## Scoped Diff Stat ```text -- docs_build/dev/PROJECT_INSTRUCTIONS.md | 47 ++ -- docs_build/dev/PROJECT_MULTI_PC.txt | 58 ++ -- ..._063-codex-instruction-enforcement-hardening.md | 48 ++ -- ...R_26171_063-instruction-compliance-checklist.md | 37 ++ -- .../PR_26171_063-manual-validation-notes.md | 19 + -- docs_build/dev/reports/PR_26171_063-validation.md | 32 + -- docs_build/dev/reports/codex_changed_files.txt | 48 ++ -- docs_build/dev/reports/codex_review.diff | 699 +++++++++------------ -- 8 files changed, 569 insertions(+), 419 deletions(-) -+ assets/theme-v2/css/tables.css | 70 +++++++- -+ assets/theme-v2/js/tool-display-mode.js | 2 +- -+ .../dev/reports/coverage_changed_js_guardrail.txt | 5 +- -+ .../dev/reports/playwright_v8_coverage_report.txt | 11 +- -+ src/shared/toolbox/tool-metadata-inventory.js | 6 +- -+ .../playwright/tools/IdeaBoardTableNotes.spec.mjs | 71 +++++++- -+ tests/playwright/tools/ToolboxRoutePages.spec.mjs | 26 ++- -+ toolbox/idea-board/index.html | 31 ++-- -+ toolbox/idea-board/index.js | 191 ++++++++++++++++++++- -+ 9 files changed, 368 insertions(+), 45 deletions(-) +- M assets/theme-v2/css/tables.css +- M assets/theme-v2/js/tool-display-mode.js +- M docs_build/dev/reports/codex_changed_files.txt +- M docs_build/dev/reports/codex_review.diff +- M docs_build/dev/reports/coverage_changed_js_guardrail.txt +- M docs_build/dev/reports/playwright_v8_coverage_report.txt +- M src/shared/toolbox/tool-metadata-inventory.js +- M tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +- M tests/playwright/tools/ToolboxRoutePages.spec.mjs +- M toolbox/idea-board/index.html +- M toolbox/idea-board/index.js ++...R_26171_061-engine-audio-ownership-checklist.md | 25 + ++ ...R_26171_061-instruction-compliance-checklist.md | 32 + ++ .../PR_26171_061-manual-validation-notes.md | 27 + ++ ...R_26171_061-old-tts-feature-parity-checklist.md | 48 ++ ++ ..._061-text2speech-engine-audio-feature-parity.md | 71 ++ ++ docs_build/dev/reports/PR_26171_061-validation.md | 42 + ++ docs_build/dev/reports/codex_changed_files.txt | 118 +-- ++ .../dev/reports/coverage_changed_js_guardrail.txt | 7 +- ++ .../dev/reports/playwright_v8_coverage_report.txt | 36 +- ++ src/engine/audio/TextToSpeechEngine.js | 208 ++++- ++ .../tools/TextToSpeechFunctional.spec.mjs | 30 + ++ toolbox/text-to-speech/index.html | 119 +-- ++ toolbox/text-to-speech/text2speech.js | 853 +++++++++++++++++---- ++ 13 files changed, 1317 insertions(+), 299 deletions(-) ``` -+## Requirement Evidence -+- PASS: Removed creator-visible Create Project from the right-side Next Steps area; no disabled `data-idea-board-create-project` panel remains. -+- PASS: Create Project is shown only in a Ready idea row's Actions column. -+- PASS: Clicking Create Project changes the idea status from Ready to Project and replaces Create Project/Delete with Open Project and Archive. -+- PASS: Project rows show Open Project and Archive and do not show Delete. -+- PASS: Added Archived status; archived rows are hidden by default. -+- PASS: Archived rows show Restore and Delete. -+- PASS: Restore returns archived ideas to their previous non-archived status, with Refining fallback in runtime. -+- PASS: Delete is guarded so Project ideas cannot be deleted unless archived first. -+- PASS: Status dropdown uses New, Exploring, Refining, Ready, Project, and Archived. -+- PASS: Added compact Show checkbox dropdown beside the visible table description line, with default statuses New, Exploring, Refining, Ready, and Project selected and Archived unselected. -+- PASS: Show dropdown supports any checkbox combination plus Select All and Clear All. -+- PASS: Updated creator-facing copy to "Capture, compare, and shape game ideas." and "Scan, compare, and update early ideas." -+- PASS: Replaced creator-visible Tool Display Mode navigation error with safe copy that does not mention server/API/local server/port/implementation details. -+- PASS: Chevron renders to the left of the Idea text, inline, same size, same color, and on the same text line; the whole Idea cell remains the expansion target. -+- PASS: Notes count remains informational only. -+- PASS: Table-first structure, inline Add Idea/Add Note, row-level Save/Cancel, status dropdown edit mode, notes indentation, and single-open accordion behavior remain covered. -+ +-## Diff Stat +-```text +- assets/theme-v2/css/tables.css | 70 +++++++- +- assets/theme-v2/js/tool-display-mode.js | 2 +- +- .../dev/reports/coverage_changed_js_guardrail.txt | 5 +- +- .../dev/reports/playwright_v8_coverage_report.txt | 11 +- +- src/shared/toolbox/tool-metadata-inventory.js | 6 +- +- .../playwright/tools/IdeaBoardTableNotes.spec.mjs | 71 +++++++- +- tests/playwright/tools/ToolboxRoutePages.spec.mjs | 26 ++- +- toolbox/idea-board/index.html | 31 ++-- +- toolbox/idea-board/index.js | 191 ++++++++++++++++++++- +- 9 files changed, 368 insertions(+), 45 deletions(-) +-``` +- +-## Requirement Evidence +-- PASS: Removed creator-visible Create Project from the right-side Next Steps area; no disabled `data-idea-board-create-project` panel remains. +-- PASS: Create Project is shown only in a Ready idea row's Actions column. +-- PASS: Clicking Create Project changes the idea status from Ready to Project and replaces Create Project/Delete with Open Project and Archive. +-- PASS: Project rows show Open Project and Archive and do not show Delete. +-- PASS: Added Archived status; archived rows are hidden by default. +-- PASS: Archived rows show Restore and Delete. +-- PASS: Restore returns archived ideas to their previous non-archived status, with Refining fallback in runtime. +-- PASS: Delete is guarded so Project ideas cannot be deleted unless archived first. +-- PASS: Status dropdown uses New, Exploring, Refining, Ready, Project, and Archived. +-- PASS: Added compact Show checkbox dropdown beside the visible table description line, with default statuses New, Exploring, Refining, Ready, and Project selected and Archived unselected. +-- PASS: Show dropdown supports any checkbox combination plus Select All and Clear All. +-- PASS: Updated creator-facing copy to "Capture, compare, and shape game ideas." and "Scan, compare, and update early ideas." +-- PASS: Replaced creator-visible Tool Display Mode navigation error with safe copy that does not mention server/API/local server/port/implementation details. +-- PASS: Chevron renders to the left of the Idea text, inline, same size, same color, and on the same text line; the whole Idea cell remains the expansion target. +-- PASS: Notes count remains informational only. +-- PASS: Table-first structure, inline Add Idea/Add Note, row-level Save/Cancel, status dropdown edit mode, notes indentation, and single-open accordion behavior remain covered. ++## Changed Files ++- src/engine/audio/TextToSpeechEngine.js ++- toolbox/text-to-speech/index.html ++- toolbox/text-to-speech/text2speech.js ++- tests/playwright/tools/TextToSpeechFunctional.spec.mjs ++- docs_build/dev/reports/coverage_changed_js_guardrail.txt ++- docs_build/dev/reports/playwright_v8_coverage_report.txt ++- docs_build/dev/reports/PR_26171_061-text2speech-engine-audio-feature-parity.md ++- docs_build/dev/reports/PR_26171_061-instruction-compliance-checklist.md ++- docs_build/dev/reports/PR_26171_061-old-tts-feature-parity-checklist.md ++- docs_build/dev/reports/PR_26171_061-engine-audio-ownership-checklist.md ++- docs_build/dev/reports/PR_26171_061-validation.md ++- docs_build/dev/reports/PR_26171_061-manual-validation-notes.md ++- docs_build/dev/reports/codex_review.diff ++- docs_build/dev/reports/codex_changed_files.txt + ## Validation --- PASS: `git diff --check` after conflict resolution. --- PASS: targeted required instruction-anchor validation after conflict resolution. --- SKIP: Playwright, because the PR explicitly requires no Playwright and changes docs/workflow only. -+- PASS: `node --check toolbox/idea-board/index.js`. -+- PASS: `node --check assets/theme-v2/js/tool-display-mode.js`. -+- PASS: `node --check src/shared/toolbox/tool-metadata-inventory.js`. -+- PASS: `node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs`. -+- PASS: `node --check tests/playwright/tools/ToolboxRoutePages.spec.mjs`. -+- PASS: `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line --timeout=90000`. -+- PASS: `npx playwright test tests/playwright/tools/ToolboxRoutePages.spec.mjs --project=playwright --workers=1 --reporter=line -g "Idea Board launches" --timeout=90000`. -+- PASS: `npm run test:workspace-v2` (workspace-contract lane, 5 passed). -+- PASS: `git diff --check`. -+- PASS: Post-conflict rerun completed after merging `origin/main` at `0a364f85516aea82e38eea2f498fde5980f9adf9`. -+- PASS: Playwright V8 coverage report produced because runtime JavaScript changed. -+- WARN: Coverage report marks `src/shared/toolbox/tool-metadata-inventory.js` as not collected by browser V8 coverage; advisory only per project instructions. -+- SKIPPED: Full samples smoke was not run per request. +-- PASS: `node --check toolbox/idea-board/index.js`. +-- PASS: `node --check assets/theme-v2/js/tool-display-mode.js`. +-- PASS: `node --check src/shared/toolbox/tool-metadata-inventory.js`. +-- PASS: `node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs`. +-- PASS: `node --check tests/playwright/tools/ToolboxRoutePages.spec.mjs`. +-- PASS: `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line --timeout=90000`. +-- PASS: `npx playwright test tests/playwright/tools/ToolboxRoutePages.spec.mjs --project=playwright --workers=1 --reporter=line -g "Idea Board launches" --timeout=90000`. +-- PASS: `npm run test:workspace-v2` (workspace-contract lane, 5 passed). ++- PASS: `node --check src\engine\audio\TextToSpeechEngine.js`. ++- PASS: `node --check toolbox\text-to-speech\text2speech.js`. ++- PASS: `node --test tests\tools\Text2SpeechShell.test.mjs`. ++- PASS: `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs`. ++- PASS: `npm run test:workspace-v2` (legacy command name; user-facing language is Project Workspace). + - PASS: `git diff --check`. +-- PASS: Post-conflict rerun completed after merging `origin/main` at `0a364f85516aea82e38eea2f498fde5980f9adf9`. +-- PASS: Playwright V8 coverage report produced because runtime JavaScript changed. +-- WARN: Coverage report marks `src/shared/toolbox/tool-metadata-inventory.js` as not collected by browser V8 coverage; advisory only per project instructions. +-- SKIPPED: Full samples smoke was not run per request. ++- PASS: Post-conflict validation rerun after merging `origin/main` into the PR branch. ## ZIP --- Path: `tmp/PR_26171_063-codex-instruction-enforcement-hardening_delta.zip`. -+- Path: `tmp/PR_26171_041-idea-board-production-polish_delta.zip`. -+- Size: final size reported in the delivery summary after conflict-resolution ZIP refresh. -+- Contents: -+ - assets/theme-v2/css/tables.css -+ - assets/theme-v2/js/tool-display-mode.js -+ - docs_build/dev/reports/codex_changed_files.txt -+ - docs_build/dev/reports/codex_review.diff -+ - docs_build/dev/reports/coverage_changed_js_guardrail.txt -+ - docs_build/dev/reports/playwright_v8_coverage_report.txt -+ - src/shared/toolbox/tool-metadata-inventory.js -+ - tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -+ - tests/playwright/tools/ToolboxRoutePages.spec.mjs -+ - toolbox/idea-board/index.html -+ - toolbox/idea-board/index.js +-- Path: `tmp/PR_26171_041-idea-board-production-polish_delta.zip`. +-- Size: final size reported in the delivery summary after conflict-resolution ZIP refresh. +-- Contents: +- - assets/theme-v2/css/tables.css +- - assets/theme-v2/js/tool-display-mode.js +- - docs_build/dev/reports/codex_changed_files.txt +- - docs_build/dev/reports/codex_review.diff +- - docs_build/dev/reports/coverage_changed_js_guardrail.txt +- - docs_build/dev/reports/playwright_v8_coverage_report.txt +- - src/shared/toolbox/tool-metadata-inventory.js +- - tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +- - tests/playwright/tools/ToolboxRoutePages.spec.mjs +- - toolbox/idea-board/index.html +- - toolbox/idea-board/index.js ++- Path: `tmp/PR_26171_061-text2speech-engine-audio-feature-parity_delta.zip`. diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt -index 076d27690..66dd0cb12 100644 +index 66dd0cb12..807990e9f 100644 --- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt +++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt -@@ -6,10 +6,9 @@ Missing changed runtime JS files are WARN, not FAIL. +@@ -6,9 +6,8 @@ Missing changed runtime JS files are WARN, not FAIL. Source: Playwright/Chromium built-in V8 coverage from the active Playwright run. Changed runtime JS files considered: --(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only - (0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only --(70%) toolbox/idea-board/index.js - executed lines 480/480; executed functions 23/33 -+(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 9/14 -+(74%) toolbox/idea-board/index.js - executed lines 640/640; executed functions 37/50 +-(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only +-(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 9/14 +-(74%) toolbox/idea-board/index.js - executed lines 640/640; executed functions 37/50 ++(71%) src/engine/audio/TextToSpeechEngine.js - executed lines 412/412; executed functions 37/52 ++(71%) toolbox/text-to-speech/text2speech.js - executed lines 835/835; executed functions 61/86 Guardrail warnings: --(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file missing from coverage; advisory only - (0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file missing from coverage; advisory only +-(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file missing from coverage; advisory only ++(100%) none - no changed runtime JS coverage warnings diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt -index cb35b4d50..4a9f71ff1 100644 +index 4a9f71ff1..dd573eabd 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt -@@ -17,9 +17,9 @@ Exercised tool entry points detected: - (63%) Theme V2 Shared JS - exercised 2 runtime JS files +@@ -12,34 +12,28 @@ Note: entry percentages use function coverage when available, otherwise line cov + Note: coverage entries are aggregated across every page/tool where coverageReporter.start(page) and coverageReporter.stop(page) ran. + + Exercised tool entry points detected: +-(78%) Toolbox Index - exercised 4 runtime JS files ++(72%) Toolbox Index - exercised 2 runtime JS files + (0%) Tool Template V2 - not exercised by this Playwright run +-(63%) Theme V2 Shared JS - exercised 2 runtime JS files ++(56%) Theme V2 Shared JS - exercised 2 runtime JS files Changed runtime JS files covered: --(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only - (0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only --(70%) toolbox/idea-board/index.js - executed lines 480/480; executed functions 23/33 -+(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 9/14 -+(74%) toolbox/idea-board/index.js - executed lines 640/640; executed functions 37/50 +-(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only +-(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 9/14 +-(74%) toolbox/idea-board/index.js - executed lines 640/640; executed functions 37/50 ++(71%) src/engine/audio/TextToSpeechEngine.js - executed lines 412/412; executed functions 37/52 ++(71%) toolbox/text-to-speech/text2speech.js - executed lines 835/835; executed functions 61/86 Files with executed line/function counts where available: - (25%) src/api/session-api-client.js - executed lines 68/68; executed functions 3/12 -@@ -30,17 +30,16 @@ Files with executed line/function counts where available: - (65%) src/api/public-config-client.js - executed lines 209/209; executed functions 17/26 - (67%) src/api/game-journey-completion-api-client.js - executed lines 15/15; executed functions 2/3 - (67%) toolbox/game-workspace/game-workspace-api-client.js - executed lines 20/20; executed functions 2/3 --(70%) toolbox/idea-board/index.js - executed lines 480/480; executed functions 23/33 -+(74%) toolbox/idea-board/index.js - executed lines 640/640; executed functions 37/50 - (78%) toolbox/tools-page-accordions.js - executed lines 1156/1156; executed functions 87/111 - (86%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 25/29 +-(25%) src/api/session-api-client.js - executed lines 68/68; executed functions 3/12 +-(33%) src/api/toolbox-votes-api-client.js - executed lines 46/46; executed functions 2/6 +-(44%) src/api/server-api-client.js - executed lines 167/167; executed functions 8/18 +-(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 977/977; executed functions 54/86 ++(36%) src/api/server-api-client.js - executed lines 167/167; executed functions 5/14 ++(38%) src/api/public-config-client.js - executed lines 209/209; executed functions 10/26 ++(54%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 977/977; executed functions 46/85 + (64%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 9/14 +-(65%) src/api/public-config-client.js - executed lines 209/209; executed functions 17/26 +-(67%) src/api/game-journey-completion-api-client.js - executed lines 15/15; executed functions 2/3 +-(67%) toolbox/game-workspace/game-workspace-api-client.js - executed lines 20/20; executed functions 2/3 +-(74%) toolbox/idea-board/index.js - executed lines 640/640; executed functions 37/50 +-(78%) toolbox/tools-page-accordions.js - executed lines 1156/1156; executed functions 87/111 +-(86%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 25/29 ++(71%) src/engine/audio/TextToSpeechEngine.js - executed lines 412/412; executed functions 37/52 ++(71%) toolbox/text-to-speech/text2speech.js - executed lines 835/835; executed functions 61/86 ++(76%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 22/29 ++(100%) src/engine/audio/TextToSpeechDefaults.js - executed lines 108/108; executed functions 1/1 Uncovered or low-coverage changed JS files: --(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: uncovered changed runtime JS file; advisory only - (0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: uncovered changed runtime JS file; advisory only +-(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: uncovered changed runtime JS file; advisory only ++(100%) none - no low-coverage changed runtime JS files Changed JS files considered: --(0%) src/dev-runtime/server/local-api-router.mjs - changed JS file not collected as browser runtime coverage - (0%) src/shared/toolbox/tool-metadata-inventory.js - changed JS file not collected as browser runtime coverage - (0%) tests/playwright/tools/IdeaBoardTableNotes.spec.mjs - changed JS file not collected as browser runtime coverage - (0%) tests/playwright/tools/ToolboxRoutePages.spec.mjs - changed JS file not collected as browser runtime coverage --(70%) toolbox/idea-board/index.js - changed JS file with browser V8 coverage -+(64%) assets/theme-v2/js/tool-display-mode.js - changed JS file with browser V8 coverage -+(74%) toolbox/idea-board/index.js - changed JS file with browser V8 coverage -diff --git a/src/shared/toolbox/tool-metadata-inventory.js b/src/shared/toolbox/tool-metadata-inventory.js -index db04eac5b..6d2ed68fa 100644 ---- a/src/shared/toolbox/tool-metadata-inventory.js -+++ b/src/shared/toolbox/tool-metadata-inventory.js -@@ -72,14 +72,14 @@ export const TOOL_REGISTRY = Object.freeze([ - "id": "idea-board", - "name": "Idea Board", - "displayName": "Idea Board", -- "shortDescription": "Capture, compare, and shape game ideas in a table.", -+ "shortDescription": "Capture, compare, and shape game ideas.", - "shortLabel": "Idea Board", - "path": "idea-board", - "folderName": "idea-board", - "entryPoint": "idea-board/index.html", - "badge": "/assets/theme-v2/images/badges/game-design.png", - "tool": "/assets/theme-v2/images/tools/game-design.png", -- "description": "Capture, compare, and shape game ideas with notes under each idea row.", -+ "description": "Capture, compare, and shape game ideas.", - "category": "Idea", - "colorGroup": "tool-group-idea", - "active": true, -@@ -93,7 +93,7 @@ export const TOOL_REGISTRY = Object.freeze([ - "progressChecklist": [ - "Idea table workflow visible", - "Add Idea and Add Note actions remain inline", -- "Create Project remains unavailable until an idea is ready" -+ "Ready ideas show project actions in their row" - ], - "deferred": false, - "hidden": false, -diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -index f5544ca88..38085a4d2 100644 ---- a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -+++ b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -@@ -13,10 +13,13 @@ function restoreEnvValue(key, value) { - async function expectIdeaChevron(page, ideaId, iconName) { - const metrics = await page.locator(`[data-idea-board-idea-row='${ideaId}'] th`).evaluate((cell, targetIdeaId) => { - const label = cell.querySelector(".idea-board-idea-label"); -+ const text = label.querySelector(".idea-board-idea-label__text"); - const icon = cell.querySelector(`[data-idea-board-chevron='${targetIdeaId}']`); - const cellStyles = getComputedStyle(cell); - const labelStyles = getComputedStyle(label); - const iconStyles = getComputedStyle(icon); -+ const textRect = text.getBoundingClientRect(); -+ const iconRect = icon.getBoundingClientRect(); - return { - iconName: icon.dataset.ideaBoardChevronIcon, - labelDisplay: labelStyles.display, -@@ -24,7 +27,11 @@ async function expectIdeaChevron(page, ideaId, iconName) { - iconHeight: Number.parseFloat(iconStyles.height), - fontSize: Number.parseFloat(cellStyles.fontSize), - iconColor: iconStyles.backgroundColor, -+ iconBottom: iconRect.bottom, -+ iconLeft: iconRect.left, - textColor: cellStyles.color, -+ textBottom: textRect.bottom, -+ textLeft: textRect.left, - maskImage: iconStyles.getPropertyValue("-webkit-mask-image") || iconStyles.maskImage, - }; - }, ideaId); -@@ -33,6 +40,8 @@ async function expectIdeaChevron(page, ideaId, iconName) { - expect(Math.abs(metrics.iconWidth - metrics.fontSize)).toBeLessThanOrEqual(1); - expect(Math.abs(metrics.iconHeight - metrics.fontSize)).toBeLessThanOrEqual(1); - expect(metrics.iconColor).toBe(metrics.textColor); -+ expect(metrics.iconLeft).toBeLessThan(metrics.textLeft); -+ expect(Math.abs(metrics.iconBottom - metrics.textBottom)).toBeLessThanOrEqual(2); - expect(metrics.maskImage).toContain(iconName); - } +-(0%) src/shared/toolbox/tool-metadata-inventory.js - changed JS file not collected as browser runtime coverage +-(0%) tests/playwright/tools/IdeaBoardTableNotes.spec.mjs - changed JS file not collected as browser runtime coverage +-(0%) tests/playwright/tools/ToolboxRoutePages.spec.mjs - changed JS file not collected as browser runtime coverage +-(64%) assets/theme-v2/js/tool-display-mode.js - changed JS file with browser V8 coverage +-(74%) toolbox/idea-board/index.js - changed JS file with browser V8 coverage ++(0%) tests/playwright/tools/TextToSpeechFunctional.spec.mjs - changed JS file not collected as browser runtime coverage ++(71%) src/engine/audio/TextToSpeechEngine.js - changed JS file with browser V8 coverage ++(71%) toolbox/text-to-speech/text2speech.js - changed JS file with browser V8 coverage +diff --git a/src/engine/audio/TextToSpeechEngine.js b/src/engine/audio/TextToSpeechEngine.js +index c8e1dced3..a5f3ccabd 100644 +--- a/src/engine/audio/TextToSpeechEngine.js ++++ b/src/engine/audio/TextToSpeechEngine.js +@@ -1,7 +1,10 @@ + import { ++ TEXT_TO_SPEECH_CHARACTER_PRESET_DEFAULTS, + TEXT_TO_SPEECH_DEFAULTS, + TEXT_TO_SPEECH_DISPLAY_NAME, +- TEXT_TO_SPEECH_RANGE_DEFAULTS ++ TEXT_TO_SPEECH_RANGE_DEFAULTS, ++ TEXT_TO_SPEECH_SSML_LIKE_PRESET_DEFAULTS, ++ TEXT_TO_SPEECH_VOICE_AGE_PRESET_DEFAULTS + } from "./TextToSpeechDefaults.js"; -@@ -92,7 +101,7 @@ async function expectExpandedNotesChildIndentation(page, ideaId, expectedInputRo - - async function expectProductionCopy(page) { - await expect(page.locator("main")).not.toContainText( -- /\bDB-shaped\b|\bin-page data model\b|\buserId\b|\bideaId\b|\bnoteId\b|\bsystem flag\b|\bmetadata\b|\bseed\b|\bdebug\b|\bselected context\b|\bmock\b|\btest\b|\binternal implementation\b|\bplaceholder\b|\bproject records\b|\bmutating API\b|\bauth\b|\bAI\b|\bdatabase behavior\b/i, -+ /\bDB-shaped\b|\bin-page data model\b|\buserId\b|\bideaId\b|\bnoteId\b|\bsystem flag\b|\bmetadata\b|\bseed\b|\bdebug\b|\bselected context\b|\bmock\b|\btest\b|\binternal implementation\b|\bplaceholder\b|\bproject records\b|\bmutating API\b|\bserver\b|\bAPI\b|\blocal server\b|\bport\b|\bunderlying systems\b|\bauth\b|\bAI\b|\bdatabase behavior\b/i, - ); + function finiteNumber(value, fallback) { +@@ -9,8 +12,180 @@ function finiteNumber(value, fallback) { + return Number.isFinite(number) ? number : fallback; } -@@ -141,6 +150,28 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { - await expect(page.locator("[data-idea-board-add-idea-row]")).toHaveCount(1); - await expect(page.locator("[data-idea-board-add-idea]")).toHaveText("Add Idea"); - await expectButtonLeftAligned(page, "[data-idea-board-add-idea]", "[data-idea-board-add-idea-row] > td"); -+ await expect(page.locator("[data-idea-board-show-filter] summary")).toHaveText("Show"); -+ const captionMetrics = await page.locator(".idea-board-table-caption").evaluate((caption) => { -+ const label = caption.querySelector("span"); -+ const filter = caption.querySelector("[data-idea-board-show-filter]"); -+ const labelRect = label.getBoundingClientRect(); -+ const filterRect = filter.getBoundingClientRect(); -+ return { -+ filterRight: filterRect.right, -+ filterTop: filterRect.top, -+ labelRight: labelRect.right, -+ labelTop: labelRect.top, -+ }; -+ }); -+ expect(captionMetrics.filterRight).toBeGreaterThan(captionMetrics.labelRight); -+ expect(Math.abs(captionMetrics.filterTop - captionMetrics.labelTop)).toBeLessThanOrEqual(4); -+ await page.locator("[data-idea-board-show-filter] summary").click(); -+ await expect(page.locator("[data-idea-board-status-filter-option]")).toHaveCount(6); -+ const checkedStatuses = await page.locator("[data-idea-board-status-filter-option]:checked").evaluateAll((inputs) => ( -+ inputs.map((input) => input.value) -+ )); -+ expect(checkedStatuses).toEqual(["New", "Exploring", "Refining", "Ready", "Project"]); -+ await expect(page.locator("[data-idea-board-status-filter-option][value='Archived']")).not.toBeChecked(); - await expect(page.getByText(/another/i)).toHaveCount(0); - await expect(page.locator("[data-idea-board-notes-chevron]")).toHaveCount(0); - await expect(page.getByText("Selected idea context")).toHaveCount(0); -@@ -223,11 +254,19 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { - const ideaInputRow = page.locator("[data-idea-board-idea-input-row]").last(); - await expect(ideaInputRow.locator("[data-idea-board-idea-action]")).toHaveText(["Save", "Cancel"]); - await expect(ideaInputRow.locator("[data-idea-board-idea-status-input]")).toHaveCount(1); -+ await expect(ideaInputRow.locator("[data-idea-board-idea-status-input] option")).toHaveText([ -+ "New", -+ "Exploring", -+ "Refining", -+ "Ready", -+ "Project", -+ "Archived", -+ ]); - await expect(ideaInputRow.locator("td").nth(2)).toHaveText(/\d{4}-\d{2}-\d{2}/); - await expect(ideaInputRow.locator("td").nth(3)).toHaveText("0 Notes"); - await page.locator("[data-idea-board-idea-input]").fill("Lantern Reef"); - await page.locator("[data-idea-board-pitch-input]").fill("Guide light through a reef that rearranges at dusk."); -- await page.locator("[data-idea-board-idea-status-input]").selectOption("Parked"); -+ await page.locator("[data-idea-board-idea-status-input]").selectOption("Refining"); - await page.locator("[data-idea-board-idea-action='save']").click(); - await expect(page.locator("[data-idea-board-idea-row='lantern-reef']")).toBeVisible(); - await expect(page.locator("[data-idea-board-notes-count='lantern-reef']")).toHaveText("0 Notes"); -@@ -236,9 +275,33 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { - await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='edit']").click(); - await expect(page.locator("[data-idea-board-idea-input-row] [data-idea-board-idea-action]")).toHaveText(["Save", "Cancel"]); - await expect(page.locator("[data-idea-board-idea-status-input]")).toHaveCount(1); -- await page.locator("[data-idea-board-idea-status-input]").selectOption("Ready to Shape"); -+ await page.locator("[data-idea-board-idea-status-input]").selectOption("Ready"); - await page.locator("[data-idea-board-idea-action='save']").click(); -- await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] td").nth(1)).toHaveText("Ready to Shape"); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] td").nth(1)).toHaveText("Ready"); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Create Project", "Delete"]); -+ await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='create-project']").click(); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] td").nth(1)).toHaveText("Project"); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Open Project", "Archive"]); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='delete']")).toHaveCount(0); -+ await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='open-project']").click(); -+ await expect(page.locator("[data-idea-board-status]")).toHaveText("Opening Lantern Reef."); -+ await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='archive']").click(); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef']")).toHaveCount(0); -+ await page.locator("[data-idea-board-status-filter-option][value='Archived']").check(); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef']")).toBeVisible(); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] td").nth(1)).toHaveText("Archived"); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action]")).toHaveText(["Restore", "Delete"]); -+ await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='restore']").click(); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] td").nth(1)).toHaveText("Project"); -+ await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='archive']").click(); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action]")).toHaveText(["Restore", "Delete"]); -+ await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='delete']").click(); -+ await expect(page.locator("[data-idea-board-idea-row='lantern-reef']")).toHaveCount(0); -+ await page.locator("[data-idea-board-filter-clear-all]").click(); -+ await expect(page.locator("[data-idea-board-idea-row]")).toHaveCount(0); -+ await expect(page.locator("[data-idea-board-add-idea]")).toBeVisible(); -+ await page.locator("[data-idea-board-filter-select-all]").click(); -+ await expect(page.locator("[data-idea-board-idea-row]")).toHaveCount(3); - - expect(mutatingApiRequests).toEqual([]); - expect(failedRequests).toEqual([]); -diff --git a/tests/playwright/tools/ToolboxRoutePages.spec.mjs b/tests/playwright/tools/ToolboxRoutePages.spec.mjs -index b3033e10d..9f40a19ad 100644 ---- a/tests/playwright/tools/ToolboxRoutePages.spec.mjs -+++ b/tests/playwright/tools/ToolboxRoutePages.spec.mjs -@@ -115,10 +115,13 @@ function restoreEnvValue(key, value) { - async function expectIdeaChevron(page, ideaId, iconName) { - const metrics = await page.locator(`[data-idea-board-idea-row='${ideaId}'] th`).evaluate((cell, targetIdeaId) => { - const label = cell.querySelector(".idea-board-idea-label"); -+ const text = label.querySelector(".idea-board-idea-label__text"); - const icon = cell.querySelector(`[data-idea-board-chevron='${targetIdeaId}']`); - const cellStyles = getComputedStyle(cell); - const labelStyles = getComputedStyle(label); - const iconStyles = getComputedStyle(icon); -+ const textRect = text.getBoundingClientRect(); -+ const iconRect = icon.getBoundingClientRect(); - return { - iconName: icon.dataset.ideaBoardChevronIcon, - labelDisplay: labelStyles.display, -@@ -126,7 +129,11 @@ async function expectIdeaChevron(page, ideaId, iconName) { - iconHeight: Number.parseFloat(iconStyles.height), - fontSize: Number.parseFloat(cellStyles.fontSize), - iconColor: iconStyles.backgroundColor, -+ iconBottom: iconRect.bottom, -+ iconLeft: iconRect.left, - textColor: cellStyles.color, -+ textBottom: textRect.bottom, -+ textLeft: textRect.left, - maskImage: iconStyles.getPropertyValue("-webkit-mask-image") || iconStyles.maskImage, - }; - }, ideaId); -@@ -135,6 +142,8 @@ async function expectIdeaChevron(page, ideaId, iconName) { - expect(Math.abs(metrics.iconWidth - metrics.fontSize)).toBeLessThanOrEqual(1); - expect(Math.abs(metrics.iconHeight - metrics.fontSize)).toBeLessThanOrEqual(1); - expect(metrics.iconColor).toBe(metrics.textColor); -+ expect(metrics.iconLeft).toBeLessThan(metrics.textLeft); -+ expect(Math.abs(metrics.iconBottom - metrics.textBottom)).toBeLessThanOrEqual(2); - expect(metrics.maskImage).toContain(iconName); +-function boundedNumber(value, { fallback, max, min }) { +- return Math.min(max, Math.max(min, finiteNumber(value, fallback))); ++function boundedNumber(value, { fallback, max, min, value: defaultValue }) { ++ const fallbackValue = fallback ?? defaultValue ?? min; ++ return Math.min(max, Math.max(min, finiteNumber(value, fallbackValue))); ++} ++ ++function shapedNumber(value, { fallback, max, min, step }) { ++ const boundedValue = boundedNumber(value, { fallback, max, min }); ++ const stepText = String(step || "1"); ++ const decimalPart = stepText.includes(".") ? stepText.split(".")[1] : ""; ++ const multiplier = 10 ** decimalPart.length; ++ return Math.round(boundedValue * multiplier) / multiplier; ++} ++ ++function textToSpeechSlugFromName(name) { ++ const slug = String(name || "") ++ .trim() ++ .toLowerCase() ++ .replace(/[^a-z0-9]+/g, "-") ++ .replace(/^-+|-+$/g, ""); ++ return slug || "speech-item"; ++} ++ ++function textToSpeechVoiceGender(option) { ++ const voiceText = `${option?.gender || ""} ${option?.name || ""} ${option?.label || ""}`; ++ const voiceLanguage = String(option?.language || "").toLowerCase(); ++ if (/\bneutral\b|\bnon[-\s]?binary\b|\bandrogynous\b/i.test(voiceText)) return "neutral"; ++ if (voiceLanguage === "es-es" || /\bmale\b|\bman\b|\bdavid\b|\bmark\b/i.test(voiceText)) return "male"; ++ if (/\bfemale\b|\bwoman\b|\bzira\b/i.test(voiceText)) return "female"; ++ return "unknown"; ++} ++ ++function genderFilterLabel(value) { ++ if (value === "male-preferred") return "Male"; ++ if (value === "female-preferred") return "Female"; ++ if (value === "neutral") return "Neutral"; ++ return "Any"; ++} ++ ++function ageFilterLabel(value) { ++ if (value === "adult") return "Adult"; ++ if (value === "child") return "Child"; ++ if (value === "elderly") return "Elderly"; ++ if (value === "teen") return "Teen"; ++ return "Any"; ++} ++ ++function payloadGenderValue(value) { ++ return value === "neutral" ? "any" : value; ++} ++ ++function filterTextToSpeechVoicesByGender(voiceOptions, genderFilter = "any") { ++ if (genderFilter === "any") return voiceOptions; ++ if (genderFilter === "male-preferred") { ++ return voiceOptions.filter((option) => textToSpeechVoiceGender(option) === "male"); ++ } ++ if (genderFilter === "female-preferred") { ++ return voiceOptions.filter((option) => textToSpeechVoiceGender(option) === "female"); ++ } ++ if (genderFilter === "neutral") { ++ return voiceOptions.filter((option) => ["neutral", "unknown"].includes(textToSpeechVoiceGender(option))); ++ } ++ return voiceOptions; ++} ++ ++function optionLabelCompare(left, right) { ++ return String(left.label).localeCompare(String(right.label), undefined, { ++ numeric: true, ++ sensitivity: "base" ++ }); ++} ++ ++function textToSpeechLanguageOptionsFromVoices(voiceOptions) { ++ const languageCounts = new Map(); ++ voiceOptions.forEach((option) => { ++ const language = String(option.language || "").trim(); ++ if (language) { ++ languageCounts.set(language, (languageCounts.get(language) || 0) + 1); ++ } ++ }); ++ return Array.from(languageCounts.entries()) ++ .map(([language, count]) => ({ ++ label: `${language} (${count} ${count === 1 ? "voice" : "voices"})`, ++ value: language ++ })) ++ .sort(optionLabelCompare); ++} ++ ++function filterTextToSpeechVoiceOptions(voiceOptions, { gender = "any", language = "" } = {}) { ++ const filteredByGender = filterTextToSpeechVoicesByGender(voiceOptions, gender); ++ const matchingVoices = filteredByGender ++ .filter((option) => !language || option.language === language) ++ .sort(optionLabelCompare); ++ return { ++ filteredVoiceCount: filteredByGender.length, ++ gender, ++ genderLabel: genderFilterLabel(gender), ++ language, ++ languageOptions: textToSpeechLanguageOptionsFromVoices(filteredByGender), ++ matchingVoices, ++ voiceCount: voiceOptions.length ++ }; ++} ++ ++function shapeTextToSpeechOptions({ ++ characterPreset = TEXT_TO_SPEECH_DEFAULTS.characterPreset, ++ pitch = TEXT_TO_SPEECH_DEFAULTS.pitch, ++ rate = TEXT_TO_SPEECH_DEFAULTS.rate, ++ ssmlLikePreset = TEXT_TO_SPEECH_DEFAULTS.ssmlLikePreset, ++ voiceAge = TEXT_TO_SPEECH_DEFAULTS.voiceAge, ++ volume = TEXT_TO_SPEECH_DEFAULTS.volume ++} = {}, sliderOverrides = {}) { ++ const character = TEXT_TO_SPEECH_CHARACTER_PRESET_DEFAULTS[characterPreset] || TEXT_TO_SPEECH_CHARACTER_PRESET_DEFAULTS.manual; ++ const age = TEXT_TO_SPEECH_VOICE_AGE_PRESET_DEFAULTS[voiceAge] || TEXT_TO_SPEECH_VOICE_AGE_PRESET_DEFAULTS.any; ++ const ssmlLike = TEXT_TO_SPEECH_SSML_LIKE_PRESET_DEFAULTS[ssmlLikePreset] || TEXT_TO_SPEECH_SSML_LIKE_PRESET_DEFAULTS.normal; ++ return { ++ characterPreset, ++ pitch: sliderOverrides.pitch ++ ? boundedNumber(pitch, TEXT_TO_SPEECH_RANGE_DEFAULTS.pitch) ++ : shapedNumber(Number(character.pitch) + Number(age.pitchOffset) + Number(ssmlLike.pitchOffset), TEXT_TO_SPEECH_RANGE_DEFAULTS.pitch), ++ rate: sliderOverrides.rate ++ ? boundedNumber(rate, TEXT_TO_SPEECH_RANGE_DEFAULTS.rate) ++ : shapedNumber(Number(character.rate) * Number(age.rateMultiplier) * Number(ssmlLike.rateMultiplier), TEXT_TO_SPEECH_RANGE_DEFAULTS.rate), ++ ssmlLikePreset, ++ voiceAge, ++ volume: sliderOverrides.volume ++ ? boundedNumber(volume, TEXT_TO_SPEECH_RANGE_DEFAULTS.volume) ++ : shapedNumber(Number(character.volume) * Number(ssmlLike.volumeMultiplier), TEXT_TO_SPEECH_RANGE_DEFAULTS.volume) ++ }; ++} ++ ++function uniqueTextToSpeechName(baseName, existingItems = []) { ++ const requestedName = String(baseName || "").trim() || "New speech item"; ++ const existingNames = new Set(existingItems.map((item) => item.name)); ++ if (!existingNames.has(requestedName)) return requestedName; ++ for (let index = 2; index < 1000; index += 1) { ++ const candidate = `${requestedName} ${index}`; ++ if (!existingNames.has(candidate)) return candidate; ++ } ++ return `${requestedName} ${Date.now().toString(36)}`; ++} ++ ++function uniqueTextToSpeechId(baseName, existingItems = []) { ++ const existingIds = new Set(existingItems.map((item) => item.id)); ++ const baseId = textToSpeechSlugFromName(baseName); ++ if (!existingIds.has(baseId)) return baseId; ++ for (let index = 2; index < 1000; index += 1) { ++ const candidate = `${baseId}-${index}`; ++ if (!existingIds.has(candidate)) return candidate; ++ } ++ return `${baseId}-${Date.now().toString(36)}`; ++} ++ ++function createTextToSpeechQueueItem({ ++ existingItems = [], ++ id = "", ++ name = "", ++ text = "", ++ ...options ++} = {}) { ++ const itemName = uniqueTextToSpeechName(name, id ? [] : existingItems); ++ return { ++ characterPreset: options.characterPreset || TEXT_TO_SPEECH_DEFAULTS.characterPreset, ++ gender: payloadGenderValue(options.gender || TEXT_TO_SPEECH_DEFAULTS.gender), ++ id: id || uniqueTextToSpeechId(itemName, existingItems), ++ language: options.language || TEXT_TO_SPEECH_DEFAULTS.language, ++ name: itemName, ++ pitch: boundedNumber(options.pitch, TEXT_TO_SPEECH_RANGE_DEFAULTS.pitch), ++ rate: boundedNumber(options.rate, TEXT_TO_SPEECH_RANGE_DEFAULTS.rate), ++ ssmlLikePreset: options.ssmlLikePreset || TEXT_TO_SPEECH_DEFAULTS.ssmlLikePreset, ++ text: String(text || "").trim() || "New speech line.", ++ voice: String(options.voice || ""), ++ voiceAge: options.voiceAge || TEXT_TO_SPEECH_DEFAULTS.voiceAge, ++ volume: boundedNumber(options.volume, TEXT_TO_SPEECH_RANGE_DEFAULTS.volume) ++ }; } -@@ -194,7 +203,7 @@ async function expectExpandedNotesChildIndentation(page, ideaId, expectedInputRo + class TextToSpeechEngine { +@@ -58,6 +233,18 @@ class TextToSpeechEngine { + })); + } - async function expectIdeaBoardProductionCopy(page) { - await expect(page.locator("main")).not.toContainText( -- /\bDB-shaped\b|\bin-page data model\b|\buserId\b|\bideaId\b|\bnoteId\b|\bsystem flag\b|\bmetadata\b|\bseed\b|\bdebug\b|\bselected context\b|\bmock\b|\btest\b|\binternal implementation\b|\bplaceholder\b|\bproject records\b|\bmutating API\b|\bauth\b|\bAI\b|\bdatabase behavior\b/i, -+ /\bDB-shaped\b|\bin-page data model\b|\buserId\b|\bideaId\b|\bnoteId\b|\bsystem flag\b|\bmetadata\b|\bseed\b|\bdebug\b|\bselected context\b|\bmock\b|\btest\b|\binternal implementation\b|\bplaceholder\b|\bproject records\b|\bmutating API\b|\bserver\b|\bAPI\b|\blocal server\b|\bport\b|\bunderlying systems\b|\bauth\b|\bAI\b|\bdatabase behavior\b/i, - ); ++ filterVoiceOptions(options = {}) { ++ return filterTextToSpeechVoiceOptions(this.voiceOptions(), options); ++ } ++ ++ shapeOptions(options = {}, sliderOverrides = {}) { ++ return shapeTextToSpeechOptions(options, sliderOverrides); ++ } ++ ++ createQueueItem(options = {}) { ++ return createTextToSpeechQueueItem(options); ++ } ++ + voiceForValue(value) { + const normalizedValue = String(value || "").trim(); + if (!normalizedValue) { +@@ -248,5 +435,18 @@ class TextToSpeechEngine { + } } -@@ -302,7 +311,6 @@ test("Idea Board launches from Toolbox with accordion table notes model", async - "Workflow", - "Status", - "Idea Table", -- "Create Project", - "Notes Governance", - "Diagnostics", - ]); -@@ -335,12 +343,10 @@ test("Idea Board launches from Toolbox with accordion table notes model", async - await expect(page.locator("[data-idea-board-add-note='top-thoughts']")).toBeVisible(); - await expect(page.locator("[data-idea-board-add-note='top-thoughts']")).toHaveText("Add Note"); - await expectExpandedNotesChildIndentation(page, "top-thoughts"); -- await expect(page.locator("[data-idea-board-create-project]")).toBeVisible(); -- await expect(page.locator("[data-idea-board-create-project]")).toBeDisabled(); -+ await expect(page.locator("[data-idea-board-create-project]")).toHaveCount(0); - await expect(page.locator("style, [style], script:not([src])")).toHaveCount(0); - await expect(page.locator("script[src='toolbox/idea-board/index.js']")).toHaveCount(1); - mutatingApiRequests.length = 0; -- await page.locator("[data-idea-board-create-project]").evaluate((button) => button.click()); - await page.locator("[data-idea-board-add-note='top-thoughts']").click(); - await page.locator("[data-idea-board-note-input]").fill("Capture traversal risks before project creation."); - await page.locator("[data-idea-board-note-action='save']").click(); -@@ -348,6 +354,16 @@ test("Idea Board launches from Toolbox with accordion table notes model", async - await page.locator("[data-idea-board-idea-cell='clockwork-courier']").click(); - await expect(page.locator("[data-idea-board-expanded-row='clockwork-courier']")).toBeVisible(); - await expect(page.locator("[data-idea-board-notes-table='clockwork-courier']")).not.toContainText("Capture traversal risks before project creation."); -+ await page.locator("[data-idea-board-add-idea]").click(); -+ await page.locator("[data-idea-board-idea-input]").fill("Launch Tile"); -+ await page.locator("[data-idea-board-pitch-input]").fill("Turn a polished board idea into a project."); -+ await page.locator("[data-idea-board-idea-status-input]").selectOption("Ready"); -+ await page.locator("[data-idea-board-idea-action='save']").click(); -+ await expect(page.locator("[data-idea-board-idea-row='launch-tile'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Create Project", "Delete"]); -+ await page.locator("[data-idea-board-idea-row='launch-tile'] [data-idea-board-idea-action='create-project']").click(); -+ await expect(page.locator("[data-idea-board-idea-row='launch-tile'] td").nth(1)).toHaveText("Project"); -+ await expect(page.locator("[data-idea-board-idea-row='launch-tile'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Open Project", "Archive"]); -+ await expect(page.locator("[data-idea-board-idea-row='launch-tile'] [data-idea-board-idea-action='delete']")).toHaveCount(0); - expect(mutatingApiRequests).toEqual([]); - - expect(failedRequests).toEqual([]); -diff --git a/toolbox/idea-board/index.html b/toolbox/idea-board/index.html -index 214a303fb..422d67b1b 100644 ---- a/toolbox/idea-board/index.html -+++ b/toolbox/idea-board/index.html -@@ -18,7 +18,7 @@ +-export { TextToSpeechEngine }; ++export { ++ ageFilterLabel as textToSpeechAgeFilterLabel, ++ createTextToSpeechQueueItem, ++ filterTextToSpeechVoiceOptions, ++ genderFilterLabel as textToSpeechGenderFilterLabel, ++ payloadGenderValue as textToSpeechPayloadGenderValue, ++ shapeTextToSpeechOptions, ++ textToSpeechLanguageOptionsFromVoices, ++ textToSpeechSlugFromName, ++ textToSpeechVoiceGender, ++ TextToSpeechEngine, ++ uniqueTextToSpeechId, ++ uniqueTextToSpeechName ++}; + export default TextToSpeechEngine; +diff --git a/tests/playwright/tools/TextToSpeechFunctional.spec.mjs b/tests/playwright/tools/TextToSpeechFunctional.spec.mjs +index ccf95e3e3..17e2c0b62 100644 +--- a/tests/playwright/tools/TextToSpeechFunctional.spec.mjs ++++ b/tests/playwright/tools/TextToSpeechFunctional.spec.mjs +@@ -66,7 +66,13 @@ async function openTextToSpeechPage(page, { speechAvailable = true } = {}) { + getVoices() { + return voices; + }, ++ pause() { ++ window.__textToSpeechCalls.push({ type: "pause" }); ++ }, + removeEventListener() {}, ++ resume() { ++ window.__textToSpeechCalls.push({ type: "resume" }); ++ }, + speak(utterance) { + window.__textToSpeechCalls.push({ + lang: utterance.lang, +@@ -101,8 +107,22 @@ 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-engine-label]")).toHaveText("Ready"); ++ await expect(page.locator("[data-tts-gender-select]")).toBeVisible(); ++ await expect(page.locator("[data-tts-language-select]")).toBeVisible(); ++ await expect(page.locator("[data-tts-age-select]")).toBeVisible(); ++ await expect(page.locator("[data-tts-character-preset-select]")).toBeVisible(); ++ await expect(page.locator("[data-tts-ssml-preset-select]")).toBeVisible(); ++ await expect(page.locator("[data-tts-import-json]")).toBeEnabled(); ++ await expect(page.locator("[data-tts-copy-json]")).toBeEnabled(); ++ await expect(page.locator("[data-tts-export-json]")).toBeEnabled(); + + await page.locator("[data-tts-text-input]").fill("Launch the next wave."); ++ await page.locator("[data-tts-item-name]").fill("Wave intro"); ++ await page.locator("[data-tts-character-preset-select]").selectOption("dramatic"); ++ await page.locator("[data-tts-age-select]").selectOption("teen"); ++ await page.locator("[data-tts-ssml-preset-select]").selectOption("whisper-ish"); ++ await expect(page.locator("[data-tts-pitch-value]")).toHaveText("1.1"); ++ await expect(page.locator("[data-tts-volume-value]")).toHaveText("0.6"); + await page.locator("[data-tts-voice-select]").selectOption("arcade-voice-uri"); + await page.locator("[data-tts-rate]").fill("1.4"); + await page.locator("[data-tts-pitch]").fill("0.8"); +@@ -111,6 +131,13 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as + await expect(page.locator("[data-tts-pitch-value]")).toHaveText("0.8"); + await expect(page.locator("[data-tts-volume-value]")).toHaveText("0.55"); + await expect(page.locator("[data-tts-text-count]")).toHaveText("21"); ++ await page.locator("[data-tts-add-item]").click(); ++ await expect(page.locator("[data-tts-queue-list]")).toContainText("Wave intro"); ++ await expect(page.locator("[data-tts-output-summary]")).toContainText("\"name\": \"Wave intro\""); ++ await page.locator("[data-tts-duplicate-item]").click(); ++ await expect(page.locator("[data-tts-queue-list]")).toContainText("Wave intro 2 copy"); ++ await page.locator("[data-tts-delete-item]").click(); ++ await expect(page.locator("[data-tts-queue-list] [data-tts-queue-item]")).toHaveCount(2); + + await expect(page.locator("[data-tts-speak]")).toBeEnabled(); + await page.locator("[data-tts-speak]").click(); +@@ -126,10 +153,13 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as + volume: 0.55, + })); + ++ await page.locator("[data-tts-pause]").click(); ++ await page.locator("[data-tts-resume]").click(); + await page.locator("[data-tts-stop]").click(); + await expect(page.locator("[data-tts-status]")).toContainText("Speech stopped"); + calls = await page.evaluate(() => window.__textToSpeechCalls); + expect(calls.at(-1)).toEqual({ type: "cancel" }); ++ expect(calls).toEqual(expect.arrayContaining([{ type: "pause" }, { type: "resume" }])); + + expect(failures.failedRequests).toEqual([]); + expect(failures.pageErrors).toEqual([]); +diff --git a/toolbox/text-to-speech/index.html b/toolbox/text-to-speech/index.html +index adaaff3a7..f5b29b19a 100644 +--- a/toolbox/text-to-speech/index.html ++++ b/toolbox/text-to-speech/index.html +@@ -6,7 +6,7 @@ + + + Text To Speech - GameFoundryStudio +- ++ + + + +@@ -18,52 +18,100 @@
-
Toolbox
-

Idea Board

--

Capture, compare, and shape game ideas in one table.

-+

Capture, compare, and shape game ideas.

+
Project Workspace / Audio
+

Text To Speech

+-

Preview spoken game text with local browser speech synthesis before deciding whether a generated audio provider is needed.

++

Create named speech lines, shape browser voices, and preview spoken game text through the shared engine audio Text To Speech module.

-@@ -38,7 +38,7 @@ -
- Status +
++ ++ +
+