diff --git a/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-dead-code-cleanup-checklist.md b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-dead-code-cleanup-checklist.md
new file mode 100644
index 000000000..63cdb0e21
--- /dev/null
+++ b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-dead-code-cleanup-checklist.md
@@ -0,0 +1,22 @@
+# PR_26171_BETA_083 Dead-Code Cleanup Checklist
+
+## Message Studio
+- PASS: Removed duplicate preview TTS Profile control outside the table.
+- PASS: Removed duplicate TTS service selector outside the table.
+- PASS: Removed dead Test Speech button and selected-item helper text.
+- PASS: Removed unused Message Studio speech-test state and render paths.
+- PASS: Kept playback routed through selected message part, TTS Profile, and Emotion Setting.
+- PASS: Kept Play Part, Play Message, and Stop Playback controls available.
+
+## TTS Studio
+- PASS: Kept Delivery in Emotion Settings.
+- PASS: Kept Presets in Emotion Settings as Delivery Preset.
+- PASS: Kept Voice Filters in profile controls.
+- PASS: No duplicate summary or output panel was added.
+- PASS: No separate Emotion Studio was added.
+
+## Tests
+- PASS: Removed dead Message speech-test selector expectations.
+- PASS: Added absence checks for removed duplicate controls.
+- PASS: Added actionable error coverage for missing TTS Profile.
+- PASS: Added actionable error coverage for missing Emotion Setting on the selected profile.
diff --git a/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-manual-validation-notes.md
new file mode 100644
index 000000000..02ba719f4
--- /dev/null
+++ b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-manual-validation-notes.md
@@ -0,0 +1,14 @@
+# PR_26171_BETA_083 Manual Validation Notes
+
+## Manual Review
+- Reviewed changed Message Studio markup for removed duplicate controls.
+- Reviewed changed Message Studio JavaScript for removed speech-test state and render paths.
+- Reviewed changed TTS Studio labels for Profile, Age Filter, and Delivery Preset wording.
+- Reviewed Playwright coverage for missing TTS Profile and missing Emotion Setting error states.
+
+## Browser Validation
+- Targeted Playwright validation covered Message Studio and TTS Studio flows.
+- No separate manual browser session was required beyond the automated browser validation.
+
+## Packaging
+- Delta ZIP is required under tmp/ and must not be staged.
diff --git a/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-message-tts-ux-checklist.md b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-message-tts-ux-checklist.md
new file mode 100644
index 000000000..7e1b4eb7f
--- /dev/null
+++ b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-message-tts-ux-checklist.md
@@ -0,0 +1,25 @@
+# PR_26171_BETA_083 Message/TTS UX Checklist
+
+## Message Studio
+- PASS: Messages parent table label is creator-friendly.
+- PASS: Message Parts child table uses Part Text, Emotion, TTS Profile, Status, and Actions.
+- PASS: Play Part label remains clear.
+- PASS: Play Message label remains clear.
+- PASS: Stop control is labeled Stop Playback.
+- PASS: Missing TTS Profile produces a visible actionable error.
+- PASS: Missing Emotion Setting on the selected TTS Profile produces a visible actionable error.
+- PASS: Duplicate TTS/Profile/Emotion controls were not left outside the tables.
+
+## TTS Studio
+- PASS: TTS Profile parent table uses creator-friendly Profile labeling.
+- PASS: Age is clarified as Age Filter.
+- PASS: Emotion Settings child table uses Delivery Preset labeling.
+- PASS: Delivery settings live in Emotion Settings.
+- PASS: Presets live in Emotion Settings.
+- PASS: Voice Filters live only in profile controls.
+- PASS: Duplicate summary/output panels were not introduced.
+
+## Ownership
+- PASS: Message Studio owns text, ordered message parts, Emotion selection, and TTS Profile selection.
+- PASS: TTS Studio owns Voice, Language, Emotion Settings, Delivery, and Presets.
+- PASS: src/engine/audio owns playback.
diff --git a/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-validation-report.md b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-validation-report.md
new file mode 100644
index 000000000..da9be32f3
--- /dev/null
+++ b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-validation-report.md
@@ -0,0 +1,24 @@
+# PR_26171_BETA_083 Validation Report
+
+## Static Checks
+- PASS: node --check toolbox/messages/messages.js
+- PASS: node --check toolbox/text-to-speech/text2speech.js
+- PASS: node --check tests/playwright/tools/MessagesTool.spec.mjs
+- PASS: node --check tests/playwright/tools/TextToSpeechFunctional.spec.mjs
+
+## Targeted Message Studio Validation
+- PASS: npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --reporter=list
+- Result: 3 passed in 38.3s
+
+## Targeted TTS Studio Validation
+- PASS: npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs --reporter=list
+- Result: 3 passed in 53.1s
+
+## Project Workspace Validation
+- PASS: npm run test:workspace-v2
+- Result: 5 passed in 1.7m
+- Note: Command name is legacy; user-facing language is Project Workspace.
+
+## Validation Notes
+- A first parallel Message Studio run timed out while TTS validation was also running. The Message Studio suite passed on the targeted rerun.
+- Generated workspace report churn was restored before PR report files were written.
diff --git a/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code.md b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code.md
new file mode 100644
index 000000000..29a06e3c6
--- /dev/null
+++ b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code.md
@@ -0,0 +1,45 @@
+# PR_26171_BETA_083-message-tts-polish-and-dead-code
+
+## Summary
+- Team: BETA
+- Scope: Message Studio and TTS Studio polish after parent/child table and playback integration.
+- Branch: pr/26171-BETA-083-message-tts-polish-and-dead-code
+- Instruction compliance: PASS
+
+## Ownership Confirmation
+- BETA owns Message Studio work.
+- BETA owns TTS Studio and TextToSpeechEngine integration work.
+- Message Studio owns message text, ordered message parts, Emotion selection, and TTS Profile selection.
+- TTS Studio owns profiles, voices, language, per-profile emotion settings, Delivery, and Presets.
+- src/engine/audio owns playback.
+
+## Changes
+- Removed dead Message Studio speech-test controls and unused selection code.
+- Replaced Message Studio speech-test status with playback readiness/status.
+- Kept Message Studio playback controls visible and clarified Stop as Stop Playback.
+- Updated Message parent and part table labels to be more creator-friendly.
+- Added explicit missing TTS Profile and missing Emotion Setting validation coverage.
+- Clarified TTS Profile table and Emotion Settings table labels.
+- Clarified that TTS presets belong to Emotion Settings as Delivery Preset.
+
+## Architecture Notes
+- Theme V2 only.
+- External JavaScript only.
+- No inline styles, style blocks, inline handlers, page-local CSS, or tool-local CSS were added.
+- No database changes.
+- No separate Emotion Studio was added.
+- Browser-owned product data was not introduced as a source of truth.
+- Silent fallback for an explicitly stale TTS Profile selection was removed.
+
+## Validation Summary
+- PASS: node --check toolbox/messages/messages.js
+- PASS: node --check toolbox/text-to-speech/text2speech.js
+- PASS: node --check tests/playwright/tools/MessagesTool.spec.mjs
+- PASS: node --check tests/playwright/tools/TextToSpeechFunctional.spec.mjs
+- PASS: npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs --reporter=list
+- PASS: npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --reporter=list
+- PASS: npm run test:workspace-v2
+
+## Notes
+- The first parallel Message Studio Playwright run timed out during report contention; the targeted Message Studio rerun passed by itself.
+- npm run test:workspace-v2 is a legacy command name; user-facing language remains Project Workspace.
diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt
index 60d4cdf24..2957acbc7 100644
--- a/docs_build/dev/reports/codex_changed_files.txt
+++ b/docs_build/dev/reports/codex_changed_files.txt
@@ -1,125 +1,13 @@
-PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff
-
-Git workflow:
-- Current branch: pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff
-- Created branch: pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff
-- Push result: PASS; pushed to origin/pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff
-- PR URL: https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/32
-- Merge approval status: pending explicit Team Alpha owner approval
-- Merge result: not performed; merge is blocked until explicit Team Alpha owner approval
-- Rebase/conflict resolution: PASS; rebased onto origin/main at e8845dae6 after GitHub reported the branch was behind current main. Conflicts were limited to generated Codex report artifacts and reports were regenerated.
-- Latest rebase/conflict resolution: PASS; rebased onto origin/main at 195c90a64 after PR_26171_BETA_075 landed. Conflicts were resolved in generated Codex report artifacts and tests/playwright/tools/RootToolsFutureState.spec.mjs; the test expectations now preserve current-main Text To Speech coverage and ALPHA_047 canonical game-hub route/filter behavior.
-- Final approved rebase: PASS; Team Alpha approval granted for ALPHA_047, rebased onto origin/main at 1451a1173, with conflicts limited to generated Codex report artifacts.
-
-Instruction compliance:
-- PASS: docs_build/dev/PROJECT_INSTRUCTIONS.md was read before file changes.
-- PASS: docs_build/dev/PROJECT_MULTI_PC.txt was read before file changes.
-- PASS: BUILD_PR.md was created and read before implementation.
-- PASS: Team Alpha owner scope applies to Game Hub, Game Journey handoff, and Idea Board continuity.
-- PASS: Full samples smoke was not run.
-
-Validation:
-- PASS: node --check toolbox/game-hub/game-hub.js
-- PASS: node --check toolbox/game-hub/game-hub-api-client.js
-- PASS: node --check toolbox/idea-board/index.js
-- PASS: node --check toolbox/game-journey/game-journey.js
-- PASS: node --check src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js
-- PASS: node --check src/dev-runtime/server/local-api-router.mjs
-- PASS: node --check tests/playwright/tools/GameHubMockRepository.spec.mjs
-- PASS: node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
-- PASS: node --check tests/playwright/tools/GameJourneyTool.spec.mjs
-- PASS: npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs --project=playwright --workers=1 --reporter=line (11 passed)
-- PASS: npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line (2 passed)
-- PASS: npx playwright test tests/playwright/tools/GameJourneyTool.spec.mjs --project=playwright --workers=1 --reporter=line --grep "Game Hub hands the active game route to Game Journey|Game Journey requires an active game" (2 passed)
-- PASS: npm run test:workspace-v2 (5 passed)
-- PASS: git diff --check (line-ending warnings only)
-- PASS: Completion revalidation for PR_26171_ALPHA_048 start-gate blocker; ALPHA_047 scoped validation was rerun while still on branch pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff, and ALPHA_048 implementation was not started.
-- PASS: Latest post-rebase validation after origin/main 195c90a64: syntax checks, targeted Game Hub Playwright, targeted Idea Board Playwright, targeted Game Journey handoff Playwright, and npm run test:workspace-v2.
-- PASS: Final approved validation after origin/main 1451a1173: syntax checks, targeted Game Hub Playwright, targeted Idea Board Playwright, targeted Game Journey handoff Playwright, and npm run test:workspace-v2.
-
-Requirement-by-requirement evidence:
-- PASS: Rename active path toolbox/game-workspace/ to toolbox/game-hub/.
-- PASS: Rename active game-workspace.js to game-hub.js.
-- PASS: Rename active game-workspace-api-client.js to game-hub-api-client.js.
-- PASS: Deep-audit and update imports, variables, data attributes, tests, toolbox navigation/registry, tool display mode slug, page titles, and creator-facing labels to Game Hub.
-- PASS: Do not leave active navigation pointing to toolbox/game-workspace; active route audit found no active legacy route.
-- PASS: Do not create duplicate active Game Hub paths; registry reads filter stale game-workspace rows and active metadata points to game-hub.
-- PASS: Idea Board Create Project creates/links a Game Hub project when a Ready idea becomes Project.
-- PASS: Project ideas lock original Idea, Pitch, and Notes.
-- PASS: Game Hub shows Source Idea/Pitch/Notes as read-only creator-facing content.
-- PASS: Game Journey receives executable Journey items from Idea notes when the linked project opens.
-- PASS: Original Idea notes are not mutated or moved.
-- PASS: Project/source-linked actions avoid Delete and use Open in Game Hub plus Archive.
-- PASS: No real database migration was added; existing shared mock/server contracts are reused.
-- PASS: Merge is not performed without explicit Team Alpha owner approval.
-
-Changed files:
-- deleted: assets/theme-v2/images/badges/game-workspace-1024.png
-- deleted: assets/theme-v2/images/badges/game-workspace.png
-- added: assets/theme-v2/images/badges/game-hub-1024.png
-- added: assets/theme-v2/images/badges/game-hub.png
-- deleted: assets/theme-v2/images/tools/game-workspace-1024.png
-- deleted: assets/theme-v2/images/tools/game-workspace.png
-- added: assets/theme-v2/images/tools/game-hub-1024.png
-- added: assets/theme-v2/images/tools/game-hub.png
-- updated: assets/theme-v2/js/account-achievements.js
-- updated: assets/theme-v2/js/gamefoundry-partials.js
-- updated: assets/theme-v2/partials/header-nav.html
-- updated: docs_build/database/ddl/game-workspace.sql
-- updated: docs_build/database/dml/DML_INDEX.md
-- updated: docs_build/database/dml/game-workspace.sql
-- updated: docs_build/database/seed/game-workspace.json
-- updated: docs_build/database/seed/guest/game-workspace.json
-- updated: docs_build/dev/admin-notes/deployment-uat-prod/index.txt
-- added: docs_build/dev/reports/codex_changed_files.txt
-- added: docs_build/dev/reports/codex_review.diff
-- added: docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/APPLY_PR.md
-- added: docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/BUILD_PR.md
-- added: docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/PLAN_PR.md
-- deleted: learn/game-workspace/index.html
-- added: learn/game-hub/index.html
-- updated: learn/getting-started/index.html
-- updated: learn/index.html
-- updated: package.json
-- updated: scripts/run-targeted-test-lanes.mjs
-- updated: scripts/validate-browser-env-agnostic.mjs
-- updated: scripts/validate-local-postgres-runtime.mjs
-- updated: src/dev-runtime/admin/header-nav.local.html
-- updated: src/dev-runtime/persistence/mock-db-store.js
-- updated: src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js
-- updated: src/dev-runtime/seed/server-seed-loader.mjs
-- updated: src/dev-runtime/server/local-api-router.mjs
-- updated: src/shared/toolbox/tool-metadata-inventory.js
-- updated: tests/dev-runtime/DevRuntimeBoundary.test.mjs
-- updated: tests/dev-runtime/ProductDataProviderContractHardening.test.mjs
-- updated: tests/dev-runtime/SupabaseProductDataCutover.test.mjs
-- updated: tests/playwright/account/AchievementsPage.spec.mjs
-- updated: tests/playwright/tools/AdminDbViewer.spec.mjs
-- added: tests/playwright/tools/GameHubMockRepository.spec.mjs
-- updated: tests/playwright/tools/GameJourneyTool.spec.mjs
-- deleted: tests/playwright/tools/GameWorkspaceMockRepository.spec.mjs
-- updated: tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
-- updated: tests/playwright/tools/RootToolsFutureState.spec.mjs
-- updated: tests/playwright/tools/ToolDisplayModeNavigation.spec.mjs
-- updated: tests/playwright/tools/ToolImageRegistry.spec.mjs
-- updated: tests/playwright/tools/ToolNavigationPrevNext.spec.mjs
-- updated: tests/playwright/tools/ToolboxRoutePages.spec.mjs
-- updated: toolbox/colors/index.html
-- updated: toolbox/controls/controls.js
-- updated: toolbox/controls/index.html
-- updated: toolbox/game-design/index.html
-- added: toolbox/game-hub/game-hub-api-client.js
-- added: toolbox/game-hub/game-hub.js
-- added: toolbox/game-hub/index.html
-- updated: toolbox/game-journey/game-journey.js
-- deleted: toolbox/game-workspace/game-workspace-api-client.js
-- deleted: toolbox/game-workspace/game-workspace.js
-- deleted: toolbox/game-workspace/index.html
-- updated: toolbox/idea-board/index.js
-- updated: toolbox/project-workspace/index.html
-- updated: toolbox/tools-page-accordions.js
-
-ZIP:
-- Path: tmp/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff_delta.zip
-- Size: generated after branch push; final byte size is reported in the delivery response because the ZIP is not committed.
-- Contents: repo-structured scoped delta files. Deleted legacy paths are documented in the changed-files list and are not represented as physical files in the archive.
+docs_build/dev/reports/codex_changed_files.txt
+docs_build/dev/reports/codex_review.diff
+docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-dead-code-cleanup-checklist.md
+docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-manual-validation-notes.md
+docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-message-tts-ux-checklist.md
+docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-validation-report.md
+docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code.md
+tests/playwright/tools/MessagesTool.spec.mjs
+tests/playwright/tools/TextToSpeechFunctional.spec.mjs
+toolbox/messages/index.html
+toolbox/messages/messages.js
+toolbox/text-to-speech/index.html
+toolbox/text-to-speech/text2speech.js
diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff
index 9b7b02d87..e67301574 100644
--- a/docs_build/dev/reports/codex_review.diff
+++ b/docs_build/dev/reports/codex_review.diff
@@ -1,2086 +1,914 @@
-diff --git a/assets/theme-v2/images/badges/game-workspace-1024.png b/assets/theme-v2/images/badges/game-hub-1024.png
-similarity index 100%
-rename from assets/theme-v2/images/badges/game-workspace-1024.png
-rename to assets/theme-v2/images/badges/game-hub-1024.png
-diff --git a/assets/theme-v2/images/badges/game-workspace.png b/assets/theme-v2/images/badges/game-hub.png
-similarity index 100%
-rename from assets/theme-v2/images/badges/game-workspace.png
-rename to assets/theme-v2/images/badges/game-hub.png
-diff --git a/assets/theme-v2/images/tools/game-workspace-1024.png b/assets/theme-v2/images/tools/game-hub-1024.png
-similarity index 100%
-rename from assets/theme-v2/images/tools/game-workspace-1024.png
-rename to assets/theme-v2/images/tools/game-hub-1024.png
-diff --git a/assets/theme-v2/images/tools/game-workspace.png b/assets/theme-v2/images/tools/game-hub.png
-similarity index 100%
-rename from assets/theme-v2/images/tools/game-workspace.png
-rename to assets/theme-v2/images/tools/game-hub.png
-diff --git a/assets/theme-v2/js/account-achievements.js b/assets/theme-v2/js/account-achievements.js
-index 365fbd818..184e4af67 100644
---- a/assets/theme-v2/js/account-achievements.js
-+++ b/assets/theme-v2/js/account-achievements.js
-@@ -7,7 +7,7 @@ const buildRows = document.querySelector("[data-achievements-build-rows]");
- const buildStatus = document.querySelector("[data-achievements-build-status]");
- const createdCount = document.querySelector("[data-achievements-build-created-count]");
- const readyCount = document.querySelector("[data-achievements-build-ready-count]");
--const repository = createServerRepositoryClient("game-workspace");
-+const repository = createServerRepositoryClient("game-hub");
-
- function setText(element, value) {
- if (element) {
-diff --git a/assets/theme-v2/js/gamefoundry-partials.js b/assets/theme-v2/js/gamefoundry-partials.js
-index 862614796..44b301916 100644
---- a/assets/theme-v2/js/gamefoundry-partials.js
-+++ b/assets/theme-v2/js/gamefoundry-partials.js
-@@ -3,7 +3,7 @@
- home: "index.html",
- toolbox: "toolbox/index.html",
- "ai-assistant": "toolbox/ai-assistant/index.html",
-- "game-workspace": "toolbox/game-workspace/index.html",
-+ "game-hub": "toolbox/game-hub/index.html",
- "game-journey": "toolbox/game-journey/index.html",
- "game-design": "toolbox/game-design/index.html",
- "game-configuration": "toolbox/game-configuration/index.html",
-diff --git a/assets/theme-v2/partials/header-nav.html b/assets/theme-v2/partials/header-nav.html
-index f2c805256..f49f136ce 100644
---- a/assets/theme-v2/partials/header-nav.html
-+++ b/assets/theme-v2/partials/header-nav.html
-@@ -35,7 +35,7 @@
- Custom Extensions
- Game Configuration
- Game Design
-- Game Hub
-+ Game Hub
- Game Journey
-
-
-diff --git a/docs_build/database/ddl/game-workspace.sql b/docs_build/database/ddl/game-workspace.sql
-index 5ccff6878..b0b2f08e5 100644
---- a/docs_build/database/ddl/game-workspace.sql
-+++ b/docs_build/database/ddl/game-workspace.sql
-@@ -1,5 +1,5 @@
- -- Game Foundry Studio DEV database DDL
---- Group: Game Workspace
-+-- Group: Game Hub
- -- Ownership: docs_build/database/ddl/game-workspace.sql
- -- Target DEV database: gamefoundry_dev
- -- Scope: executable grouped table DDL for active Supabase/server API migration.
-diff --git a/docs_build/database/dml/DML_INDEX.md b/docs_build/database/dml/DML_INDEX.md
-index 642e3ebce..3d8ee81f8 100644
---- a/docs_build/database/dml/DML_INDEX.md
-+++ b/docs_build/database/dml/DML_INDEX.md
-@@ -13,7 +13,7 @@ Direct SQL setup is intentionally narrow. Account DEV users now require server-s
- | Game Configuration | `game-configuration.sql` | Server-seed-owned | Server-side seed API |
- | Game Design | `game-design.sql` | Server-seed-owned | Server-side seed API |
- | Game Journey | `game-journey.sql` | Server-seed-owned | Server-side seed API |
--| Game Workspace | `game-workspace.sql` | Server-seed-owned | Server-side seed API |
-+| Game Hub | `game-workspace.sql` | Server-seed-owned | Server-side seed API |
- | Messages | `messages.sql` | Server-seed-owned | Messages Local API/server-side SQLite service |
- | Objects | `objects.sql` | Server-seed-owned | Server-side seed API |
- | Palette | `palette.sql` | Server-seed-owned | Server-side seed API |
-diff --git a/docs_build/database/dml/game-workspace.sql b/docs_build/database/dml/game-workspace.sql
-index 331dacd82..a8257f6f6 100644
---- a/docs_build/database/dml/game-workspace.sql
-+++ b/docs_build/database/dml/game-workspace.sql
-@@ -1,5 +1,5 @@
- -- Game Foundry Studio DEV database DML / seed review
---- Group: Game Workspace
-+-- Group: Game Hub
- -- Ownership: docs_build/database/dml/game-workspace.sql
- -- Runtime setup/seed operations for this group must run through server-side APIs.
- -- Temporary scope: DEV/review artifact only until Admin Site Setup/server seed APIs fully own grouped setup.
-diff --git a/docs_build/database/seed/game-workspace.json b/docs_build/database/seed/game-workspace.json
-index bdc7afa85..bdc5eef4d 100644
---- a/docs_build/database/seed/game-workspace.json
-+++ b/docs_build/database/seed/game-workspace.json
-@@ -1,6 +1,6 @@
- {
-- "group": "Game Workspace",
-- "groupKey": "game-workspace",
-+ "group": "Game Hub",
-+ "groupKey": "game-hub",
- "owner": "docs_build/database/seed",
- "serverSideSeedRequired": true,
- "browserAuthoritativeKeyGenerationAllowed": false,
-diff --git a/docs_build/database/seed/guest/game-workspace.json b/docs_build/database/seed/guest/game-workspace.json
-index 434050b9f..a528c5318 100644
---- a/docs_build/database/seed/guest/game-workspace.json
-+++ b/docs_build/database/seed/guest/game-workspace.json
-@@ -1,6 +1,6 @@
- {
-- "group": "Game Workspace",
-- "groupKey": "game-workspace",
-+ "group": "Game Hub",
-+ "groupKey": "game-hub",
- "owner": "docs_build/database/seed/guest",
- "readOnly": true,
- "writableByGuest": false,
-@@ -11,18 +11,18 @@
- },
- "samplePackages": [
- {
-- "key": "guest-game-workspace-starter",
-+ "key": "guest-game-hub-starter",
- "audience": "guest",
- "createdBy": "01K2GFSJ0Y0000000000000054",
-- "groupKey": "game-workspace",
-- "label": "Guest Game Workspace starter",
-- "loadablePath": "toolbox/game-workspace/index.html",
-+ "groupKey": "game-hub",
-+ "label": "Guest Game Hub starter",
-+ "loadablePath": "toolbox/game-hub/index.html",
- "readOnly": true,
- "sampleKind": "toolSeed",
- "signInRedirect": "account/sign-in.html",
- "source": "docs_build/database/seed/guest/game-workspace.json",
-- "toolKey": "game-workspace",
-- "toolName": "Game Workspace",
-+ "toolKey": "game-hub",
-+ "toolName": "Game Hub",
- "writableByGuest": false
- }
- ]
-diff --git a/docs_build/dev/admin-notes/deployment-uat-prod/index.txt b/docs_build/dev/admin-notes/deployment-uat-prod/index.txt
-index 27ee1bf8f..0a8b67b53 100644
---- a/docs_build/dev/admin-notes/deployment-uat-prod/index.txt
-+++ b/docs_build/dev/admin-notes/deployment-uat-prod/index.txt
-@@ -141,7 +141,7 @@ Example:
- "requiredRoutes": [
- "/",
- "/toolbox/",
-- "/toolbox/game-workspace/",
-+ "/toolbox/game-hub/",
- "/toolbox/game-journey/",
- "/toolbox/controls/",
- "/account/user-controls.html"
-diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt
-index 5a25f4aef..60d4cdf24 100644
---- a/docs_build/dev/reports/codex_changed_files.txt
-+++ b/docs_build/dev/reports/codex_changed_files.txt
-@@ -1,8 +1,125 @@
--docs_build/dev/reports/codex_changed_files.txt
--docs_build/dev/reports/codex_review.diff
--docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-manual-validation-notes.md
--docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-validation-report.md
--docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine.md
--src/dev-runtime/messages/messages-sqlite-service.mjs
--tests/playwright/tools/MessagesTool.spec.mjs
--toolbox/messages/messages.js
-+PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff
-+
-+Git workflow:
-+- Current branch: pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff
-+- Created branch: pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff
-+- Push result: PASS; pushed to origin/pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff
-+- PR URL: https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/32
-+- Merge approval status: pending explicit Team Alpha owner approval
-+- Merge result: not performed; merge is blocked until explicit Team Alpha owner approval
-+- Rebase/conflict resolution: PASS; rebased onto origin/main at e8845dae6 after GitHub reported the branch was behind current main. Conflicts were limited to generated Codex report artifacts and reports were regenerated.
-+- Latest rebase/conflict resolution: PASS; rebased onto origin/main at 195c90a64 after PR_26171_BETA_075 landed. Conflicts were resolved in generated Codex report artifacts and tests/playwright/tools/RootToolsFutureState.spec.mjs; the test expectations now preserve current-main Text To Speech coverage and ALPHA_047 canonical game-hub route/filter behavior.
-+- Final approved rebase: PASS; Team Alpha approval granted for ALPHA_047, rebased onto origin/main at 1451a1173, with conflicts limited to generated Codex report artifacts.
-+
-+Instruction compliance:
-+- PASS: docs_build/dev/PROJECT_INSTRUCTIONS.md was read before file changes.
-+- PASS: docs_build/dev/PROJECT_MULTI_PC.txt was read before file changes.
-+- PASS: BUILD_PR.md was created and read before implementation.
-+- PASS: Team Alpha owner scope applies to Game Hub, Game Journey handoff, and Idea Board continuity.
-+- PASS: Full samples smoke was not run.
-+
-+Validation:
-+- PASS: node --check toolbox/game-hub/game-hub.js
-+- PASS: node --check toolbox/game-hub/game-hub-api-client.js
-+- PASS: node --check toolbox/idea-board/index.js
-+- PASS: node --check toolbox/game-journey/game-journey.js
-+- PASS: node --check src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js
-+- PASS: node --check src/dev-runtime/server/local-api-router.mjs
-+- PASS: node --check tests/playwright/tools/GameHubMockRepository.spec.mjs
-+- PASS: node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
-+- PASS: node --check tests/playwright/tools/GameJourneyTool.spec.mjs
-+- PASS: npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs --project=playwright --workers=1 --reporter=line (11 passed)
-+- PASS: npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line (2 passed)
-+- PASS: npx playwright test tests/playwright/tools/GameJourneyTool.spec.mjs --project=playwright --workers=1 --reporter=line --grep "Game Hub hands the active game route to Game Journey|Game Journey requires an active game" (2 passed)
-+- PASS: npm run test:workspace-v2 (5 passed)
-+- PASS: git diff --check (line-ending warnings only)
-+- PASS: Completion revalidation for PR_26171_ALPHA_048 start-gate blocker; ALPHA_047 scoped validation was rerun while still on branch pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff, and ALPHA_048 implementation was not started.
-+- PASS: Latest post-rebase validation after origin/main 195c90a64: syntax checks, targeted Game Hub Playwright, targeted Idea Board Playwright, targeted Game Journey handoff Playwright, and npm run test:workspace-v2.
-+- PASS: Final approved validation after origin/main 1451a1173: syntax checks, targeted Game Hub Playwright, targeted Idea Board Playwright, targeted Game Journey handoff Playwright, and npm run test:workspace-v2.
-+
-+Requirement-by-requirement evidence:
-+- PASS: Rename active path toolbox/game-workspace/ to toolbox/game-hub/.
-+- PASS: Rename active game-workspace.js to game-hub.js.
-+- PASS: Rename active game-workspace-api-client.js to game-hub-api-client.js.
-+- PASS: Deep-audit and update imports, variables, data attributes, tests, toolbox navigation/registry, tool display mode slug, page titles, and creator-facing labels to Game Hub.
-+- PASS: Do not leave active navigation pointing to toolbox/game-workspace; active route audit found no active legacy route.
-+- PASS: Do not create duplicate active Game Hub paths; registry reads filter stale game-workspace rows and active metadata points to game-hub.
-+- PASS: Idea Board Create Project creates/links a Game Hub project when a Ready idea becomes Project.
-+- PASS: Project ideas lock original Idea, Pitch, and Notes.
-+- PASS: Game Hub shows Source Idea/Pitch/Notes as read-only creator-facing content.
-+- PASS: Game Journey receives executable Journey items from Idea notes when the linked project opens.
-+- PASS: Original Idea notes are not mutated or moved.
-+- PASS: Project/source-linked actions avoid Delete and use Open in Game Hub plus Archive.
-+- PASS: No real database migration was added; existing shared mock/server contracts are reused.
-+- PASS: Merge is not performed without explicit Team Alpha owner approval.
-+
-+Changed files:
-+- deleted: assets/theme-v2/images/badges/game-workspace-1024.png
-+- deleted: assets/theme-v2/images/badges/game-workspace.png
-+- added: assets/theme-v2/images/badges/game-hub-1024.png
-+- added: assets/theme-v2/images/badges/game-hub.png
-+- deleted: assets/theme-v2/images/tools/game-workspace-1024.png
-+- deleted: assets/theme-v2/images/tools/game-workspace.png
-+- added: assets/theme-v2/images/tools/game-hub-1024.png
-+- added: assets/theme-v2/images/tools/game-hub.png
-+- updated: assets/theme-v2/js/account-achievements.js
-+- updated: assets/theme-v2/js/gamefoundry-partials.js
-+- updated: assets/theme-v2/partials/header-nav.html
-+- updated: docs_build/database/ddl/game-workspace.sql
-+- updated: docs_build/database/dml/DML_INDEX.md
-+- updated: docs_build/database/dml/game-workspace.sql
-+- updated: docs_build/database/seed/game-workspace.json
-+- updated: docs_build/database/seed/guest/game-workspace.json
-+- updated: docs_build/dev/admin-notes/deployment-uat-prod/index.txt
-+- added: docs_build/dev/reports/codex_changed_files.txt
-+- added: docs_build/dev/reports/codex_review.diff
-+- added: docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/APPLY_PR.md
-+- added: docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/BUILD_PR.md
-+- added: docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/PLAN_PR.md
-+- deleted: learn/game-workspace/index.html
-+- added: learn/game-hub/index.html
-+- updated: learn/getting-started/index.html
-+- updated: learn/index.html
-+- updated: package.json
-+- updated: scripts/run-targeted-test-lanes.mjs
-+- updated: scripts/validate-browser-env-agnostic.mjs
-+- updated: scripts/validate-local-postgres-runtime.mjs
-+- updated: src/dev-runtime/admin/header-nav.local.html
-+- updated: src/dev-runtime/persistence/mock-db-store.js
-+- updated: src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js
-+- updated: src/dev-runtime/seed/server-seed-loader.mjs
-+- updated: src/dev-runtime/server/local-api-router.mjs
-+- updated: src/shared/toolbox/tool-metadata-inventory.js
-+- updated: tests/dev-runtime/DevRuntimeBoundary.test.mjs
-+- updated: tests/dev-runtime/ProductDataProviderContractHardening.test.mjs
-+- updated: tests/dev-runtime/SupabaseProductDataCutover.test.mjs
-+- updated: tests/playwright/account/AchievementsPage.spec.mjs
-+- updated: tests/playwright/tools/AdminDbViewer.spec.mjs
-+- added: tests/playwright/tools/GameHubMockRepository.spec.mjs
-+- updated: tests/playwright/tools/GameJourneyTool.spec.mjs
-+- deleted: tests/playwright/tools/GameWorkspaceMockRepository.spec.mjs
-+- updated: tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
-+- updated: tests/playwright/tools/RootToolsFutureState.spec.mjs
-+- updated: tests/playwright/tools/ToolDisplayModeNavigation.spec.mjs
-+- updated: tests/playwright/tools/ToolImageRegistry.spec.mjs
-+- updated: tests/playwright/tools/ToolNavigationPrevNext.spec.mjs
-+- updated: tests/playwright/tools/ToolboxRoutePages.spec.mjs
-+- updated: toolbox/colors/index.html
-+- updated: toolbox/controls/controls.js
-+- updated: toolbox/controls/index.html
-+- updated: toolbox/game-design/index.html
-+- added: toolbox/game-hub/game-hub-api-client.js
-+- added: toolbox/game-hub/game-hub.js
-+- added: toolbox/game-hub/index.html
-+- updated: toolbox/game-journey/game-journey.js
-+- deleted: toolbox/game-workspace/game-workspace-api-client.js
-+- deleted: toolbox/game-workspace/game-workspace.js
-+- deleted: toolbox/game-workspace/index.html
-+- updated: toolbox/idea-board/index.js
-+- updated: toolbox/project-workspace/index.html
-+- updated: toolbox/tools-page-accordions.js
-+
-+ZIP:
-+- Path: tmp/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff_delta.zip
-+- Size: generated after branch push; final byte size is reported in the delivery response because the ZIP is not committed.
-+- Contents: repo-structured scoped delta files. Deleted legacy paths are documented in the changed-files list and are not represented as physical files in the archive.
-diff --git a/docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/APPLY_PR.md b/docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/APPLY_PR.md
+diff --git a/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-dead-code-cleanup-checklist.md b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-dead-code-cleanup-checklist.md
new file mode 100644
-index 000000000..cc605d860
+index 000000000..63cdb0e21
--- /dev/null
-+++ b/docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/APPLY_PR.md
-@@ -0,0 +1,48 @@
-+# PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff APPLY
-+
-+## Git Workflow
-+- Created branch: `pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff`
-+- Push result: PASS; pushed to `origin/pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff`
-+- PR URL: https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/32
-+- Merge approval status: pending explicit Team Alpha owner approval
-+- Merge result: not allowed without explicit Team Alpha owner approval
-+- Rebase/conflict resolution: PASS; rebased onto `origin/main` at `e8845dae6` and resolved conflicts limited to generated Codex report artifacts.
-+- Latest rebase/conflict resolution: PASS; rebased onto `origin/main` at `195c90a64`, resolved generated report conflicts and `tests/playwright/tools/RootToolsFutureState.spec.mjs` expectations for the canonical `game-hub` route and filtered legacy `game-workspace` registry row.
-+- Final approved rebase: PASS; Team Alpha approval granted, rebased onto `origin/main` at `1451a1173`, with conflicts limited to generated Codex report artifacts.
-+
-+## Validation
-+- `node --check toolbox/game-hub/game-hub.js`: PASS
-+- `node --check toolbox/game-hub/game-hub-api-client.js`: PASS
-+- `node --check toolbox/idea-board/index.js`: PASS
-+- `node --check toolbox/game-journey/game-journey.js`: PASS
-+- `node --check src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js`: PASS
-+- `node --check src/dev-runtime/server/local-api-router.mjs`: PASS
-+- `node --check tests/playwright/tools/GameHubMockRepository.spec.mjs`: PASS
-+- `node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs`: PASS
-+- `node --check tests/playwright/tools/GameJourneyTool.spec.mjs`: PASS
-+- `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs --project=playwright --workers=1 --reporter=line`: PASS, 11 passed
-+- `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line`: PASS, 2 passed
-+- `npx playwright test tests/playwright/tools/GameJourneyTool.spec.mjs --project=playwright --workers=1 --reporter=line --grep "Game Hub hands the active game route to Game Journey|Game Journey requires an active game"`: PASS, 2 passed
-+- `npm run test:workspace-v2`: PASS, 5 passed
-+- `git diff --check`: PASS, line-ending warnings only
-+- Completion revalidation for `PR_26171_ALPHA_048` start-gate blocker: PASS; ALPHA_047 scoped validation was rerun on branch `pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff` before starting ALPHA_048.
-+- Latest post-rebase validation: PASS; syntax checks, targeted Game Hub Playwright, targeted Idea Board Playwright, targeted Game Journey handoff Playwright, and `npm run test:workspace-v2` were rerun after rebasing onto `195c90a64`.
-+- Final approved validation: PASS; syntax checks, targeted Game Hub Playwright, targeted Idea Board Playwright, targeted Game Journey handoff Playwright, and `npm run test:workspace-v2` were rerun after rebasing onto `1451a1173`.
-+
-+## ZIP
-+- Path: `tmp/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff_delta.zip`
-+- Size: generated after branch push; final byte size is reported in the delivery response because the ZIP is not committed.
-+- Contents: repo-structured scoped delta files, with deleted legacy paths documented in `docs_build/dev/reports/codex_changed_files.txt`.
-+
-+## Requirement Evidence
-+- Instruction compliance: PASS; PROJECT_INSTRUCTIONS.md, PROJECT_MULTI_PC.txt, and BUILD_PR.md were read before implementation.
-+- Team Alpha owner: PASS; scope is Game Hub, Game Journey handoff, and Idea Board continuity.
-+- Canonical active path: PASS; active route is `toolbox/game-hub/` with no active navigation pointing to `toolbox/game-workspace/`.
-+- Active files renamed: PASS; active Game Hub JS files now use `game-hub.js` and `game-hub-api-client.js`.
-+- Deep reference audit: PASS; imports, tests, navigation registry, tool display mode slug, titles, and creator labels were updated to Game Hub.
-+- No duplicate active Game Hub path: PASS; legacy Game Workspace registry rows are filtered from active registry reads.
-+- Idea to Hub handoff: PASS; Ready ideas create/link a Game Hub project, become Project, lock the source idea and notes, and expose Open in Game Hub/Archive actions.
-+- Game Hub source display: PASS; Game Hub renders Source Idea, Pitch, and Notes read-only for source-linked projects.
-+- Journey handoff: PASS; opening the project creates executable Game Journey items from source idea notes without moving or mutating the original notes.
-+- Delete avoidance: PASS; source-linked projects do not expose Delete Open Game.
-+- Merge gate: PASS; merge is intentionally blocked until explicit Team Alpha owner approval.
-diff --git a/docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/BUILD_PR.md b/docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/BUILD_PR.md
++++ b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-dead-code-cleanup-checklist.md
+@@ -0,0 +1,22 @@
++# PR_26171_BETA_083 Dead-Code Cleanup Checklist
++
++## Message Studio
++- PASS: Removed duplicate preview TTS Profile control outside the table.
++- PASS: Removed duplicate TTS service selector outside the table.
++- PASS: Removed dead Test Speech button and selected-item helper text.
++- PASS: Removed unused Message Studio speech-test state and render paths.
++- PASS: Kept playback routed through selected message part, TTS Profile, and Emotion Setting.
++- PASS: Kept Play Part, Play Message, and Stop Playback controls available.
++
++## TTS Studio
++- PASS: Kept Delivery in Emotion Settings.
++- PASS: Kept Presets in Emotion Settings as Delivery Preset.
++- PASS: Kept Voice Filters in profile controls.
++- PASS: No duplicate summary or output panel was added.
++- PASS: No separate Emotion Studio was added.
++
++## Tests
++- PASS: Removed dead Message speech-test selector expectations.
++- PASS: Added absence checks for removed duplicate controls.
++- PASS: Added actionable error coverage for missing TTS Profile.
++- PASS: Added actionable error coverage for missing Emotion Setting on the selected profile.
+diff --git a/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-manual-validation-notes.md
new file mode 100644
-index 000000000..7b3a0d377
+index 000000000..02ba719f4
--- /dev/null
-+++ b/docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/BUILD_PR.md
-@@ -0,0 +1,51 @@
-+# PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff BUILD
-+
-+## Start Gate
-+- Read `docs_build/dev/PROJECT_INSTRUCTIONS.md`.
-+- Read `docs_build/dev/PROJECT_MULTI_PC.txt`.
-+- Confirm current branch is clean latest `main` before branch creation.
-+- Confirm Team Alpha owns Game Hub, Game Journey, and Idea flow work.
-+- Confirm merge requires explicit Team Alpha owner approval.
-+
-+## Exact Targets
-+- `toolbox/game-workspace/` -> `toolbox/game-hub/`
-+- `toolbox/game-hub/index.html`
-+- `toolbox/game-hub/game-hub.js`
-+- `toolbox/game-hub/game-hub-api-client.js`
-+- `toolbox/idea-board/index.js`
-+- `toolbox/game-journey/game-journey.js`
-+- Active navigation/registry references to `toolbox/game-workspace` or `game-workspace`
-+- Targeted Playwright tests for Game Hub, Idea Board handoff, and navigation routes
-+- `docs_build/dev/reports/codex_review.diff`
-+- `docs_build/dev/reports/codex_changed_files.txt`
-+
-+## Required Implementation
-+- Rename active path `toolbox/game-workspace/` to `toolbox/game-hub/`.
-+- Rename active files to `game-hub.js` and `game-hub-api-client.js`.
-+- Do not leave active navigation pointing to `toolbox/game-workspace`.
-+- Do not create a duplicate active Game Hub path.
-+- Update imports, route references, registry path/folder/entry point, tool display mode slug, page titles, creator-facing labels, and tests.
-+- Preserve the existing repository/API contract unless a small compatibility alias is required.
-+- Idea Board Create Project opens the canonical Game Hub route.
-+- Project source-linked actions avoid Delete and use Open in Game Hub and Archive.
-+- When an idea becomes Project, original Idea, Pitch, and Notes remain locked/read-only.
-+- Game Hub displays Source Idea, Pitch, and Notes read-only.
-+- Game Journey receives executable Journey items from Idea notes without mutating or moving original notes.
-+
-+## Required Validation
-+- `node --check toolbox/game-hub/game-hub.js`
-+- `node --check toolbox/idea-board/index.js`
-+- Changed-file syntax checks for affected Game Journey JavaScript.
-+- Targeted Game Hub Playwright.
-+- Targeted Idea Board Playwright if handoff code changes.
-+- `npm run test:workspace-v2`
-+- Do not run full samples smoke.
-+
-+## Required Delivery
-+- Update required reports.
-+- Produce repo-structured ZIP under `tmp/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff_delta.zip`.
-+- Stage only scoped files.
-+- Commit.
-+- Push branch.
-+- Create PR.
-+- Stop before merge unless explicit Team Alpha owner approval is present.
-diff --git a/docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/PLAN_PR.md b/docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/PLAN_PR.md
++++ b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-manual-validation-notes.md
+@@ -0,0 +1,14 @@
++# PR_26171_BETA_083 Manual Validation Notes
++
++## Manual Review
++- Reviewed changed Message Studio markup for removed duplicate controls.
++- Reviewed changed Message Studio JavaScript for removed speech-test state and render paths.
++- Reviewed changed TTS Studio labels for Profile, Age Filter, and Delivery Preset wording.
++- Reviewed Playwright coverage for missing TTS Profile and missing Emotion Setting error states.
++
++## Browser Validation
++- Targeted Playwright validation covered Message Studio and TTS Studio flows.
++- No separate manual browser session was required beyond the automated browser validation.
++
++## Packaging
++- Delta ZIP is required under tmp/ and must not be staged.
+diff --git a/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-message-tts-ux-checklist.md b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-message-tts-ux-checklist.md
new file mode 100644
-index 000000000..b307026fb
+index 000000000..7e1b4eb7f
--- /dev/null
-+++ b/docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/PLAN_PR.md
-@@ -0,0 +1,27 @@
-+# PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff PLAN
-+
-+## Source Of Truth
-+- `docs_build/dev/PROJECT_INSTRUCTIONS.md`
-+- `docs_build/dev/PROJECT_MULTI_PC.txt`
-+- User request: `PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff`
-+
-+## Purpose
-+Canonicalize the active Game Hub route from `toolbox/game-workspace/` to `toolbox/game-hub/` and preserve the Idea Board to Game Hub to Game Journey handoff.
++++ b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-message-tts-ux-checklist.md
+@@ -0,0 +1,25 @@
++# PR_26171_BETA_083 Message/TTS UX Checklist
++
++## Message Studio
++- PASS: Messages parent table label is creator-friendly.
++- PASS: Message Parts child table uses Part Text, Emotion, TTS Profile, Status, and Actions.
++- PASS: Play Part label remains clear.
++- PASS: Play Message label remains clear.
++- PASS: Stop control is labeled Stop Playback.
++- PASS: Missing TTS Profile produces a visible actionable error.
++- PASS: Missing Emotion Setting on the selected TTS Profile produces a visible actionable error.
++- PASS: Duplicate TTS/Profile/Emotion controls were not left outside the tables.
++
++## TTS Studio
++- PASS: TTS Profile parent table uses creator-friendly Profile labeling.
++- PASS: Age is clarified as Age Filter.
++- PASS: Emotion Settings child table uses Delivery Preset labeling.
++- PASS: Delivery settings live in Emotion Settings.
++- PASS: Presets live in Emotion Settings.
++- PASS: Voice Filters live only in profile controls.
++- PASS: Duplicate summary/output panels were not introduced.
+
+## Ownership
-+- Team: Alpha
-+- Area: Game Hub, Idea flow, Game Journey handoff
-+- Merge gate: do not merge without explicit Team Alpha owner approval.
-+
-+## Scope
-+- Rename active Game Hub path and active Game Hub JavaScript files.
-+- Update active navigation, registry, imports, tests, tool display mode slug, and creator-facing paths.
-+- Keep the existing shared repository/API contract unless a minimal alias is required for route compatibility.
-+- Ensure Idea Board Create Project opens the canonical Game Hub route and preserves source Idea/Pitch/Notes.
-+- Ensure Game Journey can display executable journey items created from source idea notes.
++- PASS: Message Studio owns text, ordered message parts, Emotion selection, and TTS Profile selection.
++- PASS: TTS Studio owns Voice, Language, Emotion Settings, Delivery, and Presets.
++- PASS: src/engine/audio owns playback.
+diff --git a/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-validation-report.md b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-validation-report.md
+new file mode 100644
+index 000000000..da9be32f3
+--- /dev/null
++++ b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-validation-report.md
+@@ -0,0 +1,24 @@
++# PR_26171_BETA_083 Validation Report
++
++## Static Checks
++- PASS: node --check toolbox/messages/messages.js
++- PASS: node --check toolbox/text-to-speech/text2speech.js
++- PASS: node --check tests/playwright/tools/MessagesTool.spec.mjs
++- PASS: node --check tests/playwright/tools/TextToSpeechFunctional.spec.mjs
++
++## Targeted Message Studio Validation
++- PASS: npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --reporter=list
++- Result: 3 passed in 38.3s
++
++## Targeted TTS Studio Validation
++- PASS: npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs --reporter=list
++- Result: 3 passed in 53.1s
++
++## Project Workspace Validation
++- PASS: npm run test:workspace-v2
++- Result: 5 passed in 1.7m
++- Note: Command name is legacy; user-facing language is Project Workspace.
++
++## Validation Notes
++- A first parallel Message Studio run timed out while TTS validation was also running. The Message Studio suite passed on the targeted rerun.
++- Generated workspace report churn was restored before PR report files were written.
+diff --git a/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code.md b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code.md
+new file mode 100644
+index 000000000..29a06e3c6
+--- /dev/null
++++ b/docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code.md
+@@ -0,0 +1,45 @@
++# PR_26171_BETA_083-message-tts-polish-and-dead-code
++
++## Summary
++- Team: BETA
++- Scope: Message Studio and TTS Studio polish after parent/child table and playback integration.
++- Branch: pr/26171-BETA-083-message-tts-polish-and-dead-code
++- Instruction compliance: PASS
++
++## Ownership Confirmation
++- BETA owns Message Studio work.
++- BETA owns TTS Studio and TextToSpeechEngine integration work.
++- Message Studio owns message text, ordered message parts, Emotion selection, and TTS Profile selection.
++- TTS Studio owns profiles, voices, language, per-profile emotion settings, Delivery, and Presets.
++- src/engine/audio owns playback.
++
++## Changes
++- Removed dead Message Studio speech-test controls and unused selection code.
++- Replaced Message Studio speech-test status with playback readiness/status.
++- Kept Message Studio playback controls visible and clarified Stop as Stop Playback.
++- Updated Message parent and part table labels to be more creator-friendly.
++- Added explicit missing TTS Profile and missing Emotion Setting validation coverage.
++- Clarified TTS Profile table and Emotion Settings table labels.
++- Clarified that TTS presets belong to Emotion Settings as Delivery Preset.
++
++## Architecture Notes
++- Theme V2 only.
++- External JavaScript only.
++- No inline styles, style blocks, inline handlers, page-local CSS, or tool-local CSS were added.
++- No database changes.
++- No separate Emotion Studio was added.
++- Browser-owned product data was not introduced as a source of truth.
++- Silent fallback for an explicitly stale TTS Profile selection was removed.
++
++## Validation Summary
++- PASS: node --check toolbox/messages/messages.js
++- PASS: node --check toolbox/text-to-speech/text2speech.js
++- PASS: node --check tests/playwright/tools/MessagesTool.spec.mjs
++- PASS: node --check tests/playwright/tools/TextToSpeechFunctional.spec.mjs
++- PASS: npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs --reporter=list
++- PASS: npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --reporter=list
++- PASS: npm run test:workspace-v2
++
++## Notes
++- The first parallel Message Studio Playwright run timed out during report contention; the targeted Message Studio rerun passed by itself.
++- npm run test:workspace-v2 is a legacy command name; user-facing language remains Project Workspace.
+diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt
+index 60d4cdf24..2957acbc7 100644
+--- a/docs_build/dev/reports/codex_changed_files.txt
++++ b/docs_build/dev/reports/codex_changed_files.txt
+@@ -1,125 +1,13 @@
+-PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff
+-
+-Git workflow:
+-- Current branch: pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff
+-- Created branch: pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff
+-- Push result: PASS; pushed to origin/pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff
+-- PR URL: https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/32
+-- Merge approval status: pending explicit Team Alpha owner approval
+-- Merge result: not performed; merge is blocked until explicit Team Alpha owner approval
+-- Rebase/conflict resolution: PASS; rebased onto origin/main at e8845dae6 after GitHub reported the branch was behind current main. Conflicts were limited to generated Codex report artifacts and reports were regenerated.
+-- Latest rebase/conflict resolution: PASS; rebased onto origin/main at 195c90a64 after PR_26171_BETA_075 landed. Conflicts were resolved in generated Codex report artifacts and tests/playwright/tools/RootToolsFutureState.spec.mjs; the test expectations now preserve current-main Text To Speech coverage and ALPHA_047 canonical game-hub route/filter behavior.
+-- Final approved rebase: PASS; Team Alpha approval granted for ALPHA_047, rebased onto origin/main at 1451a1173, with conflicts limited to generated Codex report artifacts.
+-
+-Instruction compliance:
+-- PASS: docs_build/dev/PROJECT_INSTRUCTIONS.md was read before file changes.
+-- PASS: docs_build/dev/PROJECT_MULTI_PC.txt was read before file changes.
+-- PASS: BUILD_PR.md was created and read before implementation.
+-- PASS: Team Alpha owner scope applies to Game Hub, Game Journey handoff, and Idea Board continuity.
+-- PASS: Full samples smoke was not run.
+-
+-Validation:
+-- PASS: node --check toolbox/game-hub/game-hub.js
+-- PASS: node --check toolbox/game-hub/game-hub-api-client.js
+-- PASS: node --check toolbox/idea-board/index.js
+-- PASS: node --check toolbox/game-journey/game-journey.js
+-- PASS: node --check src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js
+-- PASS: node --check src/dev-runtime/server/local-api-router.mjs
+-- PASS: node --check tests/playwright/tools/GameHubMockRepository.spec.mjs
+-- PASS: node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
+-- PASS: node --check tests/playwright/tools/GameJourneyTool.spec.mjs
+-- PASS: npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs --project=playwright --workers=1 --reporter=line (11 passed)
+-- PASS: npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line (2 passed)
+-- PASS: npx playwright test tests/playwright/tools/GameJourneyTool.spec.mjs --project=playwright --workers=1 --reporter=line --grep "Game Hub hands the active game route to Game Journey|Game Journey requires an active game" (2 passed)
+-- PASS: npm run test:workspace-v2 (5 passed)
+-- PASS: git diff --check (line-ending warnings only)
+-- PASS: Completion revalidation for PR_26171_ALPHA_048 start-gate blocker; ALPHA_047 scoped validation was rerun while still on branch pr/26171-ALPHA-047-game-hub-canonical-path-journey-handoff, and ALPHA_048 implementation was not started.
+-- PASS: Latest post-rebase validation after origin/main 195c90a64: syntax checks, targeted Game Hub Playwright, targeted Idea Board Playwright, targeted Game Journey handoff Playwright, and npm run test:workspace-v2.
+-- PASS: Final approved validation after origin/main 1451a1173: syntax checks, targeted Game Hub Playwright, targeted Idea Board Playwright, targeted Game Journey handoff Playwright, and npm run test:workspace-v2.
+-
+-Requirement-by-requirement evidence:
+-- PASS: Rename active path toolbox/game-workspace/ to toolbox/game-hub/.
+-- PASS: Rename active game-workspace.js to game-hub.js.
+-- PASS: Rename active game-workspace-api-client.js to game-hub-api-client.js.
+-- PASS: Deep-audit and update imports, variables, data attributes, tests, toolbox navigation/registry, tool display mode slug, page titles, and creator-facing labels to Game Hub.
+-- PASS: Do not leave active navigation pointing to toolbox/game-workspace; active route audit found no active legacy route.
+-- PASS: Do not create duplicate active Game Hub paths; registry reads filter stale game-workspace rows and active metadata points to game-hub.
+-- PASS: Idea Board Create Project creates/links a Game Hub project when a Ready idea becomes Project.
+-- PASS: Project ideas lock original Idea, Pitch, and Notes.
+-- PASS: Game Hub shows Source Idea/Pitch/Notes as read-only creator-facing content.
+-- PASS: Game Journey receives executable Journey items from Idea notes when the linked project opens.
+-- PASS: Original Idea notes are not mutated or moved.
+-- PASS: Project/source-linked actions avoid Delete and use Open in Game Hub plus Archive.
+-- PASS: No real database migration was added; existing shared mock/server contracts are reused.
+-- PASS: Merge is not performed without explicit Team Alpha owner approval.
+-
+-Changed files:
+-- deleted: assets/theme-v2/images/badges/game-workspace-1024.png
+-- deleted: assets/theme-v2/images/badges/game-workspace.png
+-- added: assets/theme-v2/images/badges/game-hub-1024.png
+-- added: assets/theme-v2/images/badges/game-hub.png
+-- deleted: assets/theme-v2/images/tools/game-workspace-1024.png
+-- deleted: assets/theme-v2/images/tools/game-workspace.png
+-- added: assets/theme-v2/images/tools/game-hub-1024.png
+-- added: assets/theme-v2/images/tools/game-hub.png
+-- updated: assets/theme-v2/js/account-achievements.js
+-- updated: assets/theme-v2/js/gamefoundry-partials.js
+-- updated: assets/theme-v2/partials/header-nav.html
+-- updated: docs_build/database/ddl/game-workspace.sql
+-- updated: docs_build/database/dml/DML_INDEX.md
+-- updated: docs_build/database/dml/game-workspace.sql
+-- updated: docs_build/database/seed/game-workspace.json
+-- updated: docs_build/database/seed/guest/game-workspace.json
+-- updated: docs_build/dev/admin-notes/deployment-uat-prod/index.txt
+-- added: docs_build/dev/reports/codex_changed_files.txt
+-- added: docs_build/dev/reports/codex_review.diff
+-- added: docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/APPLY_PR.md
+-- added: docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/BUILD_PR.md
+-- added: docs_build/pr/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff/PLAN_PR.md
+-- deleted: learn/game-workspace/index.html
+-- added: learn/game-hub/index.html
+-- updated: learn/getting-started/index.html
+-- updated: learn/index.html
+-- updated: package.json
+-- updated: scripts/run-targeted-test-lanes.mjs
+-- updated: scripts/validate-browser-env-agnostic.mjs
+-- updated: scripts/validate-local-postgres-runtime.mjs
+-- updated: src/dev-runtime/admin/header-nav.local.html
+-- updated: src/dev-runtime/persistence/mock-db-store.js
+-- updated: src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js
+-- updated: src/dev-runtime/seed/server-seed-loader.mjs
+-- updated: src/dev-runtime/server/local-api-router.mjs
+-- updated: src/shared/toolbox/tool-metadata-inventory.js
+-- updated: tests/dev-runtime/DevRuntimeBoundary.test.mjs
+-- updated: tests/dev-runtime/ProductDataProviderContractHardening.test.mjs
+-- updated: tests/dev-runtime/SupabaseProductDataCutover.test.mjs
+-- updated: tests/playwright/account/AchievementsPage.spec.mjs
+-- updated: tests/playwright/tools/AdminDbViewer.spec.mjs
+-- added: tests/playwright/tools/GameHubMockRepository.spec.mjs
+-- updated: tests/playwright/tools/GameJourneyTool.spec.mjs
+-- deleted: tests/playwright/tools/GameWorkspaceMockRepository.spec.mjs
+-- updated: tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
+-- updated: tests/playwright/tools/RootToolsFutureState.spec.mjs
+-- updated: tests/playwright/tools/ToolDisplayModeNavigation.spec.mjs
+-- updated: tests/playwright/tools/ToolImageRegistry.spec.mjs
+-- updated: tests/playwright/tools/ToolNavigationPrevNext.spec.mjs
+-- updated: tests/playwright/tools/ToolboxRoutePages.spec.mjs
+-- updated: toolbox/colors/index.html
+-- updated: toolbox/controls/controls.js
+-- updated: toolbox/controls/index.html
+-- updated: toolbox/game-design/index.html
+-- added: toolbox/game-hub/game-hub-api-client.js
+-- added: toolbox/game-hub/game-hub.js
+-- added: toolbox/game-hub/index.html
+-- updated: toolbox/game-journey/game-journey.js
+-- deleted: toolbox/game-workspace/game-workspace-api-client.js
+-- deleted: toolbox/game-workspace/game-workspace.js
+-- deleted: toolbox/game-workspace/index.html
+-- updated: toolbox/idea-board/index.js
+-- updated: toolbox/project-workspace/index.html
+-- updated: toolbox/tools-page-accordions.js
+-
+-ZIP:
+-- Path: tmp/PR_26171_ALPHA_047-game-hub-canonical-path-journey-handoff_delta.zip
+-- Size: generated after branch push; final byte size is reported in the delivery response because the ZIP is not committed.
+-- Contents: repo-structured scoped delta files. Deleted legacy paths are documented in the changed-files list and are not represented as physical files in the archive.
++docs_build/dev/reports/codex_changed_files.txt
++docs_build/dev/reports/codex_review.diff
++docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-dead-code-cleanup-checklist.md
++docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-manual-validation-notes.md
++docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-message-tts-ux-checklist.md
++docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code-validation-report.md
++docs_build/dev/reports/PR_26171_BETA_083-message-tts-polish-and-dead-code.md
++tests/playwright/tools/MessagesTool.spec.mjs
++tests/playwright/tools/TextToSpeechFunctional.spec.mjs
++toolbox/messages/index.html
++toolbox/messages/messages.js
++toolbox/text-to-speech/index.html
++toolbox/text-to-speech/text2speech.js
+diff --git a/tests/playwright/tools/MessagesTool.spec.mjs b/tests/playwright/tools/MessagesTool.spec.mjs
+index 6c00fa6f5..69068a2e2 100644
+--- a/tests/playwright/tools/MessagesTool.spec.mjs
++++ b/tests/playwright/tools/MessagesTool.spec.mjs
+@@ -167,11 +167,16 @@ test("Message Studio renders Messages with child Message Parts and plays ordered
+ await expect(page.locator("[data-messages-tts-add-row]")).toHaveCount(0);
+ await expect(page.locator("[data-messages-emotions]")).toHaveCount(0);
+ await expect(page.locator("[data-messages-tts-profiles]")).toHaveCount(0);
+- await expect(page.getByRole("columnheader", { name: "Message Name" })).toBeVisible();
+- await expect(page.getByRole("columnheader", { name: "Default TTS Profile" })).toBeVisible();
++ await expect(page.locator("[data-messages-preview-tts-profile]")).toHaveCount(0);
++ await expect(page.locator("[data-messages-tts-service]")).toHaveCount(0);
++ await expect(page.locator("[data-messages-test-speech]")).toHaveCount(0);
++ await expect(page.getByText("Speech Test", { exact: true })).toHaveCount(0);
++ await expect(page.getByRole("columnheader", { name: "Message", exact: true })).toBeVisible();
++ await expect(page.getByRole("columnheader", { exact: true, name: "TTS Profile" })).toBeVisible();
+ await expect(page.locator("[data-messages-segment-count]")).toHaveText("0");
+ await expect(page.locator("[data-messages-add-control-row]")).toBeVisible();
+- await expect(page.getByRole("button", { name: "Stop" })).toBeEnabled();
++ await expect(page.getByRole("button", { name: "Stop Playback" })).toBeEnabled();
++ await expect(page.locator("[data-messages-playback-status]")).toHaveText("Ready for Message Studio playback.");
+
+ await page.getByRole("button", { name: "Add Message" }).click();
+ await expect(page.locator("[data-messages-row-editor='__new__']")).toBeVisible();
+@@ -209,7 +214,7 @@ test("Message Studio renders Messages with child Message Parts and plays ordered
+ await expect(page.getByRole("heading", { name: "Message Parts" })).toBeVisible();
+ const partsTable = page.getByLabel("Message parts");
+ await expect(partsTable.getByRole("columnheader", { name: "Order" })).toHaveCount(0);
+- await expect(partsTable.getByRole("columnheader", { name: "Text" })).toBeVisible();
++ await expect(partsTable.getByRole("columnheader", { name: "Part Text" })).toBeVisible();
+ await expect(partsTable.getByRole("columnheader", { name: "Emotion" })).toBeVisible();
+ await expect(partsTable.getByRole("columnheader", { exact: true, name: "TTS Profile" })).toBeVisible();
+ await expect(partsTable.getByRole("columnheader", { name: "Status" })).toBeVisible();
+@@ -251,6 +256,20 @@ test("Message Studio renders Messages with child Message Parts and plays ordered
+ volume: 1,
+ }),
+ ]));
++ const urgentPartRow = page.locator("[data-messages-segment-row]").filter({ hasText: "Keep your torch high." });
++ await urgentPartRow.locator("[data-segment-tts-profile]").evaluate((select) => {
++ const option = document.createElement("option");
++ option.value = "missing-profile";
++ option.textContent = "Missing Profile";
++ select.append(option);
++ select.value = option.value;
++ select.dispatchEvent(new Event("change", { bubbles: true }));
++ });
++ await urgentPartRow.getByRole("button", { name: "Play Part" }).click();
++ await expect(page.locator("[data-messages-validation-card]")).toBeVisible();
++ await expect(page.locator("[data-messages-validation-errors]")).toContainText("Select a TTS profile before playback.");
++ await expect(page.locator("[data-messages-playback-status]")).toHaveText("Select a TTS profile before playback.");
++ await page.locator("[data-messages-segment-row]").filter({ hasText: "Keep your torch high." }).locator("[data-segment-tts-profile]").selectOption({ label: "Default Balanced TTS Profile" });
+
+ await page.locator("[data-messages-row]").filter({ hasText: "Bat Encounter" }).getByRole("button", { name: "Play Message" }).click();
+ await expect(page.locator("[data-messages-log]")).toHaveText("Play Message queued 2 parts for Bat Encounter.");
+@@ -274,7 +293,7 @@ test("Message Studio renders Messages with child Message Parts and plays ordered
+ type: "speak",
+ voiceName: "Test Voice",
+ }));
+- await page.getByRole("button", { name: "Stop" }).click();
++ await page.getByRole("button", { name: "Stop Playback" }).click();
+ await expect(page.locator("[data-messages-log]")).toHaveText("Message Studio playback stopped. Cleared 2 queued items.");
+ speechCalls = await page.evaluate(() => window.__messagesSpeechCalls);
+ expect(speechCalls.at(-1)).toEqual({ type: "cancel" });
+@@ -359,13 +378,72 @@ test("Message Studio shows actionable playback error when audio engine is unavai
+ await expect(page.locator("[data-messages-validation-card]")).toBeVisible();
+ await expect(page.locator("[data-messages-validation-errors]")).toContainText("Audio engine is unavailable. Use a browser with SpeechSynthesis support and reload Message Studio.");
+ await expect(page.locator("[data-messages-log]")).toHaveText("Audio engine is unavailable. Use a browser with SpeechSynthesis support and reload Message Studio.");
+- await expect(page.locator("[data-messages-preview-status]")).toHaveText("Audio engine is unavailable. Use a browser with SpeechSynthesis support and reload Message Studio.");
++ await expect(page.locator("[data-messages-playback-status]")).toHaveText("Audio engine is unavailable. Use a browser with SpeechSynthesis support and reload Message Studio.");
++ expect(await page.evaluate(() => window.__messagesSpeechCalls)).toEqual([]);
++
++ expect(failures.failedRequests).toEqual([]);
++ expect(failures.pageErrors).toEqual([]);
++ expect(failures.consoleErrors).toEqual([]);
++ } finally {
++ await closeMessagesRun(failures, page);
++ await fs.rm(sqlitePath, { force: true });
++ }
++});
++
++test("Message Studio shows actionable playback error when selected TTS profile lacks the selected emotion", async ({ page }) => {
++ const sqlitePath = messagesDbPath();
++ await fs.rm(sqlitePath, { force: true });
++ await page.route("**/api/messages/tts-profiles", async (route) => {
++ if (route.request().method() !== "GET") {
++ await route.continue();
++ return;
++ }
++ await route.fulfill({
++ contentType: "application/json",
++ body: JSON.stringify({
++ data: {
++ persistence: { owner: "messages" },
++ ttsProfiles: [{
++ active: true,
++ emotionSettings: [{ active: true, emotion: "calm", emotionLabel: "Calm", pitch: 1, rate: 1, ssmlLikePreset: "normal", volume: 1 }],
++ key: "calm-only-profile",
++ language: "en-US",
++ name: "Calm Only Profile",
++ providerKey: "browser-speech",
++ voiceName: "",
++ }],
++ },
++ ok: true,
++ }),
++ });
++ });
++ const failures = await openMessagesPage(page, sqlitePath);
++
++ try {
++ await addMessage(page, {
++ emotion: "Urgent",
++ name: "Urgent Encounter",
++ text: "Danger is close.",
++ });
++ await openMessageParts(page, "Urgent Encounter");
++ await addPart(page, {
++ emotion: "Urgent",
++ order: 1,
++ text: "Danger is close.",
++ });
+
-+## Non-Scope
-+- No full database migration.
-+- No real Postgres/API expansion.
-+- No duplicate active Game Hub path.
-+- No merge without Team Alpha approval.
-diff --git a/learn/game-workspace/index.html b/learn/game-hub/index.html
-similarity index 77%
-rename from learn/game-workspace/index.html
-rename to learn/game-hub/index.html
-index 5353eeb4b..ac0d7e308 100644
---- a/learn/game-workspace/index.html
-+++ b/learn/game-hub/index.html
-@@ -4,8 +4,8 @@
-
-
-
-- Game Workspace Learn - GameFoundryStudio
--
-+ Game Hub Learn - GameFoundryStudio
-+
-
-
-
-@@ -15,8 +15,8 @@
-
-
-
--
Learn / Game Workspace
--
Game Workspace
-+
Learn / Game Hub
-+
Game Hub
-
Plan current focus, readiness, and recommended next tool for one game project.
-
-
-@@ -26,37 +26,37 @@
-
-
-
Overview
--
Game Workspace guidance overview placeholder for the future Learn system.
-+
Game Hub guidance overview placeholder for the future Learn system.
-
-
-
-
-
Quick Start
--
Static quick-start checklist for reviewing Game Workspace before real tutorials exist.
-+
Static quick-start checklist for reviewing Game Hub before real tutorials exist.
-
-
-
-
-
Common Tasks
--
Task cards will eventually describe frequent Game Workspace creator workflows.
-+
Task cards will eventually describe frequent Game Hub creator workflows.
-
-
-
-
-
Related Documentation
--
Documentation links for Game Workspace will be connected after the docs registry exists.
-+
Documentation links for Game Hub will be connected after the docs registry exists.
-
-
-
-
-
Related Videos
--
Video references for Game Workspace are planned; no embeds are active in this wireframe.
-+
Video references for Game Hub are planned; no embeds are active in this wireframe.
-
-
-
-
-
Examples
--
Example games and snippets for Game Workspace will be linked in a future implementation.
-+
Example games and snippets for Game Hub will be linked in a future implementation.
-
-
-
-@@ -68,7 +68,7 @@
- Continue learning
-
-
-
-diff --git a/learn/getting-started/index.html b/learn/getting-started/index.html
-index e1d340e29..2ab1e9005 100644
---- a/learn/getting-started/index.html
-+++ b/learn/getting-started/index.html
-@@ -68,7 +68,7 @@
- Continue learning
-
-
-
-diff --git a/learn/index.html b/learn/index.html
-index 9f40647b7..8c0ba1f40 100644
---- a/learn/index.html
-+++ b/learn/index.html
-@@ -79,7 +79,7 @@
- Colors
- Game Configuration
- Game Design
-- Game Workspace
-+ Game Hub
- Getting Started
- Objects
- Publish
-diff --git a/package.json b/package.json
-index 505b71d5f..d13364e3f 100644
---- a/package.json
-+++ b/package.json
-@@ -17,7 +17,7 @@
- "test:lane:tool-images": "node ./scripts/run-targeted-test-lanes.mjs --lane tool-images",
- "test:lane:game-configuration": "node ./scripts/run-targeted-test-lanes.mjs --lane game-configuration",
- "test:lane:game-design": "node ./scripts/run-targeted-test-lanes.mjs --lane game-design",
-- "test:lane:game-workspace": "node ./scripts/run-targeted-test-lanes.mjs --lane game-workspace",
-+ "test:lane:game-hub": "node ./scripts/run-targeted-test-lanes.mjs --lane game-hub",
- "test:lane:tool-runtime": "node ./scripts/run-targeted-test-lanes.mjs --lane tool-runtime",
- "test:lane:game-runtime": "node ./scripts/run-targeted-test-lanes.mjs --lane game-runtime",
- "test:lane:integration": "node ./scripts/run-targeted-test-lanes.mjs --lane integration",
-diff --git a/scripts/run-targeted-test-lanes.mjs b/scripts/run-targeted-test-lanes.mjs
-index 095584f64..3fef73319 100644
---- a/scripts/run-targeted-test-lanes.mjs
-+++ b/scripts/run-targeted-test-lanes.mjs
-@@ -122,17 +122,17 @@ const laneDefinitions = Object.freeze({
- requiresPreflight: true,
- reason: "Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes."
- },
-- "game-workspace": {
-- affectedSurface: "Game Workspace mock repository, Game Workspace UI, and Toolbox Progress/Build Path game-state bridge",
-+ "game-hub": {
-+ affectedSurface: "Game Hub mock repository, Game Hub UI, and Toolbox Progress/Build Path game-state bridge",
- commands: [
-- playwrightCommand("tests/playwright/tools/GameWorkspaceMockRepository.spec.mjs")
-+ playwrightCommand("tests/playwright/tools/GameHubMockRepository.spec.mjs")
- ],
- dependencies: [],
- discoveryTargets: [
-- "tests/playwright/tools/GameWorkspaceMockRepository.spec.mjs"
-+ "tests/playwright/tools/GameHubMockRepository.spec.mjs"
- ],
- fixtures: [
-- "repo-served Game Workspace page",
-+ "repo-served Game Hub page",
- "repo-served Toolbox page with role simulation",
- "in-memory SQL-shaped mock game repository"
- ],
-@@ -140,7 +140,7 @@ const laneDefinitions = Object.freeze({
- ownership: "tools",
- playwrightDir: "tests/playwright/tools",
- requiresPreflight: true,
-- reason: "Game Workspace rebuild slice validates mock users/games/game_members data actions, game lifecycle controls, and game-driven Progress/Build Path copy without exercising unrelated toolbox routes."
-+ reason: "Game Hub rebuild slice validates mock users/games/game_members data actions, game lifecycle controls, and game-driven Progress/Build Path copy without exercising unrelated toolbox routes."
- },
- "game-design": {
- affectedSurface: "Game Design mock repository, project purpose flow, validation overlay, capability demo authoring, and Toolbox progress handoff",
-@@ -155,7 +155,7 @@ const laneDefinitions = Object.freeze({
- "repo-served Game Design page",
- "repo-served Toolbox Progress and Build Path views",
- "in-memory SQL-shaped Game Design mock repository",
-- "Game Workspace mock game context"
-+ "Game Hub mock game context"
- ],
- fixturePaths: [],
- ownership: "tools",
-@@ -218,7 +218,7 @@ const laneDefinitions = Object.freeze({
- fixtures: [
- "repo-served Toolbox page",
- "repo-served Admin Tools Progress page",
-- "Game Workspace mock game context",
-+ "Game Hub mock game context",
- "Toolbox role simulation"
- ],
- fixturePaths: [],
-@@ -259,7 +259,7 @@ const laneDefinitions = Object.freeze({
- ],
- fixtures: [
- "repo-served Admin Tools Progress page",
-- "repo-served Game Workspace, Game Design, and Game Configuration tool pages",
-+ "repo-served Game Hub, Game Design, and Game Configuration tool pages",
- "repo-served Toolbox Group view with URL-selected accordion",
- "Toolbox registry build sequence and route metadata"
- ],
-@@ -279,7 +279,7 @@ const laneDefinitions = Object.freeze({
- "tests/playwright/tools/ToolDisplayModeNavigation.spec.mjs"
- ],
- fixtures: [
-- "repo-served Game Workspace, Game Design, Game Configuration, and AI Assistant tool pages",
-+ "repo-served Game Hub, Game Design, Game Configuration, and AI Assistant tool pages",
- "repo-served Toolbox Group view with URL-selected accordion",
- "Toolbox registry build sequence and route metadata",
- "shared Theme V2 Tool Display Mode script"
-diff --git a/scripts/validate-browser-env-agnostic.mjs b/scripts/validate-browser-env-agnostic.mjs
-index 2f8492652..ac69a4324 100644
---- a/scripts/validate-browser-env-agnostic.mjs
-+++ b/scripts/validate-browser-env-agnostic.mjs
-@@ -66,7 +66,7 @@ const productApiClientFiles = Object.freeze([
- "toolbox/game-configuration/game-configuration-api-client.js",
- "toolbox/game-design/game-design-api-client.js",
- "toolbox/game-journey/game-journey-api-client.js",
-- "toolbox/game-workspace/game-workspace-api-client.js",
-+ "toolbox/game-hub/game-hub-api-client.js",
- "toolbox/objects/objects-api-client.js",
- "toolbox/tags/tags-api-client.js",
- ]);
-diff --git a/scripts/validate-local-postgres-runtime.mjs b/scripts/validate-local-postgres-runtime.mjs
-index 26da8b5cd..23aeac932 100644
---- a/scripts/validate-local-postgres-runtime.mjs
-+++ b/scripts/validate-local-postgres-runtime.mjs
-@@ -294,13 +294,13 @@ async function validateApiRuntime({ adapter, client }) {
- const messageRow = savedBannerRows.find((row) => row.settingKey === "platform.banner.message");
- assert(messageRow?.settingValue === message, "Local Postgres platform_settings message row was not updated.");
-
-- const repository = await apiJson(server.baseUrl, "/api/toolbox/game-workspace/repositories", {
-+ const repository = await apiJson(server.baseUrl, "/api/toolbox/game-hub/repositories", {
- body: {},
- method: "POST",
- });
-- assert(repository.repositoryId, "Game Workspace repository was not created.");
-+ assert(repository.repositoryId, "Game Hub repository was not created.");
-
-- const created = await apiJson(server.baseUrl, `/api/toolbox/game-workspace/repositories/${repository.repositoryId}/methods/createGame`, {
-+ const created = await apiJson(server.baseUrl, `/api/toolbox/game-hub/repositories/${repository.repositoryId}/methods/createGame`, {
- body: {
- args: [
- {
-@@ -312,14 +312,14 @@ async function validateApiRuntime({ adapter, client }) {
- },
- method: "POST",
- });
-- assert(created.result?.name === GAME_NAME, "Game Workspace create did not return the validation game.");
-+ assert(created.result?.name === GAME_NAME, "Game Hub create did not return the validation game.");
-
-- const active = await apiJson(server.baseUrl, `/api/toolbox/game-workspace/repositories/${repository.repositoryId}/methods/getActiveGame`, {
-+ const active = await apiJson(server.baseUrl, `/api/toolbox/game-hub/repositories/${repository.repositoryId}/methods/getActiveGame`, {
- body: { args: [] },
- method: "POST",
- });
-- assert(active.result?.name === GAME_NAME, "Game Workspace active game did not resolve the validation game.");
-- assert(Array.isArray(active.result?.members), "Game Workspace active game did not include members.");
-+ assert(active.result?.name === GAME_NAME, "Game Hub active game did not resolve the validation game.");
-+ assert(Array.isArray(active.result?.members), "Game Hub active game did not include members.");
++ await page.locator("[data-messages-segment-row]").filter({ hasText: "Danger is close." }).getByRole("button", { name: "Play Part" }).click();
++ const expectedError = 'Selected TTS Profile "Calm Only Profile" does not include an Emotion Setting for "Urgent".';
++ await expect(page.locator("[data-messages-validation-card]")).toBeVisible();
++ await expect(page.locator("[data-messages-validation-errors]")).toContainText(expectedError);
++ await expect(page.locator("[data-messages-playback-status]")).toHaveText(expectedError);
+ expect(await page.evaluate(() => window.__messagesSpeechCalls)).toEqual([]);
- const gameRows = await adapter.getProductTableRows("game_workspace_games");
- assert(gameRows.some((row) => row.name === GAME_NAME), "Local Postgres game_workspace_games did not include the validation game.");
-@@ -373,8 +373,8 @@ async function main() {
- console.log(`PASS - Seeded identity rows: users=${identity.written.users}, roles=${identity.written.roles}, user_roles=${identity.written.user_roles}.`);
- console.log(`PASS - Banner saved and read through API; local platform_settings banner rows observed=${apiEvidence.bannerRows}.`);
- console.log(`PASS - Supabase Auth health calls=${fakeSupabase.requests.filter((request) => request.path === "/auth/v1/health").length}; Supabase platform_settings REST calls=0.`);
-- console.log(`PASS - Game Workspace create/getActiveGame used repository ${apiEvidence.repositoryId}; local game rows observed=${apiEvidence.gameRows}.`);
-- console.log("PASS - Validation cleanup restored banner rows and removed PR197 Game Workspace rows.");
-+ console.log(`PASS - Game Hub create/getActiveGame used repository ${apiEvidence.repositoryId}; local game rows observed=${apiEvidence.gameRows}.`);
-+ console.log("PASS - Validation cleanup restored banner rows and removed PR197 Game Hub rows.");
+ expect(failures.failedRequests).toEqual([]);
+ expect(failures.pageErrors).toEqual([]);
+ expect(failures.consoleErrors).toEqual([]);
} finally {
- await fakeSupabase.close();
- Object.entries(originalSupabaseEnv).forEach(([key, value]) => {
-diff --git a/src/dev-runtime/admin/header-nav.local.html b/src/dev-runtime/admin/header-nav.local.html
-index f2c805256..f49f136ce 100644
---- a/src/dev-runtime/admin/header-nav.local.html
-+++ b/src/dev-runtime/admin/header-nav.local.html
-@@ -35,7 +35,7 @@
- Custom Extensions
- Game Configuration
- Game Design
-- Game Hub
-+ Game Hub
- Game Journey
-
-
-diff --git a/src/dev-runtime/persistence/mock-db-store.js b/src/dev-runtime/persistence/mock-db-store.js
-index 543834d94..2305910bc 100644
---- a/src/dev-runtime/persistence/mock-db-store.js
-+++ b/src/dev-runtime/persistence/mock-db-store.js
-@@ -34,7 +34,7 @@ export const MOCK_DB_SESSION_MODES = Object.freeze([
- ]);
-
- export const MOCK_DB_TOOL_GROUPS = Object.freeze({
-- "game-workspace": Object.freeze({
-+ "game-hub": Object.freeze({
- label: "Game Hub",
- tableNames: Object.freeze(["game_workspace_games", "game_workspace_progress"]),
- }),
-diff --git a/src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js b/src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js
-index bd537110b..1bbbc2527 100644
---- a/src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js
-+++ b/src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js
-@@ -100,6 +100,7 @@ const GENERATED_ULID_SEQUENCE = Object.freeze({
- });
- const RECOMMENDED_TARGET_LINKED_RECORD_TYPE = "recommended-target";
- const RECOMMENDED_TARGET_NOTE_KEY = GAME_JOURNEY_KEYS.notes.designPass;
-+const SOURCE_IDEA_LINKED_RECORD_TYPE = "source-idea-note";
-
- export const GAME_JOURNEY_STATUSES = [
- {
-@@ -645,6 +646,13 @@ export function createGameJourneyMockRepository(options = {}) {
- return item?.linkedRecordType === RECOMMENDED_TARGET_LINKED_RECORD_TYPE;
++ await page.unroute("**/api/messages/tts-profiles");
+ await closeMessagesRun(failures, page);
+ await fs.rm(sqlitePath, { force: true });
}
+diff --git a/tests/playwright/tools/TextToSpeechFunctional.spec.mjs b/tests/playwright/tools/TextToSpeechFunctional.spec.mjs
+index c9a659a4d..bf68c80f4 100644
+--- a/tests/playwright/tools/TextToSpeechFunctional.spec.mjs
++++ b/tests/playwright/tools/TextToSpeechFunctional.spec.mjs
+@@ -120,6 +120,8 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as
+ await expect(page.locator("[data-tts-profile-table]")).toContainText("Default Balanced Profile");
+ await expect(page.locator("[data-tts-profile-table]")).toContainText("Man Profile 1");
+ await expect(page.locator("[data-tts-profile-table]")).toContainText("Woman Profile 2");
++ await expect(page.getByRole("columnheader", { exact: true, name: "Profile" })).toBeVisible();
++ await expect(page.getByRole("columnheader", { name: "Age Filter" })).toBeVisible();
+ const defaultProfileRow = page.locator("[data-tts-profile-row]").filter({ hasText: "Default Balanced Profile" });
+ await expect(defaultProfileRow.getByRole("button", { name: "Delete" })).toBeDisabled();
+ await expect(page.locator("[data-tts-emotion-host]")).toHaveCount(0);
+@@ -130,7 +132,7 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as
+ await expect(defaultProfileRow.locator("[data-tts-profile-name-cell]")).toHaveAttribute("aria-expanded", "true");
+ await expect(page.getByRole("heading", { name: "Emotion Settings" })).toBeVisible();
+ await expect(page.getByRole("columnheader", { name: "Emotion", exact: true })).toBeVisible();
+- await expect(page.getByRole("columnheader", { name: "Preset" })).toBeVisible();
++ await expect(page.getByRole("columnheader", { name: "Delivery Preset" })).toBeVisible();
+ await expect(page.locator("[data-tts-emotion-row]")).toHaveCount(4);
+ await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Neutral" }).getByRole("button", { name: "Delete" })).toBeDisabled();
+ await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Happy" })).toBeVisible();
+diff --git a/toolbox/messages/index.html b/toolbox/messages/index.html
+index 55cbd6ccb..363fe4a34 100644
+--- a/toolbox/messages/index.html
++++ b/toolbox/messages/index.html
+@@ -64,11 +64,11 @@
+
+
+
+- | Message Name |
++ Message |
+ Type |
+ Status |
+ Parts |
+- Default TTS Profile |
++ TTS Profile |
+ Actions |
+
+
+@@ -109,18 +109,12 @@
+
+
+
+- Speech Test
++ Playback
+
+-
+-
+-
+-
+-
Selected item: None
+
+-
+-
++
+
+-
Select a message row or segment row before testing speech.
++
Loading playback engine.
+
+
+
+diff --git a/toolbox/messages/messages.js b/toolbox/messages/messages.js
+index 4af71adaa..5fdd0a47b 100644
+--- a/toolbox/messages/messages.js
++++ b/toolbox/messages/messages.js
+@@ -39,20 +39,16 @@ const elements = {
+ persistenceEngine: document.querySelector("[data-messages-persistence-engine]"),
+ persistenceOwner: document.querySelector("[data-messages-persistence-owner]"),
+ persistenceSource: document.querySelector("[data-messages-persistence-source]"),
+- previewStatus: document.querySelector("[data-messages-preview-status]"),
+- previewTtsProfile: document.querySelector("[data-messages-preview-tts-profile]"),
++ playbackStatus: document.querySelector("[data-messages-playback-status]"),
+ selectedEmotion: document.querySelector("[data-messages-selected-emotion]"),
+ selectedName: document.querySelector("[data-messages-selected-name]"),
+ selectedSegment: document.querySelector("[data-messages-selected-segment]"),
+ selectedStatus: document.querySelector("[data-messages-selected-status]"),
+ selectedText: document.querySelector("[data-messages-selected-text]"),
+ segmentCount: document.querySelector("[data-messages-segment-count]"),
+- speechTestTarget: document.querySelector("[data-messages-speech-test-target]"),
+ stopSpeech: document.querySelector("[data-messages-stop-speech]"),
+ table: document.querySelector("[data-messages-table]"),
+- testSpeech: document.querySelector("[data-messages-test-speech]"),
+ ttsCount: document.querySelector("[data-messages-tts-count]"),
+- ttsService: document.querySelector("[data-messages-tts-service]"),
+ validationCard: document.querySelector("[data-messages-validation-card]"),
+ validationErrors: document.querySelector("[data-messages-validation-errors]"),
+ };
+@@ -173,28 +169,6 @@ function createTtsProfileSelect(value, dataName, identityKey) {
+ return select;
+ }
-+ function journeyGameKey(game) {
-+ const gameId = String(game?.id || "").trim();
-+ return !gameId || gameId === GAME_JOURNEY_ROUTE_GAME_ALIAS
-+ ? GAME_JOURNEY_KEYS.game
-+ : `game-hub:${gameId}`;
-+ }
-+
- function currentUserCanSeeNote(note) {
- const sessionUser = currentSessionUser();
- return Boolean(sessionUser.userKey && (sessionUser.isAdmin || note.ownerKey === sessionUser.userKey));
-@@ -679,6 +687,102 @@ export function createGameJourneyMockRepository(options = {}) {
- persistTables();
- }
+-function populateSelect(select, options, placeholder) {
+- if (!select) {
+- return;
+- }
+- const currentValue = select.value;
+- select.replaceChildren();
+- const placeholderOption = document.createElement("option");
+- placeholderOption.value = "";
+- placeholderOption.textContent = placeholder;
+- select.append(placeholderOption);
+- options.forEach((optionValue) => {
+- const option = document.createElement("option");
+- option.value = optionValue.key;
+- option.textContent = optionValue.name;
+- option.disabled = optionValue.disabled === true;
+- select.append(option);
+- });
+- select.value = options.some((optionValue) => optionValue.key === currentValue && optionValue.disabled !== true)
+- ? currentValue
+- : "";
+-}
+-
+ function createField(labelText, field) {
+ const label = document.createElement("label");
+ const span = document.createElement("span");
+@@ -307,57 +281,32 @@ function defaultTtsProfileKey() {
+ }
-+ function slugSegment(value, fallback = "source-idea") {
-+ const slug = String(value || "")
-+ .toLowerCase()
-+ .replace(/[^a-z0-9]+/g, "-")
-+ .replace(/^-+|-+$/g, "");
-+ return slug || fallback;
-+ }
-+
-+ function normalizeSourceIdeaNotes(sourceIdea) {
-+ return Array.isArray(sourceIdea?.notes)
-+ ? sourceIdea.notes.map((note) => String(note || "").trim()).filter(Boolean)
-+ : [];
-+ }
-+
-+ function sourceIdeaJourneyNoteName(sourceIdea) {
-+ const idea = String(sourceIdea?.idea || "").trim();
-+ return idea ? `Source Idea: ${idea}` : "Source Idea";
-+ }
-+
-+ function ensureSourceIdeaJourneyItems(activeGame) {
-+ const sourceIdea = activeGame?.sourceIdea && typeof activeGame.sourceIdea === "object"
-+ ? activeGame.sourceIdea
-+ : null;
-+ const sourceNotes = normalizeSourceIdeaNotes(sourceIdea);
-+ if (!activeGame || !sourceNotes.length) {
-+ return [];
-+ }
-+
-+ const ownerKey = safeCurrentUserKey();
-+ const timestampValue = new Date().toISOString();
-+ const noteSlug = `source-idea-${slugSegment(activeGame.id || sourceIdea?.idea)}`;
-+ let note = tables.game_journey_notes.find(
-+ (candidate) => candidate.gameKey === activeGame.key && candidate.slug === noteSlug,
-+ );
-+
-+ if (!note) {
-+ note = {
-+ key: makeUlid(nextNoteNumber),
-+ slug: noteSlug,
-+ gameKey: activeGame.key,
-+ ownerKey,
-+ name: sourceIdeaJourneyNoteName(sourceIdea),
-+ typeKey: GAME_JOURNEY_KEYS.noteTypes.idea,
-+ createdAt: timestampValue,
-+ updatedAt: timestampValue,
-+ createdBy: ownerKey,
-+ updatedBy: ownerKey,
-+ };
-+ nextNoteNumber += 1;
-+ tables.game_journey_notes.push(note);
-+ }
-+
-+ const existingLinkedIds = new Set(
-+ getItemsForNote(note.key)
-+ .filter((item) => item.linkedRecordType === SOURCE_IDEA_LINKED_RECORD_TYPE)
-+ .map((item) => item.linkedRecordId),
-+ );
-+ const created = [];
-+ sourceNotes.forEach((sourceNote, index) => {
-+ const linkedRecordId = `${slugSegment(activeGame.id || activeGame.key)}:${index + 1}:${slugSegment(sourceNote).slice(0, 48)}`;
-+ if (existingLinkedIds.has(linkedRecordId)) {
-+ return;
-+ }
-+ const existingItems = getItemsForNote(note.key);
-+ const item = {
-+ key: makeUlid(nextItemNumber),
-+ gameKey: activeGame.key,
-+ noteKey: note.key,
-+ status: "not-started",
-+ title: sourceNote,
-+ userDetails: "",
-+ createdBy: ownerKey,
-+ updatedBy: ownerKey,
-+ templateKey: "",
-+ linkedRecordType: SOURCE_IDEA_LINKED_RECORD_TYPE,
-+ linkedRecordId,
-+ indent: 0,
-+ order: existingItems.length + 1,
-+ createdAt: timestampValue,
-+ updatedAt: timestampValue,
-+ };
-+ nextItemNumber += 1;
-+ tables.game_journey_items.push(item);
-+ created.push(hydrateItem(item));
-+ });
-+
-+ if (created.length) {
-+ selectedNoteKey = note.key;
-+ selectedItemKey = created[0]?.key || selectedItemKey;
-+ touchNote(note.key, ownerKey);
-+ addActivity(activeGame.key, note.key, `Created ${created.length} Game Journey item${created.length === 1 ? "" : "s"} from Source Idea.`, ownerKey);
-+ }
-+
-+ return created;
+ function ttsProfileOptionByKey(profileKey) {
+- return activeTtsProfileOptions().find((profile) => profile.key === profileKey)
+- || activeTtsProfileOptions()[0]
+- || DEFAULT_TTS_PROFILE;
++ const activeProfiles = activeTtsProfileOptions();
++ if (!profileKey) {
++ return activeProfiles[0] || DEFAULT_TTS_PROFILE;
+ }
-+
- function getActiveGame() {
- const game = gameWorkspaceRepository.getActiveGame();
- if (!game) {
-@@ -686,7 +790,7 @@ export function createGameJourneyMockRepository(options = {}) {
- }
- return {
- ...game,
-- key: GAME_JOURNEY_KEYS.game,
-+ key: journeyGameKey(game),
- };
- }
++ return activeProfiles.find((profile) => profile.key === profileKey) || null;
+ }
-@@ -1392,7 +1496,11 @@ export function createGameJourneyMockRepository(options = {}) {
- function openGame(gameId) {
- const workspaceGameId =
- gameId === GAME_JOURNEY_KEYS.game ? GAME_JOURNEY_ROUTE_GAME_ALIAS : gameId;
-- return gameWorkspaceRepository.openGame(workspaceGameId);
-+ const openedGame = gameWorkspaceRepository.openGame(workspaceGameId);
-+ if (openedGame) {
-+ ensureSourceIdeaJourneyItems(getActiveGame());
-+ }
-+ return openedGame;
- }
+ function selectedTtsProfileForMessage(messageKey) {
+- return ttsProfileOptionByKey(state.messageTtsProfileKeys.get(messageKey) || defaultTtsProfileKey());
++ const profileKey = state.messageTtsProfileKeys.get(messageKey);
++ return ttsProfileOptionByKey(profileKey || defaultTtsProfileKey());
+ }
- return {
-diff --git a/src/dev-runtime/seed/server-seed-loader.mjs b/src/dev-runtime/seed/server-seed-loader.mjs
-index 170f40137..f67e53065 100644
---- a/src/dev-runtime/seed/server-seed-loader.mjs
-+++ b/src/dev-runtime/seed/server-seed-loader.mjs
-@@ -649,7 +649,7 @@ function humanToolStateSampleRows(generateKey) {
- { displayName: "User 1", toolKey: "game-journey", userKey: SEED_DB_KEYS.users.user1 },
- { displayName: "User 2", toolKey: "palette", userKey: SEED_DB_KEYS.users.user2 },
- { displayName: "User 3", toolKey: "asset", userKey: SEED_DB_KEYS.users.user3 },
-- { displayName: "DavidQ", toolKey: "game-workspace", userKey: SEED_DB_KEYS.users.admin },
-+ { displayName: "DavidQ", toolKey: "game-hub", userKey: SEED_DB_KEYS.users.admin },
- ];
- return users.map((user, index) => {
- const tool = toolByKey.get(user.toolKey) || tools[index] || {
-diff --git a/src/dev-runtime/server/local-api-router.mjs b/src/dev-runtime/server/local-api-router.mjs
-index 9b98fca14..79a846f6d 100644
---- a/src/dev-runtime/server/local-api-router.mjs
-+++ b/src/dev-runtime/server/local-api-router.mjs
-@@ -201,7 +201,7 @@ const DB_VIEWER_GROUP_ORDER = Object.freeze([
- Object.freeze({ id: "game-configuration", label: "Game Configuration", ownerId: "game-configuration", type: "tool" }),
- Object.freeze({ id: "game-design", label: "Game Design", ownerId: "game-design", type: "tool" }),
- Object.freeze({ id: "game-journey", label: "Game Journey", ownerId: "game-journey", type: "tool" }),
-- Object.freeze({ id: "game-workspace", label: "Game Hub", ownerId: "game-workspace", type: "tool" }),
-+ Object.freeze({ id: "game-hub", label: "Game Hub", ownerId: "game-hub", type: "tool" }),
- Object.freeze({ id: "objects", label: "Objects", ownerId: "objects", type: "tool" }),
- Object.freeze({ id: "palette", label: "Palette", ownerId: "palette", type: "tool" }),
- Object.freeze({ id: "tags", label: "Tags", ownerId: "tags", type: "tool" }),
-@@ -1427,7 +1427,7 @@ function normalizedToolKey(row) {
- return String(row?.toolKey || row?.toolId || row?.id || "").trim();
+ function selectedTtsProfileForSegment(segmentKey, messageKey = state.selectedMessageKey) {
+- return ttsProfileOptionByKey(
+- state.segmentTtsProfileKeys.get(segmentKey)
+- || state.messageTtsProfileKeys.get(messageKey)
+- || defaultTtsProfileKey(),
+- );
+-}
+-
+-function selectedTtsProfile() {
+- return ttsProfileOptionByKey(elements.previewTtsProfile?.value || defaultTtsProfileKey());
++ const segmentProfileKey = state.segmentTtsProfileKeys.get(segmentKey);
++ if (segmentProfileKey) {
++ return ttsProfileOptionByKey(segmentProfileKey);
++ }
++ const messageProfileKey = state.messageTtsProfileKeys.get(messageKey);
++ if (messageProfileKey) {
++ return ttsProfileOptionByKey(messageProfileKey);
++ }
++ return ttsProfileOptionByKey(defaultTtsProfileKey());
}
--const SOURCE_CONTROLLED_TOOLBOX_TOOL_IDS = new Set(["game-workspace", "idea-board", "messages", "tags", "text-to-speech", "users"]);
-+const SOURCE_CONTROLLED_TOOLBOX_TOOL_IDS = new Set(["game-hub", "idea-board", "messages", "tags", "text-to-speech", "users"]);
- const SOURCE_CONTROLLED_TOOLBOX_METADATA_FIELDS = Object.freeze([
- "active",
- "adminOnly",
-@@ -1450,6 +1450,20 @@ const SOURCE_CONTROLLED_TOOLBOX_METADATA_FIELDS = Object.freeze([
- "toolboxGroup",
- "visibleInToolsList",
- ]);
-+
-+function isGameHubToolId(toolId) {
-+ return ["game-hub", "game-workspace"].includes(String(toolId || ""));
-+}
-+
-+function isLegacyGameWorkspaceToolId(toolId) {
-+ return String(toolId || "") === "game-workspace";
-+}
-+
-+function withoutLegacyGameWorkspaceToolRows(rows) {
-+ return Array.isArray(rows)
-+ ? rows.filter((row) => !isLegacyGameWorkspaceToolId(normalizedToolKey(row)))
-+ : [];
-+}
- const SOURCE_CONTROLLED_TOOLBOX_PLANNING_FIELDS = Object.freeze([
- "progressChecklist",
- ]);
-@@ -2027,7 +2041,7 @@ function gameWorkspaceGameKey(gameId) {
- if (isUlidKey(normalizedGameId)) {
- return normalizedGameId;
- }
-- return runtimeGeneratedKeyForSource(`game-workspace-game:${normalizedGameId}`);
-+ return runtimeGeneratedKeyForSource(`game-hub-game:${normalizedGameId}`);
+ function selectedTtsService() {
+- return state.ttsServices.find((service) => service.key === elements.ttsService?.value) || null;
+-}
+-
+-function selectedSpeechTarget() {
+- const segment = selectedSegment();
+- if (segment) {
+- return {
+- emotionProfile: emotionProfileByKey(segment.emotionProfileKey),
+- id: segment.key,
+- label: `Part ${segment.displayOrder}`,
+- name: `${segment.messageName || "Message"} part ${segment.displayOrder}`,
+- profile: selectedTtsProfileForSegment(segment.key, segment.messageKey),
+- text: segment.segmentText,
+- type: "part",
+- };
+- }
+- const message = selectedMessage();
+- if (!message) {
+- return null;
+- }
+- return {
+- emotionProfile: emotionProfileByKey(message.emotionProfileKey),
+- id: message.key,
+- label: `Message: ${message.name}`,
+- name: message.name,
+- profile: selectedTtsProfileForMessage(message.key),
+- text: message.messageText,
+- type: "message",
+- };
++ return state.ttsServices.find((service) => service.available) || null;
}
- function votePercent(count, total) {
-@@ -2055,11 +2069,11 @@ function gameWorkspaceTables(repository) {
- status: project.status,
- }));
- const activeGameKey = gameWorkspaceGameKey(activeGame?.id);
-- return normalizeOwnedTables("game-workspace", {
-+ return normalizeOwnedTables("game-hub", {
- game_workspace_games: gameWorkspaceGames,
- game_workspace_progress: activeGame ? [{
- ...snapshotAuditFields(80, SEED_DB_KEYS.users.user1),
-- key: runtimeGeneratedKeyForSource(`game-workspace-progress:${activeGameKey}`),
-+ key: runtimeGeneratedKeyForSource(`game-hub-progress:${activeGameKey}`),
- gameKey: activeGameKey,
- currentFocus: progress.currentFocus,
- gameProgress: progress.gameProgress,
-@@ -4680,8 +4694,14 @@ LIMIT 1;
+ function messageSegments(messageKey) {
+@@ -409,78 +358,25 @@ function renderSelectedMessage() {
+ setText(elements.selectedText, segment?.segmentText || selected?.messageText || "No message selected.");
+ }
- ensureToolboxToolMetadataRows() {
- const rows = this.toolboxToolMetadataRows();
-- const activeTools = getActiveToolRegistry();
- let changed = false;
-+ for (let index = rows.length - 1; index >= 0; index -= 1) {
-+ if (isLegacyGameWorkspaceToolId(normalizedToolKey(rows[index]))) {
-+ rows.splice(index, 1);
-+ changed = true;
-+ }
-+ }
-+ const activeTools = getActiveToolRegistry();
- activeTools.forEach((tool, index) => {
- const defaults = this.defaultToolboxMetadata(tool, index);
- const existingRow = rows.find((row) => (row.toolKey || row.toolId) === tool.id);
-@@ -4796,7 +4816,7 @@ LIMIT 1;
+-function renderTtsServiceOptions() {
++function refreshTtsServices() {
+ state.ttsServices = ttsServiceRegistry.listServices();
+- const options = state.ttsServices.map((service) => ({
+- disabled: !service.available,
+- key: service.key,
+- name: service.available ? service.name : `${service.name} unavailable`,
+- }));
+- populateSelect(elements.ttsService, options, "No TTS service available");
+- const availableService = state.ttsServices.find((service) => service.available);
+- if (!elements.ttsService?.value && availableService) {
+- elements.ttsService.value = availableService.key;
+- }
+- if (elements.ttsService) {
+- elements.ttsService.disabled = !availableService;
+- }
+ }
- async supabaseToolboxToolMetadataRows() {
- const adapter = this.supabaseDatabaseAdapter("Reading Supabase Toolbox tool metadata");
-- const rows = await adapter.getProductTableRows("toolbox_tool_metadata");
-+ const rows = withoutLegacyGameWorkspaceToolRows(await adapter.getProductTableRows("toolbox_tool_metadata"));
- const activeTools = getActiveToolRegistry();
- const rowsByToolKey = new Map(rows.map((row) => [row.toolKey || row.toolId, row]));
- const existingToolKeys = new Set(rowsByToolKey.keys());
-@@ -4818,7 +4838,7 @@ LIMIT 1;
- });
- if (missingRows.length || syncedRows.length) {
- await adapter.upsertProductTable("toolbox_tool_metadata", [...missingRows, ...syncedRows]);
-- return adapter.getProductTableRows("toolbox_tool_metadata");
-+ return withoutLegacyGameWorkspaceToolRows(await adapter.getProductTableRows("toolbox_tool_metadata"));
+-function renderTtsProfileOptions() {
+- const activeProfiles = activeTtsProfileOptions();
+- populateSelect(elements.previewTtsProfile, activeProfiles, "Select TTS profile");
+- const selected = selectedTtsProfile();
+- if (!selected && activeProfiles[0]) {
+- elements.previewTtsProfile.value = activeProfiles[0].key;
+- }
+- if (elements.previewTtsProfile) {
+- elements.previewTtsProfile.disabled = activeProfiles.length === 0;
+- }
+-}
+-
+-function speechTestReadiness() {
++function playbackReadinessMessage() {
++ refreshTtsServices();
+ const service = selectedTtsService();
+- const profile = selectedTtsProfile();
+- const target = selectedSpeechTarget();
+ if (!service) {
+ const unavailableService = state.ttsServices.find((candidate) => !candidate.available);
+ if (unavailableService) {
+- return { message: unavailableService.unavailableMessage || "No TTS service is available in this browser.", ok: false };
++ return unavailableService.unavailableMessage || "No TTS service is available in this browser.";
}
- return rows;
+- return { message: "No TTS service is selected.", ok: false };
+- }
+- if (!service.available) {
+- return { message: service.unavailableMessage || "Selected TTS service is unavailable.", ok: false };
+- }
+- if (!target) {
+- return { message: "Select a message row or segment row before testing speech.", ok: false };
+- }
+- if (!profile) {
+- return { message: "Select an active TTS profile before testing speech.", ok: false };
+- }
+- if (!target.emotionProfile) {
+- return { message: "Selected item needs an Emotion before testing speech.", ok: false };
+- }
+- const emotionSetting = selectedEmotionSettingForProfile(profile, target.emotionProfile);
+- if (!emotionSetting.ok) {
+- return { message: emotionSetting.message, ok: false };
+- }
+- if (!String(target.text || "").trim()) {
+- return { message: "Selected item needs message text before testing speech.", ok: false };
++ return "No TTS service is available in this browser.";
}
-@@ -4912,7 +4932,7 @@ LIMIT 1;
- toolRegistrySnapshot() {
- const planningRows = this.ensureToolboxToolPlanningRows();
- const planningByToolKey = new Map(planningRows.map((row) => [row.toolKey, row]));
-- const tools = this.ensureToolboxToolMetadataRows()
-+ const tools = withoutLegacyGameWorkspaceToolRows(this.ensureToolboxToolMetadataRows())
- .map((row, index) => serverRegistryTool({
- ...row,
- ...planningByToolKey.get(normalizedToolKey(row)),
-@@ -5082,7 +5102,7 @@ LIMIT 1;
- repositoryForTool(toolId) {
- this.assertProductDatabaseProvider(`Opening ${toolId} repository`);
- if (toolId === "workspace") return this.gameWorkspaceRepository;
-- if (toolId === "game-workspace") return this.gameWorkspaceRepository;
-+ if (isGameHubToolId(toolId)) return this.gameWorkspaceRepository;
- if (toolId === "game-design") return this.gameDesignRepository;
- if (toolId === "game-configuration") return this.gameConfigurationRepository;
- if (toolId === "objects") return this.objectsRepository;
-@@ -5097,7 +5117,7 @@ LIMIT 1;
- }
-
- constantsForTool(toolId) {
-- if (toolId === "game-workspace") {
-+ if (isGameHubToolId(toolId)) {
- return {
- GAME_WORKSPACE_MEMBER_ROLES,
- GAME_WORKSPACE_GAME_PURPOSES,
-diff --git a/src/shared/toolbox/tool-metadata-inventory.js b/src/shared/toolbox/tool-metadata-inventory.js
-index 9c81eb6ef..7a4cb7587 100644
---- a/src/shared/toolbox/tool-metadata-inventory.js
-+++ b/src/shared/toolbox/tool-metadata-inventory.js
-@@ -133,16 +133,16 @@ export const TOOL_REGISTRY = Object.freeze([
- "toolboxGroup": "Create"
- },
- {
-- "id": "game-workspace",
-+ "id": "game-hub",
- "name": "Game Hub",
- "displayName": "Game Hub",
- "shortDescription": "Coordinate Build, Play, and Share readiness for one game project.",
- "shortLabel": "Game Hub",
-- "path": "game-workspace",
-- "folderName": "game-workspace",
-- "entryPoint": "game-workspace/index.html",
-- "badge": "/assets/theme-v2/images/badges/game-workspace.png",
-- "tool": "/assets/theme-v2/images/tools/game-workspace.png",
-+ "path": "game-hub",
-+ "folderName": "game-hub",
-+ "entryPoint": "game-hub/index.html",
-+ "badge": "/assets/theme-v2/images/badges/game-hub.png",
-+ "tool": "/assets/theme-v2/images/tools/game-hub.png",
- "description": "Coordinate Build, Play, and Share readiness for one game project.",
- "category": "Build/Create",
- "colorGroup": "tool-group-build",
-@@ -173,8 +173,8 @@ export const TOOL_REGISTRY = Object.freeze([
- "path": "game-journey",
- "folderName": "game-journey",
- "entryPoint": "game-journey/index.html",
-- "badge": "/assets/theme-v2/images/badges/game-workspace.png",
-- "tool": "/assets/theme-v2/images/tools/game-workspace.png",
-+ "badge": "/assets/theme-v2/images/badges/game-hub.png",
-+ "tool": "/assets/theme-v2/images/tools/game-hub.png",
- "description": "Track game notes, nested work rows, status counts, and suggested toolbox handoffs.",
- "category": "Build/Create",
- "colorGroup": "tool-group-build",
-@@ -184,7 +184,7 @@ export const TOOL_REGISTRY = Object.freeze([
- "requiredForTestable": true,
- "requiredForPublish": true,
- "requires": [
-- "game-workspace"
-+ "game-hub"
- ],
- "status": "beta",
- "releaseChannel": "beta",
-@@ -219,7 +219,7 @@ export const TOOL_REGISTRY = Object.freeze([
- "requiredForTestable": true,
- "requiredForPublish": true,
- "requires": [
-- "game-workspace"
-+ "game-hub"
- ],
- "status": "Ready",
- "releaseChannel": "beta",
-@@ -371,7 +371,7 @@ export const TOOL_REGISTRY = Object.freeze([
- "requiredForTestable": true,
- "requiredForPublish": true,
- "requires": [
-- "game-workspace"
-+ "game-hub"
- ],
- "status": "beta",
- "releaseChannel": "beta",
-@@ -1680,7 +1680,7 @@ const AVAILABLE_TOOL_IMAGE_PATHS = Object.freeze([
- "/assets/theme-v2/images/badges/particles.png",
- "/assets/theme-v2/images/badges/performance.png",
- "/assets/theme-v2/images/badges/platform-settings.png",
-- "/assets/theme-v2/images/badges/game-workspace.png",
-+ "/assets/theme-v2/images/badges/game-hub.png",
- "/assets/theme-v2/images/badges/publish-studio.png",
- "/assets/theme-v2/images/badges/publish.png",
- "/assets/theme-v2/images/badges/ratings.png",
-@@ -1743,7 +1743,7 @@ const AVAILABLE_TOOL_IMAGE_PATHS = Object.freeze([
- "/assets/theme-v2/images/tools/particles.png",
- "/assets/theme-v2/images/tools/performance.png",
- "/assets/theme-v2/images/tools/platform-settings.png",
-- "/assets/theme-v2/images/tools/game-workspace.png",
-+ "/assets/theme-v2/images/tools/game-hub.png",
- "/assets/theme-v2/images/tools/publish-studio.png",
- "/assets/theme-v2/images/tools/publish.png",
- "/assets/theme-v2/images/tools/ratings.png",
-diff --git a/tests/dev-runtime/DevRuntimeBoundary.test.mjs b/tests/dev-runtime/DevRuntimeBoundary.test.mjs
-index f2908a876..0b5b1660f 100644
---- a/tests/dev-runtime/DevRuntimeBoundary.test.mjs
-+++ b/tests/dev-runtime/DevRuntimeBoundary.test.mjs
-@@ -32,7 +32,7 @@ const retiredToolboxDevRuntimeFiles = [
- "toolbox/game-configuration/game-configuration-mock-repository.js",
- "toolbox/game-design/game-design-mock-repository.js",
- "toolbox/game-journey/game-journey-mock-repository.js",
-- "toolbox/game-workspace/game-workspace-mock-repository.js",
-+ "toolbox/game-hub/game-workspace-mock-repository.js",
- ];
-
- const retiredBrowserCompatibilityModules = new Set([
-@@ -123,6 +123,6 @@ test("server seed loading and guest package loading use dev-runtime modules with
- assert.doesNotMatch(router, /mock-db-state/);
- assert.match(router, /parts\[1\] === "guest"/);
- assert.match(router, /parts\[2\] === "seed"/);
-- assert.doesNotMatch(router, /toolbox\/(?:assets|colors|game-configuration|game-design|game-journey|game-workspace)\/.*mock-repository/);
-+ assert.doesNotMatch(router, /toolbox\/(?:assets|colors|game-configuration|game-design|game-journey|game-hub)\/.*mock-repository/);
- assert.doesNotMatch(router, /toolbox\/colors\/palette-source-mock-db/);
- });
-diff --git a/tests/dev-runtime/ProductDataProviderContractHardening.test.mjs b/tests/dev-runtime/ProductDataProviderContractHardening.test.mjs
-index f0a342677..15fd5ae10 100644
---- a/tests/dev-runtime/ProductDataProviderContractHardening.test.mjs
-+++ b/tests/dev-runtime/ProductDataProviderContractHardening.test.mjs
-@@ -115,7 +115,7 @@ test("Active browser product-data entrypoints use API or service clients", () =>
- "toolbox/game-configuration/game-configuration-api-client.js",
- "toolbox/game-design/game-design-api-client.js",
- "toolbox/game-journey/game-journey-api-client.js",
-- "toolbox/game-workspace/game-workspace-api-client.js",
-+ "toolbox/game-hub/game-hub-api-client.js",
- "toolbox/objects/objects-api-client.js",
- "toolbox/tags/tags-api-client.js",
- ];
-@@ -128,7 +128,7 @@ test("Active browser product-data entrypoints use API or service clients", () =>
-
- assert.match(readRepoFile("admin/tool-votes.js"), /src\/engine\/api\/toolbox-votes-api-client\.js/);
- assert.match(readRepoFile("toolbox/tools-page-accordions.js"), /src\/engine\/api\/toolbox-votes-api-client\.js/);
-- assert.match(readRepoFile("assets/theme-v2/js/account-achievements.js"), /createServerRepositoryClient\("game-workspace"\)/);
-+ assert.match(readRepoFile("assets/theme-v2/js/account-achievements.js"), /createServerRepositoryClient\("game-hub"\)/);
-
- [
- "admin/tool-votes.js",
-diff --git a/tests/dev-runtime/SupabaseProductDataCutover.test.mjs b/tests/dev-runtime/SupabaseProductDataCutover.test.mjs
-index e28122bb9..5a1e05d3d 100644
---- a/tests/dev-runtime/SupabaseProductDataCutover.test.mjs
-+++ b/tests/dev-runtime/SupabaseProductDataCutover.test.mjs
-@@ -382,13 +382,13 @@ test("Supabase-selected toolbox repositories open and persist through product ta
- }, async () => {
- const server = await startApiServer();
- try {
-- const repository = await apiJson(server.baseUrl, "/api/toolbox/game-workspace/repositories", {
-+ const repository = await apiJson(server.baseUrl, "/api/toolbox/game-hub/repositories", {
- body: { options: {} },
- method: "POST",
- });
-- assert.match(repository.repositoryId, /^game-workspace-\d+$/);
-+ assert.match(repository.repositoryId, /^game-hub-\d+$/);
-
-- const snapshot = await apiJson(server.baseUrl, `/api/toolbox/game-workspace/repositories/${repository.repositoryId}/methods/getSnapshot`, {
-+ const snapshot = await apiJson(server.baseUrl, `/api/toolbox/game-hub/repositories/${repository.repositoryId}/methods/getSnapshot`, {
- body: { args: [] },
- method: "POST",
- });
-diff --git a/tests/playwright/account/AchievementsPage.spec.mjs b/tests/playwright/account/AchievementsPage.spec.mjs
-index 4f6594aa2..9f27a0091 100644
---- a/tests/playwright/account/AchievementsPage.spec.mjs
-+++ b/tests/playwright/account/AchievementsPage.spec.mjs
-@@ -97,7 +97,7 @@ test("account achievements page switches Build Play Share views", async ({ page
- await expect(page.getByText("Games I shared")).toBeHidden();
- await expect(page.locator("[data-achievements-build-created-count]")).toHaveText("4");
- await expect(page.locator("[data-achievements-build-ready-count]")).toHaveText("0");
-- await expect(page.locator("[data-achievements-build-status]")).toContainText("Game Workspace game source");
-+ await expect(page.locator("[data-achievements-build-status]")).toContainText("Game Hub game source");
- const buildRows = await page.locator("[data-achievements-build-rows] tr").evaluateAll((rows) => rows.map((row) => {
- const cells = Array.from(row.querySelectorAll("td")).map((cell) => cell.textContent.trim());
- return {
-diff --git a/tests/playwright/tools/AdminDbViewer.spec.mjs b/tests/playwright/tools/AdminDbViewer.spec.mjs
-index 216019fd9..d4d9eb713 100644
---- a/tests/playwright/tools/AdminDbViewer.spec.mjs
-+++ b/tests/playwright/tools/AdminDbViewer.spec.mjs
-@@ -169,7 +169,7 @@ function configuredDbViewerGroups(tableNames) {
- { id: "game-configuration", label: "Game Configuration", tableNames: include(["game_configuration_records", "game_configuration_validation_items"]), type: "tool" },
- { id: "game-design", label: "Game Design", tableNames: include(["game_design_documents", "game_design_validation_items"]), type: "tool" },
- { id: "game-journey", label: "Game Journey", tableNames: include(["game_journey_completion_metrics", "game_journey_note_types", "game_journey_notes", "game_journey_templates", "game_journey_items", "game_journey_activity"]), type: "tool" },
-- { id: "game-workspace", label: "Game Hub", tableNames: include(["game_workspace_games", "game_workspace_progress"]), type: "tool" },
-+ { id: "game-hub", label: "Game Hub", tableNames: include(["game_workspace_games", "game_workspace_progress"]), type: "tool" },
- { id: "objects", label: "Objects", tableNames: include(["object_definition_records"]), type: "tool" },
- { id: "palette", label: "Palette", tableNames: include(["palette_colors", "palette_source_swatches", "palette_swatch_usages", "project_workspace_palette_globals"]), type: "tool" },
- { id: "tags", label: "Tags", tableNames: include(["workspace_tag_records"]), type: "tool" },
-diff --git a/tests/playwright/tools/GameWorkspaceMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs
-similarity index 91%
-rename from tests/playwright/tools/GameWorkspaceMockRepository.spec.mjs
-rename to tests/playwright/tools/GameHubMockRepository.spec.mjs
-index 1c1f742a9..2d583e1d7 100644
---- a/tests/playwright/tools/GameWorkspaceMockRepository.spec.mjs
-+++ b/tests/playwright/tools/GameHubMockRepository.spec.mjs
-@@ -99,15 +99,15 @@ function startFakeSupabaseServer() {
- test.beforeAll(async () => {
- previousSupabaseEnv = Object.fromEntries(SUPABASE_ENV_KEYS.map((key) => [key, process.env[key]]));
- fakeSupabaseServer = await startFakeSupabaseServer();
-- process.env.GAMEFOUNDRY_SUPABASE_ANON_KEY = "game-workspace-anon-key";
-- process.env.GAMEFOUNDRY_DATABASE_URL = "postgres://game-workspace:test@127.0.0.1:5432/game_workspace";
-- process.env.GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY = "game-workspace-service-role-key";
-+ process.env.GAMEFOUNDRY_SUPABASE_ANON_KEY = "game-hub-anon-key";
-+ delete process.env.GAMEFOUNDRY_DATABASE_URL;
-+ process.env.GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY = "game-hub-service-role-key";
- process.env.GAMEFOUNDRY_SUPABASE_URL = fakeSupabaseServer.baseUrl;
- });
+- return { message: `Ready to test ${target.label}.`, ok: true };
++ return "Ready for Message Studio playback.";
+ }
- test.beforeEach(async ({ page }) => {
- await installPlaywrightStorageIsolation(page, {
-- lane: "game-workspace",
-+ lane: "game-hub",
- surface: "game workspace mock repository"
- });
- });
-@@ -183,7 +183,7 @@ async function openRepoPage(page, pathName, options = {}) {
- });
+-function renderSpeechTestControls() {
+- renderTtsServiceOptions();
+- renderTtsProfileOptions();
+- const target = selectedSpeechTarget();
+- const readiness = speechTestReadiness();
+- setText(elements.speechTestTarget, target?.label || "None");
+- setText(elements.previewStatus, readiness.message);
+- if (elements.testSpeech) {
+- elements.testSpeech.disabled = !readiness.ok;
+- }
++function renderPlaybackControls() {
++ setText(elements.playbackStatus, playbackReadinessMessage());
+ if (elements.stopSpeech) {
+ elements.stopSpeech.disabled = !selectedTtsService()?.available;
}
-
-- if (pathName.includes("/toolbox/game-workspace/") || pathName.includes("/toolbox/project-workspace/")) {
-+ if (pathName.includes("/toolbox/game-hub/") || pathName.includes("/toolbox/project-workspace/")) {
- await page.route("**/api/platform-settings/banner", async (route) => {
- await route.fulfill({
- contentType: "application/json",
-@@ -235,7 +235,7 @@ test("Deprecated project workspace route points creators to Game Hub", async ({
- await expect(page.getByRole("heading", { name: "Game Hub" })).toBeVisible();
- await expect(page.locator("main")).toContainText("This route is kept for older links.");
- await expect(page.locator("main")).not.toContainText("Project Workspace");
-- await expect(page.getByRole("link", { name: "Open Game Hub" })).toHaveAttribute("href", "toolbox/game-workspace/index.html");
-+ await expect(page.getByRole("link", { name: "Open Game Hub" })).toHaveAttribute("href", "toolbox/game-hub/index.html");
-
- await expectNoPageFailures(failures);
- } finally {
-@@ -244,7 +244,7 @@ test("Deprecated project workspace route points creators to Game Hub", async ({
- });
-
- test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
-- const failures = await openRepoPage(page, "/toolbox/game-workspace/index.html", { session: creatorSession() });
-+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() });
-
- try {
- await expect(page.locator(".tool-workspace")).toBeVisible();
-@@ -275,7 +275,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
- await expect(page.locator("[data-game-list]")).toContainText("Launch Test Game");
- await expect(page.locator("[data-game-project-information]")).toContainText("Launch Test Game");
- await expect(page.locator("[data-game-row='launch-test-game-1']").getByRole("button", { name: "Open Launch Test Game (Active)" })).toHaveClass(/primary/);
-- await expect(page.locator("[data-game-workspace-log]")).toHaveText("Created and opened Launch Test Game.");
-+ await expect(page.locator("[data-game-hub-log]")).toHaveText("Created and opened Launch Test Game.");
-
- await page.getByLabel("Game Name").fill("Archive Game");
- await page.getByRole("button", { name: "Create Game" }).click();
-@@ -284,12 +284,12 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
- await page.getByRole("button", { name: "Open Launch Test Game" }).click();
- await expect(page.locator("[data-active-game-name]")).toHaveText("Launch Test Game");
- await expect(page.locator("[data-game-row='launch-test-game-1']").getByRole("button", { name: "Open Launch Test Game (Active)" })).toHaveAttribute("data-game-active", "true");
-- await expect(page.locator("[data-game-workspace-log]")).toHaveText("Opened Launch Test Game.");
-+ await expect(page.locator("[data-game-hub-log]")).toHaveText("Opened Launch Test Game.");
-
- await page.getByRole("button", { name: "Delete Open Game" }).click();
- await expect(page.locator("[data-active-game-name]")).not.toHaveText("Launch Test Game");
- await expect(page.locator("[data-game-list]")).not.toContainText("Launch Test Game");
-- await expect(page.locator("[data-game-workspace-log]")).toHaveText("Deleted Launch Test Game.");
-+ await expect(page.locator("[data-game-hub-log]")).toHaveText("Deleted Launch Test Game.");
-
- await expectNoPageFailures(failures);
- } finally {
-@@ -298,7 +298,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
- });
-
- test("Game Hub preserves guest browsing and blocks guest saves", async ({ page }) => {
-- const failures = await openRepoPage(page, "/toolbox/game-workspace/index.html");
-+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html");
-
- try {
- await expect(page.locator("[data-active-game-name]")).toHaveText("Demo Game");
-@@ -314,7 +314,7 @@ test("Game Hub preserves guest browsing and blocks guest saves", async ({ page }
-
- await page.getByRole("button", { name: "Open Gravity Demo" }).click();
- await expect(page.locator("[data-active-game-name]")).toHaveText("Gravity Demo");
-- await expect(page.locator("[data-game-workspace-log]")).toHaveText("Sign in to create or update Game Hub projects.");
-+ await expect(page.locator("[data-game-hub-log]")).toHaveText("Sign in to create or update Game Hub projects.");
-
- await expectNoPageFailures(failures);
- } finally {
-@@ -323,7 +323,7 @@ test("Game Hub preserves guest browsing and blocks guest saves", async ({ page }
- });
-
- test("Game Hub shows active-game errors without throwing", async ({ page }) => {
-- await page.route("**/api/toolbox/game-workspace/repositories/*/methods/getActiveGame", async (route) => {
-+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/getActiveGame", async (route) => {
- await route.fulfill({
- body: JSON.stringify({
- error: "Active game unavailable for validation.",
-@@ -334,12 +334,12 @@ test("Game Hub shows active-game errors without throwing", async ({ page }) => {
- status: 502,
- });
- });
-- const failures = await openRepoPage(page, "/toolbox/game-workspace/index.html");
-+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html");
-
- try {
- expect(failures.failedRequests.some((request) => request.includes("502") && request.includes("/methods/getActiveGame"))).toBe(true);
- await expect(page.locator("[data-active-game-name]")).toHaveText("No game open");
-- await expect(page.locator("[data-game-workspace-log]")).toContainText("Active game is temporarily unavailable.");
-+ await expect(page.locator("[data-game-hub-log]")).toContainText("Active game is temporarily unavailable.");
- expect(failures.pageErrors).toEqual([]);
- expect(failures.consoleErrors.filter((message) => !message.includes("status of 502"))).toEqual([]);
- } finally {
-@@ -348,7 +348,7 @@ test("Game Hub shows active-game errors without throwing", async ({ page }) => {
- });
-
- test("Game Hub reports malformed active-game payloads without throwing", async ({ page }) => {
-- await page.route("**/api/toolbox/game-workspace/repositories/*/methods/getActiveGame", async (route) => {
-+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/getActiveGame", async (route) => {
- await route.fulfill({
- body: JSON.stringify({
- data: {
-@@ -364,12 +364,12 @@ test("Game Hub reports malformed active-game payloads without throwing", async (
- status: 200,
- });
- });
-- const failures = await openRepoPage(page, "/toolbox/game-workspace/index.html");
-+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html");
-
- try {
- await expect(page.locator("[data-active-game-name]")).toHaveText("No game open");
- await expect(page.locator("[data-current-user-role]")).toHaveText("Viewer");
-- await expect(page.locator("[data-game-workspace-log]")).toContainText("Active game is temporarily unavailable.");
-+ await expect(page.locator("[data-game-hub-log]")).toContainText("Active game is temporarily unavailable.");
- await expect(page.getByLabel("Game Purpose")).toBeDisabled();
-
- await expectNoPageFailures(failures);
-@@ -379,7 +379,7 @@ test("Game Hub reports malformed active-game payloads without throwing", async (
- });
-
- test("Game Hub displays and edits game purpose and member role", async ({ page }) => {
-- const failures = await openRepoPage(page, "/toolbox/game-workspace/index.html", { session: creatorSession() });
-+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() });
-
- try {
- await expect(page.locator("#gamePurposeInput option")).toHaveText([
-@@ -411,15 +411,15 @@ test("Game Hub displays and edits game purpose and member role", async ({ page }
-
- await page.getByLabel("Game Purpose").selectOption("Learning Game");
- await expect(page.locator("[data-active-game-purpose]")).toHaveText("Learning Game");
-- await expect(page.locator("[data-game-workspace-log]")).toHaveText("Updated Demo Game purpose to Learning Game.");
-+ await expect(page.locator("[data-game-hub-log]")).toHaveText("Updated Demo Game purpose to Learning Game.");
-
- await page.getByLabel("Game Status").selectOption("Ready for Testing");
- await expect(page.locator("[data-active-game-status]")).toHaveText("Ready for Testing");
-- await expect(page.locator("[data-game-workspace-log]")).toHaveText("Updated Demo Game status to Ready for Testing.");
-+ await expect(page.locator("[data-game-hub-log]")).toHaveText("Updated Demo Game status to Ready for Testing.");
-
- await page.getByLabel("Current User Role").selectOption("Designer");
- await expect(page.locator("[data-current-user-role]")).toHaveText("Designer");
-- await expect(page.locator("[data-game-workspace-log]")).toHaveText("Updated current user role to Designer.");
-+ await expect(page.locator("[data-game-hub-log]")).toHaveText("Updated current user role to Designer.");
-
- await page.getByLabel("Game Purpose").selectOption("Capability Demo");
- await page.getByLabel("Game Name").fill("Purpose Review Game");
-@@ -436,7 +436,7 @@ test("Game Hub displays and edits game purpose and member role", async ({ page }
- });
-
- test("Game Hub progress panels update from mock game state", async ({ page }) => {
-- const failures = await openRepoPage(page, "/toolbox/game-workspace/index.html", { session: creatorSession() });
-+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() });
-
- try {
- await expect(page.locator("[data-game-status]")).toHaveText("Under Construction");
-@@ -452,7 +452,7 @@ test("Game Hub progress panels update from mock game state", async ({ page }) =>
- const panelOrderIsCorrect = await page.locator(".tool-center-panel").evaluate((panel) => {
- const projectInformation = panel.querySelector("[data-game-project-information]");
- const sourceIdea = panel.querySelector("[data-source-idea-section]");
-- const staticOverlay = panel.querySelector("[data-game-workspace-foundation]");
-+ const staticOverlay = panel.querySelector("[data-game-hub-foundation]");
- const outputPanels = panel.querySelector("[data-game-output-panels]");
- return Boolean(
- projectInformation &&
-@@ -484,7 +484,7 @@ test("Game Hub progress panels update from mock game state", async ({ page }) =>
-
- test("Game Hub uses the wide Theme V2 tool layout at desktop widths", async ({ page }) => {
- await page.setViewportSize({ width: 1440, height: 1100 });
-- const failures = await openRepoPage(page, "/toolbox/game-workspace/index.html");
-+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html");
-
- try {
- await expect(page.locator(".container--tool-wide")).toBeVisible();
-@@ -609,7 +609,7 @@ test("Toolbox member-role filters focus tools without exposing admin-only contro
- const failures = await openRepoPage(page, "/toolbox/index.html");
-
- try {
-- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 11/39");
-+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43");
- await expect(page.locator("[data-toolbox-role-focus]")).toHaveCount(0);
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Game Hub$/ }) })).toBeVisible();
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Game Journey$/ }) })).toBeVisible();
-@@ -620,7 +620,7 @@ test("Toolbox member-role filters focus tools without exposing admin-only contro
-
- await page.goto(`${failures.server.baseUrl}/toolbox/index.html?memberRole=Designer`, { waitUntil: "networkidle" });
- await expect(page.locator("[data-toolbox-role-focus='Designer']")).toBeVisible();
-- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 7/39");
-+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 8/43");
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Game Hub$/ }) })).toBeVisible();
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Game Journey$/ }) })).toBeVisible();
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Game Design$/ }) })).toBeVisible();
-@@ -632,15 +632,16 @@ test("Toolbox member-role filters focus tools without exposing admin-only contro
-
- await page.goto(`${failures.server.baseUrl}/toolbox/index.html?memberRole=Audio%20Creator`, { waitUntil: "networkidle" });
- await expect(page.locator("[data-toolbox-role-focus='Audio Creator']")).toBeVisible();
-- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 1/39");
-+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 2/43");
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Assets$/ }) })).toBeVisible();
-+ await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Text To Speech$/ }) })).toBeVisible();
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Audio$/ }) })).toHaveCount(0);
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^MIDI$/ }) })).toHaveCount(0);
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Game Design$/ }) })).toHaveCount(0);
-
- await page.goto(`${failures.server.baseUrl}/toolbox/index.html?memberRole=Viewer`, { waitUntil: "networkidle" });
- await expect(page.locator("[data-toolbox-role-focus='Viewer']")).toBeVisible();
-- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 9/39");
-+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 10/43");
- await expect(page.getByText("Viewer focus shows preview-safe read-only tiles only.")).toBeVisible();
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Game Hub$/ }) })).toBeVisible();
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Game Journey$/ }) })).toBeVisible();
-@@ -649,7 +650,7 @@ test("Toolbox member-role filters focus tools without exposing admin-only contro
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Assets$/ }) })).toBeVisible();
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Debug$/ }) })).toHaveCount(0);
- await page.goto(`${failures.server.baseUrl}/toolbox/index.html`, { waitUntil: "networkidle" });
-- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 11/39");
-+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43");
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^Cloud$/ }) })).toHaveCount(0);
-
- await expectNoPageFailures(failures);
-diff --git a/tests/playwright/tools/GameJourneyTool.spec.mjs b/tests/playwright/tools/GameJourneyTool.spec.mjs
-index 2cc895fef..429f6f381 100644
---- a/tests/playwright/tools/GameJourneyTool.spec.mjs
-+++ b/tests/playwright/tools/GameJourneyTool.spec.mjs
-@@ -1444,8 +1444,8 @@ test("Game Journey requires an active game before editing", async ({ page }) =>
- await expect(page.getByRole("button", { name: "Update Item" })).toBeDisabled();
- await expect(page.getByRole("button", { name: "Add Note", exact: true })).toBeDisabled();
- await expect(page.locator("[data-journey-title-input]")).toBeDisabled();
-- await expect(page.locator("[data-journey-note-type-select]")).toBeDisabled();
-- await expect(page.locator("[data-journey-new-note-type]")).toBeDisabled();
-+ await expect(page.locator("[data-journey-note-type-select]")).toHaveCount(0);
-+ await expect(page.locator("[data-journey-new-note-type]")).toHaveCount(0);
-
- await expectNoPageFailures(failures);
- } finally {
-@@ -1454,7 +1454,7 @@ test("Game Journey requires an active game before editing", async ({ page }) =>
- });
-
- test("Game Hub hands the active game route to Game Journey", async ({ page }) => {
-- const failures = await openRepoPage(page, "/toolbox/game-workspace/index.html");
-+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html");
-
- try {
- const journeyLink = page.getByRole("link", { name: "Open Game Journey" });
-diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
-index a18205fa4..934faf46f 100644
---- a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
-+++ b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
-@@ -346,15 +346,22 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => {
- 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(["Open in Game Hub", "Archive"]);
- await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='open-project']").click();
-- await page.waitForURL(/\/toolbox\/game-workspace\/index\.html\?game=lantern-reef-\d+$/);
-+ await page.waitForURL(/\/toolbox\/game-hub\/index\.html\?game=lantern-reef-\d+$/);
- await expect(page.getByRole("heading", { level: 1, name: "Game Hub" })).toBeVisible();
- await expect(page.locator("[data-active-game-name]")).toHaveText("Lantern Reef");
- await expect(page.locator("[data-source-idea-display]")).toHaveText("Lantern Reef");
- await expect(page.locator("[data-source-idea-pitch]")).toHaveText("Guide light through a reef that rearranges at dusk.");
- await expect(page.locator("[data-source-idea-notes]")).toContainText("Use dusk tide changes as the first Game Hub planning note.");
-+ await expect(page.getByRole("button", { name: "Delete Open Game" })).toHaveCount(0);
- await expect(page.locator("main")).not.toContainText(/\bproject records\b|\bAPI\b|\bDB\b|\bmock\b|\bseed\b|\bdebug\b|\binternal\b/i);
-+ await page.getByRole("link", { name: "Open Game Journey" }).click();
-+ await page.waitForURL(/\/toolbox\/game-journey\/index\.html\?game=lantern-reef-\d+$/);
-+ await expect(page.locator("[data-journey-active-game]")).toHaveText("Active game: Lantern Reef.");
-+ await expect(page.locator("[data-journey-summary-body]")).toContainText("Source Idea: Lantern Reef");
-+ await expect(page.locator("[data-journey-summary-body]")).toContainText("10000011");
-+ await expect(page.locator("[data-journey-recent-activity]")).toContainText("Created 1 Game Journey item from Source Idea.");
-
-- expect(mutatingApiRequests.some((request) => request.includes("/api/toolbox/game-workspace/repositories"))).toBe(true);
-+ expect(mutatingApiRequests.some((request) => request.includes("/api/toolbox/game-hub/repositories"))).toBe(true);
- expect(mutatingApiRequests.some((request) => request.includes("/methods/createGame"))).toBe(true);
- expect(failedRequests).toEqual([]);
- expect(pageErrors).toEqual([]);
-diff --git a/tests/playwright/tools/RootToolsFutureState.spec.mjs b/tests/playwright/tools/RootToolsFutureState.spec.mjs
-index ba97f5c6a..8110dbe04 100644
---- a/tests/playwright/tools/RootToolsFutureState.spec.mjs
-+++ b/tests/playwright/tools/RootToolsFutureState.spec.mjs
-@@ -277,7 +277,7 @@ test("root tools surface links current tool pages without old_* routes", async (
- await expect(page.getByRole("button", { name: "Progress" })).toHaveCount(0);
- await expect(page.getByRole("button", { name: "Build Path" })).toBeVisible();
- await expect(page.getByRole("button", { name: "Build Path" })).not.toHaveAttribute("aria-disabled", "true");
-- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/44");
-+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43");
- await expect(page.locator("[data-toolbox-admin-nav-group]")).toHaveCount(0);
- await expect(page.locator("nav.nav-links > .nav-item > a[data-route='admin']")).toHaveCount(0);
- await expect(page.locator("nav.nav-links > a[data-route='learn']")).toHaveCount(1);
-@@ -311,15 +311,18 @@ test("root tools surface links current tool pages without old_* routes", async (
- await expect(page.getByText("Build Path Wireframe")).toHaveCount(0);
- await expect(page.locator("[data-toolbox-wireframe]")).toHaveCount(0);
- await expect(page.locator("style, [style], script:not([src])")).toHaveCount(0);
-- await expect(page.locator("main a.btn[data-toolbox-launch-link='Game Hub'][href='../toolbox/game-workspace/index.html']")).toBeVisible();
-+ const readyGameWorkspaceCard = page.locator("main .control-card").filter({
-+ has: page.locator("h3", { hasText: "Game Hub" })
-+ });
-+ await expect(readyGameWorkspaceCard.locator("a.btn")).toHaveAttribute("href", "../toolbox/game-hub/index.html");
- const defaultToolLabels = await page.locator("main [data-tools-accordion-list] .control-card h3").evaluateAll((labels) => labels.map((label) => label.textContent.trim()));
-- expect(defaultToolLabels).toEqual(["Achievements", "Assets", "Colors", "Controls", "Game Configuration", "Game Design", "Game Hub", "Game Hub", "Game Journey", "Idea Board", "Languages", "Message Studio", "Objects", "Saved Data", "Tags", "Text To Speech"]);
-+ expect(defaultToolLabels).toEqual(["Achievements", "Assets", "Colors", "Controls", "Game Configuration", "Game Design", "Game Hub", "Game Journey", "Idea Board", "Languages", "Message Studio", "Objects", "Saved Data", "Tags", "Text To Speech"]);
- const textToSpeechCard = page.locator("main .control-card").filter({
- has: page.locator("h3", { hasText: /^Text To Speech$/ })
- });
- await expect(textToSpeechCard.locator("[data-toolbox-tool-name-link='Text To Speech']")).toHaveAttribute("href", "/toolbox/text-to-speech/index.html");
- await expect(textToSpeechCard.locator("[data-toolbox-tool-name-link='Text To Speech']")).toHaveAttribute("data-registered-tool-route", "toolbox/text-to-speech/index.html");
-- await expect(page.locator("[data-toolbox-readiness]")).toHaveText(["Wireframe", "Beta", "Complete", "Wireframe", "Beta", "Beta", "Beta", "Beta", "Beta", "Wireframe", "Wireframe", "Beta", "Beta", "Wireframe", "Beta", "Beta"]);
-+ await expect(page.locator("[data-toolbox-readiness]")).toHaveText(["Wireframe", "Beta", "Complete", "Wireframe", "Beta", "Beta", "Beta", "Beta", "Wireframe", "Wireframe", "Beta", "Beta", "Wireframe", "Beta", "Beta"]);
- await expect(page.locator("main .control-card").filter({ has: page.locator("h3", { hasText: /^AI Command Center$/ }) })).toHaveCount(0);
- const oldStandaloneLabels = [
- ["Palette", "Manager"].join(" "),
-@@ -389,7 +392,7 @@ test("root tools surface links current tool pages without old_* routes", async (
- const guestGroupLabels = await page.locator("[data-tools-accordion-list] details[data-tools-accordion]").evaluateAll((groups) => (
- groups.map((group) => group.dataset.toolsAccordion)
+@@ -503,7 +399,7 @@ function createMessageEditRows(message = null) {
+
+ const ttsCell = document.createElement("td");
+ ttsCell.append(createTtsProfileSelect(
+- message ? selectedTtsProfileForMessage(message.key).key : defaultTtsProfileKey(),
++ message ? selectedTtsProfileForMessage(message.key)?.key : defaultTtsProfileKey(),
+ "messageDefaultTtsProfile",
+ key,
+ ));
+@@ -551,7 +447,7 @@ function createMessageSegmentTable() {
+ table.setAttribute("aria-label", "Message parts");
+ const thead = document.createElement("thead");
+ const headerRow = document.createElement("tr");
+- ["Text", "Emotion", "TTS Profile", "Status", "Actions"].forEach((label) => {
++ ["Part Text", "Emotion", "TTS Profile", "Status", "Actions"].forEach((label) => {
+ const header = document.createElement("th");
+ header.scope = "col";
+ header.textContent = label;
+@@ -587,7 +483,7 @@ function createMessageSegmentTable() {
));
-- expect(guestGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression", "Publish"]);
-+ expect(guestGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression"]);
- await expect(page.locator("[data-tools-accordion='Admin']")).toHaveCount(0);
- await expect(page.getByRole("button", { name: "Progress" })).toHaveCount(0);
- await expect(page.locator("[data-tools-accordion-list] .control-card h3", { hasText: /^Progress$/ })).toHaveCount(0);
-@@ -415,13 +418,13 @@ test("root tools surface links current tool pages without old_* routes", async (
- expect(failedRequests.filter((request) => request.includes("/toolbox/old_"))).toEqual([]);
-
- await page.goto(`${server.baseUrl}/toolbox/index.html`, { waitUntil: "networkidle" });
-- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/44");
-+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43");
- await expect(page.locator("main").getByText("Users", { exact: true })).toHaveCount(0);
- await expect(page.locator("main").getByText("Creators", { exact: true })).toHaveCount(0);
- await expect(page.locator("[data-toolbox-admin-nav-group]")).toHaveCount(0);
- await setServerSession(server, MOCK_DB_KEYS.users.admin);
- await page.goto(`${server.baseUrl}/toolbox/index.html`, { waitUntil: "networkidle" });
-- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/47");
-+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/46");
- await expect(page.locator("[data-toolbox-admin-nav-group]")).toHaveCount(0);
- const adminLabels = await page.locator("main [data-tools-accordion-list] .control-card h3").evaluateAll((labels) => labels.map((label) => label.textContent.trim()));
- expect(adminLabels).toEqual(defaultToolLabels);
-@@ -432,7 +435,7 @@ test("root tools surface links current tool pages without old_* routes", async (
- "Platform Settings"
- ]));
- await expect(page.locator("main .control-card").filter({
-- has: page.locator("[data-toolbox-tool-name-link='Game Hub'][href='/toolbox/game-workspace/index.html']")
-+ has: page.locator("[data-toolbox-tool-name-link='Game Hub'][href='/toolbox/game-hub/index.html']")
- }).locator("[data-toolbox-readiness]")).toHaveText("Beta");
- await expect(page.locator("main .control-card").filter({
- has: page.locator("h3", { hasText: /^Game Configuration$/ })
-@@ -458,7 +461,7 @@ test("root tools surface links current tool pages without old_* routes", async (
- const adminGroupLabels = await page.locator("[data-tools-accordion-list] details[data-tools-accordion]").evaluateAll((groups) => (
- groups.map((group) => group.dataset.toolsAccordion)
+ const ttsCell = document.createElement("td");
+ ttsCell.append(createTtsProfileSelect(
+- selectedTtsProfileForSegment(segment.key, segment.messageKey).key,
++ selectedTtsProfileForSegment(segment.key, segment.messageKey)?.key || "",
+ "segmentTtsProfile",
+ segment.key,
));
-- expect(adminGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression", "Publish"]);
-+ expect(adminGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression"]);
- await expect(page.getByRole("button", { name: "Progress" })).toHaveCount(0);
- await page.getByRole("button", { name: "Build Path" }).click();
- await expect(page.locator("[data-build-path-table='workflow']")).toBeVisible();
-@@ -468,7 +471,7 @@ test("root tools surface links current tool pages without old_* routes", async (
- await expect(page.locator("[data-toolbox-admin-nav-group]")).toHaveCount(0);
- await setServerSession(server, "");
- await page.goto(`${server.baseUrl}/toolbox/index.html`, { waitUntil: "networkidle" });
-- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/44");
-+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43");
- await expect(page.locator("main").getByText("Users", { exact: true })).toHaveCount(0);
- await expect(page.locator("main").getByText("Creators", { exact: true })).toHaveCount(0);
- expect(pageErrors).toEqual([]);
-@@ -570,7 +573,7 @@ test("learn wireframe pages load with shared Theme V2 structure", async ({ page
- headings: ["Desktop Target", "Ideal Width", "Minimum Comfortable Width", "Panel Layout", "Smaller Screens", "Wireframe Status"]
- },
- {
-- path: "/learn/game-workspace/index.html",
-+ path: "/learn/game-hub/index.html",
- headings: ["Overview", "Quick Start", "Common Tasks", "Related Documentation", "Related Videos", "Examples"]
- },
- {
-@@ -664,7 +667,7 @@ test("tool template future-state page loads from root Theme V2 paths", async ({
- test("representative active tool pages align center cleanup and registry group colors", async ({ page }) => {
- const representativeToolIds = [
- "ai-assistant",
-- "game-workspace",
-+ "game-hub",
- "game-design",
- "colors",
- "audio",
-diff --git a/tests/playwright/tools/ToolDisplayModeNavigation.spec.mjs b/tests/playwright/tools/ToolDisplayModeNavigation.spec.mjs
-index ace2d1a03..45de2a0c6 100644
---- a/tests/playwright/tools/ToolDisplayModeNavigation.spec.mjs
-+++ b/tests/playwright/tools/ToolDisplayModeNavigation.spec.mjs
-@@ -225,8 +225,8 @@ test("Game Design renders identity and navigation rows with registry anchor link
- });
-
- test("Game Hub and Game Configuration use registry order without page hardcoding", async ({ page }) => {
-- const failures = await openRepoPage(page, "/toolbox/game-workspace/index.html");
-- const gameWorkspaceNavigation = getToolNavigationTargets("game-workspace");
-+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html");
-+ const gameWorkspaceNavigation = getToolNavigationTargets("game-hub");
- const gameConfigurationNavigation = getToolNavigationTargets("game-configuration");
-
- try {
-diff --git a/tests/playwright/tools/ToolImageRegistry.spec.mjs b/tests/playwright/tools/ToolImageRegistry.spec.mjs
-index d4d03175a..3827e38e8 100644
---- a/tests/playwright/tools/ToolImageRegistry.spec.mjs
-+++ b/tests/playwright/tools/ToolImageRegistry.spec.mjs
-@@ -157,7 +157,7 @@ test("Toolbox cards consume registry image sources and expose visible image diag
-
- test("representative tool pages consume registry images in Tool Display Mode", async ({ page }) => {
- const toolCases = [
-- { path: "/toolbox/game-workspace/index.html", toolId: "game-workspace" },
-+ { path: "/toolbox/game-hub/index.html", toolId: "game-hub" },
- { path: "/toolbox/game-design/index.html", toolId: "game-design" },
- { path: "/toolbox/game-configuration/index.html", toolId: "game-configuration" },
- { path: "/toolbox/build-game/index.html", toolId: "build-game" }
-diff --git a/tests/playwright/tools/ToolNavigationPrevNext.spec.mjs b/tests/playwright/tools/ToolNavigationPrevNext.spec.mjs
-index b2c71bdc9..d2aab4f10 100644
---- a/tests/playwright/tools/ToolNavigationPrevNext.spec.mjs
-+++ b/tests/playwright/tools/ToolNavigationPrevNext.spec.mjs
-@@ -125,8 +125,8 @@ test("Tool Display Mode renders build-order previous and next controls", async (
- });
-
- test("Game Hub Tool Display Mode follows registry route targets", async ({ page }) => {
-- const failures = await openRepoPage(page, "/toolbox/game-workspace/index.html");
-- const navigation = getToolNavigationTargets("game-workspace");
-+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html");
-+ const navigation = getToolNavigationTargets("game-hub");
+@@ -627,7 +523,7 @@ function createSegmentEditRow(segment = null) {
+
+ const ttsCell = document.createElement("td");
+ ttsCell.append(createTtsProfileSelect(
+- segment ? selectedTtsProfileForSegment(segment.key, segment.messageKey).key : selectedTtsProfileForMessage(state.selectedMessageKey).key,
++ segment ? selectedTtsProfileForSegment(segment.key, segment.messageKey)?.key : selectedTtsProfileForMessage(state.selectedMessageKey)?.key,
+ "segmentTtsProfile",
+ key,
+ ));
+@@ -687,7 +583,7 @@ function renderMessageRows() {
+ nameCell.textContent = `${isExpanded ? "v" : ">"} ${message.name}`;
+ const ttsCell = document.createElement("td");
+ ttsCell.append(createTtsProfileSelect(
+- selectedTtsProfileForMessage(message.key).key,
++ selectedTtsProfileForMessage(message.key)?.key || "",
+ "messageDefaultTtsProfile",
+ message.key,
+ ));
+@@ -721,7 +617,7 @@ function render(persistence = {}) {
+ renderSelectedMessage();
+ renderCounts();
+ renderPersistence(persistence);
+- renderSpeechTestControls();
++ renderPlaybackControls();
+ }
- try {
- await expectNavigationTarget(page.locator("[data-tool-nav-previous]"), "Previous", navigation.previous);
-diff --git a/tests/playwright/tools/ToolboxRoutePages.spec.mjs b/tests/playwright/tools/ToolboxRoutePages.spec.mjs
-index 0cf4d475c..12e463488 100644
---- a/tests/playwright/tools/ToolboxRoutePages.spec.mjs
-+++ b/tests/playwright/tools/ToolboxRoutePages.spec.mjs
-@@ -778,7 +778,7 @@ test("toolbox status kickers, filters, card order, and voting controls work from
- await expect(adminBuildVoteRow).toHaveAttribute("draggable", "true");
- await page.evaluate(() => {
- const source = document.querySelector("[data-toolbox-votes-tool-id='build-game']");
-- const target = document.querySelector("[data-toolbox-votes-tool-id='game-workspace']");
-+ const target = document.querySelector("[data-toolbox-votes-tool-id='game-hub']");
- if (!source || !target) {
- throw new Error("Toolbox vote drag/drop rows were not available.");
- }
-@@ -799,7 +799,7 @@ test("toolbox status kickers, filters, card order, and voting controls work from
- });
- await expect(page.locator("[data-toolbox-votes-status]")).toContainText("Rows were renumbered with whole-number order values.");
- await expect(adminBuildVoteRow.locator("td").nth(1)).toHaveText("1");
-- await expect(page.locator("[data-toolbox-votes-tool-id='game-workspace'] td").nth(1)).toHaveText("2");
-+ await expect(page.locator("[data-toolbox-votes-tool-id='game-hub'] td").nth(1)).toHaveText("2");
- await expect(adminBuildVoteRow).toHaveAttribute("aria-selected", "true");
- await expect(page.locator("[data-toolbox-votes-tool-id='publish'] td").nth(5)).toHaveText("1");
- await expect(page.locator("[data-toolbox-votes-tool-id='publish'] td").nth(7)).toHaveText("1");
-diff --git a/toolbox/colors/index.html b/toolbox/colors/index.html
-index 4c7e98f44..a0e9e8276 100644
---- a/toolbox/colors/index.html
-+++ b/toolbox/colors/index.html
-@@ -34,7 +34,7 @@
-
-
No active project.
-
Active project required.
--
Open Game Hub
-+
Open Game Hub
-
-
-
-@@ -222,7 +222,7 @@
-
-
- Open Game Hub before editing project swatches.
-- Open Game Hub
-+ Open Game Hub
-
-
-
-diff --git a/toolbox/controls/controls.js b/toolbox/controls/controls.js
-index 901288790..c3355a91e 100644
---- a/toolbox/controls/controls.js
-+++ b/toolbox/controls/controls.js
-@@ -642,7 +642,7 @@ function showWorkspaceReturnIfNeeded() {
- const params = new URLSearchParams(window.location.search);
- const returnTo = normalizeText(params.get("returnTo"));
- const shouldShow = params.has("workspace") || params.has("project") || params.get("source") === "workspace" || params.has("workspaceLaunch") || returnTo;
-- if (returnTo.startsWith("/toolbox/game-workspace/")) {
-+ if (returnTo.startsWith("/toolbox/game-hub/")) {
- elements.returnWorkspace.href = returnTo;
+ function editorValue(root, selector) {
+@@ -912,36 +808,16 @@ async function disableSegment(key) {
}
- elements.returnWorkspace.hidden = !shouldShow;
-diff --git a/toolbox/controls/index.html b/toolbox/controls/index.html
-index dc3d404eb..5e23377e8 100644
---- a/toolbox/controls/index.html
-+++ b/toolbox/controls/index.html
-@@ -19,7 +19,7 @@
-
Toolbox / Controls
-
Controls
-
Define game-owned controls with normalized actions and creator-facing usage labels.
--
Return to Game Hub
-+
Return to Game Hub
-
-
-
-diff --git a/toolbox/game-design/index.html b/toolbox/game-design/index.html
-index 14215637c..3335515ac 100644
---- a/toolbox/game-design/index.html
-+++ b/toolbox/game-design/index.html
-@@ -46,7 +46,7 @@
- Actions
-
-@@ -142,7 +142,7 @@
- Missing Game
-
- Open or seed a Game Hub game before saving Game Design.
-- Open Game Hub
-+ Open Game Hub
-
-
-
-diff --git a/toolbox/game-hub/game-hub-api-client.js b/toolbox/game-hub/game-hub-api-client.js
-new file mode 100644
-index 000000000..6088a99d0
---- /dev/null
-+++ b/toolbox/game-hub/game-hub-api-client.js
-@@ -0,0 +1,32 @@
-+import {
-+ createServerRepositoryClient,
-+ requireServerApiData,
-+ readServerToolConstants,
-+ requireServerConstant,
-+ safeRequestServerApi,
-+} from "../../src/api/server-api-client.js";
-+
-+const constants = readServerToolConstants("game-hub");
-+
-+export const GAME_HUB_MEMBER_ROLES = Object.freeze(requireServerConstant(constants, "GAME_WORKSPACE_MEMBER_ROLES", "game-hub"));
-+export const GAME_HUB_GAME_PURPOSES = Object.freeze(requireServerConstant(constants, "GAME_WORKSPACE_GAME_PURPOSES", "game-hub"));
-+export const GAME_HUB_GAME_STATUSES = Object.freeze(requireServerConstant(constants, "GAME_WORKSPACE_GAME_STATUSES", "game-hub"));
-+
-+export function createGameHubApiRepository(options = {}) {
-+ return createServerRepositoryClient("game-hub", options);
-+}
-+
-+export const GAME_WORKSPACE_MEMBER_ROLES = GAME_HUB_MEMBER_ROLES;
-+export const GAME_WORKSPACE_GAME_PURPOSES = GAME_HUB_GAME_PURPOSES;
-+export const GAME_WORKSPACE_GAME_STATUSES = GAME_HUB_GAME_STATUSES;
-+
-+export function createGameWorkspaceApiRepository(options = {}) {
-+ return createGameHubApiRepository(options);
-+}
-+
-+export function readProjectWorkspaceProjectRecords() {
-+ return requireServerApiData(
-+ safeRequestServerApi("/project-workspace/projects"),
-+ "Game Hub project records",
-+ );
-+}
-diff --git a/toolbox/game-workspace/game-workspace.js b/toolbox/game-hub/game-hub.js
-similarity index 94%
-rename from toolbox/game-workspace/game-workspace.js
-rename to toolbox/game-hub/game-hub.js
-index 90aea08f8..d2260fe04 100644
---- a/toolbox/game-workspace/game-workspace.js
-+++ b/toolbox/game-hub/game-hub.js
-@@ -1,12 +1,12 @@
- import {
-- GAME_WORKSPACE_MEMBER_ROLES,
-- GAME_WORKSPACE_GAME_PURPOSES,
-- GAME_WORKSPACE_GAME_STATUSES,
-- createGameWorkspaceApiRepository,
--} from "./game-workspace-api-client.js";
-+ GAME_HUB_MEMBER_ROLES,
-+ GAME_HUB_GAME_PURPOSES,
-+ GAME_HUB_GAME_STATUSES,
-+ createGameHubApiRepository,
-+} from "./game-hub-api-client.js";
- import { getSessionCurrent } from "../../src/api/session-api-client.js";
-
--const repository = createGameWorkspaceApiRepository();
-+const repository = createGameHubApiRepository();
-
- const elements = {
- activeGameName: document.querySelector("[data-active-game-name]"),
-@@ -35,7 +35,7 @@ const elements = {
- gameStatusInput: document.querySelector("[data-game-status-input]"),
- publishingProgress: document.querySelector("[data-publishing-progress]"),
- recommendedNextTool: document.querySelectorAll("[data-recommended-next-tool]"),
-- statusLog: document.querySelector("[data-game-workspace-log]"),
-+ statusLog: document.querySelector("[data-game-hub-log]"),
- tableCounts: document.querySelector("[data-game-table-counts]"),
- };
-
-@@ -64,6 +64,10 @@ function isRepositoryErrorResult(value) {
- return isRecord(value) && value.error === true;
}
-+function isSourceLinkedGame(activeGame) {
-+ return isRecord(activeGame?.sourceIdea);
-+}
-+
- function repositoryErrorMessage(value, context) {
- return `${context} is temporarily unavailable. Refresh the page or try again shortly.`;
- }
-@@ -137,7 +141,7 @@ function setProjectRecordStatus(message) {
- setText(elements.projectRecordStatus, message);
+-function testSelectedSpeech() {
+- const readiness = speechTestReadiness();
+- if (!readiness.ok) {
+- setText(elements.previewStatus, readiness.message);
+- setText(elements.log, readiness.message);
+- return;
+- }
+- const service = selectedTtsService();
+- const target = selectedSpeechTarget();
+- const result = speakTarget(service, target, target.profile || selectedTtsProfile());
+- if (!result.ok) {
+- setText(elements.previewStatus, result.message || "Speech test failed.");
+- setText(elements.log, result.message || "Speech test failed.");
+- return;
+- }
+- const message = `Speech test started for ${target.label} using ${service.name}.`;
+- setText(elements.previewStatus, message);
+- setText(elements.log, message);
+-}
+-
+ function visiblePlaybackError(message) {
+ const safeMessage = message || "Message Studio playback failed. Check the selected message, part, and TTS profile.";
+ showValidation([safeMessage]);
+- setText(elements.previewStatus, safeMessage);
++ setText(elements.playbackStatus, safeMessage);
+ setText(elements.log, safeMessage);
+ return { message: safeMessage, ok: false };
}
--function refreshSaveControls() {
-+function refreshSaveControls(activeGame = null) {
- const saveAllowed = projectRecordsSaveAllowed();
- [elements.nameInput, elements.purposeInput, elements.gameStatusInput, elements.currentUserRoleInput].forEach((control) => {
- if (control) {
-@@ -149,7 +153,9 @@ function refreshSaveControls() {
- submitButton.disabled = !saveAllowed;
- }
- if (elements.deleteOpenGame) {
-- elements.deleteOpenGame.disabled = !saveAllowed;
-+ const sourceLinked = isSourceLinkedGame(activeGame);
-+ elements.deleteOpenGame.disabled = !saveAllowed || sourceLinked;
-+ elements.deleteOpenGame.hidden = sourceLinked;
- }
- if (!saveAllowed) {
- const currentStatus = String(elements.statusLog?.textContent || "");
-@@ -424,7 +430,7 @@ function renderWorkspace() {
- renderChecklist(progress);
- renderProjectInformation(activeGame, currentMember, progress);
- renderSourceIdea(activeGame);
-- refreshSaveControls();
-+ refreshSaveControls(activeGame);
+ function playbackService() {
+- return selectedTtsService() || state.ttsServices.find((service) => service.available) || null;
++ return selectedTtsService();
}
- elements.form?.addEventListener("submit", (event) => {
-@@ -481,6 +487,11 @@ elements.deleteOpenGame?.addEventListener("click", () => {
- renderWorkspace();
- return;
- }
-+ if (isSourceLinkedGame(activeGame)) {
-+ setStatusLog("Source-linked projects stay connected to Idea Board.");
-+ renderWorkspace();
-+ return;
-+ }
-
- repository.deleteGame(activeGame.id);
- setStatusLog(`Deleted ${activeGame.name}.`);
-@@ -529,9 +540,9 @@ elements.currentUserRoleInput?.addEventListener("change", () => {
- renderWorkspace();
- });
-
--populateSelect(elements.purposeInput, GAME_WORKSPACE_GAME_PURPOSES);
--populateSelect(elements.gameStatusInput, GAME_WORKSPACE_GAME_STATUSES);
--populateSelect(elements.currentUserRoleInput, GAME_WORKSPACE_MEMBER_ROLES);
-+populateSelect(elements.purposeInput, GAME_HUB_GAME_PURPOSES);
-+populateSelect(elements.gameStatusInput, GAME_HUB_GAME_STATUSES);
-+populateSelect(elements.currentUserRoleInput, GAME_HUB_MEMBER_ROLES);
- const requestedGameId = new URL(window.location.href).searchParams.get("game");
- if (requestedGameId) {
- const openedGame = repository.openGame(requestedGameId);
-diff --git a/toolbox/game-workspace/index.html b/toolbox/game-hub/index.html
-similarity index 96%
-rename from toolbox/game-workspace/index.html
-rename to toolbox/game-hub/index.html
-index f204166d8..ef7b50004 100644
---- a/toolbox/game-workspace/index.html
-+++ b/toolbox/game-hub/index.html
-@@ -65,7 +65,7 @@
-
-
-
--
-+
-