From 0cf9a657e5021271702ed985c4bad7a72137b014 Mon Sep 17 00:00:00 2001 From: "Mr. Q" <97194984+ToolboxAid@users.noreply.github.com> Date: Sat, 20 Jun 2026 19:59:48 -0400 Subject: [PATCH 1/8] Create admin system health foundation - PR_26171_GAMMA_011-admin-system-health-foundation --- admin/system-health.html | 360 +-- ...dation-instruction-compliance-checklist.md | 32 + ...alth-foundation-manual-validation-notes.md | 34 + ...AMMA_011-admin-system-health-foundation.md | 65 + .../dev/reports/codex_changed_files.txt | 58 +- docs_build/dev/reports/codex_review.diff | 2240 +++++------------ .../reports/coverage_changed_js_guardrail.txt | 6 +- .../reports/playwright_v8_coverage_report.txt | 31 +- .../tools/AdminHealthOperationsPage.spec.mjs | 61 +- 9 files changed, 930 insertions(+), 1957 deletions(-) create mode 100644 docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation-instruction-compliance-checklist.md create mode 100644 docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation-manual-validation-notes.md create mode 100644 docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation.md diff --git a/admin/system-health.html b/admin/system-health.html index c5ef74f44..65a7ac26f 100644 --- a/admin/system-health.html +++ b/admin/system-health.html @@ -6,7 +6,7 @@ System Health - Game Foundry Studio LLC - + @@ -18,7 +18,7 @@
Admin

System Health

-

Review safe runtime readiness across account, Local DB, R2 storage, migrations, package planning, promotion safety, and storage connectivity.

+

Review the foundation view for environment, Postgres, Cloudflare R2, runtime configuration, capacity, and diagnostics.

@@ -36,21 +36,14 @@

Admin

- Status Sources + Health Sections
-

Account/session readiness

-

Product Data / Local DB

-

Project Asset Storage / R2

-

Project package readiness

-
-
-
- Storage Connectivity -
- - - - +

Environment Summary

+

Database Health

+

Storage Health

+

Runtime Environment

+

Limits & Capacity

+

Diagnostics Log

@@ -58,329 +51,139 @@

Admin

-
Read-only Status
-

System Health Overview

-
-
- - - - - - - - - - - - - - - -
System Health Dashboard Summary
StatusScorePASSWARNFAILLast Refresh
Loading0000Loading refresh timestamp.
-
-
- - - - - - - - - - - - -
System Health Overview
AreaStatusSummary
System HealthLoadingLoading safe status.
-
-
- - - - - - - - - - - - - -
Operational Health Summary
AreaStatusCountDiagnostic
OperationsLoading0Loading operational health.
-
-
- - - - - - - - - - - - - - -
Membership Support State
FilterValue
Plan - -
+
Foundation
+

System Health Tables

- - +
Membership Assignments
+ - - + + + - - - - - - -
Environment Summary
UserPlanEnvironmentPurposePromotion Gate StatusSource
LoadingLoadingLoadingLoading membership state.
-
-
- - - - - - - - - - - - - - -
Invite Support
FilterValue
Status - -
-
-
- - - - - - - - - - - - - -
Invite Records
EmailStatusCodeExpires
LoadingLoadingLoadingLoading invite state.
-
-
- - - - - - - - - - - -
AI Usage Monitoring
FilterValue
Action - -
-
-
- - - - - - - - - - - - - - -
AI Usage Rows
UserActionSourceDeltaBalance After
LoadingLoadingLoading0Loading AI usage.
-
-
- - - - - - - - - - - - - - -
Operational Configuration Issues
AreaStatusTableIssueNext Step
OperationsLoadingLoadingLoading configuration issues.Loading next step.
-
-
- - - - - - - - - - - - -
Configured Connections
ConnectionStatusMode
AccountLoadingLoading configured connection summary.
-
-
- - - - - - - - - - - + + + +
Database Status
FieldStatusValue
Connection ConfiguredLoadingLoading database status.
DEVLocal implementation and diagnosticsDeveloper validationWARN
ISTIntegrated system testingCross-feature validationWARN
UATUser acceptance testingOwner reviewWARN
PRDProduction runtimeEOD approvalWARN
- - +
Project Asset Storage Status
+ - - - - - - -
Database Health - Postgres Only
FieldStatus Value
Storage ConfiguredLoadingLoading storage status.
-
-
- - - - - - - - - - + + + + + + +
R2 Operational Readiness
AreaSignal StatusValueNext Step
Project Asset Storage / R2StatusLoadingLoadingLoading R2 readiness.
ProviderPostgresPASS
HostConfigured host placeholderWARN
Port5432WARN
DatabaseConfigured database placeholderWARN
Migration VersionPending migration readerWARN
StatusConnection check pendingWARN
- - +
Storage Connectivity Results
+ - + + - - - - + + + + + +
Storage Health - Cloudflare R2
ActionCheckTarget StatusExecutedMessage
startupSKIPnoStorage connectivity startup has not run yet.
BucketConfigured bucket placeholderWARN
ListObjects prefixWARN
ReadHealth objectWARN
WriteHealth objectWARN
DeleteHealth objectWARN
- - +
System Health Details
+ - - + + - - - + + + + + +
Runtime Environment - Alphabetical Variables
AreaFieldVariableDisplay StatusValue
System HealthStatusLoadingLoading safe details.
CLOUDFLARE_R2_ACCESS_KEY_ID********WARN
CLOUDFLARE_R2_BUCKETConfigured bucket placeholderWARN
CLOUDFLARE_R2_SECRET_ACCESS_KEY********WARN
DATABASE_URL********WARN
GAMEFOUNDRY_ENVIRONMENTDEV / IST / UAT / PRDWARN
- - +
Environment Limits from current .env; values may differ by DEV/IST/UAT/PRD
+ - - - - + + + - - + + + + + +
Limits & Capacity
Variable LimitUsagePressureNext StepCurrentCapacity SignalStatus
System HealthLoadingLoadingLoadingLoading limit diagnostics.
DB SizePending metricThreshold not connectedWARN
ConnectionsPending metricPool usage not connectedWARN
StoragePending metricBucket usage not connectedWARN
Class A OpsPending metricR2 write/list budget not connectedWARN
Class B OpsPending metricR2 read budget not connectedWARN
-
-
--

TTS Studio

--
--
0Characters
--
0TTS Profiles
--
0Emotion Settings
--
0Voices
--
CheckingEngine
-+
-+ TTS Studio -+
Characters 0
-+
TTS Profiles 0
-+
Emotion Settings 0
-+
Voices 0
-+
Engine Checking
-
-
-
+- const membershipFilter = page.locator("[data-admin-health-membership-plan-filter]"); +- await expect(membershipFilter).toContainText("FREE"); +- await membershipFilter.selectOption("FREE"); +- await expect(page.locator("[data-admin-health-membership-rows]")).toContainText("FREE"); +- +- await expect(page.locator("[data-admin-health-invitation-status-filter]")).toBeVisible(); +- await expect(page.locator("[data-admin-health-ai-action-filter]")).toBeVisible(); ++ await expect(page.getByRole("table", { name: "Environment summary" })).toContainText("DEV"); ++ await expect(page.getByRole("table", { name: "Environment summary" })).toContainText("IST"); ++ await expect(page.getByRole("table", { name: "Environment summary" })).toContainText("UAT"); ++ await expect(page.getByRole("table", { name: "Environment summary" })).toContainText("PRD"); ++ await expect(page.getByRole("table", { name: "Database health" })).toContainText("Postgres"); ++ await expect(page.getByRole("table", { name: "Storage health" })).toContainText("Cloudflare R2"); ++ await expect(page.getByRole("table", { name: "Runtime environment" })).toContainText("********"); ++ await expect(page.getByRole("table", { name: "Limits and capacity" })).toContainText("Class A Ops"); ++ await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("PASS"); ++ await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("WARN"); ++ await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("FAIL"); ++ expect(context.requestUrls.some((url) => url.includes("/api/admin/system-health"))).toBe(false); ++ await expect(page.locator("[data-admin-system-health-storage-action]")).toHaveCount(0); +@@ -92 +116 @@ test("Creator sessions cannot access Admin System Health operations", async ({ p +- await expect(page.locator("[data-admin-health-summary-rows]")).toHaveCount(0); ++ await expect(page.getByRole("table", { name: "Environment summary" })).toHaveCount(0); +@@ -107 +131,2 @@ test("Admin System Health operations page keeps scripts and styles external", as +- expect(pageSource).toContain("assets/theme-v2/js/admin-system-health.js"); ++ expect(pageSource).not.toContain("assets/theme-v2/js/admin-system-health.js"); ++ expect(pageSource).toContain("assets/theme-v2/js/admin-owner-navigation.js"); diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt index 59de15fb6..7b1c51f19 100644 --- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt +++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt @@ -6,9 +6,7 @@ Missing changed runtime JS files are WARN, not FAIL. Source: Playwright/Chromium built-in V8 coverage from the active Playwright run. Changed runtime JS files considered: -(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(0%) toolbox/messages/messages.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only +(100%) none changed - no changed runtime JS files Guardrail warnings: -(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file missing from coverage; advisory only -(0%) toolbox/messages/messages.js - WARNING: changed runtime JS file missing from coverage; advisory only +(100%) none changed - no changed runtime JS files diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt index 94d5e709f..36e3b18b0 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt @@ -12,33 +12,24 @@ Note: entry percentages use function coverage when available, otherwise line cov Note: coverage entries are aggregated across every page/tool where coverageReporter.start(page) and coverageReporter.stop(page) ran. Exercised tool entry points detected: -(70%) Toolbox Index - exercised 3 runtime JS files +(46%) Toolbox Index - exercised 1 runtime JS files (0%) Tool Template V2 - not exercised by this Playwright run -(72%) Theme V2 Shared JS - exercised 1 runtime JS files +(75%) Theme V2 Shared JS - exercised 3 runtime JS files Changed runtime JS files covered: -(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(0%) toolbox/messages/messages.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only +(100%) none changed - no changed runtime JS files Files with executed line/function counts where available: -(25%) src/api/session-api-client.js - executed lines 68/68; executed functions 3/12 -(33%) src/api/toolbox-votes-api-client.js - executed lines 46/46; executed functions 2/6 -(44%) src/api/server-api-client.js - executed lines 167/167; executed functions 8/18 -(48%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 11/23 +(36%) src/api/server-api-client.js - executed lines 167/167; executed functions 5/14 +(46%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 12/26 +(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 204/204; executed functions 9/14 (65%) src/api/public-config-client.js - executed lines 209/209; executed functions 17/26 -(67%) src/api/game-journey-completion-api-client.js - executed lines 15/15; executed functions 2/3 -(67%) toolbox/game-workspace/game-workspace-api-client.js - executed lines 20/20; executed functions 2/3 -(72%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 977/977; executed functions 65/90 -(74%) toolbox/tools-page-accordions.js - executed lines 1156/1156; executed functions 81/109 +(74%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 977/977; executed functions 67/90 +(80%) src/api/admin-owner-navigation.js - executed lines 42/42; executed functions 4/5 +(91%) assets/theme-v2/js/admin-owner-navigation.js - executed lines 58/58; executed functions 10/11 Uncovered or low-coverage changed JS files: -(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: uncovered changed runtime JS file; advisory only -(0%) toolbox/messages/messages.js - WARNING: uncovered changed runtime JS file; advisory only +(100%) none changed - no changed runtime JS files Changed JS files considered: -(0%) src/shared/toolbox/tool-metadata-inventory.js - changed JS file not collected as browser runtime coverage -(0%) tests/playwright/tools/MessagesTool.spec.mjs - changed JS file not collected as browser runtime coverage -(0%) tests/playwright/tools/RootToolsFutureState.spec.mjs - changed JS file not collected as browser runtime coverage -(0%) tests/playwright/tools/TextToSpeechFunctional.spec.mjs - changed JS file not collected as browser runtime coverage -(0%) tests/playwright/tools/ToolboxRoutePages.spec.mjs - changed JS file not collected as browser runtime coverage -(0%) toolbox/messages/messages.js - changed JS file not collected as browser runtime coverage +(0%) tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - changed JS file not collected as browser runtime coverage diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs index e19b4da0b..fb16b40e8 100644 --- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -6,6 +6,11 @@ import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; async function setSessionUser(server, userKey) { + await fetch(`${server.baseUrl}/api/session/mode`, { + body: JSON.stringify({ modeId: "local-db" }), + headers: { "content-type": "application/json" }, + method: "POST", + }); await fetch(`${server.baseUrl}/api/session/user`, { body: JSON.stringify({ userKey }), headers: { "content-type": "application/json" }, @@ -15,9 +20,14 @@ async function setSessionUser(server, userKey) { async function openAdminSystemHealthPage(page, userKey) { const server = await startRepoServer(); + const previousApiUrl = process.env.GAMEFOUNDRY_API_URL; + const previousSiteUrl = process.env.GAMEFOUNDRY_SITE_URL; + process.env.GAMEFOUNDRY_API_URL = `${server.baseUrl}/api`; + process.env.GAMEFOUNDRY_SITE_URL = server.baseUrl; const failedRequests = []; const pageErrors = []; const consoleErrors = []; + const requestUrls = []; page.on("pageerror", (error) => { pageErrors.push(error.message); }); @@ -34,6 +44,9 @@ async function openAdminSystemHealthPage(page, userKey) { page.on("requestfailed", (request) => { failedRequests.push(`FAILED ${request.url()}`); }); + page.on("request", (request) => { + requestUrls.push(request.url()); + }); await setSessionUser(server, userKey); await workspaceV2CoverageReporter.start(page); await page.goto(`${server.baseUrl}/admin/system-health.html`, { waitUntil: "networkidle" }); @@ -41,39 +54,50 @@ async function openAdminSystemHealthPage(page, userKey) { consoleErrors, failedRequests, pageErrors, + previousApiUrl, + previousSiteUrl, + requestUrls, server, }; } +function restoreEnvValue(key, value) { + if (value === undefined) { + delete process.env[key]; + return; + } + process.env[key] = value; +} + async function closeAdminSystemHealthPage(page, context) { await workspaceV2CoverageReporter.stop(page); await context.server.close(); + restoreEnvValue("GAMEFOUNDRY_API_URL", context.previousApiUrl); + restoreEnvValue("GAMEFOUNDRY_SITE_URL", context.previousSiteUrl); } test.afterAll(async () => { await workspaceV2CoverageReporter.writeReport(); }); -test("Admin System Health renders operational health summaries and filters", async ({ page }) => { +test("Admin System Health renders foundation tables without page API calls", async ({ page }) => { const context = await openAdminSystemHealthPage(page, SEED_DB_KEYS.users.admin); try { await expect(page).toHaveTitle(/System Health - Game Foundry Studio LLC/); await expect(page.getByRole("heading", { exact: true, name: "System Health" })).toBeVisible(); - await expect(page.locator("[data-admin-system-health-status]")).toContainText("Admin System Health loaded safe status only."); - await expect(page.locator("[data-admin-health-summary-rows]")).toContainText("Membership operations"); - await expect(page.locator("[data-admin-health-summary-rows]")).toContainText("Invitation support"); - await expect(page.locator("[data-admin-health-summary-rows]")).toContainText("AI credit monitoring"); - await expect(page.locator("[data-admin-health-summary-rows]")).toContainText("Marketplace revenue health"); - await expect(page.locator("[data-admin-health-summary-rows]")).toContainText("Team enforcement health"); - await expect(page.locator("[data-admin-health-config-issue-rows]")).toContainText("Required Admin operations tables and records are available."); - - const membershipFilter = page.locator("[data-admin-health-membership-plan-filter]"); - await expect(membershipFilter).toContainText("FREE"); - await membershipFilter.selectOption("FREE"); - await expect(page.locator("[data-admin-health-membership-rows]")).toContainText("FREE"); - - await expect(page.locator("[data-admin-health-invitation-status-filter]")).toBeVisible(); - await expect(page.locator("[data-admin-health-ai-action-filter]")).toBeVisible(); + await expect(page.getByRole("table", { name: "Environment summary" })).toContainText("DEV"); + await expect(page.getByRole("table", { name: "Environment summary" })).toContainText("IST"); + await expect(page.getByRole("table", { name: "Environment summary" })).toContainText("UAT"); + await expect(page.getByRole("table", { name: "Environment summary" })).toContainText("PRD"); + await expect(page.getByRole("table", { name: "Database health" })).toContainText("Postgres"); + await expect(page.getByRole("table", { name: "Storage health" })).toContainText("Cloudflare R2"); + await expect(page.getByRole("table", { name: "Runtime environment" })).toContainText("********"); + await expect(page.getByRole("table", { name: "Limits and capacity" })).toContainText("Class A Ops"); + await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("PASS"); + await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("WARN"); + await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("FAIL"); + expect(context.requestUrls.some((url) => url.includes("/api/admin/system-health"))).toBe(false); + await expect(page.locator("[data-admin-system-health-storage-action]")).toHaveCount(0); await expect(page.locator("[data-owner-ai-save], [data-owner-membership-save], [data-owner-ai-credits], [data-owner-memberships]")).toHaveCount(0); await expect(page.locator("style, [style], script:not([src])")).toHaveCount(0); expect(context.pageErrors).toEqual([]); @@ -89,7 +113,7 @@ test("Creator sessions cannot access Admin System Health operations", async ({ p try { await expect(page.getByRole("heading", { name: "Admin role required" })).toBeVisible(); await expect(page.locator("[data-session-access-blocked='admin']")).toBeVisible(); - await expect(page.locator("[data-admin-health-summary-rows]")).toHaveCount(0); + await expect(page.getByRole("table", { name: "Environment summary" })).toHaveCount(0); expect(context.pageErrors).toEqual([]); expect(context.consoleErrors).toEqual([]); expect(context.failedRequests).toEqual([]); @@ -104,5 +128,6 @@ test("Admin System Health operations page keeps scripts and styles external", as expect(pageSource).not.toMatch(/]+src=)/i); expect(pageSource).not.toMatch(/\son[a-z]+\s*=/i); expect(pageSource).not.toMatch(/\sstyle\s*=/i); - expect(pageSource).toContain("assets/theme-v2/js/admin-system-health.js"); + expect(pageSource).not.toContain("assets/theme-v2/js/admin-system-health.js"); + expect(pageSource).toContain("assets/theme-v2/js/admin-owner-navigation.js"); }); From 33bcbf07338c593cec3bf8a1ded422ce3ab690d6 Mon Sep 17 00:00:00 2001 From: "Mr. Q" <97194984+ToolboxAid@users.noreply.github.com> Date: Sat, 20 Jun 2026 21:07:48 -0400 Subject: [PATCH 2/8] Clean up admin system health status reasons - PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup --- admin/system-health.html | 56 +- ...leanup-instruction-compliance-checklist.md | 57 + ...-reason-cleanup-manual-validation-notes.md | 26 + ...min-system-health-status-reason-cleanup.md | 69 + .../dev/reports/codex_changed_files.txt | 70 +- docs_build/dev/reports/codex_review.diff | 2163 ++++++++++++++++- .../tools/AdminHealthOperationsPage.spec.mjs | 17 +- 7 files changed, 2366 insertions(+), 92 deletions(-) create mode 100644 docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md create mode 100644 docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md create mode 100644 docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md diff --git a/admin/system-health.html b/admin/system-health.html index 65a7ac26f..092a94f04 100644 --- a/admin/system-health.html +++ b/admin/system-health.html @@ -66,10 +66,10 @@

System Health Tables

- DEVLocal implementation and diagnosticsDeveloper validationWARN - ISTIntegrated system testingCross-feature validationWARN - UATUser acceptance testingOwner reviewWARN - PRDProduction runtimeEOD approvalWARN + DEVLocal implementation and diagnosticsDeveloper validationPASS + ISTIntegrated system testingCross-feature validationPASS + UATUser acceptance testingOwner reviewPASS + PRDProduction runtimeEOD approvalPASS
@@ -84,12 +84,12 @@

System Health Tables

- ProviderPostgresPASS - HostConfigured host placeholderWARN - Port5432WARN - DatabaseConfigured database placeholderWARN - Migration VersionPending migration readerWARN - StatusConnection check pendingWARN + ProviderPostgresPASS + HostConfigured host placeholderPENDING + Port5432PASS + DatabaseConfigured database placeholderPENDING + Migration VersionPending migration readerPENDING + StatusConnection check pendingPENDING
@@ -104,11 +104,11 @@

System Health Tables

- BucketConfigured bucket placeholderWARN - ListObjects prefixWARN - ReadHealth objectWARN - WriteHealth objectWARN - DeleteHealth objectWARN + BucketConfigured bucket placeholderPENDING + ListObjects prefixPENDING + ReadHealth objectPENDING + WriteHealth objectPENDING + DeleteHealth objectPENDING @@ -123,11 +123,11 @@

System Health Tables

- CLOUDFLARE_R2_ACCESS_KEY_ID********WARN - CLOUDFLARE_R2_BUCKETConfigured bucket placeholderWARN - CLOUDFLARE_R2_SECRET_ACCESS_KEY********WARN - DATABASE_URL********WARN - GAMEFOUNDRY_ENVIRONMENTDEV / IST / UAT / PRDWARN + CLOUDFLARE_R2_ACCESS_KEY_ID********PASS + CLOUDFLARE_R2_BUCKETConfigured bucket placeholderPENDING + CLOUDFLARE_R2_SECRET_ACCESS_KEY********PASS + DATABASE_URL********PASS + GAMEFOUNDRY_ENVIRONMENTDEV / IST / UAT / PRDPASS @@ -143,11 +143,11 @@

System Health Tables

- DB SizePending metricThreshold not connectedWARN - ConnectionsPending metricPool usage not connectedWARN - StoragePending metricBucket usage not connectedWARN - Class A OpsPending metricR2 write/list budget not connectedWARN - Class B OpsPending metricR2 read budget not connectedWARN + DB SizePending metricThreshold not connectedPENDING + ConnectionsPending metricPool usage not connectedPENDING + StoragePending metricBucket usage not connectedPENDING + Class A OpsPending metricR2 write/list budget not connectedPENDING + Class B OpsPending metricR2 read budget not connectedPENDING @@ -171,9 +171,9 @@

Diagnostics

- PASSDatabase HealthPostgres is the only planned database provider. - WARNRuntime EnvironmentSecret values are masked until server-owned readers are approved. - FAILDiagnosticsNo active failure is declared in this foundation view. + PASSDatabase HealthPostgres is the only planned database provider. + PASSRuntime EnvironmentSecret values are masked in the foundation view. + PENDINGDiagnosticsLive diagnostics log wiring is intentionally deferred. diff --git a/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md new file mode 100644 index 000000000..3a93297cf --- /dev/null +++ b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md @@ -0,0 +1,57 @@ +# PR_26171_GAMMA_012 Instruction Compliance Checklist + +## Required Reads + +- PASS: Read `docs_build/dev/PROJECT_INSTRUCTIONS.md`. +- PASS: Read `docs_build/dev/PROJECT_MULTI_PC.txt`. +- PASS: Read exact target file `admin/system-health.html`. +- PASS: Read existing target route test `tests/playwright/tools/AdminHealthOperationsPage.spec.mjs`. + +## Ownership And Branch + +- PASS: PR name includes TEAM token `GAMMA`. +- PASS: TEAM ownership was verified against `PROJECT_MULTI_PC.txt`. +- PASS: User explicitly directed continuation on `team/GAMMA/admin`. +- PASS: User explicitly directed updating existing draft PR #36 and not creating a separate PR012 GitHub PR. +- PASS: Repository was clean before edits. +- PASS: Branch was synced with origin before edits. +- PASS: Work remained within the Admin diagnostics/foundation scope. + +## Scope Compliance + +- PASS: Applied status cleanup to existing Admin System Health draft PR/workstream branch. +- PASS: Removed fake `FAIL` row. +- PASS: Reduced `WARN` usage by removing `WARN` health statuses from the page. +- PASS: Used `PASS` for declared foundation facts. +- PASS: Used `PENDING` for intentionally unwired foundation items. +- PASS: Added hover/accessibility reason text for every non-`PASS` status. +- PASS: Preserved Postgres-only wording. +- PASS: Preserved Cloudflare R2 wording. +- PASS: Did not add persistence. +- PASS: Did not add API wiring. +- PASS: Did not add SQLite. +- PASS: Did not change samples. + +## Validation Compliance + +- PASS: Ran `git diff --check`. +- PASS: Ran targeted Admin System Health static validation. +- PASS: Verified every non-`PASS` status has a reason. +- PASS: Verified no fake failure exists in `admin/system-health.html`. +- PASS: Verified no `WARN` or `FAIL` health status remains in `admin/system-health.html`. +- PASS: Ran existing targeted Admin System Health Playwright route spec after updating its assertions. +- PASS: Did not run samples. + +## Reports And Packaging + +- PASS: Created queued-scope PR report. +- PASS: Created manual validation notes. +- PASS: Created instruction compliance checklist. +- PASS: Generate `codex_review.diff` after staging scoped files. +- PASS: Generate `codex_changed_files.txt` after staging scoped files. +- PASS: Create repo-structured delta ZIP under `tmp/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup_delta.zip`. + +## Merge Control + +- PASS: No merge performed. +- PASS: Owner-controlled EOD merge approval remains required. diff --git a/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md new file mode 100644 index 000000000..abffac83a --- /dev/null +++ b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md @@ -0,0 +1,26 @@ +# PR_26171_GAMMA_012 Manual Validation Notes + +## Manual Review Notes + +- Reviewed `admin/system-health.html` after the cleanup. +- Confirmed diagnostics no longer includes the fake failure row that said no active failure was declared. +- Confirmed intentionally unwired foundation items use `PENDING` instead of `WARN`. +- Confirmed every `PENDING` status has `title` and `aria-label` reason text. +- Confirmed `PASS` is used for declared facts such as DEV/IST/UAT/PRD, Postgres provider, default Postgres port, masked secret display, and the known environment target list. +- Confirmed Postgres-only wording remains in Database Health. +- Confirmed Cloudflare R2 wording remains in Storage Health. +- Confirmed no SQLite wording appears in the Admin System Health page. +- Confirmed no page-local CSS, inline script, inline style, persistence, or Admin System Health API wiring was added. + +## Validation Notes + +- `git diff --check` passed. +- Targeted static status validation passed. +- Existing targeted Admin System Health Playwright route spec passed with 3 tests. +- Samples were not run because samples are outside this queued status cleanup scope. + +## User Review Focus + +- Review whether `PENDING` is the desired final wording for intentionally unwired health checks. +- Review the reason text in hover/accessibility attributes for clarity. +- Confirm owner approval before any EOD merge. diff --git a/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md new file mode 100644 index 000000000..094b6dfbe --- /dev/null +++ b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md @@ -0,0 +1,69 @@ +# PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup + +## Summary + +Queued scope 012 was applied to the existing draft PR #36 workstream branch: + +- PR #36: `PR_26171_GAMMA_011-admin-system-health-foundation` +- Branch: `team/GAMMA/admin` + +This queued scope cleans up Admin System Health foundation status semantics without creating a separate GitHub PR. + +## Scope Evidence + +- Removed the fake `FAIL` diagnostics row. +- Removed foundation placeholder `WARN` statuses from `admin/system-health.html`. +- Kept declared foundation facts as `PASS`, including deployment target rows, Postgres provider, masked secret display, and known environment target list. +- Used `PENDING` for intentionally unwired foundation checks and metrics. +- Added `title` and `aria-label` reason text to every non-`PASS` health status cell. +- Preserved Postgres-only wording. +- Preserved Cloudflare R2 wording. +- Kept the page static and Theme V2-only. +- Did not add persistence, API wiring, or SQLite. + +## Instruction Start Gate + +- Instructions read: PASS +- `docs_build/dev/PROJECT_INSTRUCTIONS.md`: read before edits +- `docs_build/dev/PROJECT_MULTI_PC.txt`: read before edits +- Current branch: `team/GAMMA/admin` +- Clean status before edits: PASS +- Local/remote sync before edits: PASS (`0 0`) +- TEAM token: `GAMMA` +- TEAM ownership: PASS by explicit Master Control/user assignment for diagnostics/admin workstream +- Implementation path: `admin/system-health.html` +- Existing draft PR target: PR #36 +- Separate PR creation: SKIP by explicit user instruction +- Merge: SKIP, owner-controlled EOD approval remains required + +Note: the merged instruction file still contains the older main-only branch guard. This queued update proceeded only after explicit user instruction to continue on `team/GAMMA/admin` and update existing draft PR #36 rather than creating a separate PR. + +## Validation + +- PASS: `git diff --check` +- PASS: targeted Admin System Health source check found 28 status cells and 16 non-`PASS` statuses with reasons. +- PASS: targeted source check found no fake failure text, no `WARN`/`FAIL` health status cells, and no SQLite text in `admin/system-health.html`. +- PASS: targeted source check verified Postgres and Cloudflare R2 wording remains present. +- PASS: `npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --config=codex_playwright_system_chrome.config.cjs --project=playwright` (3 passed) + +## Skipped Lanes + +- Full samples smoke: skipped by request because this status cleanup does not touch samples. +- Full Playwright suite: skipped because the existing targeted Admin System Health route spec covers the changed page behavior. +- Runtime/API validation: skipped because no API wiring or runtime persistence was added. + +## Required Reports + +- `docs_build/dev/reports/codex_review.diff` +- `docs_build/dev/reports/codex_changed_files.txt` +- `docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md` +- `docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md` +- `docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md` + +## ZIP Artifact + +- `tmp/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup_delta.zip` + +## EOD Approval + +No merge was performed. EOD merge remains owner-controlled and requires explicit approval. diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index a1f459308..99f7fd9ab 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,10 +1,66 @@ -M docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation.md +M admin/system-health.html +A docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md +A docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md +A docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md +M tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - admin/system-health.html | 360 +++++---------------- - ...-foundation-instruction-compliance-checklist.md | 32 ++ + admin/system-health.html | 360 +++-------- + ...ble-cleanup-instruction-compliance-checklist.md | 14 - + ...essage-table-cleanup-manual-validation-notes.md | 10 - + ...-tts-message-table-cleanup-validation-report.md | 25 - + .../PR_26171_BETA_075-tts-message-table-cleanup.md | 42 -- + ...e-parent-child-table-manual-validation-notes.md | 11 - + ...profile-parent-child-table-validation-report.md | 17 - + ...6171_BETA_077-tts-profile-parent-child-table.md | 19 - + ...ild-table-completion-manual-validation-notes.md | 11 - + ...ent-child-table-completion-validation-report.md | 17 - + ...message-studio-parent-child-table-completion.md | 20 - + ...k-through-tts-engine-manual-validation-notes.md | 11 - + ...layback-through-tts-engine-validation-report.md | 21 - + ...BETA_081-message-playback-through-tts-engine.md | 18 - + ...-foundation-instruction-compliance-checklist.md | 32 + ...em-health-foundation-manual-validation-notes.md | 34 ++ - ...171_GAMMA_011-admin-system-health-foundation.md | 65 ++++ + ...171_GAMMA_011-admin-system-health-foundation.md | 65 ++ + ...son-cleanup-instruction-compliance-checklist.md | 57 ++ + ...tatus-reason-cleanup-manual-validation-notes.md | 26 + + ...12-admin-system-health-status-reason-cleanup.md | 69 +++ .../dev/reports/coverage_changed_js_guardrail.txt | 6 +- - .../dev/reports/playwright_v8_coverage_report.txt | 31 +- - .../tools/AdminHealthOperationsPage.spec.mjs | 61 ++-- - 7 files changed, 268 insertions(+), 321 deletions(-) + docs_build/dev/reports/dependency_gating_report.md | 2 +- + .../reports/dependency_hydration_reuse_report.md | 2 +- + .../dev/reports/execution_graph_reuse_report.md | 4 +- + .../dev/reports/failure_fingerprint_report.md | 2 +- + .../reports/filesystem_scan_reduction_report.md | 2 +- + .../dev/reports/incremental_validation_report.md | 2 +- + docs_build/dev/reports/lane_compilation_report.md | 2 +- + .../dev/reports/lane_deduplication_report.md | 2 +- + .../dev/reports/lane_input_validation_report.md | 2 +- + .../reports/lane_manifests/workspace-contract.json | 8 +- + .../reports/lane_runtime_optimization_report.md | 2 +- + docs_build/dev/reports/lane_snapshot_report.md | 4 +- + .../reports/lane_snapshots/workspace-contract.json | 16 +- + docs_build/dev/reports/lane_warm_start_report.md | 4 +- + .../lane_warm_starts/workspace-contract.json | 8 +- + .../dev/reports/monolith_trigger_removal_report.md | 2 +- + .../dev/reports/persistent_lane_manifest_report.md | 6 +- + .../playwright_discovery_ownership_report.md | 2 +- + .../reports/playwright_discovery_scope_report.md | 2 +- + .../dev/reports/playwright_structure_audit.md | 2 +- + .../dev/reports/playwright_v8_coverage_report.txt | 43 +- + docs_build/dev/reports/retry_suppression_report.md | 2 +- + docs_build/dev/reports/slow_path_pruning_report.md | 14 +- + docs_build/dev/reports/static_validation_report.md | 4 +- + .../dev/reports/targeted_file_manifest_report.md | 4 +- + .../dev/reports/test_cleanup_performance_report.md | 16 +- + .../dev/reports/test_cleanup_routing_report.md | 2 +- + .../dev/reports/testing_lane_execution_report.md | 18 +- + docs_build/dev/reports/validation_cache_report.md | 30 +- + .../dev/reports/zero_browser_preflight_report.md | 2 +- + .../messages/messages-sqlite-service.mjs | 33 +- + .../tools/AdminHealthOperationsPage.spec.mjs | 74 ++- + tests/playwright/tools/MessagesTool.spec.mjs | 55 +- + .../playwright/tools/RootToolsFutureState.spec.mjs | 23 +- + .../tools/TextToSpeechFunctional.spec.mjs | 86 ++- + toolbox/messages/messages.js | 71 +-- + toolbox/text-to-speech/index.html | 87 ++- + toolbox/text-to-speech/text2speech.js | 662 +++++++++++++++++---- + 59 files changed, 1219 insertions(+), 968 deletions(-) diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 9c5ca4743..a51872ef3 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,5 +1,5 @@ diff --git a/admin/system-health.html b/admin/system-health.html -index c5ef74f44..65a7ac26f 100644 +index c5ef74f44..092a94f04 100644 --- a/admin/system-health.html +++ b/admin/system-health.html @@ -9 +9 @@ @@ -243,10 +243,10 @@ index c5ef74f44..65a7ac26f 100644 - - - Connection ConfiguredLoadingLoading database status. -+ DEVLocal implementation and diagnosticsDeveloper validationWARN -+ ISTIntegrated system testingCross-feature validationWARN -+ UATUser acceptance testingOwner reviewWARN -+ PRDProduction runtimeEOD approvalWARN ++ DEVLocal implementation and diagnosticsDeveloper validationPASS ++ ISTIntegrated system testingCross-feature validationPASS ++ UATUser acceptance testingOwner reviewPASS ++ PRDProduction runtimeEOD approvalPASS @@ -273,2 +77,2 @@ - - @@ -276,12 +276,12 @@ index c5ef74f44..65a7ac26f 100644 - - + -+ -+ -+ -+ -+ -+ ++ ++ ++ ++ ++ ++ @@ -305,2 +97,2 @@ -
Project Asset Storage Status
Project Asset Storage / R2StatusLoadingLoadingLoading R2 readiness.
ProviderPostgresPASS
HostConfigured host placeholderWARN
Port5432WARN
DatabaseConfigured database placeholderWARN
Migration VersionPending migration readerWARN
StatusConnection check pendingWARN
ProviderPostgresPASS
HostConfigured host placeholderPENDING
Port5432PASS
DatabaseConfigured database placeholderPENDING
Migration VersionPending migration readerPENDING
StatusConnection check pendingPENDING
- @@ -298,11 +298,11 @@ index c5ef74f44..65a7ac26f 100644 - - + -+ -+ -+ -+ -+ ++ ++ ++ ++ ++ @@ -321,2 +116,2 @@ -
Storage Connectivity Results
startupSKIPnoStorage connectivity startup has not run yet.
BucketConfigured bucket placeholderWARN
ListObjects prefixWARN
ReadHealth objectWARN
WriteHealth objectWARN
DeleteHealth objectWARN
BucketConfigured bucket placeholderPENDING
ListObjects prefixPENDING
ReadHealth objectPENDING
WriteHealth objectPENDING
DeleteHealth objectPENDING
- @@ -319,11 +319,11 @@ index c5ef74f44..65a7ac26f 100644 - - + -+ -+ -+ -+ -+ ++ ++ ++ ++ ++ @@ -337,2 +135,2 @@ -
System Health Details
System HealthStatusLoadingLoading safe details.
CLOUDFLARE_R2_ACCESS_KEY_ID********WARN
CLOUDFLARE_R2_BUCKETConfigured bucket placeholderWARN
CLOUDFLARE_R2_SECRET_ACCESS_KEY********WARN
DATABASE_URL********WARN
GAMEFOUNDRY_ENVIRONMENTDEV / IST / UAT / PRDWARN
CLOUDFLARE_R2_ACCESS_KEY_ID********PASS
CLOUDFLARE_R2_BUCKETConfigured bucket placeholderPENDING
CLOUDFLARE_R2_SECRET_ACCESS_KEY********PASS
DATABASE_URL********PASS
GAMEFOUNDRY_ENVIRONMENTDEV / IST / UAT / PRDPASS
- @@ -342,11 +342,11 @@ index c5ef74f44..65a7ac26f 100644 - - + -+ -+ -+ -+ -+ ++ ++ ++ ++ ++ @@ -356 +157 @@ -

Inspector

+

Diagnostics

@@ -373,9 +373,9 @@ index c5ef74f44..65a7ac26f 100644 + + + -+ -+ -+ ++ ++ ++ + +
Environment Limits from current .env; values may differ by DEV/IST/UAT/PRD
System HealthLoadingLoadingLoadingLoading limit diagnostics.
DB SizePending metricThreshold not connectedWARN
ConnectionsPending metricPool usage not connectedWARN
StoragePending metricBucket usage not connectedWARN
Class A OpsPending metricR2 write/list budget not connectedWARN
Class B OpsPending metricR2 read budget not connectedWARN
DB SizePending metricThreshold not connectedPENDING
ConnectionsPending metricPool usage not connectedPENDING
StoragePending metricBucket usage not connectedPENDING
Class A OpsPending metricR2 write/list budget not connectedPENDING
Class B OpsPending metricR2 read budget not connectedPENDING
PASSDatabase HealthPostgres is the only planned database provider.
WARNRuntime EnvironmentSecret values are masked until server-owned readers are approved.
FAILDiagnosticsNo active failure is declared in this foundation view.
PASSDatabase HealthPostgres is the only planned database provider.
PASSRuntime EnvironmentSecret values are masked in the foundation view.
PENDINGDiagnosticsLive diagnostics log wiring is intentionally deferred.
+ @@ -392,6 +392,320 @@ index c5ef74f44..65a7ac26f 100644 + Admin Operations @@ -396 +198,0 @@ - +diff --git a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-instruction-compliance-checklist.md b/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-instruction-compliance-checklist.md +deleted file mode 100644 +index 5a5c8e58d..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-instruction-compliance-checklist.md ++++ /dev/null +@@ -1,14 +0,0 @@ +-# PR_26171_BETA_075 Instruction Compliance Checklist +- +-Team: BETA +- +-- PASS: Started from `main`. +-- PASS: Pulled latest `origin/main` before branch creation. +-- PASS: Repository was clean before creating `pr/26171-BETA-075-tts-message-table-cleanup`. +-- PASS: PR name includes required TEAM token `BETA`. +-- PASS: `docs_build/dev/PROJECT_MULTI_PC.txt` assigns Messages, Text To Speech, and TTS work to Team BETA. +-- PASS: Active implementation paths are `toolbox/text-to-speech/` and `toolbox/messages/`. +-- PASS: No `tools/text2speech/` path was created. +-- PASS: Theme V2 only; no inline styles, style blocks, inline handlers, page-local CSS, or tool-local CSS were added. +-- PASS: Validation scope includes targeted TTS Studio, targeted Message Studio, and legacy `npm run test:workspace-v2`. +-- PASS: Required reports and repo ZIP under `tmp/` are required before completion. +diff --git a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-manual-validation-notes.md +deleted file mode 100644 +index 2fab2dae1..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-manual-validation-notes.md ++++ /dev/null +@@ -1,10 +0,0 @@ +-# PR_26171_BETA_075 Manual Validation Notes +- +-Team: BETA +- +-- Reviewed `toolbox/text-to-speech/index.html` and confirmed no Named Sentence, Queue, or Output Summary surface remains. +-- Reviewed `toolbox/text-to-speech/text2speech.js` and confirmed preview playback uses TTS Profile plus Emotion Settings instead of a named-sentence queue. +-- Reviewed `toolbox/messages/messages.js` and confirmed Message Parts visible columns are Text, Emotion, TTS Profile, Status, and Actions. +-- Confirmed no inline styles, style blocks, inline event handlers, page-local CSS, or tool-local CSS were added. +-- Confirmed no `tools/text2speech/` path was created. +-- Confirmed required delta ZIP path: `tmp/PR_26171_BETA_075-tts-message-table-cleanup_delta.zip`. +diff --git a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-validation-report.md b/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-validation-report.md +deleted file mode 100644 +index 09b7cba7a..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-validation-report.md ++++ /dev/null +@@ -1,25 +0,0 @@ +-# PR_26171_BETA_075 Validation Report +- +-Team: BETA +- +-## Commands +- +-- PASS: `node --check toolbox/text-to-speech/text2speech.js` +-- PASS: `node --check toolbox/messages/messages.js` +-- PASS: `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs` +- - Result: 3 passed. +-- PASS: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --trace=off` +- - Result: 2 passed after scoping the Message Parts table assertion. +-- PASS: `node --test tests/tools/Text2SpeechShell.test.mjs` +- - Result: 4 passed. +-- PASS: `npm run test:workspace-v2` +- - Result: 5 passed. +- - Note: Command name is legacy; user-facing language is Project Workspace and Game Hub. +-- PASS: `git diff --check` +- - Result: no whitespace errors; Git reported line-ending notices only. +- +-## Validation Notes +- +-- Initial `npm run test:workspace-v2` runs exposed stale root toolbox expectations for the current registered toolbox count, duplicate Game Hub entries, and Publish group visibility. +-- Those expectations were updated in the targeted workspace test file, then the legacy workspace command passed. +-- Node emitted experimental SQLite warnings during validation; no validation failed because of those warnings. +diff --git a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup.md b/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup.md +deleted file mode 100644 +index 0da2fe50c..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup.md ++++ /dev/null +@@ -1,42 +0,0 @@ +-# PR_26171_BETA_075 TTS Message Table Cleanup +- +-Team: BETA +- +-## Summary +- +-- Removed the duplicate center summary text `TTS Studio` from the Text To Speech summary row. +-- Removed the stale Text To Speech left-column Voice Filters, Presets, Delivery, and Named Sentence controls. +-- Removed the stale Text To Speech Queue and right-column Output Summary surfaces. +-- Kept voice, language, gender, and age editing in the TTS Profile inline table row. +-- Kept pitch, rate, volume, and preset editing in the Emotion Settings inline table row. +-- Updated Text To Speech preview playback to speak the current text through the selected TTS Profile and selected/neutral Emotion Setting. +-- Updated Message Studio Message Parts to show Text, Emotion, TTS Profile, Status, and Actions while preserving internal display order. +-- Updated focused Playwright tests and workspace count/group expectations to match the current registered toolbox surface. +- +-## Requirement Checklist +- +-- PASS: Removed duplicate center-column `TTS Studio` summary text. +-- PASS: Removed duplicate left-column items that belong to Profile or Emotion Settings rows. +-- PASS: Delivery values are owned by Emotion Settings through Pitch, Rate, and Volume fields. +-- PASS: Preset value is owned by Emotion Settings through the Preset field. +-- PASS: Removed right-column Output Summary. +-- PASS: Voice Filters are owned by the Profile row dropdowns. +-- PASS: Removed Named Sentence and queue UI. +-- PASS: Removed related dead HTML, JavaScript, and test code for the stale queue/output controls. +-- PASS: Message Studio child table uses the approved visible Message Parts columns. +-- PASS: Theme V2 only; no local CSS or inline handlers/styles were added. +- +-## Validation +- +-- PASS: `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs` +-- PASS: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --trace=off` +-- PASS: `node --test tests/tools/Text2SpeechShell.test.mjs` +-- PASS: `npm run test:workspace-v2` +-- PASS: `git diff --check` +- +-`npm run test:workspace-v2` is a legacy command name; the user-facing product language remains Project Workspace and Game Hub. +- +-## Notes +- +-- The Text To Speech page still supports local preview playback through the shared engine audio module. +-- Message Part display order remains persisted internally and is managed with Move Up and Move Down actions rather than a visible Order column. +diff --git a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-manual-validation-notes.md +deleted file mode 100644 +index b050febf8..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-manual-validation-notes.md ++++ /dev/null +@@ -1,11 +0,0 @@ +-# PR_26171_BETA_077 Manual Validation Notes +- +-## Review +-- Confirmed the TTS Profiles table keeps profile fields in the parent row. +-- Confirmed Emotion Settings open below the selected profile as a child table. +-- Confirmed Add Profile and Add Emotion controls render as table rows. +-- Confirmed non-name parent table cells do not open Emotion Settings. +-- Confirmed no separate Emotion Studio or duplicate left-column controls were introduced. +- +-## Manual Browser Coverage +-- Covered by targeted Playwright validation for TTS Studio load, profile expansion, child emotion rows, inline add/edit, and speech preview. +diff --git a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-validation-report.md b/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-validation-report.md +deleted file mode 100644 +index 43ad39062..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-validation-report.md ++++ /dev/null +@@ -1,17 +0,0 @@ +-# PR_26171_BETA_077 Validation Report +- +-## Commands +-- PASS: node --check toolbox/text-to-speech/text2speech.js +-- PASS: node --test tests/tools/Text2SpeechShell.test.mjs +-- PASS: npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs +-- PASS: npm run test:workspace-v2 +-- PASS: git diff --check +- +-## Targeted Results +-- Text2Speech Node contract tests: 4 passed. +-- Text To Speech Playwright tests: 3 passed. +-- Project Workspace legacy validation: 5 passed. +- +-## Notes +-- npm run test:workspace-v2 is a legacy command name; user-facing language remains Project Workspace. +-- Standard generated validation-report churn was restored before staging this PR. +diff --git a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table.md b/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table.md +deleted file mode 100644 +index 3cafc0697..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table.md ++++ /dev/null +@@ -1,19 +0,0 @@ +-# PR_26171_BETA_077-tts-profile-parent-child-table +- +-## Team Ownership +-- TEAM: BETA +-- Ownership source: docs_build/dev/PROJECT_MULTI_PC.txt +-- Scope confirmed: Text To Speech / TTS Studio work is owned by Team BETA. +- +-## Summary +-- Converted the TTS Profile add action into an inline parent-table action row. +-- Converted the Emotion Setting add action into an inline child-table action row. +-- Restricted profile expansion to the Profile Name cell so non-control parent cells remain table data. +-- Added keyboard support and aria-expanded state for the Profile Name accordion trigger. +- +-## Scope Guard +-- Theme V2 only. +-- External JS only. +-- No inline styles, style blocks, inline handlers, page-local CSS, or tool-local CSS. +-- No duplicate left-column controls added. +-- Delivery and Preset values remain in the Emotion Settings child table. +diff --git a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-manual-validation-notes.md +deleted file mode 100644 +index 61db9dd99..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-manual-validation-notes.md ++++ /dev/null +@@ -1,11 +0,0 @@ +-# PR_26171_BETA_079 Manual Validation Notes +- +-## Review +-- Confirmed the Messages table remains the parent table. +-- Confirmed clicking a non-control Message row cell opens the Message Parts child table. +-- Confirmed Message Parts expose Text, Emotion, TTS Profile, Status, and Actions columns. +-- Confirmed Add Part opens an inline child-table editor with Emotion and TTS Profile selectors. +-- Confirmed TTS Studio compatibility validation still passes. +- +-## Manual Browser Coverage +-- Covered by targeted Playwright validation for Message Studio load, row expansion, add/edit flows, and Message Part selectors. +diff --git a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-validation-report.md b/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-validation-report.md +deleted file mode 100644 +index 19361ead5..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-validation-report.md ++++ /dev/null +@@ -1,17 +0,0 @@ +-# PR_26171_BETA_079 Validation Report +- +-## Commands +-- PASS: node --check toolbox/messages/messages.js +-- PASS: npx playwright test tests/playwright/tools/MessagesTool.spec.mjs +-- PASS: npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs +-- PASS: npm run test:workspace-v2 +-- PASS: git diff --check +- +-## Targeted Results +-- Message Studio Playwright tests: 2 passed. +-- Text To Speech compatibility Playwright tests: 3 passed. +-- Project Workspace legacy validation: 5 passed. +- +-## Notes +-- npm run test:workspace-v2 is a legacy command name; user-facing language remains Project Workspace. +-- Standard generated validation-report churn was restored before staging this PR. +diff --git a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion.md b/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion.md +deleted file mode 100644 +index dd4c6c13c..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion.md ++++ /dev/null +@@ -1,20 +0,0 @@ +-# PR_26171_BETA_079-message-studio-parent-child-table-completion +- +-## Team Ownership +-- TEAM: BETA +-- Ownership source: docs_build/dev/PROJECT_MULTI_PC.txt +-- Scope confirmed: Message Studio, Messages, and TTS selection integration are owned by Team BETA. +- +-## Summary +-- Completed Message Studio row-click behavior for the Messages parent table. +-- Kept Message Parts as the selected Message child table. +-- Kept Message Part controls for Text, Emotion, TTS Profile, Status, and Actions. +-- Preserved existing Play Part, Play Message, and Stop controls for the next playback PR. +- +-## Scope Guard +-- Theme V2 only. +-- External JS only. +-- No inline styles, style blocks, inline handlers, page-local CSS, or tool-local CSS. +-- No database schema changes. +-- No separate Emotion Studio. +-- No browser-owned product data source of truth added. +diff --git a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-manual-validation-notes.md +deleted file mode 100644 +index f9749c887..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-manual-validation-notes.md ++++ /dev/null +@@ -1,11 +0,0 @@ +-# PR_26171_BETA_081 Manual Validation Notes +- +-## Review +-- Confirmed /api/messages/tts-profiles returns server-owned emotionSettings for playback. +-- Confirmed Play Message queues each active Message Part through the TextToSpeechEngine-backed registry. +-- Confirmed Play Part uses the selected part TTS Profile and matching Emotion Setting values. +-- Confirmed Stop continues through the TextToSpeechEngine-backed registry stop path. +-- Confirmed missing Emotion Settings produce a visible validation error instead of falling back silently. +- +-## Manual Browser Coverage +-- Covered by targeted Playwright validation for Message Studio playback, TTS profile emotion settings, Play Message, Play Part, and Stop. +diff --git a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-validation-report.md b/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-validation-report.md +deleted file mode 100644 +index 7fbc9bb8d..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-validation-report.md ++++ /dev/null +@@ -1,21 +0,0 @@ +-# PR_26171_BETA_081 Validation Report +- +-## Commands +-- PASS: node --check toolbox/messages/messages.js +-- PASS: node --check src/dev-runtime/messages/messages-sqlite-service.mjs +-- PASS: npx playwright test tests/playwright/tools/MessagesTool.spec.mjs +-- PASS: npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs --reporter=list +-- PASS: node --test tests/tools/Text2SpeechShell.test.mjs +-- PASS: npm run test:workspace-v2 +-- PASS: git diff --check +- +-## Targeted Results +-- Message Studio Playwright tests: 2 passed. +-- Text To Speech compatibility Playwright tests: 3 passed. +-- Text2Speech Node contract tests: 4 passed. +-- Project Workspace legacy validation: 5 passed. +- +-## Notes +-- A parallel Playwright run caused an HTML reporter file-copy collision after tests passed; the TTS compatibility lane was rerun with the list reporter and passed cleanly. +-- npm run test:workspace-v2 is a legacy command name; user-facing language remains Project Workspace. +-- Standard generated validation-report churn was restored before staging this PR. +diff --git a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine.md b/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine.md +deleted file mode 100644 +index 261cae8e6..000000000 +--- a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine.md ++++ /dev/null +@@ -1,18 +0,0 @@ +-# PR_26171_BETA_081-message-playback-through-tts-engine +- +-## Team Ownership +-- TEAM: BETA +-- Ownership source: docs_build/dev/PROJECT_MULTI_PC.txt +-- Scope confirmed: Audio, Messages, Text To Speech, and TTS are owned by Team BETA. +- +-## Summary +-- Added server-owned Emotion Settings to Messages TTS profile API responses without changing database schema. +-- Updated Message Studio playback readiness to require a matching Emotion Setting on the selected TTS Profile. +-- Wired Play Part and Play Message playback values through the existing TextToSpeechEngine registry path using selected TTS Profile language/voice and Emotion Setting pitch/rate/volume/preset. +-- Kept Stop routed through the existing TextToSpeechEngine stop path. +- +-## Scope Guard +-- No database schema changes. +-- No engine core changes. +-- No silent playback fallback when a selected TTS Profile lacks the selected Emotion Setting. +-- Theme V2 and external JS only. diff --git a/docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation-instruction-compliance-checklist.md b/docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation-instruction-compliance-checklist.md new file mode 100644 index 000000000..261e4b70c @@ -541,62 +855,634 @@ index 000000000..27b5f972c +- `docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation.md` +- `docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation-manual-validation-notes.md` +- `docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation-instruction-compliance-checklist.md` +diff --git a/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md +new file mode 100644 +index 000000000..3a93297cf +--- /dev/null ++++ b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md +@@ -0,0 +1,57 @@ ++# PR_26171_GAMMA_012 Instruction Compliance Checklist ++ ++## Required Reads ++ ++- PASS: Read `docs_build/dev/PROJECT_INSTRUCTIONS.md`. ++- PASS: Read `docs_build/dev/PROJECT_MULTI_PC.txt`. ++- PASS: Read exact target file `admin/system-health.html`. ++- PASS: Read existing target route test `tests/playwright/tools/AdminHealthOperationsPage.spec.mjs`. ++ ++## Ownership And Branch ++ ++- PASS: PR name includes TEAM token `GAMMA`. ++- PASS: TEAM ownership was verified against `PROJECT_MULTI_PC.txt`. ++- PASS: User explicitly directed continuation on `team/GAMMA/admin`. ++- PASS: User explicitly directed updating existing draft PR #36 and not creating a separate PR012 GitHub PR. ++- PASS: Repository was clean before edits. ++- PASS: Branch was synced with origin before edits. ++- PASS: Work remained within the Admin diagnostics/foundation scope. ++ ++## Scope Compliance ++ ++- PASS: Applied status cleanup to existing Admin System Health draft PR/workstream branch. ++- PASS: Removed fake `FAIL` row. ++- PASS: Reduced `WARN` usage by removing `WARN` health statuses from the page. ++- PASS: Used `PASS` for declared foundation facts. ++- PASS: Used `PENDING` for intentionally unwired foundation items. ++- PASS: Added hover/accessibility reason text for every non-`PASS` status. ++- PASS: Preserved Postgres-only wording. ++- PASS: Preserved Cloudflare R2 wording. ++- PASS: Did not add persistence. ++- PASS: Did not add API wiring. ++- PASS: Did not add SQLite. ++- PASS: Did not change samples. ++ ++## Validation Compliance ++ ++- PASS: Ran `git diff --check`. ++- PASS: Ran targeted Admin System Health static validation. ++- PASS: Verified every non-`PASS` status has a reason. ++- PASS: Verified no fake failure exists in `admin/system-health.html`. ++- PASS: Verified no `WARN` or `FAIL` health status remains in `admin/system-health.html`. ++- PASS: Ran existing targeted Admin System Health Playwright route spec after updating its assertions. ++- PASS: Did not run samples. ++ ++## Reports And Packaging ++ ++- PASS: Created queued-scope PR report. ++- PASS: Created manual validation notes. ++- PASS: Created instruction compliance checklist. ++- PASS: Generate `codex_review.diff` after staging scoped files. ++- PASS: Generate `codex_changed_files.txt` after staging scoped files. ++- PASS: Create repo-structured delta ZIP under `tmp/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup_delta.zip`. ++ ++## Merge Control ++ ++- PASS: No merge performed. ++- PASS: Owner-controlled EOD merge approval remains required. +diff --git a/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md +new file mode 100644 +index 000000000..abffac83a +--- /dev/null ++++ b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md +@@ -0,0 +1,26 @@ ++# PR_26171_GAMMA_012 Manual Validation Notes ++ ++## Manual Review Notes ++ ++- Reviewed `admin/system-health.html` after the cleanup. ++- Confirmed diagnostics no longer includes the fake failure row that said no active failure was declared. ++- Confirmed intentionally unwired foundation items use `PENDING` instead of `WARN`. ++- Confirmed every `PENDING` status has `title` and `aria-label` reason text. ++- Confirmed `PASS` is used for declared facts such as DEV/IST/UAT/PRD, Postgres provider, default Postgres port, masked secret display, and the known environment target list. ++- Confirmed Postgres-only wording remains in Database Health. ++- Confirmed Cloudflare R2 wording remains in Storage Health. ++- Confirmed no SQLite wording appears in the Admin System Health page. ++- Confirmed no page-local CSS, inline script, inline style, persistence, or Admin System Health API wiring was added. ++ ++## Validation Notes ++ ++- `git diff --check` passed. ++- Targeted static status validation passed. ++- Existing targeted Admin System Health Playwright route spec passed with 3 tests. ++- Samples were not run because samples are outside this queued status cleanup scope. ++ ++## User Review Focus ++ ++- Review whether `PENDING` is the desired final wording for intentionally unwired health checks. ++- Review the reason text in hover/accessibility attributes for clarity. ++- Confirm owner approval before any EOD merge. +diff --git a/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md +new file mode 100644 +index 000000000..094b6dfbe +--- /dev/null ++++ b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md +@@ -0,0 +1,69 @@ ++# PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup ++ ++## Summary ++ ++Queued scope 012 was applied to the existing draft PR #36 workstream branch: ++ ++- PR #36: `PR_26171_GAMMA_011-admin-system-health-foundation` ++- Branch: `team/GAMMA/admin` ++ ++This queued scope cleans up Admin System Health foundation status semantics without creating a separate GitHub PR. ++ ++## Scope Evidence ++ ++- Removed the fake `FAIL` diagnostics row. ++- Removed foundation placeholder `WARN` statuses from `admin/system-health.html`. ++- Kept declared foundation facts as `PASS`, including deployment target rows, Postgres provider, masked secret display, and known environment target list. ++- Used `PENDING` for intentionally unwired foundation checks and metrics. ++- Added `title` and `aria-label` reason text to every non-`PASS` health status cell. ++- Preserved Postgres-only wording. ++- Preserved Cloudflare R2 wording. ++- Kept the page static and Theme V2-only. ++- Did not add persistence, API wiring, or SQLite. ++ ++## Instruction Start Gate ++ ++- Instructions read: PASS ++- `docs_build/dev/PROJECT_INSTRUCTIONS.md`: read before edits ++- `docs_build/dev/PROJECT_MULTI_PC.txt`: read before edits ++- Current branch: `team/GAMMA/admin` ++- Clean status before edits: PASS ++- Local/remote sync before edits: PASS (`0 0`) ++- TEAM token: `GAMMA` ++- TEAM ownership: PASS by explicit Master Control/user assignment for diagnostics/admin workstream ++- Implementation path: `admin/system-health.html` ++- Existing draft PR target: PR #36 ++- Separate PR creation: SKIP by explicit user instruction ++- Merge: SKIP, owner-controlled EOD approval remains required ++ ++Note: the merged instruction file still contains the older main-only branch guard. This queued update proceeded only after explicit user instruction to continue on `team/GAMMA/admin` and update existing draft PR #36 rather than creating a separate PR. ++ ++## Validation ++ ++- PASS: `git diff --check` ++- PASS: targeted Admin System Health source check found 28 status cells and 16 non-`PASS` statuses with reasons. ++- PASS: targeted source check found no fake failure text, no `WARN`/`FAIL` health status cells, and no SQLite text in `admin/system-health.html`. ++- PASS: targeted source check verified Postgres and Cloudflare R2 wording remains present. ++- PASS: `npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --config=codex_playwright_system_chrome.config.cjs --project=playwright` (3 passed) ++ ++## Skipped Lanes ++ ++- Full samples smoke: skipped by request because this status cleanup does not touch samples. ++- Full Playwright suite: skipped because the existing targeted Admin System Health route spec covers the changed page behavior. ++- Runtime/API validation: skipped because no API wiring or runtime persistence was added. ++ ++## Required Reports ++ ++- `docs_build/dev/reports/codex_review.diff` ++- `docs_build/dev/reports/codex_changed_files.txt` ++- `docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md` ++- `docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md` ++- `docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md` ++ ++## ZIP Artifact ++ ++- `tmp/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup_delta.zip` ++ ++## EOD Approval ++ ++No merge was performed. EOD merge remains owner-controlled and requires explicit approval. diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt -index 59de15fb6..7b1c51f19 100644 +index 86850b15b..7b1c51f19 100644 --- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt +++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt @@ -9,2 +9 @@ Changed runtime JS files considered: --(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(0%) toolbox/messages/messages.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only +-(0%) toolbox/text-to-speech/text2speech.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only +(100%) none changed - no changed runtime JS files @@ -13,2 +12 @@ Guardrail warnings: --(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file missing from coverage; advisory only -(0%) toolbox/messages/messages.js - WARNING: changed runtime JS file missing from coverage; advisory only +-(0%) toolbox/text-to-speech/text2speech.js - WARNING: changed runtime JS file missing from coverage; advisory only +(100%) none changed - no changed runtime JS files +diff --git a/docs_build/dev/reports/dependency_gating_report.md b/docs_build/dev/reports/dependency_gating_report.md +index 319848da9..3c18f9166 100644 +--- a/docs_build/dev/reports/dependency_gating_report.md ++++ b/docs_build/dev/reports/dependency_gating_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.215Z ++Generated: 2026-06-20T22:58:20.683Z +diff --git a/docs_build/dev/reports/dependency_hydration_reuse_report.md b/docs_build/dev/reports/dependency_hydration_reuse_report.md +index 0f8c874e3..88a4c3471 100644 +--- a/docs_build/dev/reports/dependency_hydration_reuse_report.md ++++ b/docs_build/dev/reports/dependency_hydration_reuse_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.216Z ++Generated: 2026-06-20T22:58:20.684Z +diff --git a/docs_build/dev/reports/execution_graph_reuse_report.md b/docs_build/dev/reports/execution_graph_reuse_report.md +index ddbdf90a1..977ef5c81 100644 +--- a/docs_build/dev/reports/execution_graph_reuse_report.md ++++ b/docs_build/dev/reports/execution_graph_reuse_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.216Z ++Generated: 2026-06-20T22:58:20.684Z +@@ -19 +19 @@ Prevented targeted scheduling work: 0 +-| workspace-contract | INVALIDATED | INVALIDATED | 51dc9d019b71a923 | Lane snapshot is part of the selected targeted execution graph. | ++| workspace-contract | INVALIDATED | INVALIDATED | 744556e5d675e213 | Lane snapshot is part of the selected targeted execution graph. | +diff --git a/docs_build/dev/reports/failure_fingerprint_report.md b/docs_build/dev/reports/failure_fingerprint_report.md +index c9822fe4a..5e9437dba 100644 +--- a/docs_build/dev/reports/failure_fingerprint_report.md ++++ b/docs_build/dev/reports/failure_fingerprint_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:11:18.106Z ++Generated: 2026-06-20T22:59:39.176Z +diff --git a/docs_build/dev/reports/filesystem_scan_reduction_report.md b/docs_build/dev/reports/filesystem_scan_reduction_report.md +index 3fc7accce..4164a7dd3 100644 +--- a/docs_build/dev/reports/filesystem_scan_reduction_report.md ++++ b/docs_build/dev/reports/filesystem_scan_reduction_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.181Z ++Generated: 2026-06-20T22:58:20.655Z +diff --git a/docs_build/dev/reports/incremental_validation_report.md b/docs_build/dev/reports/incremental_validation_report.md +index d4aa9a02b..efee248fd 100644 +--- a/docs_build/dev/reports/incremental_validation_report.md ++++ b/docs_build/dev/reports/incremental_validation_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.218Z ++Generated: 2026-06-20T22:58:20.685Z +diff --git a/docs_build/dev/reports/lane_compilation_report.md b/docs_build/dev/reports/lane_compilation_report.md +index 1b65b72a4..9e3895ceb 100644 +--- a/docs_build/dev/reports/lane_compilation_report.md ++++ b/docs_build/dev/reports/lane_compilation_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.215Z ++Generated: 2026-06-20T22:58:20.683Z +diff --git a/docs_build/dev/reports/lane_deduplication_report.md b/docs_build/dev/reports/lane_deduplication_report.md +index ca9adb0a3..5b55db348 100644 +--- a/docs_build/dev/reports/lane_deduplication_report.md ++++ b/docs_build/dev/reports/lane_deduplication_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.215Z ++Generated: 2026-06-20T22:58:20.683Z +diff --git a/docs_build/dev/reports/lane_input_validation_report.md b/docs_build/dev/reports/lane_input_validation_report.md +index 0c7546c68..998c71c08 100644 +--- a/docs_build/dev/reports/lane_input_validation_report.md ++++ b/docs_build/dev/reports/lane_input_validation_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.218Z ++Generated: 2026-06-20T22:58:20.685Z +diff --git a/docs_build/dev/reports/lane_manifests/workspace-contract.json b/docs_build/dev/reports/lane_manifests/workspace-contract.json +index fb867147c..9d5178fda 100644 +--- a/docs_build/dev/reports/lane_manifests/workspace-contract.json ++++ b/docs_build/dev/reports/lane_manifests/workspace-contract.json +@@ -14 +14 @@ +- "tests/playwright/tools/RootToolsFutureState.spec.mjs": "032c57abb5289a23" ++ "tests/playwright/tools/RootToolsFutureState.spec.mjs": "7094370f23d8085e" +@@ -33 +33 @@ +- "inputHash": "5145d05e2885e902", ++ "inputHash": "63bec7503b20624b", +@@ -36 +36 @@ +- "manifestHash": "d98d3245976c92f4", ++ "manifestHash": "1291241716cd7ab6", +@@ -42 +42 @@ +- "generatedAt": "2026-06-21T00:10:08.792Z", ++ "generatedAt": "2026-06-20T22:58:19.300Z", +diff --git a/docs_build/dev/reports/lane_runtime_optimization_report.md b/docs_build/dev/reports/lane_runtime_optimization_report.md +index a8d52d441..8667df0bd 100644 +--- a/docs_build/dev/reports/lane_runtime_optimization_report.md ++++ b/docs_build/dev/reports/lane_runtime_optimization_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.215Z ++Generated: 2026-06-20T22:58:20.683Z +diff --git a/docs_build/dev/reports/lane_snapshot_report.md b/docs_build/dev/reports/lane_snapshot_report.md +index 5037cf0b8..190ba8c41 100644 +--- a/docs_build/dev/reports/lane_snapshot_report.md ++++ b/docs_build/dev/reports/lane_snapshot_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.216Z ++Generated: 2026-06-20T22:58:20.684Z +@@ -20 +20 @@ Prevented manifest traversal: 0 +-| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_snapshots/workspace-contract.json | d98d3245976c92f4 | 53e56ebae6e84541 | 7d3db838f9f780e0 | 6c4fac7630b0b6f3 | e5ac9cbc103c3984 | 51dc9d019b71a923 | 13ffd9b4d27b4d42 | Lane snapshot executionGraphHash changed for workspace-contract.; Lane snapshot inputHash changed for workspace-contract.; Lane snapshot manifestHash changed for workspace-contract.; Lane snapshot snapshotHash changed for workspace-contract.; Lane snapshot warmStartHash changed for workspace-contract. | ++| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_snapshots/workspace-contract.json | 1291241716cd7ab6 | 53e56ebae6e84541 | 7d3db838f9f780e0 | 6c4fac7630b0b6f3 | e5ac9cbc103c3984 | 744556e5d675e213 | 7135cba84942c652 | Lane snapshot executionGraphHash changed for workspace-contract.; Lane snapshot inputHash changed for workspace-contract.; Lane snapshot manifestHash changed for workspace-contract.; Lane snapshot snapshotHash changed for workspace-contract.; Lane snapshot warmStartHash changed for workspace-contract. | +diff --git a/docs_build/dev/reports/lane_snapshots/workspace-contract.json b/docs_build/dev/reports/lane_snapshots/workspace-contract.json +index e0fe4f05f..ac4f12de1 100644 +--- a/docs_build/dev/reports/lane_snapshots/workspace-contract.json ++++ b/docs_build/dev/reports/lane_snapshots/workspace-contract.json +@@ -29 +29 @@ +- "executionGraphHash": "51dc9d019b71a923", ++ "executionGraphHash": "744556e5d675e213", +@@ -50 +50 @@ +- "inputHash": "5145d05e2885e902", ++ "inputHash": "63bec7503b20624b", +@@ -64 +64 @@ +- "tests/playwright/tools/RootToolsFutureState.spec.mjs": "032c57abb5289a23" ++ "tests/playwright/tools/RootToolsFutureState.spec.mjs": "7094370f23d8085e" +@@ -83 +83 @@ +- "manifestHash": "d98d3245976c92f4", ++ "manifestHash": "1291241716cd7ab6", +@@ -90 +90 @@ +- "manifestHash": "d98d3245976c92f4", ++ "manifestHash": "1291241716cd7ab6", +@@ -117 +117 @@ +- "snapshotHash": "13ffd9b4d27b4d42", ++ "snapshotHash": "7135cba84942c652", +@@ -119,2 +119,2 @@ +- "warmStartHash": "f32a5831d6b39914", +- "generatedAt": "2026-06-21T00:10:10.207Z" ++ "warmStartHash": "fe298d7913eac5ca", ++ "generatedAt": "2026-06-20T22:58:20.677Z" +diff --git a/docs_build/dev/reports/lane_warm_start_report.md b/docs_build/dev/reports/lane_warm_start_report.md +index d06a40a33..806f7dbc5 100644 +--- a/docs_build/dev/reports/lane_warm_start_report.md ++++ b/docs_build/dev/reports/lane_warm_start_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.216Z ++Generated: 2026-06-20T22:58:20.684Z +@@ -20 +20 @@ Prevented lane graph assembly: 0 +-| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_warm_starts/workspace-contract.json | d98d3245976c92f4 | f32a5831d6b39914 | 355ba7a85dbb3cdb | Warm-start inputHash changed for workspace-contract.; Warm-start manifestHash changed for workspace-contract.; Warm-start warmStartHash changed for workspace-contract. | ++| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_warm_starts/workspace-contract.json | 1291241716cd7ab6 | fe298d7913eac5ca | 355ba7a85dbb3cdb | Warm-start inputHash changed for workspace-contract.; Warm-start manifestHash changed for workspace-contract.; Warm-start warmStartHash changed for workspace-contract. | +diff --git a/docs_build/dev/reports/lane_warm_starts/workspace-contract.json b/docs_build/dev/reports/lane_warm_starts/workspace-contract.json +index 3c29e890d..fc55da06b 100644 +--- a/docs_build/dev/reports/lane_warm_starts/workspace-contract.json ++++ b/docs_build/dev/reports/lane_warm_starts/workspace-contract.json +@@ -42 +42 @@ +- "inputHash": "5145d05e2885e902", ++ "inputHash": "63bec7503b20624b", +@@ -46 +46 @@ +- "manifestHash": "d98d3245976c92f4", ++ "manifestHash": "1291241716cd7ab6", +@@ -48 +48 @@ +- "warmStartHash": "f32a5831d6b39914", ++ "warmStartHash": "fe298d7913eac5ca", +@@ -50 +50 @@ +- "generatedAt": "2026-06-21T00:10:08.802Z", ++ "generatedAt": "2026-06-20T22:58:19.312Z", +diff --git a/docs_build/dev/reports/monolith_trigger_removal_report.md b/docs_build/dev/reports/monolith_trigger_removal_report.md +index 97a55c340..7141f5d34 100644 +--- a/docs_build/dev/reports/monolith_trigger_removal_report.md ++++ b/docs_build/dev/reports/monolith_trigger_removal_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:11:18.108Z ++Generated: 2026-06-20T22:59:39.177Z +diff --git a/docs_build/dev/reports/persistent_lane_manifest_report.md b/docs_build/dev/reports/persistent_lane_manifest_report.md +index f3f131d4a..f4baf21cb 100644 +--- a/docs_build/dev/reports/persistent_lane_manifest_report.md ++++ b/docs_build/dev/reports/persistent_lane_manifest_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.218Z ++Generated: 2026-06-20T22:58:20.685Z +@@ -18 +18 @@ Prevented discovery scans: 0 +-| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_manifests/workspace-contract.json | 5145d05e2885e902 | d98d3245976c92f4 | Persistent manifest input hash changed for workspace-contract.; Persistent manifest hash changed for workspace-contract. | ++| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_manifests/workspace-contract.json | 63bec7503b20624b | 1291241716cd7ab6 | Persistent manifest input hash changed for workspace-contract.; Persistent manifest hash changed for workspace-contract. | +@@ -24 +24 @@ Prevented discovery scans: 0 +-| workspace-contract | tools | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | 53e56ebae6e84541 | d98d3245976c92f4 | ++| workspace-contract | tools | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | 53e56ebae6e84541 | 1291241716cd7ab6 | +diff --git a/docs_build/dev/reports/playwright_discovery_ownership_report.md b/docs_build/dev/reports/playwright_discovery_ownership_report.md +index a5a1b3dea..977875caf 100644 +--- a/docs_build/dev/reports/playwright_discovery_ownership_report.md ++++ b/docs_build/dev/reports/playwright_discovery_ownership_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.169Z ++Generated: 2026-06-20T22:58:20.646Z +diff --git a/docs_build/dev/reports/playwright_discovery_scope_report.md b/docs_build/dev/reports/playwright_discovery_scope_report.md +index 7ba7a7a0e..69c83d957 100644 +--- a/docs_build/dev/reports/playwright_discovery_scope_report.md ++++ b/docs_build/dev/reports/playwright_discovery_scope_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.176Z ++Generated: 2026-06-20T22:58:20.652Z +diff --git a/docs_build/dev/reports/playwright_structure_audit.md b/docs_build/dev/reports/playwright_structure_audit.md +index dc6386691..392a08ee3 100644 +--- a/docs_build/dev/reports/playwright_structure_audit.md ++++ b/docs_build/dev/reports/playwright_structure_audit.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.141Z ++Generated: 2026-06-20T22:58:20.624Z diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt -index 94d5e709f..36e3b18b0 100644 +index e8623a06d..36e3b18b0 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt @@ -15 +15 @@ Exercised tool entry points detected: --(70%) Toolbox Index - exercised 3 runtime JS files +-(71%) Toolbox Index - exercised 10 runtime JS files +(46%) Toolbox Index - exercised 1 runtime JS files @@ -17 +17 @@ Exercised tool entry points detected: --(72%) Theme V2 Shared JS - exercised 1 runtime JS files +-(61%) Theme V2 Shared JS - exercised 7 runtime JS files +(75%) Theme V2 Shared JS - exercised 3 runtime JS files @@ -20,2 +20 @@ Changed runtime JS files covered: --(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(0%) toolbox/messages/messages.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only +-(0%) toolbox/text-to-speech/text2speech.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only +(100%) none changed - no changed runtime JS files -@@ -24,4 +23,3 @@ Files with executed line/function counts where available: +@@ -24,11 +23,2 @@ Files with executed line/function counts where available: +-(14%) assets/theme-v2/js/account-auth-service.js - executed lines 64/64; executed functions 1/7 +-(14%) assets/theme-v2/js/admin-setup-actions.js - executed lines 55/55; executed functions 1/7 +-(18%) assets/theme-v2/js/account-page-data.js - executed lines 150/150; executed functions 3/17 +-(20%) assets/theme-v2/js/admin-owner-navigation.js - executed lines 58/58; executed functions 2/10 +-(25%) src/api/admin-owner-navigation.js - executed lines 42/42; executed functions 1/4 -(25%) src/api/session-api-client.js - executed lines 68/68; executed functions 3/12 +-(29%) src/engine/input/NormalizedInputRegistry.js - executed lines 341/341; executed functions 6/21 +-(33%) src/api/admin-setup-api-client.js - executed lines 13/13; executed functions 1/3 -(33%) src/api/toolbox-votes-api-client.js - executed lines 46/46; executed functions 2/6 --(44%) src/api/server-api-client.js - executed lines 167/167; executed functions 8/18 --(48%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 11/23 +-(56%) toolbox/colors/colors.js - executed lines 1848/1848; executed functions 115/204 +-(58%) src/api/server-api-client.js - executed lines 167/167; executed functions 11/19 +(36%) src/api/server-api-client.js - executed lines 167/167; executed functions 5/14 +(46%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 12/26 -+(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 204/204; executed functions 9/14 -@@ -29,4 +27,3 @@ Files with executed line/function counts where available: +@@ -36 +25,0 @@ Files with executed line/function counts where available: +-(64%) toolbox/controls/controls.js - executed lines 610/610; executed functions 36/56 +@@ -38,3 +26,0 @@ Files with executed line/function counts where available: -(67%) src/api/game-journey-completion-api-client.js - executed lines 15/15; executed functions 2/3 -(67%) toolbox/game-workspace/game-workspace-api-client.js - executed lines 20/20; executed functions 2/3 --(72%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 977/977; executed functions 65/90 --(74%) toolbox/tools-page-accordions.js - executed lines 1156/1156; executed functions 81/109 -+(74%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 977/977; executed functions 67/90 +-(71%) toolbox/game-workspace/game-workspace.js - executed lines 472/472; executed functions 32/45 +@@ -42,8 +28,2 @@ Files with executed line/function counts where available: +-(89%) toolbox/tools-page-accordions.js - executed lines 1156/1156; executed functions 105/118 +-(90%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 26/29 +-(91%) toolbox/game-design/game-design.js - executed lines 254/254; executed functions 21/23 +-(94%) assets/theme-v2/js/marketplace-page.js - executed lines 170/170; executed functions 16/17 +-(100%) src/api/marketplace-api-client.js - executed lines 16/16; executed functions 3/3 +-(100%) toolbox/colors/palette-api-client.js - executed lines 28/28; executed functions 4/4 +-(100%) toolbox/controls/controls-api-client.js - executed lines 33/33; executed functions 5/5 +-(100%) toolbox/game-design/game-design-api-client.js - executed lines 13/13; executed functions 2/2 +(80%) src/api/admin-owner-navigation.js - executed lines 42/42; executed functions 4/5 +(91%) assets/theme-v2/js/admin-owner-navigation.js - executed lines 58/58; executed functions 10/11 -@@ -35,2 +32 @@ Uncovered or low-coverage changed JS files: --(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: uncovered changed runtime JS file; advisory only +@@ -52,2 +32 @@ Uncovered or low-coverage changed JS files: -(0%) toolbox/messages/messages.js - WARNING: uncovered changed runtime JS file; advisory only +-(0%) toolbox/text-to-speech/text2speech.js - WARNING: uncovered changed runtime JS file; advisory only +(100%) none changed - no changed runtime JS files -@@ -39,6 +35 @@ Changed JS files considered: --(0%) src/shared/toolbox/tool-metadata-inventory.js - changed JS file not collected as browser runtime coverage +@@ -56,5 +35 @@ Changed JS files considered: -(0%) tests/playwright/tools/MessagesTool.spec.mjs - changed JS file not collected as browser runtime coverage -(0%) tests/playwright/tools/RootToolsFutureState.spec.mjs - changed JS file not collected as browser runtime coverage -(0%) tests/playwright/tools/TextToSpeechFunctional.spec.mjs - changed JS file not collected as browser runtime coverage --(0%) tests/playwright/tools/ToolboxRoutePages.spec.mjs - changed JS file not collected as browser runtime coverage -(0%) toolbox/messages/messages.js - changed JS file not collected as browser runtime coverage +-(0%) toolbox/text-to-speech/text2speech.js - changed JS file not collected as browser runtime coverage +(0%) tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - changed JS file not collected as browser runtime coverage +diff --git a/docs_build/dev/reports/retry_suppression_report.md b/docs_build/dev/reports/retry_suppression_report.md +index 5391d7a38..b85b2ce3f 100644 +--- a/docs_build/dev/reports/retry_suppression_report.md ++++ b/docs_build/dev/reports/retry_suppression_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:11:18.107Z ++Generated: 2026-06-20T22:59:39.176Z +diff --git a/docs_build/dev/reports/slow_path_pruning_report.md b/docs_build/dev/reports/slow_path_pruning_report.md +index 0fc071745..4aa32ba0e 100644 +--- a/docs_build/dev/reports/slow_path_pruning_report.md ++++ b/docs_build/dev/reports/slow_path_pruning_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:11:18.108Z ++Generated: 2026-06-20T22:59:39.177Z +@@ -10 +10 @@ PR_26146_038 measured lane elapsed time: 169.71s +-Current measured lane elapsed time: 67.79s ++Current measured lane elapsed time: 78.45s +@@ -34,5 +34,5 @@ Validation cache hits: 18 +-| current targeted run | workspace-contract | 15.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | +-| current targeted run | workspace-contract | 14.70s | tests\playwright\tools\RootToolsFutureState.spec.mjs:664:1 > representative active tool pages align center cleanup and registry group colors | +-| current targeted run | workspace-contract | 13.10s | tests\playwright\tools\RootToolsFutureState.spec.mjs:562:1 > learn wireframe pages load with shared Theme V2 structure | +-| current targeted run | workspace-contract | 11.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:482:1 > common header renders primary navigation order across active pages | +-| current targeted run | workspace-contract | 2.30s | tests\playwright\tools\RootToolsFutureState.spec.mjs:641:1 > tool template future-state page loads from root Theme V2 paths | ++| current targeted run | workspace-contract | 18.90s | tests\playwright\tools\RootToolsFutureState.spec.mjs:667:1 > representative active tool pages align center cleanup and registry group colors | ++| current targeted run | workspace-contract | 17.80s | tests\playwright\tools\RootToolsFutureState.spec.mjs:565:1 > learn wireframe pages load with shared Theme V2 structure | ++| current targeted run | workspace-contract | 14.40s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | ++| current targeted run | workspace-contract | 10.50s | tests\playwright\tools\RootToolsFutureState.spec.mjs:485:1 > common header renders primary navigation order across active pages | ++| current targeted run | workspace-contract | 2.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:644:1 > tool template future-state page loads from root Theme V2 paths | +diff --git a/docs_build/dev/reports/static_validation_report.md b/docs_build/dev/reports/static_validation_report.md +index ef23805b9..3752d284f 100644 +--- a/docs_build/dev/reports/static_validation_report.md ++++ b/docs_build/dev/reports/static_validation_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.202Z ++Generated: 2026-06-20T22:58:20.674Z +@@ -25 +25 @@ Reason: No deterministic static validation failure was found. +-| targeted file manifests | PASS | workspace-contract:d98d3245976c92f4 | ++| targeted file manifests | PASS | workspace-contract:1291241716cd7ab6 | +diff --git a/docs_build/dev/reports/targeted_file_manifest_report.md b/docs_build/dev/reports/targeted_file_manifest_report.md +index fd6fbb064..d06980143 100644 +--- a/docs_build/dev/reports/targeted_file_manifest_report.md ++++ b/docs_build/dev/reports/targeted_file_manifest_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.217Z ++Generated: 2026-06-20T22:58:20.685Z +@@ -10 +10 @@ Status: PASS +-| workspace-contract | tools | PASS | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | src/dev-runtime/admin/admin-notes-directory.mjs; src/dev-runtime/admin/admin-notes-menu.mjs; src/dev-runtime/persistence/mock-db-store.js; src/dev-runtime/server/local-api-router.mjs; tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | 53e56ebae6e84541 | d98d3245976c92f4 | Manifest ownership, helpers, fixtures, imports, and command targets are deterministic before runtime. | ++| workspace-contract | tools | PASS | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | src/dev-runtime/admin/admin-notes-directory.mjs; src/dev-runtime/admin/admin-notes-menu.mjs; src/dev-runtime/persistence/mock-db-store.js; src/dev-runtime/server/local-api-router.mjs; tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | 53e56ebae6e84541 | 1291241716cd7ab6 | Manifest ownership, helpers, fixtures, imports, and command targets are deterministic before runtime. | +diff --git a/docs_build/dev/reports/test_cleanup_performance_report.md b/docs_build/dev/reports/test_cleanup_performance_report.md +index db3a814e7..3aa560665 100644 +--- a/docs_build/dev/reports/test_cleanup_performance_report.md ++++ b/docs_build/dev/reports/test_cleanup_performance_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:11:18.107Z ++Generated: 2026-06-20T22:59:39.177Z +@@ -8 +8 @@ Status: PASS +-Total measured lane elapsed time: 67.79s ++Total measured lane elapsed time: 78.45s +@@ -26 +26 @@ Prevented redundant dependency traversal: 0 +-| workspace-contract | PASS | 67.79s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | ++| workspace-contract | PASS | 78.45s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | +@@ -46,5 +46,5 @@ Prevented redundant dependency traversal: 0 +-| workspace-contract | 15.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | +-| workspace-contract | 14.70s | tests\playwright\tools\RootToolsFutureState.spec.mjs:664:1 > representative active tool pages align center cleanup and registry group colors | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | +-| workspace-contract | 13.10s | tests\playwright\tools\RootToolsFutureState.spec.mjs:562:1 > learn wireframe pages load with shared Theme V2 structure | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | +-| workspace-contract | 11.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:482:1 > common header renders primary navigation order across active pages | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | +-| workspace-contract | 2.30s | tests\playwright\tools\RootToolsFutureState.spec.mjs:641:1 > tool template future-state page loads from root Theme V2 paths | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | ++| workspace-contract | 18.90s | tests\playwright\tools\RootToolsFutureState.spec.mjs:667:1 > representative active tool pages align center cleanup and registry group colors | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | ++| workspace-contract | 17.80s | tests\playwright\tools\RootToolsFutureState.spec.mjs:565:1 > learn wireframe pages load with shared Theme V2 structure | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | ++| workspace-contract | 14.40s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | ++| workspace-contract | 10.50s | tests\playwright\tools\RootToolsFutureState.spec.mjs:485:1 > common header renders primary navigation order across active pages | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | ++| workspace-contract | 2.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:644:1 > tool template future-state page loads from root Theme V2 paths | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | +diff --git a/docs_build/dev/reports/test_cleanup_routing_report.md b/docs_build/dev/reports/test_cleanup_routing_report.md +index ed7ec4550..51d699234 100644 +--- a/docs_build/dev/reports/test_cleanup_routing_report.md ++++ b/docs_build/dev/reports/test_cleanup_routing_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:11:18.108Z ++Generated: 2026-06-20T22:59:39.177Z +diff --git a/docs_build/dev/reports/testing_lane_execution_report.md b/docs_build/dev/reports/testing_lane_execution_report.md +index c9cbce468..7713de7a1 100644 +--- a/docs_build/dev/reports/testing_lane_execution_report.md ++++ b/docs_build/dev/reports/testing_lane_execution_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:11:18.159Z ++Generated: 2026-06-20T22:59:39.207Z +@@ -12 +12 @@ SKIP: 14 +-Total lane elapsed time: 67.79s ++Total lane elapsed time: 78.45s +@@ -108 +108 @@ Prevented Workspace lane reruns: 0 +-| workspace-contract | PASS | 67.79s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | Root tools future-state navigation and Tool Template V2 contract | repo-served root tools page; Tool Template V2 future-state page; Theme V2 shared partials and assets | ++| workspace-contract | PASS | 78.45s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | Root tools future-state navigation and Tool Template V2 contract | repo-served root tools page; Tool Template V2 future-state page; Theme V2 shared partials and assets | +@@ -128,5 +128,5 @@ Prevented Workspace lane reruns: 0 +-| workspace-contract | 15.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | +-| workspace-contract | 14.70s | tests\playwright\tools\RootToolsFutureState.spec.mjs:664:1 > representative active tool pages align center cleanup and registry group colors | +-| workspace-contract | 13.10s | tests\playwright\tools\RootToolsFutureState.spec.mjs:562:1 > learn wireframe pages load with shared Theme V2 structure | +-| workspace-contract | 11.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:482:1 > common header renders primary navigation order across active pages | +-| workspace-contract | 2.30s | tests\playwright\tools\RootToolsFutureState.spec.mjs:641:1 > tool template future-state page loads from root Theme V2 paths | ++| workspace-contract | 18.90s | tests\playwright\tools\RootToolsFutureState.spec.mjs:667:1 > representative active tool pages align center cleanup and registry group colors | ++| workspace-contract | 17.80s | tests\playwright\tools\RootToolsFutureState.spec.mjs:565:1 > learn wireframe pages load with shared Theme V2 structure | ++| workspace-contract | 14.40s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | ++| workspace-contract | 10.50s | tests\playwright\tools\RootToolsFutureState.spec.mjs:485:1 > common header renders primary navigation order across active pages | ++| workspace-contract | 2.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:644:1 > tool template future-state page loads from root Theme V2 paths | +@@ -137 +137 @@ Prevented Workspace lane reruns: 0 +-- PASS 67.79s C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list ++- PASS 78.44s C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list +diff --git a/docs_build/dev/reports/validation_cache_report.md b/docs_build/dev/reports/validation_cache_report.md +index 2afd84b03..e19c36558 100644 +--- a/docs_build/dev/reports/validation_cache_report.md ++++ b/docs_build/dev/reports/validation_cache_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.220Z ++Generated: 2026-06-20T22:58:20.687Z +@@ -18,3 +18,3 @@ Validations computed: 10 +-| targeted file manifest validation | MISS | c5c4b7876c948959 | initial computation | lane definitions change; fixture ownership changes; helper/import graph changes; targeted files change | +-| lane warm-start validation | MISS | a00444576ec05d24 | initial computation | lane definitions change; targeted files change; ownership metadata changes; dependency graph changes; helper/fixture placement changes; lane configuration changes | +-| structural ownership validation | MISS | f0b916009d8b7a2e | initial computation | fixture ownership changes; helper/import graph changes; targeted files change | ++| targeted file manifest validation | MISS | 070fd74956239d48 | initial computation | lane definitions change; fixture ownership changes; helper/import graph changes; targeted files change | ++| lane warm-start validation | MISS | 6f24967ea6249f42 | initial computation | lane definitions change; targeted files change; ownership metadata changes; dependency graph changes; helper/fixture placement changes; lane configuration changes | ++| structural ownership validation | MISS | ce9fb480b7ef245b | initial computation | fixture ownership changes; helper/import graph changes; targeted files change | +@@ -24 +24 @@ Validations computed: 10 +-| lane snapshot validation | MISS | 7ebcc49fd0ce56c8 | initial computation | targeted files change; dependency graph changes; helper/fixture ownership changes; lane configuration changes; runtime configuration changes | ++| lane snapshot validation | MISS | 5d2cdac11ae1ca38 | initial computation | targeted files change; dependency graph changes; helper/fixture ownership changes; lane configuration changes; runtime configuration changes | +@@ -26,2 +26,2 @@ Validations computed: 10 +-| structural ownership validation | HIT | f0b916009d8b7a2e | static validation report | unchanged within execution cycle | +-| structural ownership validation | HIT | f0b916009d8b7a2e | zero-browser preflight report | unchanged within execution cycle | ++| structural ownership validation | HIT | ce9fb480b7ef245b | static validation report | unchanged within execution cycle | ++| structural ownership validation | HIT | ce9fb480b7ef245b | zero-browser preflight report | unchanged within execution cycle | +@@ -30,8 +30,8 @@ Validations computed: 10 +-| targeted file manifest validation | HIT | c5c4b7876c948959 | lane input validation report | unchanged within execution cycle | +-| targeted file manifest validation | HIT | c5c4b7876c948959 | runtime scheduling blockers | unchanged within execution cycle | +-| lane warm-start validation | HIT | a00444576ec05d24 | warm-start report | unchanged within execution cycle | +-| lane warm-start validation | HIT | a00444576ec05d24 | dependency hydration reuse report | unchanged within execution cycle | +-| lane warm-start validation | HIT | a00444576ec05d24 | runtime scheduling | unchanged within execution cycle | +-| lane snapshot validation | HIT | 7ebcc49fd0ce56c8 | lane snapshot report | unchanged within execution cycle | +-| lane snapshot validation | HIT | 7ebcc49fd0ce56c8 | execution graph reuse report | unchanged within execution cycle | +-| lane snapshot validation | HIT | 7ebcc49fd0ce56c8 | runtime scheduling | unchanged within execution cycle | ++| targeted file manifest validation | HIT | 070fd74956239d48 | lane input validation report | unchanged within execution cycle | ++| targeted file manifest validation | HIT | 070fd74956239d48 | runtime scheduling blockers | unchanged within execution cycle | ++| lane warm-start validation | HIT | 6f24967ea6249f42 | warm-start report | unchanged within execution cycle | ++| lane warm-start validation | HIT | 6f24967ea6249f42 | dependency hydration reuse report | unchanged within execution cycle | ++| lane warm-start validation | HIT | 6f24967ea6249f42 | runtime scheduling | unchanged within execution cycle | ++| lane snapshot validation | HIT | 5d2cdac11ae1ca38 | lane snapshot report | unchanged within execution cycle | ++| lane snapshot validation | HIT | 5d2cdac11ae1ca38 | execution graph reuse report | unchanged within execution cycle | ++| lane snapshot validation | HIT | 5d2cdac11ae1ca38 | runtime scheduling | unchanged within execution cycle | +diff --git a/docs_build/dev/reports/zero_browser_preflight_report.md b/docs_build/dev/reports/zero_browser_preflight_report.md +index 6b9f2cec7..19b494b64 100644 +--- a/docs_build/dev/reports/zero_browser_preflight_report.md ++++ b/docs_build/dev/reports/zero_browser_preflight_report.md +@@ -3 +3 @@ +-Generated: 2026-06-21T00:10:10.219Z ++Generated: 2026-06-20T22:58:20.686Z +diff --git a/src/dev-runtime/messages/messages-sqlite-service.mjs b/src/dev-runtime/messages/messages-sqlite-service.mjs +index 6d3894a85..d1a1cee33 100644 +--- a/src/dev-runtime/messages/messages-sqlite-service.mjs ++++ b/src/dev-runtime/messages/messages-sqlite-service.mjs +@@ -99,8 +98,0 @@ function normalizeNumber(value, fallback) { +-function emotionSettingKey(value) { +- return normalizeText(value) +- .trim() +- .toLowerCase() +- .replace(/[^a-z0-9]+/g, "-") +- .replace(/^-+|-+$/g, "") || "neutral"; +-} +- +@@ -193,13 +185 @@ function emotionProfileFromRow(row, usage = {}) { +-function ttsEmotionSettingFromEmotionProfile(profile) { +- return { +- active: profile.active !== false, +- emotion: emotionSettingKey(profile.name), +- emotionLabel: profile.name, +- pitch: Number(profile.pitch), +- rate: Number(profile.rate), +- ssmlLikePreset: "normal", +- volume: Number(profile.volume), +- }; +-} +- +-function ttsProfileFromRow(row, emotionSettings = []) { ++function ttsProfileFromRow(row) { +@@ -211 +190,0 @@ function ttsProfileFromRow(row, emotionSettings = []) { +- emotionSettings, +@@ -617,3 +595,0 @@ export class MessagesSqliteService { +- const emotionSettings = this.listEmotionProfiles() +- .filter((profile) => profile.active !== false) +- .map(ttsEmotionSettingFromEmotionProfile); +@@ -623 +599 @@ export class MessagesSqliteService { +- `).all().map((row) => ttsProfileFromRow(row, emotionSettings)); ++ `).all().map(ttsProfileFromRow); +@@ -631,4 +607 @@ export class MessagesSqliteService { +- const emotionSettings = this.listEmotionProfiles() +- .filter((profile) => profile.active !== false) +- .map(ttsEmotionSettingFromEmotionProfile); +- return ttsProfileFromRow(row, emotionSettings); ++ return ttsProfileFromRow(row); diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs -index e19b4da0b..fb16b40e8 100644 +index e19b4da0b..b26a01036 100644 --- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -8,0 +9,5 @@ async function setSessionUser(server, userKey) { @@ -635,7 +1521,7 @@ index e19b4da0b..fb16b40e8 100644 @@ -57 +83 @@ test.afterAll(async () => { -test("Admin System Health renders operational health summaries and filters", async ({ page }) => { +test("Admin System Health renders foundation tables without page API calls", async ({ page }) => { -@@ -62,15 +88,13 @@ test("Admin System Health renders operational health summaries and filters", asy +@@ -62,15 +88,24 @@ test("Admin System Health renders operational health summaries and filters", asy - await expect(page.locator("[data-admin-system-health-status]")).toContainText("Admin System Health loaded safe status only."); - await expect(page.locator("[data-admin-health-summary-rows]")).toContainText("Membership operations"); - await expect(page.locator("[data-admin-health-summary-rows]")).toContainText("Invitation support"); @@ -660,14 +1546,1181 @@ index e19b4da0b..fb16b40e8 100644 + await expect(page.getByRole("table", { name: "Runtime environment" })).toContainText("********"); + await expect(page.getByRole("table", { name: "Limits and capacity" })).toContainText("Class A Ops"); + await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("PASS"); -+ await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("WARN"); -+ await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("FAIL"); ++ await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("PENDING"); ++ await expect(page.getByRole("table", { name: "Diagnostics log" })).not.toContainText("FAIL"); ++ await expect(page.getByText("No active failure is declared")).toHaveCount(0); ++ await expect(page.locator("[data-health-status='WARN'], [data-health-status='FAIL']")).toHaveCount(0); ++ const nonPassStatuses = page.locator("[data-health-status]:not([data-health-status='PASS'])"); ++ const nonPassStatusCount = await nonPassStatuses.count(); ++ expect(nonPassStatusCount).toBeGreaterThan(0); ++ for (let index = 0; index < nonPassStatusCount; index += 1) { ++ const statusCell = nonPassStatuses.nth(index); ++ const title = await statusCell.getAttribute("title"); ++ const ariaLabel = await statusCell.getAttribute("aria-label"); ++ expect((title || ariaLabel || "").trim()).not.toEqual(""); ++ } + expect(context.requestUrls.some((url) => url.includes("/api/admin/system-health"))).toBe(false); + await expect(page.locator("[data-admin-system-health-storage-action]")).toHaveCount(0); -@@ -92 +116 @@ test("Creator sessions cannot access Admin System Health operations", async ({ p +@@ -92 +127 @@ test("Creator sessions cannot access Admin System Health operations", async ({ p - await expect(page.locator("[data-admin-health-summary-rows]")).toHaveCount(0); + await expect(page.getByRole("table", { name: "Environment summary" })).toHaveCount(0); -@@ -107 +131,2 @@ test("Admin System Health operations page keeps scripts and styles external", as +@@ -107 +142,4 @@ test("Admin System Health operations page keeps scripts and styles external", as - expect(pageSource).toContain("assets/theme-v2/js/admin-system-health.js"); ++ expect(pageSource).not.toMatch(/data-health-status="(?:WARN|FAIL)"/); ++ expect(pageSource).not.toContain("No active failure is declared"); + expect(pageSource).not.toContain("assets/theme-v2/js/admin-system-health.js"); + expect(pageSource).toContain("assets/theme-v2/js/admin-owner-navigation.js"); +diff --git a/tests/playwright/tools/MessagesTool.spec.mjs b/tests/playwright/tools/MessagesTool.spec.mjs +index 6c00fa6f5..5c2e1a212 100644 +--- a/tests/playwright/tools/MessagesTool.spec.mjs ++++ b/tests/playwright/tools/MessagesTool.spec.mjs +@@ -147,0 +148 @@ async function addPart(page, values) { ++ await page.locator("[data-messages-segment-editor='__new__'] [data-segment-order]").fill(String(values.order)); +@@ -153,4 +153,0 @@ async function addPart(page, values) { +-async function openMessageParts(page, messageName) { +- await page.locator("[data-messages-row]").filter({ hasText: messageName }).locator("td").nth(1).click(); +-} +- +@@ -199,0 +197,2 @@ test("Message Studio renders Messages with child Message Parts and plays ordered ++ await expect(page.locator("[data-messages-segment-host]")).toBeVisible(); ++ await messageNameCell.click(); +@@ -201 +199,0 @@ test("Message Studio renders Messages with child Message Parts and plays ordered +- await expect(messageNameCell).toHaveAttribute("aria-expanded", "false"); +@@ -203,3 +200,0 @@ test("Message Studio renders Messages with child Message Parts and plays ordered +- await expect(page.locator("[data-messages-segment-host]")).toBeVisible(); +- await expect(messageNameCell).toHaveAttribute("aria-expanded", "true"); +- await messageRow.locator("td").nth(2).click(); +@@ -210,7 +205,3 @@ test("Message Studio renders Messages with child Message Parts and plays ordered +- 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: "Emotion" })).toBeVisible(); +- await expect(partsTable.getByRole("columnheader", { exact: true, name: "TTS Profile" })).toBeVisible(); +- await expect(partsTable.getByRole("columnheader", { name: "Status" })).toBeVisible(); +- await expect(partsTable.getByRole("columnheader", { name: "Actions" })).toBeVisible(); ++ await expect(page.getByRole("columnheader", { name: "Order" })).toBeVisible(); ++ await expect(page.getByRole("columnheader", { name: "Text" })).toBeVisible(); ++ await expect(page.getByRole("columnheader", { exact: true, name: "TTS Profile" })).toBeVisible(); +@@ -222,0 +214 @@ test("Message Studio renders Messages with child Message Parts and plays ordered ++ await page.locator("[data-messages-segment-editor='__new__'] [data-segment-order]").fill(""); +@@ -226 +218 @@ test("Message Studio renders Messages with child Message Parts and plays ordered +- await expect(page.locator("[data-messages-validation-errors]")).not.toContainText("Display Order is required."); ++ await expect(page.locator("[data-messages-validation-errors]")).toContainText("Display Order is required."); +@@ -243,11 +234,0 @@ test("Message Studio renders Messages with child Message Parts and plays ordered +- const ttsProfilesResult = await jsonRequest(`${failures.server.baseUrl}/api/messages/tts-profiles`); +- expect(ttsProfilesResult.response.ok).toBe(true); +- expect(ttsProfilesResult.payload.data.ttsProfiles[0].emotionSettings).toEqual(expect.arrayContaining([ +- expect.objectContaining({ +- emotion: "urgent", +- emotionLabel: "Urgent", +- pitch: 1.08, +- rate: 1.15, +- volume: 1, +- }), +- ])); +@@ -258,13 +239,3 @@ test("Message Studio renders Messages with child Message Parts and plays ordered +- expect(speechCalls.slice(-2)).toEqual([ +- expect.objectContaining({ +- pitch: 1, +- rate: 1, +- text: "Bats drop from the rafters.", +- volume: 1, +- }), +- expect.objectContaining({ +- pitch: 1.08, +- rate: 1.15, +- text: "Keep your torch high.", +- volume: 1, +- }), ++ expect(speechCalls.slice(-2).map((call) => call.text)).toEqual([ ++ "Bats drop from the rafters.", ++ "Keep your torch high.", +@@ -286,2 +256,0 @@ test("Message Studio renders Messages with child Message Parts and plays ordered +- pitch: 1.08, +- rate: 1.15, +@@ -291 +259,0 @@ test("Message Studio renders Messages with child Message Parts and plays ordered +- volume: 1, +@@ -351 +318,0 @@ test("Message Studio shows actionable playback error when audio engine is unavai +- await openMessageParts(page, "Bat Encounter"); +diff --git a/tests/playwright/tools/RootToolsFutureState.spec.mjs b/tests/playwright/tools/RootToolsFutureState.spec.mjs +index ba97f5c6a..1f043323e 100644 +--- a/tests/playwright/tools/RootToolsFutureState.spec.mjs ++++ b/tests/playwright/tools/RootToolsFutureState.spec.mjs +@@ -280 +280 @@ test("root tools surface links current tool pages without old_* routes", async ( +- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/44"); ++ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43"); +@@ -314 +314,4 @@ test("root tools surface links current tool pages without old_* routes", async ( +- 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-workspace/index.html"); +@@ -316 +319 @@ test("root tools surface links current tool pages without old_* routes", async ( +- 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"]); +@@ -322 +325 @@ test("root tools surface links current tool pages without old_* routes", async ( +- 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"]); +@@ -392 +395 @@ test("root tools surface links current tool pages without old_* routes", async ( +- expect(guestGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression", "Publish"]); ++ expect(guestGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression"]); +@@ -418 +421 @@ test("root tools surface links current tool pages without old_* routes", async ( +- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/44"); ++ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43"); +@@ -424 +427 @@ test("root tools surface links current tool pages without old_* routes", async ( +- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/47"); ++ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/46"); +@@ -435 +438 @@ test("root tools surface links current tool pages without old_* routes", async ( +- has: page.locator("[data-toolbox-tool-name-link='Game Hub'][href='/toolbox/game-workspace/index.html']") ++ has: page.locator("h3", { hasText: /^Game Hub$/ }) +@@ -461 +464 @@ test("root tools surface links current tool pages without old_* routes", async ( +- expect(adminGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression", "Publish"]); ++ expect(adminGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression"]); +@@ -471 +474 @@ test("root tools surface links current tool pages without old_* routes", async ( +- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/44"); ++ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43"); +diff --git a/tests/playwright/tools/TextToSpeechFunctional.spec.mjs b/tests/playwright/tools/TextToSpeechFunctional.spec.mjs +index c9a659a4d..fa1050126 100644 +--- a/tests/playwright/tools/TextToSpeechFunctional.spec.mjs ++++ b/tests/playwright/tools/TextToSpeechFunctional.spec.mjs +@@ -107,9 +107 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as +- await expect(page.locator("[data-tts-summary]")).not.toContainText("TTS Studio"); +- await expect(page.locator("[data-tts-output-summary]")).toHaveCount(0); +- await expect(page.locator("[data-tts-queue-list]")).toHaveCount(0); +- await expect(page.locator("[data-tts-item-name]")).toHaveCount(0); +- await expect(page.getByText("Named Sentence", { exact: true })).toHaveCount(0); +- await expect(page.getByText("Output Summary", { exact: true })).toHaveCount(0); +- await expect(page.getByText("Voice Filters", { exact: true })).toHaveCount(0); +- await expect(page.getByText("Delivery", { exact: true })).toHaveCount(0); +- await expect(page.getByText("Presets", { exact: true })).toHaveCount(0); ++ await expect(page.locator("[data-tts-summary]")).toContainText("TTS Studio"); +@@ -116,0 +109 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as ++ await expect(page.locator("[data-tts-voice-select]")).toContainText("Arcade Voice"); +@@ -123,8 +116,2 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as +- 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); +- await defaultProfileRow.locator("td").nth(5).click(); +- await expect(page.locator("[data-tts-emotion-host]")).toHaveCount(0); +- await expect(defaultProfileRow.locator("[data-tts-profile-name-cell]")).toHaveAttribute("aria-expanded", "false"); +- await defaultProfileRow.locator("[data-tts-profile-name-cell]").click(); +- await expect(defaultProfileRow.locator("[data-tts-profile-name-cell]")).toHaveAttribute("aria-expanded", "true"); ++ await expect(page.locator("[data-tts-profile-row]").filter({ hasText: "Default Balanced Profile" }).getByRole("button", { name: "Delete" })).toBeDisabled(); ++ await page.locator("[data-tts-profile-row]").filter({ hasText: "Default Balanced Profile" }).click(); +@@ -139,2 +126 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as +- await expect(page.locator("[data-tts-emotion-add-control-row]").getByRole("button", { name: "Add Emotion" })).toBeVisible(); +- await page.locator("[data-tts-profile-row]").filter({ hasText: "Man Profile 1" }).locator("[data-tts-profile-name-cell]").click(); ++ await page.locator("[data-tts-profile-row]").filter({ hasText: "Man Profile 1" }).click(); +@@ -146 +132 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as +- await page.locator("[data-tts-profile-row]").filter({ hasText: "Woman Profile 2" }).locator("[data-tts-profile-name-cell]").click(); ++ await page.locator("[data-tts-profile-row]").filter({ hasText: "Woman Profile 2" }).click(); +@@ -153,2 +139 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as +- await expect(page.locator("[data-tts-profile-add-control-row]").getByRole("button", { name: "Add Profile" })).toBeVisible(); +- await page.locator("[data-tts-profile-add-control-row]").getByRole("button", { name: "Add Profile" }).click(); ++ await page.getByRole("button", { name: "Add Profile" }).click(); +@@ -165,3 +150 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as +- await expect(page.locator("[data-tts-emotion-add-control-row]").getByRole("button", { name: "Add Emotion" })).toBeVisible(); +- await page.locator("[data-tts-emotion-add-control-row]").getByRole("button", { name: "Add Emotion" }).click(); +- await expect(page.locator("[data-tts-emotion-add-control-row]")).toHaveCount(0); ++ await page.getByRole("button", { name: "Add Emotion" }).click(); +@@ -180 +163,2 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as +- await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Urgent" })).toContainText("Whisper-ish"); ++ await expect(page.locator("[data-tts-output-summary]")).toContainText("\"contractVersion\": \"tts-profile-emotion-v1\""); ++ await expect(page.locator("[data-tts-output-summary]")).toContainText("\"name\": \"Creature Profile Updated\""); +@@ -182,14 +166,8 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as +- await expect(page.locator("[data-tts-gender-select]")).toHaveCount(0); +- await expect(page.locator("[data-tts-language-select]")).toHaveCount(0); +- await expect(page.locator("[data-tts-age-select]")).toHaveCount(0); +- await expect(page.locator("[data-tts-character-preset-select]")).toHaveCount(0); +- await expect(page.locator("[data-tts-ssml-preset-select]")).toHaveCount(0); +- await expect(page.locator("[data-tts-import-json]")).toHaveCount(0); +- await expect(page.locator("[data-tts-copy-json]")).toHaveCount(0); +- await expect(page.locator("[data-tts-export-json]")).toHaveCount(0); +- await page.locator("[data-tts-profile-row]").filter({ hasText: "Creature Profile Updated" }).getByRole("button", { name: "Edit Profile" }).click(); +- await expect(page.locator("[data-tts-profile-editor] [data-tts-profile-voice]")).toBeVisible(); +- await expect(page.locator("[data-tts-profile-editor] [data-tts-profile-language]")).toBeVisible(); +- await expect(page.locator("[data-tts-profile-editor] [data-tts-profile-gender]")).toBeVisible(); +- await expect(page.locator("[data-tts-profile-editor] [data-tts-profile-age]")).toBeVisible(); +- await page.locator("[data-tts-profile-editor] [data-tts-cancel-profile]").click(); ++ await expect(page.locator("[data-tts-gender-select]")).toBeVisible(); ++ await expect(page.locator("[data-tts-language-select]")).toBeVisible(); ++ await expect(page.locator("[data-tts-age-select]")).toBeVisible(); ++ await expect(page.locator("[data-tts-character-preset-select]")).toBeVisible(); ++ await expect(page.locator("[data-tts-ssml-preset-select]")).toBeVisible(); ++ await expect(page.locator("[data-tts-import-json]")).toBeEnabled(); ++ await expect(page.locator("[data-tts-copy-json]")).toBeEnabled(); ++ await expect(page.locator("[data-tts-export-json]")).toBeEnabled(); +@@ -197,0 +176,13 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as ++ await page.locator("[data-tts-item-name]").fill("Wave intro"); ++ await page.locator("[data-tts-character-preset-select]").selectOption("dramatic"); ++ await page.locator("[data-tts-age-select]").selectOption("teen"); ++ await page.locator("[data-tts-ssml-preset-select]").selectOption("whisper-ish"); ++ await expect(page.locator("[data-tts-pitch-value]")).toHaveText("1.1"); ++ await expect(page.locator("[data-tts-volume-value]")).toHaveText("0.6"); ++ await page.locator("[data-tts-voice-select]").selectOption("arcade-voice-uri"); ++ await page.locator("[data-tts-rate]").fill("1.4"); ++ await page.locator("[data-tts-pitch]").fill("0.8"); ++ await page.locator("[data-tts-volume]").fill("0.55"); ++ await expect(page.locator("[data-tts-rate-value]")).toHaveText("1.4"); ++ await expect(page.locator("[data-tts-pitch-value]")).toHaveText("0.8"); ++ await expect(page.locator("[data-tts-volume-value]")).toHaveText("0.55"); +@@ -198,0 +190,7 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as ++ await page.locator("[data-tts-add-item]").click(); ++ await expect(page.locator("[data-tts-queue-list]")).toContainText("Wave intro"); ++ await expect(page.locator("[data-tts-output-summary]")).toContainText("\"name\": \"Wave intro\""); ++ await page.locator("[data-tts-duplicate-item]").click(); ++ await expect(page.locator("[data-tts-queue-list]")).toContainText("Wave intro 2 copy"); ++ await page.locator("[data-tts-delete-item]").click(); ++ await expect(page.locator("[data-tts-queue-list] [data-tts-queue-item]")).toHaveCount(2); +@@ -206,2 +204,2 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as +- pitch: 1.2, +- rate: 1.1, ++ pitch: 0.8, ++ rate: 1.4, +@@ -211 +209 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as +- volume: 0.7, ++ volume: 0.55, +@@ -285 +283 @@ test("Text To Speech shows actionable error when browser speech synthesis is una +- await expect(page.locator("[data-tts-voice-select]")).toHaveCount(0); ++ await expect(page.locator("[data-tts-voice-select]")).toContainText("No browser voices available"); +diff --git a/toolbox/messages/messages.js b/toolbox/messages/messages.js +index 4af71adaa..c881cc0b4 100644 +--- a/toolbox/messages/messages.js ++++ b/toolbox/messages/messages.js +@@ -15,6 +14,0 @@ const DEFAULT_TTS_PROFILE_KEY = "__default-balanced-tts__"; +-const DEFAULT_TTS_EMOTION_SETTINGS = Object.freeze([ +- Object.freeze({ active: true, emotion: "calm", emotionLabel: "Calm", pitch: 1, rate: 1, ssmlLikePreset: "normal", volume: 1 }), +- Object.freeze({ active: true, emotion: "urgent", emotionLabel: "Urgent", pitch: 1.08, rate: 1.15, ssmlLikePreset: "normal", volume: 1 }), +- Object.freeze({ active: true, emotion: "whisper", emotionLabel: "Whisper", pitch: 0.95, rate: 0.9, ssmlLikePreset: "normal", volume: 0.55 }), +- Object.freeze({ active: true, emotion: "angry", emotionLabel: "Angry", pitch: 0.98, rate: 1.1, ssmlLikePreset: "normal", volume: 1 }), +-]); +@@ -24 +17,0 @@ const DEFAULT_TTS_PROFILE = Object.freeze({ +- emotionSettings: DEFAULT_TTS_EMOTION_SETTINGS, +@@ -231 +224 @@ function createSegmentAddControlRow() { +- const row = tableActionRow(5, createButton("Add Part", "messagesSegmentAddRow", state.selectedMessageKey)); ++ const row = tableActionRow(6, createButton("Add Part", "messagesSegmentAddRow", state.selectedMessageKey)); +@@ -274,26 +266,0 @@ function emotionProfileByKey(profileKey) { +-function emotionSettingKey(value) { +- return String(value || "") +- .trim() +- .toLowerCase() +- .replace(/[^a-z0-9]+/g, "-") +- .replace(/^-+|-+$/g, "") || "neutral"; +-} +- +-function selectedEmotionSettingForProfile(profile, emotionProfile) { +- const settings = Array.isArray(profile?.emotionSettings) +- ? profile.emotionSettings.filter((setting) => setting?.active !== false) +- : []; +- const selectedEmotionKey = emotionSettingKey(emotionProfile?.name); +- const setting = settings.find((candidate) => ( +- emotionSettingKey(candidate.emotion) === selectedEmotionKey +- || emotionSettingKey(candidate.emotionLabel) === selectedEmotionKey +- )); +- if (!setting) { +- return { +- message: `Selected TTS Profile "${profile?.name || "Unknown"}" does not include an Emotion Setting for "${emotionProfile?.name || "Unknown"}".`, +- ok: false, +- }; +- } +- return { ok: true, setting }; +-} +- +@@ -464,4 +430,0 @@ function speechTestReadiness() { +- const emotionSetting = selectedEmotionSettingForProfile(profile, target.emotionProfile); +- if (!emotionSetting.ok) { +- return { message: emotionSetting.message, ok: false }; +- } +@@ -554 +517 @@ function createMessageSegmentTable() { +- ["Text", "Emotion", "TTS Profile", "Status", "Actions"].forEach((label) => { ++ ["Order", "Text", "Emotion", "TTS Profile", "Status", "Actions"].forEach((label) => { +@@ -566 +529 @@ function createMessageSegmentTable() { +- tbody.append(tableMessage(5, "No message parts saved for this message.")); ++ tbody.append(tableMessage(6, "No message parts saved for this message.")); +@@ -594,0 +558 @@ function createMessageSegmentTable() { ++ createCell(String(segment.displayOrder)), +@@ -620 +584,3 @@ function createSegmentEditRow(segment = null) { +- row.dataset.messagesSegmentOrder = String(segment?.displayOrder || nextSegmentOrder()); ++ ++ const orderCell = document.createElement("td"); ++ orderCell.append(createNumberInput(segment?.displayOrder || nextSegmentOrder(), "segmentOrder", { min: 1, step: 1 })); +@@ -644 +610 @@ function createSegmentEditRow(segment = null) { +- row.append(textCell, emotionCell, ttsCell, statusCell, actions); ++ row.append(orderCell, textCell, emotionCell, ttsCell, statusCell, actions); +@@ -765 +731 @@ function segmentValues(key) { +- displayOrder: editorValue(root, "[data-segment-order]") || root?.dataset.messagesSegmentOrder || String(nextSegmentOrder()), ++ displayOrder: editorValue(root, "[data-segment-order]"), +@@ -833,0 +800 @@ async function commitMessage(key) { ++ state.selectedMessageKey = result.message.key; +@@ -835 +801,0 @@ async function commitMessage(key) { +- state.selectedMessageKey = ""; +@@ -837,2 +802,0 @@ async function commitMessage(key) { +- } else { +- state.selectedMessageKey = result.message.key; +@@ -840 +804 @@ async function commitMessage(key) { +- await reloadAfterChange(state.selectedMessageKey, state.selectedSegmentKey); ++ await reloadAfterChange(result.message.key); +@@ -963,4 +926,0 @@ function speakTarget(service, target, profile) { +- const emotionSetting = selectedEmotionSettingForProfile(profile, target.emotionProfile); +- if (!emotionSetting.ok) { +- return visiblePlaybackError(emotionSetting.message); +- } +@@ -972,2 +932,2 @@ function speakTarget(service, target, profile) { +- pitch: emotionSetting.setting.pitch ?? profile.pitch ?? 1, +- rate: emotionSetting.setting.rate ?? profile.rate ?? 1, ++ pitch: target.emotionProfile.pitch ?? profile.pitch ?? 1, ++ rate: target.emotionProfile.rate ?? profile.rate ?? 1, +@@ -976 +935,0 @@ function speakTarget(service, target, profile) { +- ssmlLikePreset: emotionSetting.setting.ssmlLikePreset || "normal", +@@ -979 +938 @@ function speakTarget(service, target, profile) { +- volume: emotionSetting.setting.volume ?? profile.volume ?? 1, ++ volume: target.emotionProfile.volume ?? profile.volume ?? 1, +@@ -1233 +1192 @@ elements.table?.addEventListener("click", async (event) => { +- if ((messageNameCell || row) && row) { ++ if (messageNameCell && row) { +diff --git a/toolbox/text-to-speech/index.html b/toolbox/text-to-speech/index.html +index 7989b8309..95672aa69 100644 +--- a/toolbox/text-to-speech/index.html ++++ b/toolbox/text-to-speech/index.html +@@ -9 +9 @@ +- ++ +@@ -21 +21 @@ +-

Create reusable speech profiles, tune emotion settings, and preview spoken game text through the shared engine audio Text To Speech module.

++

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

+@@ -25,0 +26,6 @@ ++ +@@ -32 +38 @@ +-

Studio Setup

++

Speech Options

+@@ -36 +42,13 @@ +- Profile Ownership ++ Voice Filters ++
++ ++ ++ ++ ++ ++ ++

No browser voices loaded yet.

++
++ ++
++ Presets +@@ -38,2 +56,6 @@ +-

TTS Profiles own voice, language, gender, age, and availability.

+-

Use inline Profile rows to add or edit voice filters.

++ ++ ++ ++ ++ ++ +@@ -43 +65 @@ +- Emotion Ownership ++ Delivery +@@ -45,2 +67,28 @@ +-

Emotion Settings own pitch, rate, volume, and preset values for the selected profile.

+-

Message Studio selects a TTS Profile and Emotion for playback.

++
++ ++ ++ 1 ++
++
++ ++ ++ 1 ++
++
++ ++ ++ 1 ++
++

Voice Age and SSML-like presets shape rate, pitch, and volume. Manual slider changes are preserved until a new character preset is selected.

++ ++
++
++ Named Sentence ++
++ ++ ++
++ ++ ++ ++
+@@ -53,0 +102 @@ ++ TTS Studio +@@ -84,0 +134,3 @@ ++
++ ++
+@@ -103,0 +156,9 @@ ++
++
++
++
Named Sentences
++

Queue

++
++
++
++
+@@ -107 +168 @@ +-

Status

++

Output

+@@ -110,0 +172,6 @@ ++ Output Summary ++
++
[]
++
++
++
+diff --git a/toolbox/text-to-speech/text2speech.js b/toolbox/text-to-speech/text2speech.js +index 5174c8d5e..36f3e9278 100644 +--- a/toolbox/text-to-speech/text2speech.js ++++ b/toolbox/text-to-speech/text2speech.js +@@ -1,0 +2,3 @@ import { ++ createTextToSpeechQueueItem, ++ filterTextToSpeechVoiceOptions, ++ shapeTextToSpeechOptions, +@@ -2,0 +6 @@ import { ++ textToSpeechPayloadGenderValue, +@@ -3,0 +8,2 @@ import { ++ uniqueTextToSpeechId, ++ uniqueTextToSpeechName +@@ -6,0 +13,2 @@ import { ++ TEXT_TO_SPEECH_CHARACTER_PRESET_DEFAULTS, ++ TEXT_TO_SPEECH_CHARACTER_PRESET_OPTIONS, +@@ -8,0 +17 @@ import { ++ TEXT_TO_SPEECH_GENDER_FILTER_OPTIONS, +@@ -10,0 +20 @@ import { ++ TEXT_TO_SPEECH_SCHEMA_ID, +@@ -13,0 +24,3 @@ import { ++const WORKSPACE_TOOL_STATE_KEY = "workspace.tools.text2speech-V2"; ++const TTS_URL_SOURCE_PARAM = "samplePresetPath"; ++ +@@ -362,0 +376,50 @@ function setTextContent(root, selector, text) { ++function readFileText(file) { ++ return new Promise((resolve, reject) => { ++ const reader = new FileReader(); ++ reader.addEventListener("load", () => resolve(String(reader.result || ""))); ++ reader.addEventListener("error", () => reject(reader.error || new Error("File read failed."))); ++ reader.readAsText(file); ++ }); ++} ++ ++function downloadTextFile(text, filename, documentRef = document) { ++ const blob = new Blob([text], { type: "application/json" }); ++ const url = URL.createObjectURL(blob); ++ const link = documentRef.createElement("a"); ++ link.href = url; ++ link.download = filename; ++ documentRef.body.append(link); ++ link.click(); ++ link.remove(); ++ URL.revokeObjectURL(url); ++} ++ ++function isPlainObject(value) { ++ return Boolean(value && typeof value === "object" && !Array.isArray(value)); ++} ++ ++function validateTextToSpeechPayload(payload, sourceLabel = "payload") { ++ if (!Array.isArray(payload)) { ++ return { ok: false, message: `${TEXT_TO_SPEECH_DISPLAY_NAME} ${sourceLabel} must be a root array.` }; ++ } ++ const required = ["id", "name", "text", "gender", "language", "voice", "voiceAge", "volume", "rate", "pitch", "characterPreset", "ssmlLikePreset"]; ++ for (const [index, item] of payload.entries()) { ++ if (!isPlainObject(item)) { ++ return { ok: false, message: `${TEXT_TO_SPEECH_DISPLAY_NAME} ${sourceLabel}[${index}] must be an object.` }; ++ } ++ const missing = required.filter((key) => !Object.prototype.hasOwnProperty.call(item, key)); ++ if (missing.length) { ++ return { ok: false, message: `${TEXT_TO_SPEECH_DISPLAY_NAME} ${sourceLabel}[${index}] missing: ${missing.join(", ")}.` }; ++ } ++ } ++ return { ok: true }; ++} ++ ++function queueItemMeta(item) { ++ return [ ++ item.language || "no language", ++ item.characterPreset || "manual", ++ item.voiceAge || "any" ++ ].join(" / "); ++} ++ +@@ -364,0 +428,4 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ addItem: root.querySelector("[data-tts-add-item]"), ++ addProfile: root.querySelector("[data-tts-add-profile]"), ++ age: root.querySelector("[data-tts-age-select]"), ++ characterPreset: root.querySelector("[data-tts-character-preset-select]"), +@@ -365,0 +433,10 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ copyJson: root.querySelector("[data-tts-copy-json]"), ++ deleteItem: root.querySelector("[data-tts-delete-item]"), ++ duplicateItem: root.querySelector("[data-tts-duplicate-item]"), ++ exportJson: root.querySelector("[data-tts-export-json]"), ++ gender: root.querySelector("[data-tts-gender-select]"), ++ importJson: root.querySelector("[data-tts-import-json]"), ++ importJsonInput: root.querySelector("[data-tts-import-json-input]"), ++ itemName: root.querySelector("[data-tts-item-name]"), ++ language: root.querySelector("[data-tts-language-select]"), ++ outputSummary: root.querySelector("[data-tts-output-summary]"), +@@ -366,0 +444,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ pitch: root.querySelector("[data-tts-pitch]"), ++ pitchValue: root.querySelector("[data-tts-pitch-value]"), +@@ -369,0 +449,3 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ queueList: root.querySelector("[data-tts-queue-list]"), ++ rate: root.querySelector("[data-tts-rate]"), ++ rateValue: root.querySelector("[data-tts-rate-value]"), +@@ -372,0 +455,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ ssmlPreset: root.querySelector("[data-tts-ssml-preset-select]"), ++ standaloneActions: root.querySelector("[data-tts-standalone-actions]"), +@@ -376,0 +461,3 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ voice: root.querySelector("[data-tts-voice-select]"), ++ volume: root.querySelector("[data-tts-volume]"), ++ volumeValue: root.querySelector("[data-tts-volume-value]"), +@@ -379,0 +467 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ applyingItem: false, +@@ -383 +471 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- selectedEmotionId: "", ++ queue: [], +@@ -384,0 +473,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ selectedItemId: "", ++ sliderOverrides: { pitch: false, rate: false, volume: false }, +@@ -400,2 +490,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- function selectedProfile() { +- return state.profiles.find((profile) => profile.id === state.selectedProfileId) || null; ++ function selectedItem() { ++ return state.queue.find((item) => item.id === state.selectedItemId) || null; +@@ -404,2 +494,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- function previewProfile() { +- return selectedProfile() || state.profiles.find((profile) => profile.active) || state.profiles[0] || null; ++ function selectedVoiceOptions() { ++ return Array.from(elements.voice?.options || []).map((option) => ({ ++ label: option.textContent || option.value, ++ language: elements.language?.value || TEXT_TO_SPEECH_DEFAULTS.language, ++ name: option.textContent || option.value, ++ value: option.value ++ })).filter((option) => option.value); +@@ -408,8 +503,12 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- function previewEmotion(profile = previewProfile()) { +- if (!profile?.emotions?.length) { +- return null; +- } +- return profile.emotions.find((emotion) => emotion.id === state.selectedEmotionId) +- || profile.emotions.find((emotion) => emotion.emotion === "neutral" && emotion.active !== false) +- || profile.emotions.find((emotion) => emotion.active !== false) +- || profile.emotions[0]; ++ function currentOptions() { ++ return { ++ characterPreset: elements.characterPreset?.value || TEXT_TO_SPEECH_DEFAULTS.characterPreset, ++ gender: textToSpeechPayloadGenderValue(elements.gender?.value || TEXT_TO_SPEECH_DEFAULTS.gender), ++ language: elements.language?.value || TEXT_TO_SPEECH_DEFAULTS.language, ++ pitch: Number(elements.pitch?.value || TEXT_TO_SPEECH_DEFAULTS.pitch), ++ rate: Number(elements.rate?.value || TEXT_TO_SPEECH_DEFAULTS.rate), ++ ssmlLikePreset: elements.ssmlPreset?.value || TEXT_TO_SPEECH_DEFAULTS.ssmlLikePreset, ++ voice: elements.voice?.value || "", ++ voiceAge: elements.age?.value || TEXT_TO_SPEECH_DEFAULTS.voiceAge, ++ volume: Number(elements.volume?.value || TEXT_TO_SPEECH_DEFAULTS.volume) ++ }; +@@ -418,2 +517,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- function previewSpeechLabel(profile, emotion) { +- return `${profile?.name || "TTS Profile"} / ${emotion?.emotionLabel || "Emotion"}`; ++ function selectedProfile() { ++ return state.profiles.find((profile) => profile.id === state.selectedProfileId) || null; +@@ -461,9 +559,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- function tableActionRow(colSpan, ...buttons) { +- const row = document.createElement("tr"); +- const cell = document.createElement("td"); +- cell.colSpan = colSpan; +- cell.append(createActionGroup(...buttons)); +- row.append(cell); +- return row; +- } +- +@@ -558,4 +647,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- nameCell.setAttribute("role", "button"); +- nameCell.tabIndex = 0; +- nameCell.setAttribute("aria-expanded", String(state.selectedProfileId === profile.id)); +- nameCell.title = "Open Emotion Settings"; +@@ -597,3 +682,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- if (!state.editingProfileId) { +- elements.profileTable.append(createProfileAddControlRow()); +- } +@@ -603,6 +685,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- function createProfileAddControlRow() { +- const row = tableActionRow(8, createButton("Add Profile", "ttsAddProfileRow", NEW_ROW_KEY)); +- row.dataset.ttsProfileAddControlRow = ""; +- return row; +- } +- +@@ -713,3 +789,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- if (!state.editingEmotionId) { +- tbody.append(createEmotionAddControlRow(profileId)); +- } +@@ -719 +793,4 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- wrapper.append(tableWrapper); ++ const actionGroup = document.createElement("div"); ++ actionGroup.className = "action-group"; ++ actionGroup.append(createButton("Add Emotion", "ttsAddEmotion", profileId)); ++ wrapper.append(tableWrapper, actionGroup); +@@ -723,6 +799,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- function createEmotionAddControlRow(profileId) { +- const row = tableActionRow(7, createButton("Add Emotion", "ttsAddEmotion", profileId)); +- row.dataset.ttsEmotionAddControlRow = profileId; +- return row; +- } +- +@@ -840 +910,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- state.selectedEmotionId = previewEmotion(profile)?.id || ""; +@@ -843 +913 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- refreshActionState(); ++ renderOutputSummary(); +@@ -856 +925,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- if (!state.selectedProfileId) state.selectedEmotionId = ""; +@@ -858 +927 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- refreshActionState(); ++ renderOutputSummary(); +@@ -890 +958,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- state.selectedEmotionId = emotion.id; +@@ -892 +960 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- refreshActionState(); ++ renderOutputSummary(); +@@ -905 +972,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- if (state.selectedEmotionId === key) state.selectedEmotionId = previewEmotion(profile)?.id || ""; +@@ -907 +974 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- refreshActionState(); ++ renderOutputSummary(); +@@ -914 +980,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- state.selectedEmotionId = state.selectedProfileId ? previewEmotion(selectedProfile())?.id || "" : ""; +@@ -917 +982,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- refreshActionState(); +@@ -923 +988,56 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- function renderPreviewSummary() { ++ function itemFromControls(overrides = {}) { ++ const currentItem = selectedItem(); ++ return createTextToSpeechQueueItem({ ++ ...currentOptions(), ++ existingItems: state.queue.filter((item) => item.id !== currentItem?.id), ++ id: overrides.id ?? currentItem?.id ?? "", ++ name: overrides.name ?? elements.itemName?.value ?? currentItem?.name ?? "New speech item", ++ text: overrides.text ?? elements.text?.value ?? currentItem?.text ?? "" ++ }); ++ } ++ ++ function syncRangeOutputs() { ++ if (elements.rate) elements.rate.value = formatRangeValue(elements.rate.value, "rate"); ++ if (elements.pitch) elements.pitch.value = formatRangeValue(elements.pitch.value, "pitch"); ++ if (elements.volume) elements.volume.value = formatRangeValue(elements.volume.value, "volume"); ++ if (elements.rateValue) elements.rateValue.textContent = elements.rate?.value || "1"; ++ if (elements.pitchValue) elements.pitchValue.textContent = elements.pitch?.value || "1"; ++ if (elements.volumeValue) elements.volumeValue.textContent = elements.volume?.value || "1"; ++ } ++ ++ function configureRanges() { ++ [ ++ { input: elements.volume, kind: "volume" }, ++ { input: elements.rate, kind: "rate" }, ++ { input: elements.pitch, kind: "pitch" } ++ ].forEach(({ input, kind }) => { ++ const range = TEXT_TO_SPEECH_RANGE_DEFAULTS[kind]; ++ if (!input || !range) return; ++ input.min = String(range.min); ++ input.max = String(range.max); ++ input.step = String(range.step); ++ input.value = String(range.value); ++ }); ++ syncRangeOutputs(); ++ } ++ ++ function applyShapedOptions({ resetOverrides = false } = {}) { ++ if (resetOverrides) { ++ state.sliderOverrides = { pitch: false, rate: false, volume: false }; ++ } ++ const shaped = shapeTextToSpeechOptions(currentOptions(), state.sliderOverrides); ++ if (elements.rate && !state.sliderOverrides.rate) elements.rate.value = String(shaped.rate); ++ if (elements.pitch && !state.sliderOverrides.pitch) elements.pitch.value = String(shaped.pitch); ++ if (elements.volume && !state.sliderOverrides.volume) elements.volume.value = String(shaped.volume); ++ syncRangeOutputs(); ++ } ++ ++ function renderOutputSummary() { ++ if (elements.outputSummary) { ++ elements.outputSummary.textContent = JSON.stringify({ ++ contractVersion: TTS_PROFILE_CONTRACT_VERSION, ++ messageStudioOptions: createMessageStudioTtsProfileOptions(state.profiles), ++ profiles: state.profiles, ++ queue: state.queue ++ }, null, 2); ++ } +@@ -927 +1047,139 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- function refreshVoices() { ++ function renderQueue() { ++ if (!elements.queueList) return; ++ elements.queueList.replaceChildren(...state.queue.map((item) => { ++ const button = document.createElement("button"); ++ button.type = "button"; ++ button.className = "btn"; ++ button.dataset.ttsQueueItem = item.id; ++ button.setAttribute("role", "option"); ++ button.setAttribute("aria-selected", String(item.id === state.selectedItemId)); ++ button.textContent = `${item.name} - ${queueItemMeta(item)}`; ++ return button; ++ })); ++ renderOutputSummary(); ++ } ++ ++ function markWorkspaceDirty(reason, changedKeys = []) { ++ if (!isWorkspaceLaunch()) return; ++ const rawToolState = window.sessionStorage.getItem(WORKSPACE_TOOL_STATE_KEY); ++ if (!rawToolState) { ++ writeStatus(`Cannot mark ${TEXT_TO_SPEECH_DISPLAY_NAME} dirty: missing ${WORKSPACE_TOOL_STATE_KEY}.`, "FAIL"); ++ return; ++ } ++ try { ++ const toolState = JSON.parse(rawToolState); ++ window.sessionStorage.setItem(WORKSPACE_TOOL_STATE_KEY, JSON.stringify({ ++ ...toolState, ++ data: state.queue, ++ dirty: { ++ changedAt: new Date().toISOString(), ++ changedKeys, ++ isDirty: true, ++ reason ++ } ++ })); ++ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} dirty state: true; reason=${reason}.`); ++ } catch (error) { ++ writeStatus(`Cannot mark ${TEXT_TO_SPEECH_DISPLAY_NAME} dirty: ${error.message}`, "FAIL"); ++ } ++ } ++ ++ function syncSelectedItemFromControls(reason, changedKeys = []) { ++ if (state.applyingItem) return; ++ const item = selectedItem(); ++ if (!item) return; ++ const nextItem = itemFromControls({ id: item.id, name: elements.itemName?.value || item.name }); ++ const index = state.queue.findIndex((entry) => entry.id === item.id); ++ if (index >= 0) { ++ state.queue[index] = nextItem; ++ state.selectedItemId = nextItem.id; ++ renderQueue(); ++ markWorkspaceDirty(reason, changedKeys); ++ } ++ } ++ ++ function selectItem(itemId, { writeLog = true } = {}) { ++ const item = state.queue.find((entry) => entry.id === itemId); ++ if (!item) return; ++ state.selectedItemId = item.id; ++ state.applyingItem = true; ++ try { ++ if (elements.itemName) elements.itemName.value = item.name; ++ if (elements.text) elements.text.value = item.text; ++ if (elements.gender) elements.gender.value = item.gender === "any" ? "any" : item.gender; ++ if (elements.age) elements.age.value = item.voiceAge; ++ if (elements.characterPreset) elements.characterPreset.value = item.characterPreset; ++ if (elements.ssmlPreset) elements.ssmlPreset.value = item.ssmlLikePreset; ++ if (elements.rate) elements.rate.value = String(item.rate); ++ if (elements.pitch) elements.pitch.value = String(item.pitch); ++ if (elements.volume) elements.volume.value = String(item.volume); ++ syncRangeOutputs(); ++ refreshVoices({ selectedVoice: item.voice }); ++ } finally { ++ state.applyingItem = false; ++ } ++ renderQueue(); ++ refreshActionState(); ++ if (writeLog) writeStatus(`Selected speech item: ${item.name}.`); ++ } ++ ++ function addItem() { ++ const requestedName = String(elements.itemName?.value || "").trim(); ++ if (!requestedName) { ++ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Add blocked: Name is required before creating a named speech item.`, "FAIL"); ++ return; ++ } ++ const item = createTextToSpeechQueueItem({ ++ ...currentOptions(), ++ existingItems: state.queue, ++ name: requestedName, ++ text: elements.text?.value || "New speech line." ++ }); ++ state.queue.push(item); ++ state.selectedItemId = item.id; ++ renderQueue(); ++ selectItem(item.id, { writeLog: false }); ++ markWorkspaceDirty("speech-item-added", [`queue.${item.id}`]); ++ writeStatus(`Added speech item: ${item.name}.`); ++ } ++ ++ function duplicateItem() { ++ const item = itemFromControls(); ++ if (!selectedItem()) { ++ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} duplicate failed: no named sentence is selected.`, "FAIL"); ++ return; ++ } ++ const name = uniqueTextToSpeechName(`${item.name} copy`, state.queue); ++ const copy = { ++ ...item, ++ id: uniqueTextToSpeechId(name, state.queue), ++ name ++ }; ++ state.queue.push(copy); ++ selectItem(copy.id, { writeLog: false }); ++ markWorkspaceDirty("speech-item-duplicated", [`queue.${copy.id}`]); ++ writeStatus(`Duplicated speech item: ${copy.name}.`); ++ } ++ ++ function deleteItem() { ++ const item = selectedItem(); ++ if (!item) { ++ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} delete failed: no named sentence is selected.`, "FAIL"); ++ return; ++ } ++ const index = state.queue.findIndex((entry) => entry.id === item.id); ++ state.queue.splice(index, 1); ++ state.selectedItemId = state.queue[Math.min(index, state.queue.length - 1)]?.id || ""; ++ renderQueue(); ++ if (state.selectedItemId) { ++ selectItem(state.selectedItemId, { writeLog: false }); ++ } else { ++ if (elements.itemName) elements.itemName.value = ""; ++ if (elements.text) elements.text.value = ""; ++ renderOutputSummary(); ++ } ++ markWorkspaceDirty("speech-item-deleted", ["queue"]); ++ writeStatus(`Deleted speech item: ${item.name}.`); ++ } ++ ++ function refreshVoices({ selectedVoice = elements.voice?.value || "" } = {}) { +@@ -928,0 +1187,24 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ const gender = elements.gender?.value || "any"; ++ const filteredByGender = filterTextToSpeechVoiceOptions(state.voiceOptions, { gender }); ++ const languageOptions = filteredByGender.languageOptions.length ++ ? filteredByGender.languageOptions ++ : TEXT_TO_SPEECH_LANGUAGE_OPTIONS; ++ const previousLanguage = elements.language?.value || TEXT_TO_SPEECH_DEFAULTS.language; ++ const language = languageOptions.some((option) => option.value === previousLanguage) ++ ? previousLanguage ++ : languageOptions[0]?.value || TEXT_TO_SPEECH_DEFAULTS.language; ++ populateSelect(elements.language, languageOptions, language); ++ const result = filterTextToSpeechVoiceOptions(state.voiceOptions, { gender, language }); ++ populateSelect(elements.voice, result.matchingVoices.map((option) => ({ ++ label: option.label, ++ value: option.value ++ })), selectedVoice); ++ if (elements.voice && !elements.voice.value && result.matchingVoices[0]) { ++ elements.voice.value = result.matchingVoices[0].value; ++ } ++ if (elements.voice && result.matchingVoices.length === 0) { ++ const option = document.createElement("option"); ++ option.value = ""; ++ option.textContent = state.voiceOptions.length ? `No ${result.genderLabel} voices for ${language}` : "No browser voices available"; ++ elements.voice.replaceChildren(option); ++ } +@@ -929,0 +1212,3 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ setTextContent(root, "[data-tts-voice-details]", state.voiceOptions.length ++ ? `${result.matchingVoices.length} matching ${result.genderLabel} voice${result.matchingVoices.length === 1 ? "" : "s"} for ${language}.` ++ : "No SpeechSynthesis voices loaded."); +@@ -931 +1216 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- return state.voiceOptions; ++ return result; +@@ -940 +1224,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- state.selectedEmotionId = ""; +@@ -942 +1226 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- refreshActionState(); ++ renderOutputSummary(); +@@ -947,2 +1231 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- const profile = previewProfile(); +- const hasVoice = Boolean(profile?.voice); ++ const hasVoice = Boolean(elements.voice?.value); +@@ -950,5 +1233,8 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- if (elements.speak) elements.speak.disabled = !(supported && hasText && hasVoice); +- if (elements.pause) elements.pause.disabled = !engine.canPause(); +- if (elements.resume) elements.resume.disabled = !engine.canResume(); +- if (elements.stop) elements.stop.disabled = !supported; +- renderPreviewSummary(); ++ if (elements.speak) elements.speak.disabled = !(supported && hasText && hasVoice && selectedItem()); ++ if (elements.pause) elements.pause.disabled = !(engine.canPause() && selectedItem()); ++ if (elements.resume) elements.resume.disabled = !(engine.canResume() && selectedItem()); ++ if (elements.stop) elements.stop.disabled = !(supported && selectedItem()); ++ if (elements.copyJson) elements.copyJson.disabled = isWorkspaceLaunch(); ++ if (elements.exportJson) elements.exportJson.disabled = isWorkspaceLaunch(); ++ if (elements.importJson) elements.importJson.disabled = isWorkspaceLaunch(); ++ setTextContent(root, "[data-tts-text-count]", String(String(elements.text?.value || "").length)); +@@ -965,0 +1252 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ if (elements.standaloneActions) elements.standaloneActions.hidden = workspaceLaunch; +@@ -978,5 +1265,59 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- function speak() { +- const profile = previewProfile(); +- const emotion = previewEmotion(profile); +- if (!profile) { +- writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Speak blocked: add or select a TTS Profile first.`, "FAIL"); ++ async function loadQueue() { ++ if (isWorkspaceLaunch()) { ++ const rawToolState = window.sessionStorage.getItem(WORKSPACE_TOOL_STATE_KEY); ++ if (!rawToolState) { ++ writeStatus(`Workspace launch missing ${WORKSPACE_TOOL_STATE_KEY}; queue cannot render.`, "FAIL"); ++ return; ++ } ++ try { ++ const toolState = JSON.parse(rawToolState); ++ const payload = toolState?.data ?? []; ++ const validation = validateTextToSpeechPayload(payload, WORKSPACE_TOOL_STATE_KEY); ++ if (!validation.ok) { ++ writeStatus(validation.message, "FAIL"); ++ return; ++ } ++ state.queue = payload.map((item) => ({ ...item })); ++ writeStatus(`Loaded ${state.queue.length} ${TEXT_TO_SPEECH_DISPLAY_NAME} item(s) from Project Workspace toolState.`); ++ } catch (error) { ++ writeStatus(`${WORKSPACE_TOOL_STATE_KEY} is invalid JSON: ${error.message}`, "FAIL"); ++ return; ++ } ++ } else { ++ const params = new URLSearchParams(window.location.search); ++ const samplePresetPath = String(params.get(TTS_URL_SOURCE_PARAM) || "").trim(); ++ if (samplePresetPath) { ++ try { ++ const response = await fetch(samplePresetPath, { cache: "no-store" }); ++ if (!response.ok) throw new Error(`${response.status}`); ++ const payload = await response.json(); ++ const validation = validateTextToSpeechPayload(payload, samplePresetPath); ++ if (!validation.ok) { ++ writeStatus(validation.message, "FAIL"); ++ return; ++ } ++ state.queue = payload.map((item) => ({ ...item })); ++ writeStatus(`Loaded preset for ${TEXT_TO_SPEECH_DISPLAY_NAME}: ${samplePresetPath}.`); ++ } catch (error) { ++ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} URL JSON source ${samplePresetPath} failed: ${error.message}.`, "FAIL"); ++ return; ++ } ++ } ++ } ++ if (state.queue.length === 0) { ++ state.queue = [createTextToSpeechQueueItem({ ++ ...TEXT_TO_SPEECH_DEFAULTS, ++ existingItems: [], ++ name: "Narrator welcome", ++ text: elements.text?.value || "Welcome to the arena, hero." ++ })]; ++ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} empty launch: created a local named sentence draft.`); ++ } ++ state.selectedItemId = state.queue[0]?.id || ""; ++ renderQueue(); ++ if (state.selectedItemId) selectItem(state.selectedItemId, { writeLog: false }); ++ } ++ ++ async function importJson(file) { ++ if (isWorkspaceLaunch()) { ++ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Import JSON is only available during standalone launch.`, "FAIL"); +@@ -985,2 +1326,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- if (!emotion) { +- writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Speak blocked: add or select an Emotion Setting first.`, "FAIL"); ++ if (!file) { ++ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Import JSON blocked: choose a JSON file first.`, "FAIL"); +@@ -988,0 +1330,43 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ try { ++ const payload = JSON.parse(await readFileText(file)); ++ const validation = validateTextToSpeechPayload(payload, file.name || "selected JSON file"); ++ if (!validation.ok) { ++ writeStatus(`Import JSON blocked: ${validation.message}`, "FAIL"); ++ return; ++ } ++ state.queue = payload.map((item) => ({ ...item })); ++ state.selectedItemId = state.queue[0]?.id || ""; ++ renderQueue(); ++ if (state.selectedItemId) selectItem(state.selectedItemId, { writeLog: false }); ++ writeStatus(`Imported ${state.queue.length} ${TEXT_TO_SPEECH_DISPLAY_NAME} item(s) from ${file.name || "selected JSON file"}; schema=${TEXT_TO_SPEECH_SCHEMA_ID}.`); ++ } catch (error) { ++ writeStatus(`Import JSON failed: ${error.message}`, "FAIL"); ++ } ++ } ++ ++ async function copyJson() { ++ const json = JSON.stringify(state.queue, null, 2); ++ if (!navigator.clipboard || typeof navigator.clipboard.writeText !== "function") { ++ writeStatus("Copy JSON failed: Clipboard API is unavailable.", "FAIL"); ++ return; ++ } ++ try { ++ await navigator.clipboard.writeText(json); ++ writeStatus(`Copied ${TEXT_TO_SPEECH_DISPLAY_NAME} JSON root array to clipboard (${state.queue.length} item(s)).`); ++ } catch (error) { ++ writeStatus(`Copy JSON failed: ${error.message}`, "FAIL"); ++ } ++ } ++ ++ function exportJson() { ++ downloadTextFile(JSON.stringify(state.queue, null, 2), "text-to-speech-v2.json"); ++ writeStatus(`Exported ${TEXT_TO_SPEECH_DISPLAY_NAME} JSON root array (${state.queue.length} item(s)).`); ++ } ++ ++ function speak() { ++ const item = selectedItem(); ++ if (!item) { ++ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Speak blocked: select a named speech item with text first.`, "FAIL"); ++ return; ++ } ++ syncSelectedItemFromControls("speak-sync", ["speechOptions", "text"]); +@@ -990,3 +1374 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- language: profile.language, +- pitch: emotion.pitch, +- rate: emotion.rate, ++ ...currentOptions(), +@@ -994,3 +1376 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- voice: profile.voice, +- voiceOptions: state.voiceOptions, +- volume: emotion.volume, ++ voiceOptions: selectedVoiceOptions() +@@ -1004,10 +1384,4 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- language: profile.language, +- pitch: request.pitch, +- rate: request.rate, +- speechItemId: `${profile.id}:${emotion.id}:preview`, +- speechItemName: previewSpeechLabel(profile, emotion), +- ssmlLikePreset: emotion.ssmlLikePreset, +- text: request.text, +- voice: profile.voice, +- voiceAge: profile.age, +- volume: request.volume, ++ ...currentOptions(), ++ speechItemId: item.id, ++ speechItemName: item.name, ++ text: request.text +@@ -1021 +1395 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- renderPreviewSummary(); ++ renderOutputSummary(); +@@ -1053,0 +1428 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ elements.addProfile?.addEventListener("click", addProfile); +@@ -1055 +1429,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- const addProfileButton = event.target.closest("[data-tts-add-profile-row]"); +@@ -1061 +1434,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- const emotionRow = event.target.closest("[data-tts-emotion-row]"); +@@ -1066 +1439 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- const profileNameCell = event.target.closest("[data-tts-profile-name-cell]"); ++ const profileRow = event.target.closest("[data-tts-profile-row]"); +@@ -1068,4 +1440,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- if (addProfileButton) { +- addProfile(); +- return; +- } +@@ -1085 +1453,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- state.selectedEmotionId = previewEmotion(selectedProfile())?.id || ""; +@@ -1111 +1478,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- state.selectedEmotionId = editEmotionButton.dataset.ttsEditEmotion; +@@ -1120,8 +1487,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- if (emotionRow) { +- state.selectedEmotionId = emotionRow.dataset.ttsEmotionRow; +- refreshActionState(); +- writeStatus(`Selected Emotion Setting: ${previewEmotion()?.emotionLabel || "Unknown"}.`); +- return; +- } +- if (profileNameCell) { +- selectProfile(profileNameCell.dataset.ttsProfileNameCell); ++ if (profileRow) { ++ selectProfile(profileRow.dataset.ttsProfileRow); +@@ -1130,6 +1491,3 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- elements.profileTable?.addEventListener("keydown", (event) => { +- if (event.key !== "Enter" && event.key !== " ") return; +- const profileNameCell = event.target.closest("[data-tts-profile-name-cell]"); +- if (!profileNameCell) return; +- event.preventDefault(); +- selectProfile(profileNameCell.dataset.ttsProfileNameCell); ++ elements.queueList?.addEventListener("click", (event) => { ++ const itemButton = event.target.closest("[data-tts-queue-item]"); ++ if (itemButton) selectItem(itemButton.dataset.ttsQueueItem || ""); +@@ -1136,0 +1495,3 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ elements.addItem?.addEventListener("click", addItem); ++ elements.duplicateItem?.addEventListener("click", duplicateItem); ++ elements.deleteItem?.addEventListener("click", deleteItem); +@@ -1144,0 +1506,9 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ elements.importJson?.addEventListener("click", () => elements.importJsonInput?.click()); ++ elements.importJsonInput?.addEventListener("change", () => { ++ void importJson(elements.importJsonInput.files?.[0] || null); ++ elements.importJsonInput.value = ""; ++ }); ++ elements.copyJson?.addEventListener("click", () => { ++ void copyJson(); ++ }); ++ elements.exportJson?.addEventListener("click", exportJson); +@@ -1147,0 +1518 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ elements.itemName?.addEventListener("input", () => syncSelectedItemFromControls("speech-item-renamed", ["name"])); +@@ -1148,0 +1520 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ syncSelectedItemFromControls("text-updated", ["text"]); +@@ -1150,0 +1523,46 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ [elements.gender, elements.language].forEach((select) => { ++ select?.addEventListener("change", () => { ++ refreshVoices(); ++ syncSelectedItemFromControls("voice-filter-updated", ["gender", "language", "voice"]); ++ }); ++ }); ++ elements.voice?.addEventListener("change", () => syncSelectedItemFromControls("voice-updated", ["voice"])); ++ elements.characterPreset?.addEventListener("change", () => { ++ if (elements.ssmlPreset) { ++ elements.ssmlPreset.value = TEXT_TO_SPEECH_CHARACTER_PRESET_DEFAULTS[elements.characterPreset.value]?.ssmlLikePreset || "normal"; ++ } ++ applyShapedOptions({ resetOverrides: true }); ++ syncSelectedItemFromControls("character-preset-updated", ["characterPreset", "rate", "pitch", "volume"]); ++ }); ++ [elements.age, elements.ssmlPreset].forEach((select) => { ++ select?.addEventListener("change", () => { ++ applyShapedOptions(); ++ syncSelectedItemFromControls("speech-shaping-updated", ["voiceAge", "ssmlLikePreset", "rate", "pitch", "volume"]); ++ }); ++ }); ++ [ ++ { input: elements.pitch, kind: "pitch" }, ++ { input: elements.rate, kind: "rate" }, ++ { input: elements.volume, kind: "volume" } ++ ].forEach(({ input, kind }) => { ++ input?.addEventListener("input", () => { ++ state.sliderOverrides[kind] = true; ++ syncRangeOutputs(); ++ syncSelectedItemFromControls(`${kind}-updated`, [kind]); ++ refreshActionState(); ++ }); ++ input?.addEventListener("dblclick", () => { ++ state.sliderOverrides[kind] = false; ++ applyShapedOptions(); ++ syncSelectedItemFromControls(`${kind}-reset`, [kind]); ++ }); ++ }); ++ } ++ ++ function populateStaticControls() { ++ populateSelect(elements.gender, TEXT_TO_SPEECH_GENDER_FILTER_OPTIONS, TEXT_TO_SPEECH_DEFAULTS.gender); ++ populateSelect(elements.age, TEXT_TO_SPEECH_AGE_FILTER_OPTIONS, TEXT_TO_SPEECH_DEFAULTS.voiceAge); ++ populateSelect(elements.characterPreset, TEXT_TO_SPEECH_CHARACTER_PRESET_OPTIONS, TEXT_TO_SPEECH_DEFAULTS.characterPreset); ++ populateSelect(elements.ssmlPreset, TEXT_TO_SPEECH_SSML_LIKE_PRESET_OPTIONS, TEXT_TO_SPEECH_DEFAULTS.ssmlLikePreset); ++ populateSelect(elements.language, TEXT_TO_SPEECH_LANGUAGE_OPTIONS, TEXT_TO_SPEECH_DEFAULTS.language); ++ configureRanges(); +@@ -1157 +1575 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- setTextContent(root, "[data-tts-voice-count]", "0"); ++ populateSelect(elements.voice, [{ label: "No browser voices available", value: "" }], ""); +@@ -1162,0 +1581 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ populateStaticControls(); +@@ -1164,0 +1584 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ await loadQueue(); +@@ -1175,0 +1596 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ renderOutputSummary(); +@@ -1177,0 +1599,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech ++ await loadQueue(); ++ refreshVoices({ selectedVoice: selectedItem()?.voice || elements.voice?.value || "" }); +@@ -1187 +1610 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech +- profiles: () => state.profiles.map((profile) => ({ ...profile })), ++ queue: () => state.queue.map((item) => ({ ...item })), +@@ -1211 +1634,2 @@ export { +- previewTtsMessage ++ previewTtsMessage, ++ validateTextToSpeechPayload diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs index fb16b40e8..b26a01036 100644 --- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -94,8 +94,19 @@ test("Admin System Health renders foundation tables without page API calls", asy await expect(page.getByRole("table", { name: "Runtime environment" })).toContainText("********"); await expect(page.getByRole("table", { name: "Limits and capacity" })).toContainText("Class A Ops"); await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("PASS"); - await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("WARN"); - await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("FAIL"); + await expect(page.getByRole("table", { name: "Diagnostics log" })).toContainText("PENDING"); + await expect(page.getByRole("table", { name: "Diagnostics log" })).not.toContainText("FAIL"); + await expect(page.getByText("No active failure is declared")).toHaveCount(0); + await expect(page.locator("[data-health-status='WARN'], [data-health-status='FAIL']")).toHaveCount(0); + const nonPassStatuses = page.locator("[data-health-status]:not([data-health-status='PASS'])"); + const nonPassStatusCount = await nonPassStatuses.count(); + expect(nonPassStatusCount).toBeGreaterThan(0); + for (let index = 0; index < nonPassStatusCount; index += 1) { + const statusCell = nonPassStatuses.nth(index); + const title = await statusCell.getAttribute("title"); + const ariaLabel = await statusCell.getAttribute("aria-label"); + expect((title || ariaLabel || "").trim()).not.toEqual(""); + } expect(context.requestUrls.some((url) => url.includes("/api/admin/system-health"))).toBe(false); await expect(page.locator("[data-admin-system-health-storage-action]")).toHaveCount(0); await expect(page.locator("[data-owner-ai-save], [data-owner-membership-save], [data-owner-ai-credits], [data-owner-memberships]")).toHaveCount(0); @@ -128,6 +139,8 @@ test("Admin System Health operations page keeps scripts and styles external", as expect(pageSource).not.toMatch(/]+src=)/i); expect(pageSource).not.toMatch(/\son[a-z]+\s*=/i); expect(pageSource).not.toMatch(/\sstyle\s*=/i); + expect(pageSource).not.toMatch(/data-health-status="(?:WARN|FAIL)"/); + expect(pageSource).not.toContain("No active failure is declared"); expect(pageSource).not.toContain("assets/theme-v2/js/admin-system-health.js"); expect(pageSource).toContain("assets/theme-v2/js/admin-owner-navigation.js"); }); From 2f4b8a5d493d479ed2442f5025cc2b2be863a3c3 Mon Sep 17 00:00:00 2001 From: "Mr. Q" <97194984+ToolboxAid@users.noreply.github.com> Date: Sat, 20 Jun 2026 21:10:15 -0400 Subject: [PATCH 3/8] Refresh admin health status cleanup review artifacts - PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup --- ...min-system-health-status-reason-cleanup.md | 1 + .../dev/reports/codex_changed_files.txt | 90 +- docs_build/dev/reports/codex_review.diff | 1915 +---------------- 3 files changed, 51 insertions(+), 1955 deletions(-) diff --git a/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md index 094b6dfbe..cad4a23a7 100644 --- a/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md +++ b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md @@ -63,6 +63,7 @@ Note: the merged instruction file still contains the older main-only branch guar ## ZIP Artifact - `tmp/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup_delta.zip` +- Generated from the current `team/GAMMA/admin` branch delta against the branch merge-base with `origin/main`, preserving the existing PR #36 workstream context. ## EOD Approval diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 99f7fd9ab..ad56efa47 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,66 +1,30 @@ -M admin/system-health.html -A docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md -A docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md -A docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md -M tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +Base merge-base: e8845dae6e990164072d4ee9b37ec81eda563fc3 +Head before artifact refresh: 33bcbf07338c593cec3bf8a1ded422ce3ab690d6 - admin/system-health.html | 360 +++-------- - ...ble-cleanup-instruction-compliance-checklist.md | 14 - - ...essage-table-cleanup-manual-validation-notes.md | 10 - - ...-tts-message-table-cleanup-validation-report.md | 25 - - .../PR_26171_BETA_075-tts-message-table-cleanup.md | 42 -- - ...e-parent-child-table-manual-validation-notes.md | 11 - - ...profile-parent-child-table-validation-report.md | 17 - - ...6171_BETA_077-tts-profile-parent-child-table.md | 19 - - ...ild-table-completion-manual-validation-notes.md | 11 - - ...ent-child-table-completion-validation-report.md | 17 - - ...message-studio-parent-child-table-completion.md | 20 - - ...k-through-tts-engine-manual-validation-notes.md | 11 - - ...layback-through-tts-engine-validation-report.md | 21 - - ...BETA_081-message-playback-through-tts-engine.md | 18 - - ...-foundation-instruction-compliance-checklist.md | 32 + +Working tree status at artifact generation: + M docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md + +Changed files against merge-base: +M admin/system-health.html +A docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation-instruction-compliance-checklist.md +A docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation-manual-validation-notes.md +A docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation.md +A docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-instruction-compliance-checklist.md +A docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup-manual-validation-notes.md +A docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md +M docs_build/dev/reports/coverage_changed_js_guardrail.txt +M docs_build/dev/reports/playwright_v8_coverage_report.txt +M tests/playwright/tools/AdminHealthOperationsPage.spec.mjs + +Stat against merge-base: + admin/system-health.html | 360 +++++---------------- + ...-foundation-instruction-compliance-checklist.md | 32 ++ ...em-health-foundation-manual-validation-notes.md | 34 ++ - ...171_GAMMA_011-admin-system-health-foundation.md | 65 ++ - ...son-cleanup-instruction-compliance-checklist.md | 57 ++ - ...tatus-reason-cleanup-manual-validation-notes.md | 26 + - ...12-admin-system-health-status-reason-cleanup.md | 69 +++ + ...171_GAMMA_011-admin-system-health-foundation.md | 65 ++++ + ...son-cleanup-instruction-compliance-checklist.md | 57 ++++ + ...tatus-reason-cleanup-manual-validation-notes.md | 26 ++ + ...12-admin-system-health-status-reason-cleanup.md | 70 ++++ .../dev/reports/coverage_changed_js_guardrail.txt | 6 +- - docs_build/dev/reports/dependency_gating_report.md | 2 +- - .../reports/dependency_hydration_reuse_report.md | 2 +- - .../dev/reports/execution_graph_reuse_report.md | 4 +- - .../dev/reports/failure_fingerprint_report.md | 2 +- - .../reports/filesystem_scan_reduction_report.md | 2 +- - .../dev/reports/incremental_validation_report.md | 2 +- - docs_build/dev/reports/lane_compilation_report.md | 2 +- - .../dev/reports/lane_deduplication_report.md | 2 +- - .../dev/reports/lane_input_validation_report.md | 2 +- - .../reports/lane_manifests/workspace-contract.json | 8 +- - .../reports/lane_runtime_optimization_report.md | 2 +- - docs_build/dev/reports/lane_snapshot_report.md | 4 +- - .../reports/lane_snapshots/workspace-contract.json | 16 +- - docs_build/dev/reports/lane_warm_start_report.md | 4 +- - .../lane_warm_starts/workspace-contract.json | 8 +- - .../dev/reports/monolith_trigger_removal_report.md | 2 +- - .../dev/reports/persistent_lane_manifest_report.md | 6 +- - .../playwright_discovery_ownership_report.md | 2 +- - .../reports/playwright_discovery_scope_report.md | 2 +- - .../dev/reports/playwright_structure_audit.md | 2 +- - .../dev/reports/playwright_v8_coverage_report.txt | 43 +- - docs_build/dev/reports/retry_suppression_report.md | 2 +- - docs_build/dev/reports/slow_path_pruning_report.md | 14 +- - docs_build/dev/reports/static_validation_report.md | 4 +- - .../dev/reports/targeted_file_manifest_report.md | 4 +- - .../dev/reports/test_cleanup_performance_report.md | 16 +- - .../dev/reports/test_cleanup_routing_report.md | 2 +- - .../dev/reports/testing_lane_execution_report.md | 18 +- - docs_build/dev/reports/validation_cache_report.md | 30 +- - .../dev/reports/zero_browser_preflight_report.md | 2 +- - .../messages/messages-sqlite-service.mjs | 33 +- - .../tools/AdminHealthOperationsPage.spec.mjs | 74 ++- - tests/playwright/tools/MessagesTool.spec.mjs | 55 +- - .../playwright/tools/RootToolsFutureState.spec.mjs | 23 +- - .../tools/TextToSpeechFunctional.spec.mjs | 86 ++- - toolbox/messages/messages.js | 71 +-- - toolbox/text-to-speech/index.html | 87 ++- - toolbox/text-to-speech/text2speech.js | 662 +++++++++++++++++---- - 59 files changed, 1219 insertions(+), 968 deletions(-) + .../dev/reports/playwright_v8_coverage_report.txt | 31 +- + .../tools/AdminHealthOperationsPage.spec.mjs | 74 +++-- + 10 files changed, 434 insertions(+), 321 deletions(-) diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index a51872ef3..f96131271 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -392,320 +392,6 @@ index c5ef74f44..092a94f04 100644 + Admin Operations @@ -396 +198,0 @@ - -diff --git a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-instruction-compliance-checklist.md b/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-instruction-compliance-checklist.md -deleted file mode 100644 -index 5a5c8e58d..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-instruction-compliance-checklist.md -+++ /dev/null -@@ -1,14 +0,0 @@ --# PR_26171_BETA_075 Instruction Compliance Checklist -- --Team: BETA -- --- PASS: Started from `main`. --- PASS: Pulled latest `origin/main` before branch creation. --- PASS: Repository was clean before creating `pr/26171-BETA-075-tts-message-table-cleanup`. --- PASS: PR name includes required TEAM token `BETA`. --- PASS: `docs_build/dev/PROJECT_MULTI_PC.txt` assigns Messages, Text To Speech, and TTS work to Team BETA. --- PASS: Active implementation paths are `toolbox/text-to-speech/` and `toolbox/messages/`. --- PASS: No `tools/text2speech/` path was created. --- PASS: Theme V2 only; no inline styles, style blocks, inline handlers, page-local CSS, or tool-local CSS were added. --- PASS: Validation scope includes targeted TTS Studio, targeted Message Studio, and legacy `npm run test:workspace-v2`. --- PASS: Required reports and repo ZIP under `tmp/` are required before completion. -diff --git a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-manual-validation-notes.md -deleted file mode 100644 -index 2fab2dae1..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-manual-validation-notes.md -+++ /dev/null -@@ -1,10 +0,0 @@ --# PR_26171_BETA_075 Manual Validation Notes -- --Team: BETA -- --- Reviewed `toolbox/text-to-speech/index.html` and confirmed no Named Sentence, Queue, or Output Summary surface remains. --- Reviewed `toolbox/text-to-speech/text2speech.js` and confirmed preview playback uses TTS Profile plus Emotion Settings instead of a named-sentence queue. --- Reviewed `toolbox/messages/messages.js` and confirmed Message Parts visible columns are Text, Emotion, TTS Profile, Status, and Actions. --- Confirmed no inline styles, style blocks, inline event handlers, page-local CSS, or tool-local CSS were added. --- Confirmed no `tools/text2speech/` path was created. --- Confirmed required delta ZIP path: `tmp/PR_26171_BETA_075-tts-message-table-cleanup_delta.zip`. -diff --git a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-validation-report.md b/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-validation-report.md -deleted file mode 100644 -index 09b7cba7a..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup-validation-report.md -+++ /dev/null -@@ -1,25 +0,0 @@ --# PR_26171_BETA_075 Validation Report -- --Team: BETA -- --## Commands -- --- PASS: `node --check toolbox/text-to-speech/text2speech.js` --- PASS: `node --check toolbox/messages/messages.js` --- PASS: `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs` -- - Result: 3 passed. --- PASS: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --trace=off` -- - Result: 2 passed after scoping the Message Parts table assertion. --- PASS: `node --test tests/tools/Text2SpeechShell.test.mjs` -- - Result: 4 passed. --- PASS: `npm run test:workspace-v2` -- - Result: 5 passed. -- - Note: Command name is legacy; user-facing language is Project Workspace and Game Hub. --- PASS: `git diff --check` -- - Result: no whitespace errors; Git reported line-ending notices only. -- --## Validation Notes -- --- Initial `npm run test:workspace-v2` runs exposed stale root toolbox expectations for the current registered toolbox count, duplicate Game Hub entries, and Publish group visibility. --- Those expectations were updated in the targeted workspace test file, then the legacy workspace command passed. --- Node emitted experimental SQLite warnings during validation; no validation failed because of those warnings. -diff --git a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup.md b/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup.md -deleted file mode 100644 -index 0da2fe50c..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_075-tts-message-table-cleanup.md -+++ /dev/null -@@ -1,42 +0,0 @@ --# PR_26171_BETA_075 TTS Message Table Cleanup -- --Team: BETA -- --## Summary -- --- Removed the duplicate center summary text `TTS Studio` from the Text To Speech summary row. --- Removed the stale Text To Speech left-column Voice Filters, Presets, Delivery, and Named Sentence controls. --- Removed the stale Text To Speech Queue and right-column Output Summary surfaces. --- Kept voice, language, gender, and age editing in the TTS Profile inline table row. --- Kept pitch, rate, volume, and preset editing in the Emotion Settings inline table row. --- Updated Text To Speech preview playback to speak the current text through the selected TTS Profile and selected/neutral Emotion Setting. --- Updated Message Studio Message Parts to show Text, Emotion, TTS Profile, Status, and Actions while preserving internal display order. --- Updated focused Playwright tests and workspace count/group expectations to match the current registered toolbox surface. -- --## Requirement Checklist -- --- PASS: Removed duplicate center-column `TTS Studio` summary text. --- PASS: Removed duplicate left-column items that belong to Profile or Emotion Settings rows. --- PASS: Delivery values are owned by Emotion Settings through Pitch, Rate, and Volume fields. --- PASS: Preset value is owned by Emotion Settings through the Preset field. --- PASS: Removed right-column Output Summary. --- PASS: Voice Filters are owned by the Profile row dropdowns. --- PASS: Removed Named Sentence and queue UI. --- PASS: Removed related dead HTML, JavaScript, and test code for the stale queue/output controls. --- PASS: Message Studio child table uses the approved visible Message Parts columns. --- PASS: Theme V2 only; no local CSS or inline handlers/styles were added. -- --## Validation -- --- PASS: `npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs` --- PASS: `npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --trace=off` --- PASS: `node --test tests/tools/Text2SpeechShell.test.mjs` --- PASS: `npm run test:workspace-v2` --- PASS: `git diff --check` -- --`npm run test:workspace-v2` is a legacy command name; the user-facing product language remains Project Workspace and Game Hub. -- --## Notes -- --- The Text To Speech page still supports local preview playback through the shared engine audio module. --- Message Part display order remains persisted internally and is managed with Move Up and Move Down actions rather than a visible Order column. -diff --git a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-manual-validation-notes.md -deleted file mode 100644 -index b050febf8..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-manual-validation-notes.md -+++ /dev/null -@@ -1,11 +0,0 @@ --# PR_26171_BETA_077 Manual Validation Notes -- --## Review --- Confirmed the TTS Profiles table keeps profile fields in the parent row. --- Confirmed Emotion Settings open below the selected profile as a child table. --- Confirmed Add Profile and Add Emotion controls render as table rows. --- Confirmed non-name parent table cells do not open Emotion Settings. --- Confirmed no separate Emotion Studio or duplicate left-column controls were introduced. -- --## Manual Browser Coverage --- Covered by targeted Playwright validation for TTS Studio load, profile expansion, child emotion rows, inline add/edit, and speech preview. -diff --git a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-validation-report.md b/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-validation-report.md -deleted file mode 100644 -index 43ad39062..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table-validation-report.md -+++ /dev/null -@@ -1,17 +0,0 @@ --# PR_26171_BETA_077 Validation Report -- --## Commands --- PASS: node --check toolbox/text-to-speech/text2speech.js --- PASS: node --test tests/tools/Text2SpeechShell.test.mjs --- PASS: npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs --- PASS: npm run test:workspace-v2 --- PASS: git diff --check -- --## Targeted Results --- Text2Speech Node contract tests: 4 passed. --- Text To Speech Playwright tests: 3 passed. --- Project Workspace legacy validation: 5 passed. -- --## Notes --- npm run test:workspace-v2 is a legacy command name; user-facing language remains Project Workspace. --- Standard generated validation-report churn was restored before staging this PR. -diff --git a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table.md b/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table.md -deleted file mode 100644 -index 3cafc0697..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_077-tts-profile-parent-child-table.md -+++ /dev/null -@@ -1,19 +0,0 @@ --# PR_26171_BETA_077-tts-profile-parent-child-table -- --## Team Ownership --- TEAM: BETA --- Ownership source: docs_build/dev/PROJECT_MULTI_PC.txt --- Scope confirmed: Text To Speech / TTS Studio work is owned by Team BETA. -- --## Summary --- Converted the TTS Profile add action into an inline parent-table action row. --- Converted the Emotion Setting add action into an inline child-table action row. --- Restricted profile expansion to the Profile Name cell so non-control parent cells remain table data. --- Added keyboard support and aria-expanded state for the Profile Name accordion trigger. -- --## Scope Guard --- Theme V2 only. --- External JS only. --- No inline styles, style blocks, inline handlers, page-local CSS, or tool-local CSS. --- No duplicate left-column controls added. --- Delivery and Preset values remain in the Emotion Settings child table. -diff --git a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-manual-validation-notes.md -deleted file mode 100644 -index 61db9dd99..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-manual-validation-notes.md -+++ /dev/null -@@ -1,11 +0,0 @@ --# PR_26171_BETA_079 Manual Validation Notes -- --## Review --- Confirmed the Messages table remains the parent table. --- Confirmed clicking a non-control Message row cell opens the Message Parts child table. --- Confirmed Message Parts expose Text, Emotion, TTS Profile, Status, and Actions columns. --- Confirmed Add Part opens an inline child-table editor with Emotion and TTS Profile selectors. --- Confirmed TTS Studio compatibility validation still passes. -- --## Manual Browser Coverage --- Covered by targeted Playwright validation for Message Studio load, row expansion, add/edit flows, and Message Part selectors. -diff --git a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-validation-report.md b/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-validation-report.md -deleted file mode 100644 -index 19361ead5..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion-validation-report.md -+++ /dev/null -@@ -1,17 +0,0 @@ --# PR_26171_BETA_079 Validation Report -- --## Commands --- PASS: node --check toolbox/messages/messages.js --- PASS: npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --- PASS: npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs --- PASS: npm run test:workspace-v2 --- PASS: git diff --check -- --## Targeted Results --- Message Studio Playwright tests: 2 passed. --- Text To Speech compatibility Playwright tests: 3 passed. --- Project Workspace legacy validation: 5 passed. -- --## Notes --- npm run test:workspace-v2 is a legacy command name; user-facing language remains Project Workspace. --- Standard generated validation-report churn was restored before staging this PR. -diff --git a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion.md b/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion.md -deleted file mode 100644 -index dd4c6c13c..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_079-message-studio-parent-child-table-completion.md -+++ /dev/null -@@ -1,20 +0,0 @@ --# PR_26171_BETA_079-message-studio-parent-child-table-completion -- --## Team Ownership --- TEAM: BETA --- Ownership source: docs_build/dev/PROJECT_MULTI_PC.txt --- Scope confirmed: Message Studio, Messages, and TTS selection integration are owned by Team BETA. -- --## Summary --- Completed Message Studio row-click behavior for the Messages parent table. --- Kept Message Parts as the selected Message child table. --- Kept Message Part controls for Text, Emotion, TTS Profile, Status, and Actions. --- Preserved existing Play Part, Play Message, and Stop controls for the next playback PR. -- --## Scope Guard --- Theme V2 only. --- External JS only. --- No inline styles, style blocks, inline handlers, page-local CSS, or tool-local CSS. --- No database schema changes. --- No separate Emotion Studio. --- No browser-owned product data source of truth added. -diff --git a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-manual-validation-notes.md b/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-manual-validation-notes.md -deleted file mode 100644 -index f9749c887..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-manual-validation-notes.md -+++ /dev/null -@@ -1,11 +0,0 @@ --# PR_26171_BETA_081 Manual Validation Notes -- --## Review --- Confirmed /api/messages/tts-profiles returns server-owned emotionSettings for playback. --- Confirmed Play Message queues each active Message Part through the TextToSpeechEngine-backed registry. --- Confirmed Play Part uses the selected part TTS Profile and matching Emotion Setting values. --- Confirmed Stop continues through the TextToSpeechEngine-backed registry stop path. --- Confirmed missing Emotion Settings produce a visible validation error instead of falling back silently. -- --## Manual Browser Coverage --- Covered by targeted Playwright validation for Message Studio playback, TTS profile emotion settings, Play Message, Play Part, and Stop. -diff --git a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-validation-report.md b/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-validation-report.md -deleted file mode 100644 -index 7fbc9bb8d..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine-validation-report.md -+++ /dev/null -@@ -1,21 +0,0 @@ --# PR_26171_BETA_081 Validation Report -- --## Commands --- PASS: node --check toolbox/messages/messages.js --- PASS: node --check src/dev-runtime/messages/messages-sqlite-service.mjs --- PASS: npx playwright test tests/playwright/tools/MessagesTool.spec.mjs --- PASS: npx playwright test tests/playwright/tools/TextToSpeechFunctional.spec.mjs --reporter=list --- PASS: node --test tests/tools/Text2SpeechShell.test.mjs --- PASS: npm run test:workspace-v2 --- PASS: git diff --check -- --## Targeted Results --- Message Studio Playwright tests: 2 passed. --- Text To Speech compatibility Playwright tests: 3 passed. --- Text2Speech Node contract tests: 4 passed. --- Project Workspace legacy validation: 5 passed. -- --## Notes --- A parallel Playwright run caused an HTML reporter file-copy collision after tests passed; the TTS compatibility lane was rerun with the list reporter and passed cleanly. --- npm run test:workspace-v2 is a legacy command name; user-facing language remains Project Workspace. --- Standard generated validation-report churn was restored before staging this PR. -diff --git a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine.md b/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine.md -deleted file mode 100644 -index 261cae8e6..000000000 ---- a/docs_build/dev/reports/PR_26171_BETA_081-message-playback-through-tts-engine.md -+++ /dev/null -@@ -1,18 +0,0 @@ --# PR_26171_BETA_081-message-playback-through-tts-engine -- --## Team Ownership --- TEAM: BETA --- Ownership source: docs_build/dev/PROJECT_MULTI_PC.txt --- Scope confirmed: Audio, Messages, Text To Speech, and TTS are owned by Team BETA. -- --## Summary --- Added server-owned Emotion Settings to Messages TTS profile API responses without changing database schema. --- Updated Message Studio playback readiness to require a matching Emotion Setting on the selected TTS Profile. --- Wired Play Part and Play Message playback values through the existing TextToSpeechEngine registry path using selected TTS Profile language/voice and Emotion Setting pitch/rate/volume/preset. --- Kept Stop routed through the existing TextToSpeechEngine stop path. -- --## Scope Guard --- No database schema changes. --- No engine core changes. --- No silent playback fallback when a selected TTS Profile lacks the selected Emotion Setting. --- Theme V2 and external JS only. diff --git a/docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation-instruction-compliance-checklist.md b/docs_build/dev/reports/PR_26171_GAMMA_011-admin-system-health-foundation-instruction-compliance-checklist.md new file mode 100644 index 000000000..261e4b70c @@ -952,10 +638,10 @@ index 000000000..abffac83a +- Confirm owner approval before any EOD merge. diff --git a/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md new file mode 100644 -index 000000000..094b6dfbe +index 000000000..cad4a23a7 --- /dev/null +++ b/docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md -@@ -0,0 +1,69 @@ +@@ -0,0 +1,70 @@ +# PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup + +## Summary @@ -1021,466 +707,65 @@ index 000000000..094b6dfbe +## ZIP Artifact + +- `tmp/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup_delta.zip` ++- Generated from the current `team/GAMMA/admin` branch delta against the branch merge-base with `origin/main`, preserving the existing PR #36 workstream context. + +## EOD Approval + +No merge was performed. EOD merge remains owner-controlled and requires explicit approval. diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt -index 86850b15b..7b1c51f19 100644 +index 59de15fb6..7b1c51f19 100644 --- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt +++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt @@ -9,2 +9 @@ Changed runtime JS files considered: +-(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(0%) toolbox/messages/messages.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only --(0%) toolbox/text-to-speech/text2speech.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only +(100%) none changed - no changed runtime JS files @@ -13,2 +12 @@ Guardrail warnings: +-(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file missing from coverage; advisory only -(0%) toolbox/messages/messages.js - WARNING: changed runtime JS file missing from coverage; advisory only --(0%) toolbox/text-to-speech/text2speech.js - WARNING: changed runtime JS file missing from coverage; advisory only +(100%) none changed - no changed runtime JS files -diff --git a/docs_build/dev/reports/dependency_gating_report.md b/docs_build/dev/reports/dependency_gating_report.md -index 319848da9..3c18f9166 100644 ---- a/docs_build/dev/reports/dependency_gating_report.md -+++ b/docs_build/dev/reports/dependency_gating_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.215Z -+Generated: 2026-06-20T22:58:20.683Z -diff --git a/docs_build/dev/reports/dependency_hydration_reuse_report.md b/docs_build/dev/reports/dependency_hydration_reuse_report.md -index 0f8c874e3..88a4c3471 100644 ---- a/docs_build/dev/reports/dependency_hydration_reuse_report.md -+++ b/docs_build/dev/reports/dependency_hydration_reuse_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.216Z -+Generated: 2026-06-20T22:58:20.684Z -diff --git a/docs_build/dev/reports/execution_graph_reuse_report.md b/docs_build/dev/reports/execution_graph_reuse_report.md -index ddbdf90a1..977ef5c81 100644 ---- a/docs_build/dev/reports/execution_graph_reuse_report.md -+++ b/docs_build/dev/reports/execution_graph_reuse_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.216Z -+Generated: 2026-06-20T22:58:20.684Z -@@ -19 +19 @@ Prevented targeted scheduling work: 0 --| workspace-contract | INVALIDATED | INVALIDATED | 51dc9d019b71a923 | Lane snapshot is part of the selected targeted execution graph. | -+| workspace-contract | INVALIDATED | INVALIDATED | 744556e5d675e213 | Lane snapshot is part of the selected targeted execution graph. | -diff --git a/docs_build/dev/reports/failure_fingerprint_report.md b/docs_build/dev/reports/failure_fingerprint_report.md -index c9822fe4a..5e9437dba 100644 ---- a/docs_build/dev/reports/failure_fingerprint_report.md -+++ b/docs_build/dev/reports/failure_fingerprint_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:11:18.106Z -+Generated: 2026-06-20T22:59:39.176Z -diff --git a/docs_build/dev/reports/filesystem_scan_reduction_report.md b/docs_build/dev/reports/filesystem_scan_reduction_report.md -index 3fc7accce..4164a7dd3 100644 ---- a/docs_build/dev/reports/filesystem_scan_reduction_report.md -+++ b/docs_build/dev/reports/filesystem_scan_reduction_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.181Z -+Generated: 2026-06-20T22:58:20.655Z -diff --git a/docs_build/dev/reports/incremental_validation_report.md b/docs_build/dev/reports/incremental_validation_report.md -index d4aa9a02b..efee248fd 100644 ---- a/docs_build/dev/reports/incremental_validation_report.md -+++ b/docs_build/dev/reports/incremental_validation_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.218Z -+Generated: 2026-06-20T22:58:20.685Z -diff --git a/docs_build/dev/reports/lane_compilation_report.md b/docs_build/dev/reports/lane_compilation_report.md -index 1b65b72a4..9e3895ceb 100644 ---- a/docs_build/dev/reports/lane_compilation_report.md -+++ b/docs_build/dev/reports/lane_compilation_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.215Z -+Generated: 2026-06-20T22:58:20.683Z -diff --git a/docs_build/dev/reports/lane_deduplication_report.md b/docs_build/dev/reports/lane_deduplication_report.md -index ca9adb0a3..5b55db348 100644 ---- a/docs_build/dev/reports/lane_deduplication_report.md -+++ b/docs_build/dev/reports/lane_deduplication_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.215Z -+Generated: 2026-06-20T22:58:20.683Z -diff --git a/docs_build/dev/reports/lane_input_validation_report.md b/docs_build/dev/reports/lane_input_validation_report.md -index 0c7546c68..998c71c08 100644 ---- a/docs_build/dev/reports/lane_input_validation_report.md -+++ b/docs_build/dev/reports/lane_input_validation_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.218Z -+Generated: 2026-06-20T22:58:20.685Z -diff --git a/docs_build/dev/reports/lane_manifests/workspace-contract.json b/docs_build/dev/reports/lane_manifests/workspace-contract.json -index fb867147c..9d5178fda 100644 ---- a/docs_build/dev/reports/lane_manifests/workspace-contract.json -+++ b/docs_build/dev/reports/lane_manifests/workspace-contract.json -@@ -14 +14 @@ -- "tests/playwright/tools/RootToolsFutureState.spec.mjs": "032c57abb5289a23" -+ "tests/playwright/tools/RootToolsFutureState.spec.mjs": "7094370f23d8085e" -@@ -33 +33 @@ -- "inputHash": "5145d05e2885e902", -+ "inputHash": "63bec7503b20624b", -@@ -36 +36 @@ -- "manifestHash": "d98d3245976c92f4", -+ "manifestHash": "1291241716cd7ab6", -@@ -42 +42 @@ -- "generatedAt": "2026-06-21T00:10:08.792Z", -+ "generatedAt": "2026-06-20T22:58:19.300Z", -diff --git a/docs_build/dev/reports/lane_runtime_optimization_report.md b/docs_build/dev/reports/lane_runtime_optimization_report.md -index a8d52d441..8667df0bd 100644 ---- a/docs_build/dev/reports/lane_runtime_optimization_report.md -+++ b/docs_build/dev/reports/lane_runtime_optimization_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.215Z -+Generated: 2026-06-20T22:58:20.683Z -diff --git a/docs_build/dev/reports/lane_snapshot_report.md b/docs_build/dev/reports/lane_snapshot_report.md -index 5037cf0b8..190ba8c41 100644 ---- a/docs_build/dev/reports/lane_snapshot_report.md -+++ b/docs_build/dev/reports/lane_snapshot_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.216Z -+Generated: 2026-06-20T22:58:20.684Z -@@ -20 +20 @@ Prevented manifest traversal: 0 --| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_snapshots/workspace-contract.json | d98d3245976c92f4 | 53e56ebae6e84541 | 7d3db838f9f780e0 | 6c4fac7630b0b6f3 | e5ac9cbc103c3984 | 51dc9d019b71a923 | 13ffd9b4d27b4d42 | Lane snapshot executionGraphHash changed for workspace-contract.; Lane snapshot inputHash changed for workspace-contract.; Lane snapshot manifestHash changed for workspace-contract.; Lane snapshot snapshotHash changed for workspace-contract.; Lane snapshot warmStartHash changed for workspace-contract. | -+| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_snapshots/workspace-contract.json | 1291241716cd7ab6 | 53e56ebae6e84541 | 7d3db838f9f780e0 | 6c4fac7630b0b6f3 | e5ac9cbc103c3984 | 744556e5d675e213 | 7135cba84942c652 | Lane snapshot executionGraphHash changed for workspace-contract.; Lane snapshot inputHash changed for workspace-contract.; Lane snapshot manifestHash changed for workspace-contract.; Lane snapshot snapshotHash changed for workspace-contract.; Lane snapshot warmStartHash changed for workspace-contract. | -diff --git a/docs_build/dev/reports/lane_snapshots/workspace-contract.json b/docs_build/dev/reports/lane_snapshots/workspace-contract.json -index e0fe4f05f..ac4f12de1 100644 ---- a/docs_build/dev/reports/lane_snapshots/workspace-contract.json -+++ b/docs_build/dev/reports/lane_snapshots/workspace-contract.json -@@ -29 +29 @@ -- "executionGraphHash": "51dc9d019b71a923", -+ "executionGraphHash": "744556e5d675e213", -@@ -50 +50 @@ -- "inputHash": "5145d05e2885e902", -+ "inputHash": "63bec7503b20624b", -@@ -64 +64 @@ -- "tests/playwright/tools/RootToolsFutureState.spec.mjs": "032c57abb5289a23" -+ "tests/playwright/tools/RootToolsFutureState.spec.mjs": "7094370f23d8085e" -@@ -83 +83 @@ -- "manifestHash": "d98d3245976c92f4", -+ "manifestHash": "1291241716cd7ab6", -@@ -90 +90 @@ -- "manifestHash": "d98d3245976c92f4", -+ "manifestHash": "1291241716cd7ab6", -@@ -117 +117 @@ -- "snapshotHash": "13ffd9b4d27b4d42", -+ "snapshotHash": "7135cba84942c652", -@@ -119,2 +119,2 @@ -- "warmStartHash": "f32a5831d6b39914", -- "generatedAt": "2026-06-21T00:10:10.207Z" -+ "warmStartHash": "fe298d7913eac5ca", -+ "generatedAt": "2026-06-20T22:58:20.677Z" -diff --git a/docs_build/dev/reports/lane_warm_start_report.md b/docs_build/dev/reports/lane_warm_start_report.md -index d06a40a33..806f7dbc5 100644 ---- a/docs_build/dev/reports/lane_warm_start_report.md -+++ b/docs_build/dev/reports/lane_warm_start_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.216Z -+Generated: 2026-06-20T22:58:20.684Z -@@ -20 +20 @@ Prevented lane graph assembly: 0 --| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_warm_starts/workspace-contract.json | d98d3245976c92f4 | f32a5831d6b39914 | 355ba7a85dbb3cdb | Warm-start inputHash changed for workspace-contract.; Warm-start manifestHash changed for workspace-contract.; Warm-start warmStartHash changed for workspace-contract. | -+| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_warm_starts/workspace-contract.json | 1291241716cd7ab6 | fe298d7913eac5ca | 355ba7a85dbb3cdb | Warm-start inputHash changed for workspace-contract.; Warm-start manifestHash changed for workspace-contract.; Warm-start warmStartHash changed for workspace-contract. | -diff --git a/docs_build/dev/reports/lane_warm_starts/workspace-contract.json b/docs_build/dev/reports/lane_warm_starts/workspace-contract.json -index 3c29e890d..fc55da06b 100644 ---- a/docs_build/dev/reports/lane_warm_starts/workspace-contract.json -+++ b/docs_build/dev/reports/lane_warm_starts/workspace-contract.json -@@ -42 +42 @@ -- "inputHash": "5145d05e2885e902", -+ "inputHash": "63bec7503b20624b", -@@ -46 +46 @@ -- "manifestHash": "d98d3245976c92f4", -+ "manifestHash": "1291241716cd7ab6", -@@ -48 +48 @@ -- "warmStartHash": "f32a5831d6b39914", -+ "warmStartHash": "fe298d7913eac5ca", -@@ -50 +50 @@ -- "generatedAt": "2026-06-21T00:10:08.802Z", -+ "generatedAt": "2026-06-20T22:58:19.312Z", -diff --git a/docs_build/dev/reports/monolith_trigger_removal_report.md b/docs_build/dev/reports/monolith_trigger_removal_report.md -index 97a55c340..7141f5d34 100644 ---- a/docs_build/dev/reports/monolith_trigger_removal_report.md -+++ b/docs_build/dev/reports/monolith_trigger_removal_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:11:18.108Z -+Generated: 2026-06-20T22:59:39.177Z -diff --git a/docs_build/dev/reports/persistent_lane_manifest_report.md b/docs_build/dev/reports/persistent_lane_manifest_report.md -index f3f131d4a..f4baf21cb 100644 ---- a/docs_build/dev/reports/persistent_lane_manifest_report.md -+++ b/docs_build/dev/reports/persistent_lane_manifest_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.218Z -+Generated: 2026-06-20T22:58:20.685Z -@@ -18 +18 @@ Prevented discovery scans: 0 --| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_manifests/workspace-contract.json | 5145d05e2885e902 | d98d3245976c92f4 | Persistent manifest input hash changed for workspace-contract.; Persistent manifest hash changed for workspace-contract. | -+| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_manifests/workspace-contract.json | 63bec7503b20624b | 1291241716cd7ab6 | Persistent manifest input hash changed for workspace-contract.; Persistent manifest hash changed for workspace-contract. | -@@ -24 +24 @@ Prevented discovery scans: 0 --| workspace-contract | tools | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | 53e56ebae6e84541 | d98d3245976c92f4 | -+| workspace-contract | tools | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | 53e56ebae6e84541 | 1291241716cd7ab6 | -diff --git a/docs_build/dev/reports/playwright_discovery_ownership_report.md b/docs_build/dev/reports/playwright_discovery_ownership_report.md -index a5a1b3dea..977875caf 100644 ---- a/docs_build/dev/reports/playwright_discovery_ownership_report.md -+++ b/docs_build/dev/reports/playwright_discovery_ownership_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.169Z -+Generated: 2026-06-20T22:58:20.646Z -diff --git a/docs_build/dev/reports/playwright_discovery_scope_report.md b/docs_build/dev/reports/playwright_discovery_scope_report.md -index 7ba7a7a0e..69c83d957 100644 ---- a/docs_build/dev/reports/playwright_discovery_scope_report.md -+++ b/docs_build/dev/reports/playwright_discovery_scope_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.176Z -+Generated: 2026-06-20T22:58:20.652Z -diff --git a/docs_build/dev/reports/playwright_structure_audit.md b/docs_build/dev/reports/playwright_structure_audit.md -index dc6386691..392a08ee3 100644 ---- a/docs_build/dev/reports/playwright_structure_audit.md -+++ b/docs_build/dev/reports/playwright_structure_audit.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.141Z -+Generated: 2026-06-20T22:58:20.624Z diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt -index e8623a06d..36e3b18b0 100644 +index 94d5e709f..36e3b18b0 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt @@ -15 +15 @@ Exercised tool entry points detected: --(71%) Toolbox Index - exercised 10 runtime JS files +-(70%) Toolbox Index - exercised 3 runtime JS files +(46%) Toolbox Index - exercised 1 runtime JS files @@ -17 +17 @@ Exercised tool entry points detected: --(61%) Theme V2 Shared JS - exercised 7 runtime JS files +-(72%) Theme V2 Shared JS - exercised 1 runtime JS files +(75%) Theme V2 Shared JS - exercised 3 runtime JS files @@ -20,2 +20 @@ Changed runtime JS files covered: +-(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(0%) toolbox/messages/messages.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only --(0%) toolbox/text-to-speech/text2speech.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only +(100%) none changed - no changed runtime JS files -@@ -24,11 +23,2 @@ Files with executed line/function counts where available: --(14%) assets/theme-v2/js/account-auth-service.js - executed lines 64/64; executed functions 1/7 --(14%) assets/theme-v2/js/admin-setup-actions.js - executed lines 55/55; executed functions 1/7 --(18%) assets/theme-v2/js/account-page-data.js - executed lines 150/150; executed functions 3/17 --(20%) assets/theme-v2/js/admin-owner-navigation.js - executed lines 58/58; executed functions 2/10 --(25%) src/api/admin-owner-navigation.js - executed lines 42/42; executed functions 1/4 +@@ -24,4 +23,3 @@ Files with executed line/function counts where available: -(25%) src/api/session-api-client.js - executed lines 68/68; executed functions 3/12 --(29%) src/engine/input/NormalizedInputRegistry.js - executed lines 341/341; executed functions 6/21 --(33%) src/api/admin-setup-api-client.js - executed lines 13/13; executed functions 1/3 -(33%) src/api/toolbox-votes-api-client.js - executed lines 46/46; executed functions 2/6 --(56%) toolbox/colors/colors.js - executed lines 1848/1848; executed functions 115/204 --(58%) src/api/server-api-client.js - executed lines 167/167; executed functions 11/19 +-(44%) src/api/server-api-client.js - executed lines 167/167; executed functions 8/18 +-(48%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 11/23 +(36%) src/api/server-api-client.js - executed lines 167/167; executed functions 5/14 +(46%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 12/26 -@@ -36 +25,0 @@ Files with executed line/function counts where available: --(64%) toolbox/controls/controls.js - executed lines 610/610; executed functions 36/56 -@@ -38,3 +26,0 @@ Files with executed line/function counts where available: ++(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 204/204; executed functions 9/14 +@@ -29,4 +27,3 @@ Files with executed line/function counts where available: -(67%) src/api/game-journey-completion-api-client.js - executed lines 15/15; executed functions 2/3 -(67%) toolbox/game-workspace/game-workspace-api-client.js - executed lines 20/20; executed functions 2/3 --(71%) toolbox/game-workspace/game-workspace.js - executed lines 472/472; executed functions 32/45 -@@ -42,8 +28,2 @@ Files with executed line/function counts where available: --(89%) toolbox/tools-page-accordions.js - executed lines 1156/1156; executed functions 105/118 --(90%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 26/29 --(91%) toolbox/game-design/game-design.js - executed lines 254/254; executed functions 21/23 --(94%) assets/theme-v2/js/marketplace-page.js - executed lines 170/170; executed functions 16/17 --(100%) src/api/marketplace-api-client.js - executed lines 16/16; executed functions 3/3 --(100%) toolbox/colors/palette-api-client.js - executed lines 28/28; executed functions 4/4 --(100%) toolbox/controls/controls-api-client.js - executed lines 33/33; executed functions 5/5 --(100%) toolbox/game-design/game-design-api-client.js - executed lines 13/13; executed functions 2/2 +-(72%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 977/977; executed functions 65/90 +-(74%) toolbox/tools-page-accordions.js - executed lines 1156/1156; executed functions 81/109 ++(74%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 977/977; executed functions 67/90 +(80%) src/api/admin-owner-navigation.js - executed lines 42/42; executed functions 4/5 +(91%) assets/theme-v2/js/admin-owner-navigation.js - executed lines 58/58; executed functions 10/11 -@@ -52,2 +32 @@ Uncovered or low-coverage changed JS files: +@@ -35,2 +32 @@ Uncovered or low-coverage changed JS files: +-(0%) src/shared/toolbox/tool-metadata-inventory.js - WARNING: uncovered changed runtime JS file; advisory only -(0%) toolbox/messages/messages.js - WARNING: uncovered changed runtime JS file; advisory only --(0%) toolbox/text-to-speech/text2speech.js - WARNING: uncovered changed runtime JS file; advisory only +(100%) none changed - no changed runtime JS files -@@ -56,5 +35 @@ Changed JS files considered: +@@ -39,6 +35 @@ Changed JS files considered: +-(0%) src/shared/toolbox/tool-metadata-inventory.js - changed JS file not collected as browser runtime coverage -(0%) tests/playwright/tools/MessagesTool.spec.mjs - changed JS file not collected as browser runtime coverage -(0%) tests/playwright/tools/RootToolsFutureState.spec.mjs - changed JS file not collected as browser runtime coverage -(0%) tests/playwright/tools/TextToSpeechFunctional.spec.mjs - changed JS file not collected as browser runtime coverage +-(0%) tests/playwright/tools/ToolboxRoutePages.spec.mjs - changed JS file not collected as browser runtime coverage -(0%) toolbox/messages/messages.js - changed JS file not collected as browser runtime coverage --(0%) toolbox/text-to-speech/text2speech.js - changed JS file not collected as browser runtime coverage +(0%) tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - changed JS file not collected as browser runtime coverage -diff --git a/docs_build/dev/reports/retry_suppression_report.md b/docs_build/dev/reports/retry_suppression_report.md -index 5391d7a38..b85b2ce3f 100644 ---- a/docs_build/dev/reports/retry_suppression_report.md -+++ b/docs_build/dev/reports/retry_suppression_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:11:18.107Z -+Generated: 2026-06-20T22:59:39.176Z -diff --git a/docs_build/dev/reports/slow_path_pruning_report.md b/docs_build/dev/reports/slow_path_pruning_report.md -index 0fc071745..4aa32ba0e 100644 ---- a/docs_build/dev/reports/slow_path_pruning_report.md -+++ b/docs_build/dev/reports/slow_path_pruning_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:11:18.108Z -+Generated: 2026-06-20T22:59:39.177Z -@@ -10 +10 @@ PR_26146_038 measured lane elapsed time: 169.71s --Current measured lane elapsed time: 67.79s -+Current measured lane elapsed time: 78.45s -@@ -34,5 +34,5 @@ Validation cache hits: 18 --| current targeted run | workspace-contract | 15.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | --| current targeted run | workspace-contract | 14.70s | tests\playwright\tools\RootToolsFutureState.spec.mjs:664:1 > representative active tool pages align center cleanup and registry group colors | --| current targeted run | workspace-contract | 13.10s | tests\playwright\tools\RootToolsFutureState.spec.mjs:562:1 > learn wireframe pages load with shared Theme V2 structure | --| current targeted run | workspace-contract | 11.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:482:1 > common header renders primary navigation order across active pages | --| current targeted run | workspace-contract | 2.30s | tests\playwright\tools\RootToolsFutureState.spec.mjs:641:1 > tool template future-state page loads from root Theme V2 paths | -+| current targeted run | workspace-contract | 18.90s | tests\playwright\tools\RootToolsFutureState.spec.mjs:667:1 > representative active tool pages align center cleanup and registry group colors | -+| current targeted run | workspace-contract | 17.80s | tests\playwright\tools\RootToolsFutureState.spec.mjs:565:1 > learn wireframe pages load with shared Theme V2 structure | -+| current targeted run | workspace-contract | 14.40s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | -+| current targeted run | workspace-contract | 10.50s | tests\playwright\tools\RootToolsFutureState.spec.mjs:485:1 > common header renders primary navigation order across active pages | -+| current targeted run | workspace-contract | 2.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:644:1 > tool template future-state page loads from root Theme V2 paths | -diff --git a/docs_build/dev/reports/static_validation_report.md b/docs_build/dev/reports/static_validation_report.md -index ef23805b9..3752d284f 100644 ---- a/docs_build/dev/reports/static_validation_report.md -+++ b/docs_build/dev/reports/static_validation_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.202Z -+Generated: 2026-06-20T22:58:20.674Z -@@ -25 +25 @@ Reason: No deterministic static validation failure was found. --| targeted file manifests | PASS | workspace-contract:d98d3245976c92f4 | -+| targeted file manifests | PASS | workspace-contract:1291241716cd7ab6 | -diff --git a/docs_build/dev/reports/targeted_file_manifest_report.md b/docs_build/dev/reports/targeted_file_manifest_report.md -index fd6fbb064..d06980143 100644 ---- a/docs_build/dev/reports/targeted_file_manifest_report.md -+++ b/docs_build/dev/reports/targeted_file_manifest_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.217Z -+Generated: 2026-06-20T22:58:20.685Z -@@ -10 +10 @@ Status: PASS --| workspace-contract | tools | PASS | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | src/dev-runtime/admin/admin-notes-directory.mjs; src/dev-runtime/admin/admin-notes-menu.mjs; src/dev-runtime/persistence/mock-db-store.js; src/dev-runtime/server/local-api-router.mjs; tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | 53e56ebae6e84541 | d98d3245976c92f4 | Manifest ownership, helpers, fixtures, imports, and command targets are deterministic before runtime. | -+| workspace-contract | tools | PASS | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | src/dev-runtime/admin/admin-notes-directory.mjs; src/dev-runtime/admin/admin-notes-menu.mjs; src/dev-runtime/persistence/mock-db-store.js; src/dev-runtime/server/local-api-router.mjs; tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | 53e56ebae6e84541 | 1291241716cd7ab6 | Manifest ownership, helpers, fixtures, imports, and command targets are deterministic before runtime. | -diff --git a/docs_build/dev/reports/test_cleanup_performance_report.md b/docs_build/dev/reports/test_cleanup_performance_report.md -index db3a814e7..3aa560665 100644 ---- a/docs_build/dev/reports/test_cleanup_performance_report.md -+++ b/docs_build/dev/reports/test_cleanup_performance_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:11:18.107Z -+Generated: 2026-06-20T22:59:39.177Z -@@ -8 +8 @@ Status: PASS --Total measured lane elapsed time: 67.79s -+Total measured lane elapsed time: 78.45s -@@ -26 +26 @@ Prevented redundant dependency traversal: 0 --| workspace-contract | PASS | 67.79s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | -+| workspace-contract | PASS | 78.45s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | -@@ -46,5 +46,5 @@ Prevented redundant dependency traversal: 0 --| workspace-contract | 15.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | --| workspace-contract | 14.70s | tests\playwright\tools\RootToolsFutureState.spec.mjs:664:1 > representative active tool pages align center cleanup and registry group colors | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | --| workspace-contract | 13.10s | tests\playwright\tools\RootToolsFutureState.spec.mjs:562:1 > learn wireframe pages load with shared Theme V2 structure | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | --| workspace-contract | 11.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:482:1 > common header renders primary navigation order across active pages | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | --| workspace-contract | 2.30s | tests\playwright\tools\RootToolsFutureState.spec.mjs:641:1 > tool template future-state page loads from root Theme V2 paths | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | -+| workspace-contract | 18.90s | tests\playwright\tools\RootToolsFutureState.spec.mjs:667:1 > representative active tool pages align center cleanup and registry group colors | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | -+| workspace-contract | 17.80s | tests\playwright\tools\RootToolsFutureState.spec.mjs:565:1 > learn wireframe pages load with shared Theme V2 structure | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | -+| workspace-contract | 14.40s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | -+| workspace-contract | 10.50s | tests\playwright\tools\RootToolsFutureState.spec.mjs:485:1 > common header renders primary navigation order across active pages | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | -+| workspace-contract | 2.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:644:1 > tool template future-state page loads from root Theme V2 paths | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | -diff --git a/docs_build/dev/reports/test_cleanup_routing_report.md b/docs_build/dev/reports/test_cleanup_routing_report.md -index ed7ec4550..51d699234 100644 ---- a/docs_build/dev/reports/test_cleanup_routing_report.md -+++ b/docs_build/dev/reports/test_cleanup_routing_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:11:18.108Z -+Generated: 2026-06-20T22:59:39.177Z -diff --git a/docs_build/dev/reports/testing_lane_execution_report.md b/docs_build/dev/reports/testing_lane_execution_report.md -index c9cbce468..7713de7a1 100644 ---- a/docs_build/dev/reports/testing_lane_execution_report.md -+++ b/docs_build/dev/reports/testing_lane_execution_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:11:18.159Z -+Generated: 2026-06-20T22:59:39.207Z -@@ -12 +12 @@ SKIP: 14 --Total lane elapsed time: 67.79s -+Total lane elapsed time: 78.45s -@@ -108 +108 @@ Prevented Workspace lane reruns: 0 --| workspace-contract | PASS | 67.79s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | Root tools future-state navigation and Tool Template V2 contract | repo-served root tools page; Tool Template V2 future-state page; Theme V2 shared partials and assets | -+| workspace-contract | PASS | 78.45s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | Root tools future-state navigation and Tool Template V2 contract | repo-served root tools page; Tool Template V2 future-state page; Theme V2 shared partials and assets | -@@ -128,5 +128,5 @@ Prevented Workspace lane reruns: 0 --| workspace-contract | 15.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | --| workspace-contract | 14.70s | tests\playwright\tools\RootToolsFutureState.spec.mjs:664:1 > representative active tool pages align center cleanup and registry group colors | --| workspace-contract | 13.10s | tests\playwright\tools\RootToolsFutureState.spec.mjs:562:1 > learn wireframe pages load with shared Theme V2 structure | --| workspace-contract | 11.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:482:1 > common header renders primary navigation order across active pages | --| workspace-contract | 2.30s | tests\playwright\tools\RootToolsFutureState.spec.mjs:641:1 > tool template future-state page loads from root Theme V2 paths | -+| workspace-contract | 18.90s | tests\playwright\tools\RootToolsFutureState.spec.mjs:667:1 > representative active tool pages align center cleanup and registry group colors | -+| workspace-contract | 17.80s | tests\playwright\tools\RootToolsFutureState.spec.mjs:565:1 > learn wireframe pages load with shared Theme V2 structure | -+| workspace-contract | 14.40s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | -+| workspace-contract | 10.50s | tests\playwright\tools\RootToolsFutureState.spec.mjs:485:1 > common header renders primary navigation order across active pages | -+| workspace-contract | 2.20s | tests\playwright\tools\RootToolsFutureState.spec.mjs:644:1 > tool template future-state page loads from root Theme V2 paths | -@@ -137 +137 @@ Prevented Workspace lane reruns: 0 --- PASS 67.79s C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list -+- PASS 78.44s C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 1\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list -diff --git a/docs_build/dev/reports/validation_cache_report.md b/docs_build/dev/reports/validation_cache_report.md -index 2afd84b03..e19c36558 100644 ---- a/docs_build/dev/reports/validation_cache_report.md -+++ b/docs_build/dev/reports/validation_cache_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.220Z -+Generated: 2026-06-20T22:58:20.687Z -@@ -18,3 +18,3 @@ Validations computed: 10 --| targeted file manifest validation | MISS | c5c4b7876c948959 | initial computation | lane definitions change; fixture ownership changes; helper/import graph changes; targeted files change | --| lane warm-start validation | MISS | a00444576ec05d24 | initial computation | lane definitions change; targeted files change; ownership metadata changes; dependency graph changes; helper/fixture placement changes; lane configuration changes | --| structural ownership validation | MISS | f0b916009d8b7a2e | initial computation | fixture ownership changes; helper/import graph changes; targeted files change | -+| targeted file manifest validation | MISS | 070fd74956239d48 | initial computation | lane definitions change; fixture ownership changes; helper/import graph changes; targeted files change | -+| lane warm-start validation | MISS | 6f24967ea6249f42 | initial computation | lane definitions change; targeted files change; ownership metadata changes; dependency graph changes; helper/fixture placement changes; lane configuration changes | -+| structural ownership validation | MISS | ce9fb480b7ef245b | initial computation | fixture ownership changes; helper/import graph changes; targeted files change | -@@ -24 +24 @@ Validations computed: 10 --| lane snapshot validation | MISS | 7ebcc49fd0ce56c8 | initial computation | targeted files change; dependency graph changes; helper/fixture ownership changes; lane configuration changes; runtime configuration changes | -+| lane snapshot validation | MISS | 5d2cdac11ae1ca38 | initial computation | targeted files change; dependency graph changes; helper/fixture ownership changes; lane configuration changes; runtime configuration changes | -@@ -26,2 +26,2 @@ Validations computed: 10 --| structural ownership validation | HIT | f0b916009d8b7a2e | static validation report | unchanged within execution cycle | --| structural ownership validation | HIT | f0b916009d8b7a2e | zero-browser preflight report | unchanged within execution cycle | -+| structural ownership validation | HIT | ce9fb480b7ef245b | static validation report | unchanged within execution cycle | -+| structural ownership validation | HIT | ce9fb480b7ef245b | zero-browser preflight report | unchanged within execution cycle | -@@ -30,8 +30,8 @@ Validations computed: 10 --| targeted file manifest validation | HIT | c5c4b7876c948959 | lane input validation report | unchanged within execution cycle | --| targeted file manifest validation | HIT | c5c4b7876c948959 | runtime scheduling blockers | unchanged within execution cycle | --| lane warm-start validation | HIT | a00444576ec05d24 | warm-start report | unchanged within execution cycle | --| lane warm-start validation | HIT | a00444576ec05d24 | dependency hydration reuse report | unchanged within execution cycle | --| lane warm-start validation | HIT | a00444576ec05d24 | runtime scheduling | unchanged within execution cycle | --| lane snapshot validation | HIT | 7ebcc49fd0ce56c8 | lane snapshot report | unchanged within execution cycle | --| lane snapshot validation | HIT | 7ebcc49fd0ce56c8 | execution graph reuse report | unchanged within execution cycle | --| lane snapshot validation | HIT | 7ebcc49fd0ce56c8 | runtime scheduling | unchanged within execution cycle | -+| targeted file manifest validation | HIT | 070fd74956239d48 | lane input validation report | unchanged within execution cycle | -+| targeted file manifest validation | HIT | 070fd74956239d48 | runtime scheduling blockers | unchanged within execution cycle | -+| lane warm-start validation | HIT | 6f24967ea6249f42 | warm-start report | unchanged within execution cycle | -+| lane warm-start validation | HIT | 6f24967ea6249f42 | dependency hydration reuse report | unchanged within execution cycle | -+| lane warm-start validation | HIT | 6f24967ea6249f42 | runtime scheduling | unchanged within execution cycle | -+| lane snapshot validation | HIT | 5d2cdac11ae1ca38 | lane snapshot report | unchanged within execution cycle | -+| lane snapshot validation | HIT | 5d2cdac11ae1ca38 | execution graph reuse report | unchanged within execution cycle | -+| lane snapshot validation | HIT | 5d2cdac11ae1ca38 | runtime scheduling | unchanged within execution cycle | -diff --git a/docs_build/dev/reports/zero_browser_preflight_report.md b/docs_build/dev/reports/zero_browser_preflight_report.md -index 6b9f2cec7..19b494b64 100644 ---- a/docs_build/dev/reports/zero_browser_preflight_report.md -+++ b/docs_build/dev/reports/zero_browser_preflight_report.md -@@ -3 +3 @@ --Generated: 2026-06-21T00:10:10.219Z -+Generated: 2026-06-20T22:58:20.686Z -diff --git a/src/dev-runtime/messages/messages-sqlite-service.mjs b/src/dev-runtime/messages/messages-sqlite-service.mjs -index 6d3894a85..d1a1cee33 100644 ---- a/src/dev-runtime/messages/messages-sqlite-service.mjs -+++ b/src/dev-runtime/messages/messages-sqlite-service.mjs -@@ -99,8 +98,0 @@ function normalizeNumber(value, fallback) { --function emotionSettingKey(value) { -- return normalizeText(value) -- .trim() -- .toLowerCase() -- .replace(/[^a-z0-9]+/g, "-") -- .replace(/^-+|-+$/g, "") || "neutral"; --} -- -@@ -193,13 +185 @@ function emotionProfileFromRow(row, usage = {}) { --function ttsEmotionSettingFromEmotionProfile(profile) { -- return { -- active: profile.active !== false, -- emotion: emotionSettingKey(profile.name), -- emotionLabel: profile.name, -- pitch: Number(profile.pitch), -- rate: Number(profile.rate), -- ssmlLikePreset: "normal", -- volume: Number(profile.volume), -- }; --} -- --function ttsProfileFromRow(row, emotionSettings = []) { -+function ttsProfileFromRow(row) { -@@ -211 +190,0 @@ function ttsProfileFromRow(row, emotionSettings = []) { -- emotionSettings, -@@ -617,3 +595,0 @@ export class MessagesSqliteService { -- const emotionSettings = this.listEmotionProfiles() -- .filter((profile) => profile.active !== false) -- .map(ttsEmotionSettingFromEmotionProfile); -@@ -623 +599 @@ export class MessagesSqliteService { -- `).all().map((row) => ttsProfileFromRow(row, emotionSettings)); -+ `).all().map(ttsProfileFromRow); -@@ -631,4 +607 @@ export class MessagesSqliteService { -- const emotionSettings = this.listEmotionProfiles() -- .filter((profile) => profile.active !== false) -- .map(ttsEmotionSettingFromEmotionProfile); -- return ttsProfileFromRow(row, emotionSettings); -+ return ttsProfileFromRow(row); diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs index e19b4da0b..b26a01036 100644 --- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -1570,1157 +855,3 @@ index e19b4da0b..b26a01036 100644 + expect(pageSource).not.toContain("No active failure is declared"); + expect(pageSource).not.toContain("assets/theme-v2/js/admin-system-health.js"); + expect(pageSource).toContain("assets/theme-v2/js/admin-owner-navigation.js"); -diff --git a/tests/playwright/tools/MessagesTool.spec.mjs b/tests/playwright/tools/MessagesTool.spec.mjs -index 6c00fa6f5..5c2e1a212 100644 ---- a/tests/playwright/tools/MessagesTool.spec.mjs -+++ b/tests/playwright/tools/MessagesTool.spec.mjs -@@ -147,0 +148 @@ async function addPart(page, values) { -+ await page.locator("[data-messages-segment-editor='__new__'] [data-segment-order]").fill(String(values.order)); -@@ -153,4 +153,0 @@ async function addPart(page, values) { --async function openMessageParts(page, messageName) { -- await page.locator("[data-messages-row]").filter({ hasText: messageName }).locator("td").nth(1).click(); --} -- -@@ -199,0 +197,2 @@ test("Message Studio renders Messages with child Message Parts and plays ordered -+ await expect(page.locator("[data-messages-segment-host]")).toBeVisible(); -+ await messageNameCell.click(); -@@ -201 +199,0 @@ test("Message Studio renders Messages with child Message Parts and plays ordered -- await expect(messageNameCell).toHaveAttribute("aria-expanded", "false"); -@@ -203,3 +200,0 @@ test("Message Studio renders Messages with child Message Parts and plays ordered -- await expect(page.locator("[data-messages-segment-host]")).toBeVisible(); -- await expect(messageNameCell).toHaveAttribute("aria-expanded", "true"); -- await messageRow.locator("td").nth(2).click(); -@@ -210,7 +205,3 @@ test("Message Studio renders Messages with child Message Parts and plays ordered -- 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: "Emotion" })).toBeVisible(); -- await expect(partsTable.getByRole("columnheader", { exact: true, name: "TTS Profile" })).toBeVisible(); -- await expect(partsTable.getByRole("columnheader", { name: "Status" })).toBeVisible(); -- await expect(partsTable.getByRole("columnheader", { name: "Actions" })).toBeVisible(); -+ await expect(page.getByRole("columnheader", { name: "Order" })).toBeVisible(); -+ await expect(page.getByRole("columnheader", { name: "Text" })).toBeVisible(); -+ await expect(page.getByRole("columnheader", { exact: true, name: "TTS Profile" })).toBeVisible(); -@@ -222,0 +214 @@ test("Message Studio renders Messages with child Message Parts and plays ordered -+ await page.locator("[data-messages-segment-editor='__new__'] [data-segment-order]").fill(""); -@@ -226 +218 @@ test("Message Studio renders Messages with child Message Parts and plays ordered -- await expect(page.locator("[data-messages-validation-errors]")).not.toContainText("Display Order is required."); -+ await expect(page.locator("[data-messages-validation-errors]")).toContainText("Display Order is required."); -@@ -243,11 +234,0 @@ test("Message Studio renders Messages with child Message Parts and plays ordered -- const ttsProfilesResult = await jsonRequest(`${failures.server.baseUrl}/api/messages/tts-profiles`); -- expect(ttsProfilesResult.response.ok).toBe(true); -- expect(ttsProfilesResult.payload.data.ttsProfiles[0].emotionSettings).toEqual(expect.arrayContaining([ -- expect.objectContaining({ -- emotion: "urgent", -- emotionLabel: "Urgent", -- pitch: 1.08, -- rate: 1.15, -- volume: 1, -- }), -- ])); -@@ -258,13 +239,3 @@ test("Message Studio renders Messages with child Message Parts and plays ordered -- expect(speechCalls.slice(-2)).toEqual([ -- expect.objectContaining({ -- pitch: 1, -- rate: 1, -- text: "Bats drop from the rafters.", -- volume: 1, -- }), -- expect.objectContaining({ -- pitch: 1.08, -- rate: 1.15, -- text: "Keep your torch high.", -- volume: 1, -- }), -+ expect(speechCalls.slice(-2).map((call) => call.text)).toEqual([ -+ "Bats drop from the rafters.", -+ "Keep your torch high.", -@@ -286,2 +256,0 @@ test("Message Studio renders Messages with child Message Parts and plays ordered -- pitch: 1.08, -- rate: 1.15, -@@ -291 +259,0 @@ test("Message Studio renders Messages with child Message Parts and plays ordered -- volume: 1, -@@ -351 +318,0 @@ test("Message Studio shows actionable playback error when audio engine is unavai -- await openMessageParts(page, "Bat Encounter"); -diff --git a/tests/playwright/tools/RootToolsFutureState.spec.mjs b/tests/playwright/tools/RootToolsFutureState.spec.mjs -index ba97f5c6a..1f043323e 100644 ---- a/tests/playwright/tools/RootToolsFutureState.spec.mjs -+++ b/tests/playwright/tools/RootToolsFutureState.spec.mjs -@@ -280 +280 @@ test("root tools surface links current tool pages without old_* routes", async ( -- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/44"); -+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43"); -@@ -314 +314,4 @@ test("root tools surface links current tool pages without old_* routes", async ( -- 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-workspace/index.html"); -@@ -316 +319 @@ test("root tools surface links current tool pages without old_* routes", async ( -- 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"]); -@@ -322 +325 @@ test("root tools surface links current tool pages without old_* routes", async ( -- 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"]); -@@ -392 +395 @@ test("root tools surface links current tool pages without old_* routes", async ( -- expect(guestGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression", "Publish"]); -+ expect(guestGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression"]); -@@ -418 +421 @@ test("root tools surface links current tool pages without old_* routes", async ( -- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/44"); -+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43"); -@@ -424 +427 @@ test("root tools surface links current tool pages without old_* routes", async ( -- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/47"); -+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/46"); -@@ -435 +438 @@ test("root tools surface links current tool pages without old_* routes", async ( -- has: page.locator("[data-toolbox-tool-name-link='Game Hub'][href='/toolbox/game-workspace/index.html']") -+ has: page.locator("h3", { hasText: /^Game Hub$/ }) -@@ -461 +464 @@ test("root tools surface links current tool pages without old_* routes", async ( -- expect(adminGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression", "Publish"]); -+ expect(adminGroupLabels).toEqual(["Idea", "Design", "Graphics", "Audio", "Objects", "Interface", "Controls", "Progression"]); -@@ -471 +474 @@ test("root tools surface links current tool pages without old_* routes", async ( -- await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 16/44"); -+ await expect(page.locator("[data-tools-count]")).toHaveText("Tool Count: 15/43"); -diff --git a/tests/playwright/tools/TextToSpeechFunctional.spec.mjs b/tests/playwright/tools/TextToSpeechFunctional.spec.mjs -index c9a659a4d..fa1050126 100644 ---- a/tests/playwright/tools/TextToSpeechFunctional.spec.mjs -+++ b/tests/playwright/tools/TextToSpeechFunctional.spec.mjs -@@ -107,9 +107 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -- await expect(page.locator("[data-tts-summary]")).not.toContainText("TTS Studio"); -- await expect(page.locator("[data-tts-output-summary]")).toHaveCount(0); -- await expect(page.locator("[data-tts-queue-list]")).toHaveCount(0); -- await expect(page.locator("[data-tts-item-name]")).toHaveCount(0); -- await expect(page.getByText("Named Sentence", { exact: true })).toHaveCount(0); -- await expect(page.getByText("Output Summary", { exact: true })).toHaveCount(0); -- await expect(page.getByText("Voice Filters", { exact: true })).toHaveCount(0); -- await expect(page.getByText("Delivery", { exact: true })).toHaveCount(0); -- await expect(page.getByText("Presets", { exact: true })).toHaveCount(0); -+ await expect(page.locator("[data-tts-summary]")).toContainText("TTS Studio"); -@@ -116,0 +109 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -+ await expect(page.locator("[data-tts-voice-select]")).toContainText("Arcade Voice"); -@@ -123,8 +116,2 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -- 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); -- await defaultProfileRow.locator("td").nth(5).click(); -- await expect(page.locator("[data-tts-emotion-host]")).toHaveCount(0); -- await expect(defaultProfileRow.locator("[data-tts-profile-name-cell]")).toHaveAttribute("aria-expanded", "false"); -- await defaultProfileRow.locator("[data-tts-profile-name-cell]").click(); -- await expect(defaultProfileRow.locator("[data-tts-profile-name-cell]")).toHaveAttribute("aria-expanded", "true"); -+ await expect(page.locator("[data-tts-profile-row]").filter({ hasText: "Default Balanced Profile" }).getByRole("button", { name: "Delete" })).toBeDisabled(); -+ await page.locator("[data-tts-profile-row]").filter({ hasText: "Default Balanced Profile" }).click(); -@@ -139,2 +126 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -- await expect(page.locator("[data-tts-emotion-add-control-row]").getByRole("button", { name: "Add Emotion" })).toBeVisible(); -- await page.locator("[data-tts-profile-row]").filter({ hasText: "Man Profile 1" }).locator("[data-tts-profile-name-cell]").click(); -+ await page.locator("[data-tts-profile-row]").filter({ hasText: "Man Profile 1" }).click(); -@@ -146 +132 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -- await page.locator("[data-tts-profile-row]").filter({ hasText: "Woman Profile 2" }).locator("[data-tts-profile-name-cell]").click(); -+ await page.locator("[data-tts-profile-row]").filter({ hasText: "Woman Profile 2" }).click(); -@@ -153,2 +139 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -- await expect(page.locator("[data-tts-profile-add-control-row]").getByRole("button", { name: "Add Profile" })).toBeVisible(); -- await page.locator("[data-tts-profile-add-control-row]").getByRole("button", { name: "Add Profile" }).click(); -+ await page.getByRole("button", { name: "Add Profile" }).click(); -@@ -165,3 +150 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -- await expect(page.locator("[data-tts-emotion-add-control-row]").getByRole("button", { name: "Add Emotion" })).toBeVisible(); -- await page.locator("[data-tts-emotion-add-control-row]").getByRole("button", { name: "Add Emotion" }).click(); -- await expect(page.locator("[data-tts-emotion-add-control-row]")).toHaveCount(0); -+ await page.getByRole("button", { name: "Add Emotion" }).click(); -@@ -180 +163,2 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -- await expect(page.locator("[data-tts-emotion-row]").filter({ hasText: "Urgent" })).toContainText("Whisper-ish"); -+ await expect(page.locator("[data-tts-output-summary]")).toContainText("\"contractVersion\": \"tts-profile-emotion-v1\""); -+ await expect(page.locator("[data-tts-output-summary]")).toContainText("\"name\": \"Creature Profile Updated\""); -@@ -182,14 +166,8 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -- await expect(page.locator("[data-tts-gender-select]")).toHaveCount(0); -- await expect(page.locator("[data-tts-language-select]")).toHaveCount(0); -- await expect(page.locator("[data-tts-age-select]")).toHaveCount(0); -- await expect(page.locator("[data-tts-character-preset-select]")).toHaveCount(0); -- await expect(page.locator("[data-tts-ssml-preset-select]")).toHaveCount(0); -- await expect(page.locator("[data-tts-import-json]")).toHaveCount(0); -- await expect(page.locator("[data-tts-copy-json]")).toHaveCount(0); -- await expect(page.locator("[data-tts-export-json]")).toHaveCount(0); -- await page.locator("[data-tts-profile-row]").filter({ hasText: "Creature Profile Updated" }).getByRole("button", { name: "Edit Profile" }).click(); -- await expect(page.locator("[data-tts-profile-editor] [data-tts-profile-voice]")).toBeVisible(); -- await expect(page.locator("[data-tts-profile-editor] [data-tts-profile-language]")).toBeVisible(); -- await expect(page.locator("[data-tts-profile-editor] [data-tts-profile-gender]")).toBeVisible(); -- await expect(page.locator("[data-tts-profile-editor] [data-tts-profile-age]")).toBeVisible(); -- await page.locator("[data-tts-profile-editor] [data-tts-cancel-profile]").click(); -+ await expect(page.locator("[data-tts-gender-select]")).toBeVisible(); -+ await expect(page.locator("[data-tts-language-select]")).toBeVisible(); -+ await expect(page.locator("[data-tts-age-select]")).toBeVisible(); -+ await expect(page.locator("[data-tts-character-preset-select]")).toBeVisible(); -+ await expect(page.locator("[data-tts-ssml-preset-select]")).toBeVisible(); -+ await expect(page.locator("[data-tts-import-json]")).toBeEnabled(); -+ await expect(page.locator("[data-tts-copy-json]")).toBeEnabled(); -+ await expect(page.locator("[data-tts-export-json]")).toBeEnabled(); -@@ -197,0 +176,13 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -+ await page.locator("[data-tts-item-name]").fill("Wave intro"); -+ await page.locator("[data-tts-character-preset-select]").selectOption("dramatic"); -+ await page.locator("[data-tts-age-select]").selectOption("teen"); -+ await page.locator("[data-tts-ssml-preset-select]").selectOption("whisper-ish"); -+ await expect(page.locator("[data-tts-pitch-value]")).toHaveText("1.1"); -+ await expect(page.locator("[data-tts-volume-value]")).toHaveText("0.6"); -+ await page.locator("[data-tts-voice-select]").selectOption("arcade-voice-uri"); -+ await page.locator("[data-tts-rate]").fill("1.4"); -+ await page.locator("[data-tts-pitch]").fill("0.8"); -+ await page.locator("[data-tts-volume]").fill("0.55"); -+ await expect(page.locator("[data-tts-rate-value]")).toHaveText("1.4"); -+ await expect(page.locator("[data-tts-pitch-value]")).toHaveText("0.8"); -+ await expect(page.locator("[data-tts-volume-value]")).toHaveText("0.55"); -@@ -198,0 +190,7 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -+ await page.locator("[data-tts-add-item]").click(); -+ await expect(page.locator("[data-tts-queue-list]")).toContainText("Wave intro"); -+ await expect(page.locator("[data-tts-output-summary]")).toContainText("\"name\": \"Wave intro\""); -+ await page.locator("[data-tts-duplicate-item]").click(); -+ await expect(page.locator("[data-tts-queue-list]")).toContainText("Wave intro 2 copy"); -+ await page.locator("[data-tts-delete-item]").click(); -+ await expect(page.locator("[data-tts-queue-list] [data-tts-queue-item]")).toHaveCount(2); -@@ -206,2 +204,2 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -- pitch: 1.2, -- rate: 1.1, -+ pitch: 0.8, -+ rate: 1.4, -@@ -211 +209 @@ test("Text To Speech page loads and speaks through browser speech synthesis", as -- volume: 0.7, -+ volume: 0.55, -@@ -285 +283 @@ test("Text To Speech shows actionable error when browser speech synthesis is una -- await expect(page.locator("[data-tts-voice-select]")).toHaveCount(0); -+ await expect(page.locator("[data-tts-voice-select]")).toContainText("No browser voices available"); -diff --git a/toolbox/messages/messages.js b/toolbox/messages/messages.js -index 4af71adaa..c881cc0b4 100644 ---- a/toolbox/messages/messages.js -+++ b/toolbox/messages/messages.js -@@ -15,6 +14,0 @@ const DEFAULT_TTS_PROFILE_KEY = "__default-balanced-tts__"; --const DEFAULT_TTS_EMOTION_SETTINGS = Object.freeze([ -- Object.freeze({ active: true, emotion: "calm", emotionLabel: "Calm", pitch: 1, rate: 1, ssmlLikePreset: "normal", volume: 1 }), -- Object.freeze({ active: true, emotion: "urgent", emotionLabel: "Urgent", pitch: 1.08, rate: 1.15, ssmlLikePreset: "normal", volume: 1 }), -- Object.freeze({ active: true, emotion: "whisper", emotionLabel: "Whisper", pitch: 0.95, rate: 0.9, ssmlLikePreset: "normal", volume: 0.55 }), -- Object.freeze({ active: true, emotion: "angry", emotionLabel: "Angry", pitch: 0.98, rate: 1.1, ssmlLikePreset: "normal", volume: 1 }), --]); -@@ -24 +17,0 @@ const DEFAULT_TTS_PROFILE = Object.freeze({ -- emotionSettings: DEFAULT_TTS_EMOTION_SETTINGS, -@@ -231 +224 @@ function createSegmentAddControlRow() { -- const row = tableActionRow(5, createButton("Add Part", "messagesSegmentAddRow", state.selectedMessageKey)); -+ const row = tableActionRow(6, createButton("Add Part", "messagesSegmentAddRow", state.selectedMessageKey)); -@@ -274,26 +266,0 @@ function emotionProfileByKey(profileKey) { --function emotionSettingKey(value) { -- return String(value || "") -- .trim() -- .toLowerCase() -- .replace(/[^a-z0-9]+/g, "-") -- .replace(/^-+|-+$/g, "") || "neutral"; --} -- --function selectedEmotionSettingForProfile(profile, emotionProfile) { -- const settings = Array.isArray(profile?.emotionSettings) -- ? profile.emotionSettings.filter((setting) => setting?.active !== false) -- : []; -- const selectedEmotionKey = emotionSettingKey(emotionProfile?.name); -- const setting = settings.find((candidate) => ( -- emotionSettingKey(candidate.emotion) === selectedEmotionKey -- || emotionSettingKey(candidate.emotionLabel) === selectedEmotionKey -- )); -- if (!setting) { -- return { -- message: `Selected TTS Profile "${profile?.name || "Unknown"}" does not include an Emotion Setting for "${emotionProfile?.name || "Unknown"}".`, -- ok: false, -- }; -- } -- return { ok: true, setting }; --} -- -@@ -464,4 +430,0 @@ function speechTestReadiness() { -- const emotionSetting = selectedEmotionSettingForProfile(profile, target.emotionProfile); -- if (!emotionSetting.ok) { -- return { message: emotionSetting.message, ok: false }; -- } -@@ -554 +517 @@ function createMessageSegmentTable() { -- ["Text", "Emotion", "TTS Profile", "Status", "Actions"].forEach((label) => { -+ ["Order", "Text", "Emotion", "TTS Profile", "Status", "Actions"].forEach((label) => { -@@ -566 +529 @@ function createMessageSegmentTable() { -- tbody.append(tableMessage(5, "No message parts saved for this message.")); -+ tbody.append(tableMessage(6, "No message parts saved for this message.")); -@@ -594,0 +558 @@ function createMessageSegmentTable() { -+ createCell(String(segment.displayOrder)), -@@ -620 +584,3 @@ function createSegmentEditRow(segment = null) { -- row.dataset.messagesSegmentOrder = String(segment?.displayOrder || nextSegmentOrder()); -+ -+ const orderCell = document.createElement("td"); -+ orderCell.append(createNumberInput(segment?.displayOrder || nextSegmentOrder(), "segmentOrder", { min: 1, step: 1 })); -@@ -644 +610 @@ function createSegmentEditRow(segment = null) { -- row.append(textCell, emotionCell, ttsCell, statusCell, actions); -+ row.append(orderCell, textCell, emotionCell, ttsCell, statusCell, actions); -@@ -765 +731 @@ function segmentValues(key) { -- displayOrder: editorValue(root, "[data-segment-order]") || root?.dataset.messagesSegmentOrder || String(nextSegmentOrder()), -+ displayOrder: editorValue(root, "[data-segment-order]"), -@@ -833,0 +800 @@ async function commitMessage(key) { -+ state.selectedMessageKey = result.message.key; -@@ -835 +801,0 @@ async function commitMessage(key) { -- state.selectedMessageKey = ""; -@@ -837,2 +802,0 @@ async function commitMessage(key) { -- } else { -- state.selectedMessageKey = result.message.key; -@@ -840 +804 @@ async function commitMessage(key) { -- await reloadAfterChange(state.selectedMessageKey, state.selectedSegmentKey); -+ await reloadAfterChange(result.message.key); -@@ -963,4 +926,0 @@ function speakTarget(service, target, profile) { -- const emotionSetting = selectedEmotionSettingForProfile(profile, target.emotionProfile); -- if (!emotionSetting.ok) { -- return visiblePlaybackError(emotionSetting.message); -- } -@@ -972,2 +932,2 @@ function speakTarget(service, target, profile) { -- pitch: emotionSetting.setting.pitch ?? profile.pitch ?? 1, -- rate: emotionSetting.setting.rate ?? profile.rate ?? 1, -+ pitch: target.emotionProfile.pitch ?? profile.pitch ?? 1, -+ rate: target.emotionProfile.rate ?? profile.rate ?? 1, -@@ -976 +935,0 @@ function speakTarget(service, target, profile) { -- ssmlLikePreset: emotionSetting.setting.ssmlLikePreset || "normal", -@@ -979 +938 @@ function speakTarget(service, target, profile) { -- volume: emotionSetting.setting.volume ?? profile.volume ?? 1, -+ volume: target.emotionProfile.volume ?? profile.volume ?? 1, -@@ -1233 +1192 @@ elements.table?.addEventListener("click", async (event) => { -- if ((messageNameCell || row) && row) { -+ if (messageNameCell && row) { -diff --git a/toolbox/text-to-speech/index.html b/toolbox/text-to-speech/index.html -index 7989b8309..95672aa69 100644 ---- a/toolbox/text-to-speech/index.html -+++ b/toolbox/text-to-speech/index.html -@@ -9 +9 @@ -- -+ -@@ -21 +21 @@ --

Create reusable speech profiles, tune emotion settings, and preview spoken game text through the shared engine audio Text To Speech module.

-+

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

-@@ -25,0 +26,6 @@ -+ -@@ -32 +38 @@ --

Studio Setup

-+

Speech Options

-@@ -36 +42,13 @@ -- Profile Ownership -+ Voice Filters -+
-+ -+ -+ -+ -+ -+ -+

No browser voices loaded yet.

-+
-+
-+
-+ Presets -@@ -38,2 +56,6 @@ --

TTS Profiles own voice, language, gender, age, and availability.

--

Use inline Profile rows to add or edit voice filters.

-+ -+ -+ -+ -+ -+ -@@ -43 +65 @@ -- Emotion Ownership -+ Delivery -@@ -45,2 +67,28 @@ --

Emotion Settings own pitch, rate, volume, and preset values for the selected profile.

--

Message Studio selects a TTS Profile and Emotion for playback.

-+
-+ -+ -+ 1 -+
-+
-+ -+ -+ 1 -+
-+
-+ -+ -+ 1 -+
-+

Voice Age and SSML-like presets shape rate, pitch, and volume. Manual slider changes are preserved until a new character preset is selected.

-+ -+
-+
-+ Named Sentence -+
-+ -+ -+
-+ -+ -+ -+
-@@ -53,0 +102 @@ -+ TTS Studio -@@ -84,0 +134,3 @@ -+
-+ -+
-@@ -103,0 +156,9 @@ -+
-+
-+
-+
Named Sentences
-+

Queue

-+
-+
-+
-+
-@@ -107 +168 @@ --

Status

-+

Output

-@@ -110,0 +172,6 @@ -+ Output Summary -+
-+
[]
-+
-+
-+
-diff --git a/toolbox/text-to-speech/text2speech.js b/toolbox/text-to-speech/text2speech.js -index 5174c8d5e..36f3e9278 100644 ---- a/toolbox/text-to-speech/text2speech.js -+++ b/toolbox/text-to-speech/text2speech.js -@@ -1,0 +2,3 @@ import { -+ createTextToSpeechQueueItem, -+ filterTextToSpeechVoiceOptions, -+ shapeTextToSpeechOptions, -@@ -2,0 +6 @@ import { -+ textToSpeechPayloadGenderValue, -@@ -3,0 +8,2 @@ import { -+ uniqueTextToSpeechId, -+ uniqueTextToSpeechName -@@ -6,0 +13,2 @@ import { -+ TEXT_TO_SPEECH_CHARACTER_PRESET_DEFAULTS, -+ TEXT_TO_SPEECH_CHARACTER_PRESET_OPTIONS, -@@ -8,0 +17 @@ import { -+ TEXT_TO_SPEECH_GENDER_FILTER_OPTIONS, -@@ -10,0 +20 @@ import { -+ TEXT_TO_SPEECH_SCHEMA_ID, -@@ -13,0 +24,3 @@ import { -+const WORKSPACE_TOOL_STATE_KEY = "workspace.tools.text2speech-V2"; -+const TTS_URL_SOURCE_PARAM = "samplePresetPath"; -+ -@@ -362,0 +376,50 @@ function setTextContent(root, selector, text) { -+function readFileText(file) { -+ return new Promise((resolve, reject) => { -+ const reader = new FileReader(); -+ reader.addEventListener("load", () => resolve(String(reader.result || ""))); -+ reader.addEventListener("error", () => reject(reader.error || new Error("File read failed."))); -+ reader.readAsText(file); -+ }); -+} -+ -+function downloadTextFile(text, filename, documentRef = document) { -+ const blob = new Blob([text], { type: "application/json" }); -+ const url = URL.createObjectURL(blob); -+ const link = documentRef.createElement("a"); -+ link.href = url; -+ link.download = filename; -+ documentRef.body.append(link); -+ link.click(); -+ link.remove(); -+ URL.revokeObjectURL(url); -+} -+ -+function isPlainObject(value) { -+ return Boolean(value && typeof value === "object" && !Array.isArray(value)); -+} -+ -+function validateTextToSpeechPayload(payload, sourceLabel = "payload") { -+ if (!Array.isArray(payload)) { -+ return { ok: false, message: `${TEXT_TO_SPEECH_DISPLAY_NAME} ${sourceLabel} must be a root array.` }; -+ } -+ const required = ["id", "name", "text", "gender", "language", "voice", "voiceAge", "volume", "rate", "pitch", "characterPreset", "ssmlLikePreset"]; -+ for (const [index, item] of payload.entries()) { -+ if (!isPlainObject(item)) { -+ return { ok: false, message: `${TEXT_TO_SPEECH_DISPLAY_NAME} ${sourceLabel}[${index}] must be an object.` }; -+ } -+ const missing = required.filter((key) => !Object.prototype.hasOwnProperty.call(item, key)); -+ if (missing.length) { -+ return { ok: false, message: `${TEXT_TO_SPEECH_DISPLAY_NAME} ${sourceLabel}[${index}] missing: ${missing.join(", ")}.` }; -+ } -+ } -+ return { ok: true }; -+} -+ -+function queueItemMeta(item) { -+ return [ -+ item.language || "no language", -+ item.characterPreset || "manual", -+ item.voiceAge || "any" -+ ].join(" / "); -+} -+ -@@ -364,0 +428,4 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ addItem: root.querySelector("[data-tts-add-item]"), -+ addProfile: root.querySelector("[data-tts-add-profile]"), -+ age: root.querySelector("[data-tts-age-select]"), -+ characterPreset: root.querySelector("[data-tts-character-preset-select]"), -@@ -365,0 +433,10 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ copyJson: root.querySelector("[data-tts-copy-json]"), -+ deleteItem: root.querySelector("[data-tts-delete-item]"), -+ duplicateItem: root.querySelector("[data-tts-duplicate-item]"), -+ exportJson: root.querySelector("[data-tts-export-json]"), -+ gender: root.querySelector("[data-tts-gender-select]"), -+ importJson: root.querySelector("[data-tts-import-json]"), -+ importJsonInput: root.querySelector("[data-tts-import-json-input]"), -+ itemName: root.querySelector("[data-tts-item-name]"), -+ language: root.querySelector("[data-tts-language-select]"), -+ outputSummary: root.querySelector("[data-tts-output-summary]"), -@@ -366,0 +444,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ pitch: root.querySelector("[data-tts-pitch]"), -+ pitchValue: root.querySelector("[data-tts-pitch-value]"), -@@ -369,0 +449,3 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ queueList: root.querySelector("[data-tts-queue-list]"), -+ rate: root.querySelector("[data-tts-rate]"), -+ rateValue: root.querySelector("[data-tts-rate-value]"), -@@ -372,0 +455,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ ssmlPreset: root.querySelector("[data-tts-ssml-preset-select]"), -+ standaloneActions: root.querySelector("[data-tts-standalone-actions]"), -@@ -376,0 +461,3 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ voice: root.querySelector("[data-tts-voice-select]"), -+ volume: root.querySelector("[data-tts-volume]"), -+ volumeValue: root.querySelector("[data-tts-volume-value]"), -@@ -379,0 +467 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ applyingItem: false, -@@ -383 +471 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- selectedEmotionId: "", -+ queue: [], -@@ -384,0 +473,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ selectedItemId: "", -+ sliderOverrides: { pitch: false, rate: false, volume: false }, -@@ -400,2 +490,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- function selectedProfile() { -- return state.profiles.find((profile) => profile.id === state.selectedProfileId) || null; -+ function selectedItem() { -+ return state.queue.find((item) => item.id === state.selectedItemId) || null; -@@ -404,2 +494,7 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- function previewProfile() { -- return selectedProfile() || state.profiles.find((profile) => profile.active) || state.profiles[0] || null; -+ function selectedVoiceOptions() { -+ return Array.from(elements.voice?.options || []).map((option) => ({ -+ label: option.textContent || option.value, -+ language: elements.language?.value || TEXT_TO_SPEECH_DEFAULTS.language, -+ name: option.textContent || option.value, -+ value: option.value -+ })).filter((option) => option.value); -@@ -408,8 +503,12 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- function previewEmotion(profile = previewProfile()) { -- if (!profile?.emotions?.length) { -- return null; -- } -- return profile.emotions.find((emotion) => emotion.id === state.selectedEmotionId) -- || profile.emotions.find((emotion) => emotion.emotion === "neutral" && emotion.active !== false) -- || profile.emotions.find((emotion) => emotion.active !== false) -- || profile.emotions[0]; -+ function currentOptions() { -+ return { -+ characterPreset: elements.characterPreset?.value || TEXT_TO_SPEECH_DEFAULTS.characterPreset, -+ gender: textToSpeechPayloadGenderValue(elements.gender?.value || TEXT_TO_SPEECH_DEFAULTS.gender), -+ language: elements.language?.value || TEXT_TO_SPEECH_DEFAULTS.language, -+ pitch: Number(elements.pitch?.value || TEXT_TO_SPEECH_DEFAULTS.pitch), -+ rate: Number(elements.rate?.value || TEXT_TO_SPEECH_DEFAULTS.rate), -+ ssmlLikePreset: elements.ssmlPreset?.value || TEXT_TO_SPEECH_DEFAULTS.ssmlLikePreset, -+ voice: elements.voice?.value || "", -+ voiceAge: elements.age?.value || TEXT_TO_SPEECH_DEFAULTS.voiceAge, -+ volume: Number(elements.volume?.value || TEXT_TO_SPEECH_DEFAULTS.volume) -+ }; -@@ -418,2 +517,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- function previewSpeechLabel(profile, emotion) { -- return `${profile?.name || "TTS Profile"} / ${emotion?.emotionLabel || "Emotion"}`; -+ function selectedProfile() { -+ return state.profiles.find((profile) => profile.id === state.selectedProfileId) || null; -@@ -461,9 +559,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- function tableActionRow(colSpan, ...buttons) { -- const row = document.createElement("tr"); -- const cell = document.createElement("td"); -- cell.colSpan = colSpan; -- cell.append(createActionGroup(...buttons)); -- row.append(cell); -- return row; -- } -- -@@ -558,4 +647,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- nameCell.setAttribute("role", "button"); -- nameCell.tabIndex = 0; -- nameCell.setAttribute("aria-expanded", String(state.selectedProfileId === profile.id)); -- nameCell.title = "Open Emotion Settings"; -@@ -597,3 +682,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- if (!state.editingProfileId) { -- elements.profileTable.append(createProfileAddControlRow()); -- } -@@ -603,6 +685,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- function createProfileAddControlRow() { -- const row = tableActionRow(8, createButton("Add Profile", "ttsAddProfileRow", NEW_ROW_KEY)); -- row.dataset.ttsProfileAddControlRow = ""; -- return row; -- } -- -@@ -713,3 +789,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- if (!state.editingEmotionId) { -- tbody.append(createEmotionAddControlRow(profileId)); -- } -@@ -719 +793,4 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- wrapper.append(tableWrapper); -+ const actionGroup = document.createElement("div"); -+ actionGroup.className = "action-group"; -+ actionGroup.append(createButton("Add Emotion", "ttsAddEmotion", profileId)); -+ wrapper.append(tableWrapper, actionGroup); -@@ -723,6 +799,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- function createEmotionAddControlRow(profileId) { -- const row = tableActionRow(7, createButton("Add Emotion", "ttsAddEmotion", profileId)); -- row.dataset.ttsEmotionAddControlRow = profileId; -- return row; -- } -- -@@ -840 +910,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- state.selectedEmotionId = previewEmotion(profile)?.id || ""; -@@ -843 +913 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- refreshActionState(); -+ renderOutputSummary(); -@@ -856 +925,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- if (!state.selectedProfileId) state.selectedEmotionId = ""; -@@ -858 +927 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- refreshActionState(); -+ renderOutputSummary(); -@@ -890 +958,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- state.selectedEmotionId = emotion.id; -@@ -892 +960 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- refreshActionState(); -+ renderOutputSummary(); -@@ -905 +972,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- if (state.selectedEmotionId === key) state.selectedEmotionId = previewEmotion(profile)?.id || ""; -@@ -907 +974 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- refreshActionState(); -+ renderOutputSummary(); -@@ -914 +980,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- state.selectedEmotionId = state.selectedProfileId ? previewEmotion(selectedProfile())?.id || "" : ""; -@@ -917 +982,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- refreshActionState(); -@@ -923 +988,56 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- function renderPreviewSummary() { -+ function itemFromControls(overrides = {}) { -+ const currentItem = selectedItem(); -+ return createTextToSpeechQueueItem({ -+ ...currentOptions(), -+ existingItems: state.queue.filter((item) => item.id !== currentItem?.id), -+ id: overrides.id ?? currentItem?.id ?? "", -+ name: overrides.name ?? elements.itemName?.value ?? currentItem?.name ?? "New speech item", -+ text: overrides.text ?? elements.text?.value ?? currentItem?.text ?? "" -+ }); -+ } -+ -+ function syncRangeOutputs() { -+ if (elements.rate) elements.rate.value = formatRangeValue(elements.rate.value, "rate"); -+ if (elements.pitch) elements.pitch.value = formatRangeValue(elements.pitch.value, "pitch"); -+ if (elements.volume) elements.volume.value = formatRangeValue(elements.volume.value, "volume"); -+ if (elements.rateValue) elements.rateValue.textContent = elements.rate?.value || "1"; -+ if (elements.pitchValue) elements.pitchValue.textContent = elements.pitch?.value || "1"; -+ if (elements.volumeValue) elements.volumeValue.textContent = elements.volume?.value || "1"; -+ } -+ -+ function configureRanges() { -+ [ -+ { input: elements.volume, kind: "volume" }, -+ { input: elements.rate, kind: "rate" }, -+ { input: elements.pitch, kind: "pitch" } -+ ].forEach(({ input, kind }) => { -+ const range = TEXT_TO_SPEECH_RANGE_DEFAULTS[kind]; -+ if (!input || !range) return; -+ input.min = String(range.min); -+ input.max = String(range.max); -+ input.step = String(range.step); -+ input.value = String(range.value); -+ }); -+ syncRangeOutputs(); -+ } -+ -+ function applyShapedOptions({ resetOverrides = false } = {}) { -+ if (resetOverrides) { -+ state.sliderOverrides = { pitch: false, rate: false, volume: false }; -+ } -+ const shaped = shapeTextToSpeechOptions(currentOptions(), state.sliderOverrides); -+ if (elements.rate && !state.sliderOverrides.rate) elements.rate.value = String(shaped.rate); -+ if (elements.pitch && !state.sliderOverrides.pitch) elements.pitch.value = String(shaped.pitch); -+ if (elements.volume && !state.sliderOverrides.volume) elements.volume.value = String(shaped.volume); -+ syncRangeOutputs(); -+ } -+ -+ function renderOutputSummary() { -+ if (elements.outputSummary) { -+ elements.outputSummary.textContent = JSON.stringify({ -+ contractVersion: TTS_PROFILE_CONTRACT_VERSION, -+ messageStudioOptions: createMessageStudioTtsProfileOptions(state.profiles), -+ profiles: state.profiles, -+ queue: state.queue -+ }, null, 2); -+ } -@@ -927 +1047,139 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- function refreshVoices() { -+ function renderQueue() { -+ if (!elements.queueList) return; -+ elements.queueList.replaceChildren(...state.queue.map((item) => { -+ const button = document.createElement("button"); -+ button.type = "button"; -+ button.className = "btn"; -+ button.dataset.ttsQueueItem = item.id; -+ button.setAttribute("role", "option"); -+ button.setAttribute("aria-selected", String(item.id === state.selectedItemId)); -+ button.textContent = `${item.name} - ${queueItemMeta(item)}`; -+ return button; -+ })); -+ renderOutputSummary(); -+ } -+ -+ function markWorkspaceDirty(reason, changedKeys = []) { -+ if (!isWorkspaceLaunch()) return; -+ const rawToolState = window.sessionStorage.getItem(WORKSPACE_TOOL_STATE_KEY); -+ if (!rawToolState) { -+ writeStatus(`Cannot mark ${TEXT_TO_SPEECH_DISPLAY_NAME} dirty: missing ${WORKSPACE_TOOL_STATE_KEY}.`, "FAIL"); -+ return; -+ } -+ try { -+ const toolState = JSON.parse(rawToolState); -+ window.sessionStorage.setItem(WORKSPACE_TOOL_STATE_KEY, JSON.stringify({ -+ ...toolState, -+ data: state.queue, -+ dirty: { -+ changedAt: new Date().toISOString(), -+ changedKeys, -+ isDirty: true, -+ reason -+ } -+ })); -+ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} dirty state: true; reason=${reason}.`); -+ } catch (error) { -+ writeStatus(`Cannot mark ${TEXT_TO_SPEECH_DISPLAY_NAME} dirty: ${error.message}`, "FAIL"); -+ } -+ } -+ -+ function syncSelectedItemFromControls(reason, changedKeys = []) { -+ if (state.applyingItem) return; -+ const item = selectedItem(); -+ if (!item) return; -+ const nextItem = itemFromControls({ id: item.id, name: elements.itemName?.value || item.name }); -+ const index = state.queue.findIndex((entry) => entry.id === item.id); -+ if (index >= 0) { -+ state.queue[index] = nextItem; -+ state.selectedItemId = nextItem.id; -+ renderQueue(); -+ markWorkspaceDirty(reason, changedKeys); -+ } -+ } -+ -+ function selectItem(itemId, { writeLog = true } = {}) { -+ const item = state.queue.find((entry) => entry.id === itemId); -+ if (!item) return; -+ state.selectedItemId = item.id; -+ state.applyingItem = true; -+ try { -+ if (elements.itemName) elements.itemName.value = item.name; -+ if (elements.text) elements.text.value = item.text; -+ if (elements.gender) elements.gender.value = item.gender === "any" ? "any" : item.gender; -+ if (elements.age) elements.age.value = item.voiceAge; -+ if (elements.characterPreset) elements.characterPreset.value = item.characterPreset; -+ if (elements.ssmlPreset) elements.ssmlPreset.value = item.ssmlLikePreset; -+ if (elements.rate) elements.rate.value = String(item.rate); -+ if (elements.pitch) elements.pitch.value = String(item.pitch); -+ if (elements.volume) elements.volume.value = String(item.volume); -+ syncRangeOutputs(); -+ refreshVoices({ selectedVoice: item.voice }); -+ } finally { -+ state.applyingItem = false; -+ } -+ renderQueue(); -+ refreshActionState(); -+ if (writeLog) writeStatus(`Selected speech item: ${item.name}.`); -+ } -+ -+ function addItem() { -+ const requestedName = String(elements.itemName?.value || "").trim(); -+ if (!requestedName) { -+ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Add blocked: Name is required before creating a named speech item.`, "FAIL"); -+ return; -+ } -+ const item = createTextToSpeechQueueItem({ -+ ...currentOptions(), -+ existingItems: state.queue, -+ name: requestedName, -+ text: elements.text?.value || "New speech line." -+ }); -+ state.queue.push(item); -+ state.selectedItemId = item.id; -+ renderQueue(); -+ selectItem(item.id, { writeLog: false }); -+ markWorkspaceDirty("speech-item-added", [`queue.${item.id}`]); -+ writeStatus(`Added speech item: ${item.name}.`); -+ } -+ -+ function duplicateItem() { -+ const item = itemFromControls(); -+ if (!selectedItem()) { -+ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} duplicate failed: no named sentence is selected.`, "FAIL"); -+ return; -+ } -+ const name = uniqueTextToSpeechName(`${item.name} copy`, state.queue); -+ const copy = { -+ ...item, -+ id: uniqueTextToSpeechId(name, state.queue), -+ name -+ }; -+ state.queue.push(copy); -+ selectItem(copy.id, { writeLog: false }); -+ markWorkspaceDirty("speech-item-duplicated", [`queue.${copy.id}`]); -+ writeStatus(`Duplicated speech item: ${copy.name}.`); -+ } -+ -+ function deleteItem() { -+ const item = selectedItem(); -+ if (!item) { -+ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} delete failed: no named sentence is selected.`, "FAIL"); -+ return; -+ } -+ const index = state.queue.findIndex((entry) => entry.id === item.id); -+ state.queue.splice(index, 1); -+ state.selectedItemId = state.queue[Math.min(index, state.queue.length - 1)]?.id || ""; -+ renderQueue(); -+ if (state.selectedItemId) { -+ selectItem(state.selectedItemId, { writeLog: false }); -+ } else { -+ if (elements.itemName) elements.itemName.value = ""; -+ if (elements.text) elements.text.value = ""; -+ renderOutputSummary(); -+ } -+ markWorkspaceDirty("speech-item-deleted", ["queue"]); -+ writeStatus(`Deleted speech item: ${item.name}.`); -+ } -+ -+ function refreshVoices({ selectedVoice = elements.voice?.value || "" } = {}) { -@@ -928,0 +1187,24 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ const gender = elements.gender?.value || "any"; -+ const filteredByGender = filterTextToSpeechVoiceOptions(state.voiceOptions, { gender }); -+ const languageOptions = filteredByGender.languageOptions.length -+ ? filteredByGender.languageOptions -+ : TEXT_TO_SPEECH_LANGUAGE_OPTIONS; -+ const previousLanguage = elements.language?.value || TEXT_TO_SPEECH_DEFAULTS.language; -+ const language = languageOptions.some((option) => option.value === previousLanguage) -+ ? previousLanguage -+ : languageOptions[0]?.value || TEXT_TO_SPEECH_DEFAULTS.language; -+ populateSelect(elements.language, languageOptions, language); -+ const result = filterTextToSpeechVoiceOptions(state.voiceOptions, { gender, language }); -+ populateSelect(elements.voice, result.matchingVoices.map((option) => ({ -+ label: option.label, -+ value: option.value -+ })), selectedVoice); -+ if (elements.voice && !elements.voice.value && result.matchingVoices[0]) { -+ elements.voice.value = result.matchingVoices[0].value; -+ } -+ if (elements.voice && result.matchingVoices.length === 0) { -+ const option = document.createElement("option"); -+ option.value = ""; -+ option.textContent = state.voiceOptions.length ? `No ${result.genderLabel} voices for ${language}` : "No browser voices available"; -+ elements.voice.replaceChildren(option); -+ } -@@ -929,0 +1212,3 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ setTextContent(root, "[data-tts-voice-details]", state.voiceOptions.length -+ ? `${result.matchingVoices.length} matching ${result.genderLabel} voice${result.matchingVoices.length === 1 ? "" : "s"} for ${language}.` -+ : "No SpeechSynthesis voices loaded."); -@@ -931 +1216 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- return state.voiceOptions; -+ return result; -@@ -940 +1224,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- state.selectedEmotionId = ""; -@@ -942 +1226 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- refreshActionState(); -+ renderOutputSummary(); -@@ -947,2 +1231 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- const profile = previewProfile(); -- const hasVoice = Boolean(profile?.voice); -+ const hasVoice = Boolean(elements.voice?.value); -@@ -950,5 +1233,8 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- if (elements.speak) elements.speak.disabled = !(supported && hasText && hasVoice); -- if (elements.pause) elements.pause.disabled = !engine.canPause(); -- if (elements.resume) elements.resume.disabled = !engine.canResume(); -- if (elements.stop) elements.stop.disabled = !supported; -- renderPreviewSummary(); -+ if (elements.speak) elements.speak.disabled = !(supported && hasText && hasVoice && selectedItem()); -+ if (elements.pause) elements.pause.disabled = !(engine.canPause() && selectedItem()); -+ if (elements.resume) elements.resume.disabled = !(engine.canResume() && selectedItem()); -+ if (elements.stop) elements.stop.disabled = !(supported && selectedItem()); -+ if (elements.copyJson) elements.copyJson.disabled = isWorkspaceLaunch(); -+ if (elements.exportJson) elements.exportJson.disabled = isWorkspaceLaunch(); -+ if (elements.importJson) elements.importJson.disabled = isWorkspaceLaunch(); -+ setTextContent(root, "[data-tts-text-count]", String(String(elements.text?.value || "").length)); -@@ -965,0 +1252 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ if (elements.standaloneActions) elements.standaloneActions.hidden = workspaceLaunch; -@@ -978,5 +1265,59 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- function speak() { -- const profile = previewProfile(); -- const emotion = previewEmotion(profile); -- if (!profile) { -- writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Speak blocked: add or select a TTS Profile first.`, "FAIL"); -+ async function loadQueue() { -+ if (isWorkspaceLaunch()) { -+ const rawToolState = window.sessionStorage.getItem(WORKSPACE_TOOL_STATE_KEY); -+ if (!rawToolState) { -+ writeStatus(`Workspace launch missing ${WORKSPACE_TOOL_STATE_KEY}; queue cannot render.`, "FAIL"); -+ return; -+ } -+ try { -+ const toolState = JSON.parse(rawToolState); -+ const payload = toolState?.data ?? []; -+ const validation = validateTextToSpeechPayload(payload, WORKSPACE_TOOL_STATE_KEY); -+ if (!validation.ok) { -+ writeStatus(validation.message, "FAIL"); -+ return; -+ } -+ state.queue = payload.map((item) => ({ ...item })); -+ writeStatus(`Loaded ${state.queue.length} ${TEXT_TO_SPEECH_DISPLAY_NAME} item(s) from Project Workspace toolState.`); -+ } catch (error) { -+ writeStatus(`${WORKSPACE_TOOL_STATE_KEY} is invalid JSON: ${error.message}`, "FAIL"); -+ return; -+ } -+ } else { -+ const params = new URLSearchParams(window.location.search); -+ const samplePresetPath = String(params.get(TTS_URL_SOURCE_PARAM) || "").trim(); -+ if (samplePresetPath) { -+ try { -+ const response = await fetch(samplePresetPath, { cache: "no-store" }); -+ if (!response.ok) throw new Error(`${response.status}`); -+ const payload = await response.json(); -+ const validation = validateTextToSpeechPayload(payload, samplePresetPath); -+ if (!validation.ok) { -+ writeStatus(validation.message, "FAIL"); -+ return; -+ } -+ state.queue = payload.map((item) => ({ ...item })); -+ writeStatus(`Loaded preset for ${TEXT_TO_SPEECH_DISPLAY_NAME}: ${samplePresetPath}.`); -+ } catch (error) { -+ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} URL JSON source ${samplePresetPath} failed: ${error.message}.`, "FAIL"); -+ return; -+ } -+ } -+ } -+ if (state.queue.length === 0) { -+ state.queue = [createTextToSpeechQueueItem({ -+ ...TEXT_TO_SPEECH_DEFAULTS, -+ existingItems: [], -+ name: "Narrator welcome", -+ text: elements.text?.value || "Welcome to the arena, hero." -+ })]; -+ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} empty launch: created a local named sentence draft.`); -+ } -+ state.selectedItemId = state.queue[0]?.id || ""; -+ renderQueue(); -+ if (state.selectedItemId) selectItem(state.selectedItemId, { writeLog: false }); -+ } -+ -+ async function importJson(file) { -+ if (isWorkspaceLaunch()) { -+ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Import JSON is only available during standalone launch.`, "FAIL"); -@@ -985,2 +1326,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- if (!emotion) { -- writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Speak blocked: add or select an Emotion Setting first.`, "FAIL"); -+ if (!file) { -+ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Import JSON blocked: choose a JSON file first.`, "FAIL"); -@@ -988,0 +1330,43 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ try { -+ const payload = JSON.parse(await readFileText(file)); -+ const validation = validateTextToSpeechPayload(payload, file.name || "selected JSON file"); -+ if (!validation.ok) { -+ writeStatus(`Import JSON blocked: ${validation.message}`, "FAIL"); -+ return; -+ } -+ state.queue = payload.map((item) => ({ ...item })); -+ state.selectedItemId = state.queue[0]?.id || ""; -+ renderQueue(); -+ if (state.selectedItemId) selectItem(state.selectedItemId, { writeLog: false }); -+ writeStatus(`Imported ${state.queue.length} ${TEXT_TO_SPEECH_DISPLAY_NAME} item(s) from ${file.name || "selected JSON file"}; schema=${TEXT_TO_SPEECH_SCHEMA_ID}.`); -+ } catch (error) { -+ writeStatus(`Import JSON failed: ${error.message}`, "FAIL"); -+ } -+ } -+ -+ async function copyJson() { -+ const json = JSON.stringify(state.queue, null, 2); -+ if (!navigator.clipboard || typeof navigator.clipboard.writeText !== "function") { -+ writeStatus("Copy JSON failed: Clipboard API is unavailable.", "FAIL"); -+ return; -+ } -+ try { -+ await navigator.clipboard.writeText(json); -+ writeStatus(`Copied ${TEXT_TO_SPEECH_DISPLAY_NAME} JSON root array to clipboard (${state.queue.length} item(s)).`); -+ } catch (error) { -+ writeStatus(`Copy JSON failed: ${error.message}`, "FAIL"); -+ } -+ } -+ -+ function exportJson() { -+ downloadTextFile(JSON.stringify(state.queue, null, 2), "text-to-speech-v2.json"); -+ writeStatus(`Exported ${TEXT_TO_SPEECH_DISPLAY_NAME} JSON root array (${state.queue.length} item(s)).`); -+ } -+ -+ function speak() { -+ const item = selectedItem(); -+ if (!item) { -+ writeStatus(`${TEXT_TO_SPEECH_DISPLAY_NAME} Speak blocked: select a named speech item with text first.`, "FAIL"); -+ return; -+ } -+ syncSelectedItemFromControls("speak-sync", ["speechOptions", "text"]); -@@ -990,3 +1374 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- language: profile.language, -- pitch: emotion.pitch, -- rate: emotion.rate, -+ ...currentOptions(), -@@ -994,3 +1376 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- voice: profile.voice, -- voiceOptions: state.voiceOptions, -- volume: emotion.volume, -+ voiceOptions: selectedVoiceOptions() -@@ -1004,10 +1384,4 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- language: profile.language, -- pitch: request.pitch, -- rate: request.rate, -- speechItemId: `${profile.id}:${emotion.id}:preview`, -- speechItemName: previewSpeechLabel(profile, emotion), -- ssmlLikePreset: emotion.ssmlLikePreset, -- text: request.text, -- voice: profile.voice, -- voiceAge: profile.age, -- volume: request.volume, -+ ...currentOptions(), -+ speechItemId: item.id, -+ speechItemName: item.name, -+ text: request.text -@@ -1021 +1395 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- renderPreviewSummary(); -+ renderOutputSummary(); -@@ -1053,0 +1428 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ elements.addProfile?.addEventListener("click", addProfile); -@@ -1055 +1429,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- const addProfileButton = event.target.closest("[data-tts-add-profile-row]"); -@@ -1061 +1434,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- const emotionRow = event.target.closest("[data-tts-emotion-row]"); -@@ -1066 +1439 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- const profileNameCell = event.target.closest("[data-tts-profile-name-cell]"); -+ const profileRow = event.target.closest("[data-tts-profile-row]"); -@@ -1068,4 +1440,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- if (addProfileButton) { -- addProfile(); -- return; -- } -@@ -1085 +1453,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- state.selectedEmotionId = previewEmotion(selectedProfile())?.id || ""; -@@ -1111 +1478,0 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- state.selectedEmotionId = editEmotionButton.dataset.ttsEditEmotion; -@@ -1120,8 +1487,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- if (emotionRow) { -- state.selectedEmotionId = emotionRow.dataset.ttsEmotionRow; -- refreshActionState(); -- writeStatus(`Selected Emotion Setting: ${previewEmotion()?.emotionLabel || "Unknown"}.`); -- return; -- } -- if (profileNameCell) { -- selectProfile(profileNameCell.dataset.ttsProfileNameCell); -+ if (profileRow) { -+ selectProfile(profileRow.dataset.ttsProfileRow); -@@ -1130,6 +1491,3 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- elements.profileTable?.addEventListener("keydown", (event) => { -- if (event.key !== "Enter" && event.key !== " ") return; -- const profileNameCell = event.target.closest("[data-tts-profile-name-cell]"); -- if (!profileNameCell) return; -- event.preventDefault(); -- selectProfile(profileNameCell.dataset.ttsProfileNameCell); -+ elements.queueList?.addEventListener("click", (event) => { -+ const itemButton = event.target.closest("[data-tts-queue-item]"); -+ if (itemButton) selectItem(itemButton.dataset.ttsQueueItem || ""); -@@ -1136,0 +1495,3 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ elements.addItem?.addEventListener("click", addItem); -+ elements.duplicateItem?.addEventListener("click", duplicateItem); -+ elements.deleteItem?.addEventListener("click", deleteItem); -@@ -1144,0 +1506,9 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ elements.importJson?.addEventListener("click", () => elements.importJsonInput?.click()); -+ elements.importJsonInput?.addEventListener("change", () => { -+ void importJson(elements.importJsonInput.files?.[0] || null); -+ elements.importJsonInput.value = ""; -+ }); -+ elements.copyJson?.addEventListener("click", () => { -+ void copyJson(); -+ }); -+ elements.exportJson?.addEventListener("click", exportJson); -@@ -1147,0 +1518 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ elements.itemName?.addEventListener("input", () => syncSelectedItemFromControls("speech-item-renamed", ["name"])); -@@ -1148,0 +1520 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ syncSelectedItemFromControls("text-updated", ["text"]); -@@ -1150,0 +1523,46 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ [elements.gender, elements.language].forEach((select) => { -+ select?.addEventListener("change", () => { -+ refreshVoices(); -+ syncSelectedItemFromControls("voice-filter-updated", ["gender", "language", "voice"]); -+ }); -+ }); -+ elements.voice?.addEventListener("change", () => syncSelectedItemFromControls("voice-updated", ["voice"])); -+ elements.characterPreset?.addEventListener("change", () => { -+ if (elements.ssmlPreset) { -+ elements.ssmlPreset.value = TEXT_TO_SPEECH_CHARACTER_PRESET_DEFAULTS[elements.characterPreset.value]?.ssmlLikePreset || "normal"; -+ } -+ applyShapedOptions({ resetOverrides: true }); -+ syncSelectedItemFromControls("character-preset-updated", ["characterPreset", "rate", "pitch", "volume"]); -+ }); -+ [elements.age, elements.ssmlPreset].forEach((select) => { -+ select?.addEventListener("change", () => { -+ applyShapedOptions(); -+ syncSelectedItemFromControls("speech-shaping-updated", ["voiceAge", "ssmlLikePreset", "rate", "pitch", "volume"]); -+ }); -+ }); -+ [ -+ { input: elements.pitch, kind: "pitch" }, -+ { input: elements.rate, kind: "rate" }, -+ { input: elements.volume, kind: "volume" } -+ ].forEach(({ input, kind }) => { -+ input?.addEventListener("input", () => { -+ state.sliderOverrides[kind] = true; -+ syncRangeOutputs(); -+ syncSelectedItemFromControls(`${kind}-updated`, [kind]); -+ refreshActionState(); -+ }); -+ input?.addEventListener("dblclick", () => { -+ state.sliderOverrides[kind] = false; -+ applyShapedOptions(); -+ syncSelectedItemFromControls(`${kind}-reset`, [kind]); -+ }); -+ }); -+ } -+ -+ function populateStaticControls() { -+ populateSelect(elements.gender, TEXT_TO_SPEECH_GENDER_FILTER_OPTIONS, TEXT_TO_SPEECH_DEFAULTS.gender); -+ populateSelect(elements.age, TEXT_TO_SPEECH_AGE_FILTER_OPTIONS, TEXT_TO_SPEECH_DEFAULTS.voiceAge); -+ populateSelect(elements.characterPreset, TEXT_TO_SPEECH_CHARACTER_PRESET_OPTIONS, TEXT_TO_SPEECH_DEFAULTS.characterPreset); -+ populateSelect(elements.ssmlPreset, TEXT_TO_SPEECH_SSML_LIKE_PRESET_OPTIONS, TEXT_TO_SPEECH_DEFAULTS.ssmlLikePreset); -+ populateSelect(elements.language, TEXT_TO_SPEECH_LANGUAGE_OPTIONS, TEXT_TO_SPEECH_DEFAULTS.language); -+ configureRanges(); -@@ -1157 +1575 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- setTextContent(root, "[data-tts-voice-count]", "0"); -+ populateSelect(elements.voice, [{ label: "No browser voices available", value: "" }], ""); -@@ -1162,0 +1581 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ populateStaticControls(); -@@ -1164,0 +1584 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ await loadQueue(); -@@ -1175,0 +1596 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ renderOutputSummary(); -@@ -1177,0 +1599,2 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -+ await loadQueue(); -+ refreshVoices({ selectedVoice: selectedItem()?.voice || elements.voice?.value || "" }); -@@ -1187 +1610 @@ function initializeTextToSpeechTool(root = document, { engine = new TextToSpeech -- profiles: () => state.profiles.map((profile) => ({ ...profile })), -+ queue: () => state.queue.map((item) => ({ ...item })), -@@ -1211 +1634,2 @@ export { -- previewTtsMessage -+ previewTtsMessage, -+ validateTextToSpeechPayload From 0514f858342704357ec1cdf9b8af9497a9d40a1d Mon Sep 17 00:00:00 2001 From: "Mr. Q" <97194984+ToolboxAid@users.noreply.github.com> Date: Sat, 20 Jun 2026 21:15:38 -0400 Subject: [PATCH 4/8] Refresh admin workstream review artifacts before diagnostics plan - PR_26171_GAMMA_013-admin-system-health-diagnostics-plan --- docs_build/dev/reports/codex_changed_files.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index ad56efa47..daadbb4c9 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,8 +1,7 @@ Base merge-base: e8845dae6e990164072d4ee9b37ec81eda563fc3 -Head before artifact refresh: 33bcbf07338c593cec3bf8a1ded422ce3ab690d6 +Head at artifact refresh: 2f4b8a5d493d479ed2442f5025cc2b2be863a3c3 Working tree status at artifact generation: - M docs_build/dev/reports/PR_26171_GAMMA_012-admin-system-health-status-reason-cleanup.md Changed files against merge-base: M admin/system-health.html From 49e4cca2a249909b569a2dd710c2df6d3a3fe679 Mon Sep 17 00:00:00 2001 From: "Mr. Q" <97194984+ToolboxAid@users.noreply.github.com> Date: Sat, 20 Jun 2026 21:19:09 -0400 Subject: [PATCH 5/8] Add admin system health diagnostics plan - PR_26171_GAMMA_013-admin-system-health-diagnostics-plan --- admin/system-health.html | 25 ++ ...s-plan-instruction-compliance-checklist.md | 64 +++ ...iagnostics-plan-manual-validation-notes.md | 27 ++ ...13-admin-system-health-diagnostics-plan.md | 82 ++++ .../dev/reports/codex_changed_files.txt | 19 +- docs_build/dev/reports/codex_review.diff | 415 +++++++++++++----- .../tools/AdminHealthOperationsPage.spec.mjs | 13 + 7 files changed, 541 insertions(+), 104 deletions(-) create mode 100644 docs_build/dev/reports/PR_26171_GAMMA_013-admin-system-health-diagnostics-plan-instruction-compliance-checklist.md create mode 100644 docs_build/dev/reports/PR_26171_GAMMA_013-admin-system-health-diagnostics-plan-manual-validation-notes.md create mode 100644 docs_build/dev/reports/PR_26171_GAMMA_013-admin-system-health-diagnostics-plan.md diff --git a/admin/system-health.html b/admin/system-health.html index 092a94f04..8a0f6a010 100644 --- a/admin/system-health.html +++ b/admin/system-health.html @@ -43,6 +43,7 @@

Admin

Storage Health

Runtime Environment

Limits & Capacity

+

Diagnostics Plan

Diagnostics Log

@@ -151,6 +152,30 @@

System Health Tables

+
+ + + + + + + + + + + + + + + + + + + + + +
Diagnostics Plan
Diagnostic CheckFuture SignalPlanned SourceStatus
Postgres ConnectionConnection success, latency, and actionable failure detailServer-owned Postgres health readerPENDING
Postgres Migration ReaderCurrent migration version and pending migration signalServer-owned Postgres migration readerPENDING
R2 Bucket ConfiguredConfigured bucket name presence without exposing secretsServer-owned Cloudflare R2 configuration readerPENDING
R2 ListList operation availability and scoped prefix resultServer-owned Cloudflare R2 storage diagnosticPENDING
R2 ReadHealth object read availabilityServer-owned Cloudflare R2 storage diagnosticPENDING
R2 WriteHealth object write availabilityServer-owned Cloudflare R2 storage diagnosticPENDING
R2 DeleteHealth object cleanup availabilityServer-owned Cloudflare R2 storage diagnosticPENDING
Runtime Environment MaskingMasked display verification for secret-bearing variablesServer-owned runtime environment diagnosticPENDING
Limits/Capacity MetricsDB size, connections, storage usage, and R2 Class A/Class B operation signalsServer-owned metrics diagnosticPENDING
+