Skip to content

Commit bba939a

Browse files
author
DavidQ
committed
Clean tools index entries and show Text to Speech V2 item count - PR_26130_029-tools-index-cleanup-and-text-to-speech-count
1 parent 9ce3e65 commit bba939a

6 files changed

Lines changed: 89 additions & 33 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# PR_26130_029-tools-index-cleanup-and-text-to-speech-count
2+
3+
## Summary
4+
- Removed the planned `Character Voice Presets` and `Game Character Voice / Event Integration` cards from `tools/index.html`.
5+
- Hid `Asset Browser / Import Hub` and `Tile Model Converter` from the generated tools index surface without deleting their runtime folders or changing broader registry visibility.
6+
- Replaced the Workspace Manager V2 Text to Speech V2 tile detail from `Speech synthesis ready.` to a live `N text to speech` count.
7+
- Threaded the existing Text to Speech V2 session summary count into Workspace Manager V2 tool tile rendering.
8+
- Added Playwright coverage that locks Session Inspector V2 How To Use and Read Me buttons to the same shared tools-index action class pattern used by Palette Manager V2.
9+
10+
## Scope Notes
11+
- Scope was limited to `tools/index.html`, the tools index renderer, Workspace Manager V2 tile count display, and direct Playwright coverage.
12+
- Runtime folders for `tools/asset-browser` and `tools/tile-model-converter` were not deleted.
13+
- No start_of_day changes.
14+
- Full samples smoke test was not run. Reason: this PR is limited to tools index cleanup and Workspace Manager V2 tile display; the requested `npm run test:workspace-v2` suite covers the scoped behavior.
15+
16+
## Validation
17+
- First run: `npm run test:workspace-v2` timed out at the command timeout before completion.
18+
- Final run: `npm run test:workspace-v2`
19+
- Result: 36 passed.
20+
- Playwright V8 coverage report included changed runtime JS coverage:
21+
- `(100%) tools/renderToolsIndex.js - executed lines 180/180; executed functions 28/28`
22+
- `(86%) tools/workspace-manager-v2/js/WorkspaceManagerV2App.js - executed lines 940/940; executed functions 42/49`
23+
- `(93%) tools/workspace-manager-v2/js/controls/ToolTilesControl.js - executed lines 126/126; executed functions 14/15`
24+
25+
## Artifacts
26+
- Diff report: `docs/dev/reports/codex_review.diff`
27+
- Changed files report: `docs/dev/reports/codex_changed_files.txt`
28+
- Delta ZIP: `tmp/PR_26130_029-tools-index-cleanup-and-text-to-speech-count_delta.zip`

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -984,17 +984,26 @@ test.describe("Workspace Manager V2 bootstrap", () => {
984984
const utilitiesGrid = firstClassToolsSection?.querySelector("[data-active-tools-utilities-grid]");
985985
const viewersGrid = firstClassToolsSection?.querySelector("[data-active-tools-viewers-grid]");
986986
const workspaceCard = workflowGrid?.querySelector(".tools-platform-card");
987+
const cards = Array.from(firstClassToolsSection?.querySelectorAll(".tools-platform-card") || []);
988+
const actionClassesForCard = (title) => {
989+
const toolCard = cards.find((candidate) => candidate.querySelector("h3")?.textContent?.trim() === title);
990+
return Object.fromEntries(Array.from(toolCard?.querySelectorAll(".tools-platform-card__action") || [])
991+
.map((action) => [action.textContent.trim(), action.className.trim()]));
992+
};
987993
const plannedToolsGrid = document.querySelector("[data-planned-tools-grid]");
988994
return {
989995
actionLabels: Array.from(firstClassToolsSection?.querySelectorAll(".tools-platform-card__action") || [])
990996
.map((action) => action.textContent.trim()),
997+
allCards: cards.map((toolCard) => toolCard.querySelector("h3")?.textContent?.trim() || ""),
991998
headings: Array.from(firstClassToolsSection?.querySelectorAll(":scope > h3") || [])
992999
.map((heading) => heading.textContent.trim()),
1000+
paletteManagerActionClasses: actionClassesForCard("Palette Manager V2"),
9931001
plannedCards: Array.from(plannedToolsGrid?.querySelectorAll(".card h3") || [])
9941002
.map((heading) => heading.textContent.trim()),
9951003
sampleLabels: Array.from(firstClassToolsSection?.querySelectorAll(".tools-platform-card__action") || [])
9961004
.map((action) => action.textContent.trim())
9971005
.filter((label) => label.startsWith("Samples")),
1006+
sessionInspectorActionClasses: actionClassesForCard("Session Inspector V2"),
9981007
utilitiesCards: Array.from(utilitiesGrid?.querySelectorAll(".tools-platform-card h3") || [])
9991008
.map((heading) => heading.textContent.trim()),
10001009
viewersCards: Array.from(viewersGrid?.querySelectorAll(".tools-platform-card h3") || [])
@@ -1009,10 +1018,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
10091018
expect(toolsIndexState.workflowCards).toEqual(["Workspace Manager V2"]);
10101019
expect(toolsIndexState.utilitiesCards).not.toContain("Workspace Manager V2");
10111020
expect(toolsIndexState.utilitiesCards).toContain("Text to Speech V2");
1021+
expect(toolsIndexState.allCards).not.toContain("Asset Browser / Import Hub");
1022+
expect(toolsIndexState.allCards).not.toContain("Tile Model Converter");
10121023
expect(toolsIndexState.plannedCards).toContain("Piper WASM Backend");
10131024
expect(toolsIndexState.plannedCards).toContain("Optional SSML Processing Layer");
1014-
expect(toolsIndexState.plannedCards).toContain("Character Voice Presets");
1015-
expect(toolsIndexState.plannedCards).toContain("Game Character Voice / Event Integration");
1025+
expect(toolsIndexState.plannedCards).not.toContain("Character Voice Presets");
1026+
expect(toolsIndexState.plannedCards).not.toContain("Game Character Voice / Event Integration");
10161027
expect(toolsIndexState.plannedCards).not.toEqual(expect.arrayContaining([
10171028
"Raspberry Pi Speech Deployment",
10181029
"Queue-Based Speech Playback",
@@ -1023,6 +1034,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
10231034
expect(toolsIndexState.viewersCards).toContain("Session Inspector V2");
10241035
expect(toolsIndexState.viewersCards).not.toContain("Session Inspector");
10251036
expect(toolsIndexState.workspaceActionLabels).toEqual(["How To Use", "Read Me"]);
1037+
expect(toolsIndexState.sessionInspectorActionClasses["How To Use"]).toBe(toolsIndexState.paletteManagerActionClasses["How To Use"]);
1038+
expect(toolsIndexState.sessionInspectorActionClasses["Read Me"]).toBe(toolsIndexState.paletteManagerActionClasses["Read Me"]);
1039+
expect(toolsIndexState.sessionInspectorActionClasses["How To Use"]).toBe("tools-platform-card__action tools-platform-card__action--secondary");
10261040
expect(toolsIndexState.actionLabels).not.toContain("README");
10271041
expect(toolsIndexState.sampleLabels.every((label) => /^Samples \(\d+\)$/.test(label))).toBe(true);
10281042
expect(pageErrors).toEqual([]);
@@ -2116,8 +2130,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
21162130
await selectMockRepo(page);
21172131
await page.locator("#activeGameSelect").selectOption("Asteroids");
21182132
await expectWorkspaceReturnRehydrated(page);
2119-
expect(await page.evaluate(() => Object.hasOwn(window.__workspaceManagerV2App.activeContext.tools, "text2speech-V2"))).toBe(false);
2120-
expect(await page.evaluate(() => JSON.parse(sessionStorage.getItem("workspace.tools.text2speech-V2")).data)).toBeNull();
2133+
expect(await page.evaluate(() => Object.hasOwn(window.__workspaceManagerV2App.activeContext.tools, "text2speech-V2"))).toBe(true);
2134+
expect(await page.evaluate(() => JSON.parse(sessionStorage.getItem("workspace.tools.text2speech-V2")).data)).toEqual([]);
21212135
const schemaContract = await page.evaluate(async () => {
21222136
const schema = await fetch("/tools/schemas/tools/text2speech-V2.schema.json", { cache: "no-store" }).then((response) => response.json());
21232137
return {
@@ -3198,7 +3212,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
31983212
await expect(textToSpeechToolTile).toBeEnabled();
31993213
await expect(textToSpeechToolTile).toContainText("Text to Speech V2");
32003214
await expect(textToSpeechToolTile).toContainText("Ready to launch");
3201-
await expect(textToSpeechToolTile).toContainText("Speech synthesis ready");
3215+
await expect(textToSpeechToolTile).toContainText("0 text to speech");
3216+
await expect(textToSpeechToolTile).not.toContainText("Speech synthesis ready");
32023217
await expect(sessionInspectorTile).toBeEnabled();
32033218
await expect(sessionInspectorTile).toContainText("Session Inspector V2");
32043219
await expect(sessionInspectorTile).not.toContainText("Session storage inspector");
@@ -3692,7 +3707,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
36923707
await expect(textToSpeechToolTile).toBeEnabled();
36933708
await expect(textToSpeechToolTile).toContainText("Text to Speech V2");
36943709
await expect(textToSpeechToolTile).toContainText("Ready to launch");
3695-
await expect(textToSpeechToolTile).toContainText("Speech synthesis ready");
3710+
await expect(textToSpeechToolTile).toContainText("0 text to speech");
3711+
await expect(textToSpeechToolTile).not.toContainText("Speech synthesis ready");
36963712
await expect(page.locator("#statusLog")).toHaveValue(/OK Hydrated workspace session for asset-manager-v2, palette-manager-v2, preview-generator-v2, text2speech-V2, session-inspector-v2\./);
36973713
const textToSpeechSessionData = await page.evaluate(() => JSON.parse(sessionStorage.getItem("workspace.tools.text2speech-V2")).data);
36983714
const activeContextHasTextToSpeechPayload = await page.evaluate(() => Object.hasOwn(window.__workspaceManagerV2App.activeContext.tools, "text2speech-V2"));
@@ -3737,6 +3753,17 @@ test.describe("Workspace Manager V2 bootstrap", () => {
37373753
context.tools["text2speech-V2"] = payload;
37383754
const validation = await app.contextService.validateGeneratedManifest(context);
37393755
const hostContextId = app.contextService.writePersistedContext(app.activeHostContextId, context);
3756+
const session = JSON.parse(sessionStorage.getItem("workspace.tools.text2speech-V2"));
3757+
sessionStorage.setItem("workspace.tools.text2speech-V2", JSON.stringify({
3758+
...session,
3759+
data: payload,
3760+
dirty: {
3761+
isDirty: false,
3762+
reason: null,
3763+
changedAt: null,
3764+
changedKeys: []
3765+
}
3766+
}));
37403767
const metrics = app.contextSummaryMetrics(context);
37413768
app.applyContextResult({
37423769
assetCount: metrics.assetCount,
@@ -3748,6 +3775,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
37483775
return { hostContextId, validation };
37493776
}, TEXT_TO_SPEECH_SAMPLE_PRESET_PATH);
37503777
expect(seededWorkspace.validation).toEqual({ ok: true });
3778+
await expect(page.locator('[data-workspace-tool-id="text2speech-V2"]')).toContainText("1 text to speech");
3779+
await expect(page.locator('[data-workspace-tool-id="text2speech-V2"]')).not.toContainText("Speech synthesis ready");
37513780
expect(await page.evaluate(async () => {
37523781
const app = window.__workspaceManagerV2App;
37533782
const context = structuredClone(app.activeContext);
@@ -3768,6 +3797,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
37683797
await page.locator("#returnToWorkspaceButton").click();
37693798
await expect(page).toHaveURL(new RegExp(`workspace-manager-v2/index\\.html\\?hostContextId=${seededWorkspace.hostContextId}`));
37703799
await expectWorkspaceReturnedFromTool(page, { dirty: true });
3800+
await expect(page.locator('[data-workspace-tool-id="text2speech-V2"]')).toContainText("0 text to speech");
3801+
await expect(page.locator('[data-workspace-tool-id="text2speech-V2"]')).not.toContainText("Speech synthesis ready");
37713802
const returnedState = await page.evaluate(() => {
37723803
const session = JSON.parse(sessionStorage.getItem("workspace.tools.text2speech-V2"));
37733804
const outputContext = JSON.parse(document.querySelector("#workspaceContextOutput").value);
@@ -3987,7 +4018,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
39874018
await expect(page.locator("#statusLog")).toHaveValue(/OK Saved path: games\/Asteroids\/game\.manifest\.json\./);
39884019
await expect(page.locator("#statusLog")).toHaveValue(/OK Save write validation: file content changed\./);
39894020
await expect(page.locator("#statusLog")).toHaveValue(/INFO Saved file size: \d+ bytes\./);
3990-
await expect(page.locator("#statusLog")).toHaveValue(/INFO Saved toolState items: (?:3 \(asset-manager-v2 assets=14; palette-manager-v2 swatches=11; vector-map-editor vectors=5\)|4 \(asset-manager-v2 assets=14; palette-manager-v2 swatches=11; text2speech-V2 queue=1; vector-map-editor vectors=5\))\./);
4021+
await expect(page.locator("#statusLog")).toHaveValue(/INFO Saved toolState items: (?:3 \(asset-manager-v2 assets=14; palette-manager-v2 swatches=11; vector-map-editor vectors=5\)|4 \(asset-manager-v2 assets=14; palette-manager-v2 swatches=11; text2speech-V2 queue=(?:0|1); vector-map-editor vectors=5\))\./);
39914022
await expect(page.locator("#statusLog")).toHaveValue(/OK Save validation result: game manifest valid; root game\.workspace toolState valid; saved context matched re-read file\./);
39924023
await expect(page.locator("#statusLog")).toHaveValue(/OK Save dirty\/clean validation: 1 dirty toolState payload persisted; 1 toolState key marked clean\./);
39934024
await expect(page.locator("#statusLog")).toHaveValue(/OK Saved Workspace Manager V2 toolState context workspace-manager-v2-Asteroids\./);

tools/index.html

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -121,21 +121,6 @@ <h3>Optional SSML Processing Layer</h3>
121121
</div>
122122
</div>
123123

124-
<div class="card">
125-
<h3>Character Voice Presets</h3>
126-
<p>Plan character-oriented voice presets for reusable narrator, hero, villain, alert, and game role settings.</p>
127-
<div class="meta">
128-
<span class="pill planned">Planned</span>
129-
</div>
130-
</div>
131-
132-
<div class="card">
133-
<h3>Game Character Voice / Event Integration</h3>
134-
<p>Connect character voice presets to game events, prompts, menus, hazards, and in-game feedback triggers.</p>
135-
<div class="meta">
136-
<span class="pill planned">Planned</span>
137-
</div>
138-
</div>
139124
</div>
140125
</section>
141126

tools/renderToolsIndex.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { escapeHtml } from "../src/shared/string/stringUtil.js";
33

44
const SAMPLES_INDEX_PATH = "/samples/index.html";
55
const SAMPLES_METADATA_PATH = "/samples/metadata/samples.index.metadata.json";
6+
const TOOLS_INDEX_HIDDEN_TOOL_IDS = Object.freeze(new Set([
7+
"asset-browser",
8+
"tile-model-converter"
9+
]));
610

711
function toStandaloneHref(entryPoint) {
812
const normalized = String(entryPoint || "").replace(/^\.?\/*/, "");
@@ -83,10 +87,8 @@ function classifyToolGroup(toolId) {
8387
"performance-profiler"
8488
]);
8589
const utilityToolIds = new Set([
86-
"asset-browser",
8790
"asset-manager-v2",
8891
"asset-pipeline",
89-
"tile-model-converter",
9092
"physics-sandbox",
9193
"text2speech-V2",
9294
"3d-json-payload"
@@ -154,6 +156,7 @@ function renderActiveToolsList(sampleCountByToolId) {
154156
.filter((entry) => entry.active === true)
155157
.filter((entry) => entry.visibleInToolsList === true)
156158
.filter((entry) => entry.id !== "state-inspector")
159+
.filter((entry) => !TOOLS_INDEX_HIDDEN_TOOL_IDS.has(entry.id))
157160
.sort((left, right) => String(left.displayName || "").localeCompare(String(right.displayName || "")));
158161

159162
const workflow = tools.filter((tool) => classifyToolGroup(tool.id) === "workflow").map((tool) => renderToolCard(tool, sampleCountByToolId));

tools/workspace-manager-v2/js/WorkspaceManagerV2App.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@
488488
: { context: result.context, toolSummaries: {} };
489489
const paletteSummary = sessionRefresh.toolSummaries["palette-manager-v2"] || {};
490490
const assetSummary = sessionRefresh.toolSummaries["asset-manager-v2"] || {};
491+
const textToSpeechSummary = sessionRefresh.toolSummaries["text2speech-V2"] || {};
491492
const dirtyByToolId = Object.fromEntries(Object.entries(sessionRefresh.toolSummaries)
492493
.map(([toolId, summary]) => [toolId, summary.dirtyStatus || "unknown"]));
493494
const paletteSwatchCount = Number.isInteger(paletteSummary.paletteSwatchCount)
@@ -496,6 +497,9 @@
496497
const assetCount = Number.isInteger(assetSummary.assetCount)
497498
? assetSummary.assetCount
498499
: result.assetCount;
500+
const textToSpeechCount = Number.isInteger(textToSpeechSummary.speechQueueCount)
501+
? textToSpeechSummary.speechQueueCount
502+
: 0;
499503
this.activeContext = sessionRefresh.context;
500504
this.activeGame = result.game;
501505
this.activeHostContextId = result.hostContextId || null;
@@ -517,7 +521,8 @@
517521
dirtyByToolId,
518522
enabledToolIds: hydration.ok ? hydration.hydratedToolIds : [],
519523
manifestStatus: this.activeToolStateRequiresRepoHandle ? "Repo folder required" : "Schema-valid manifest",
520-
paletteSwatchCount
524+
paletteSwatchCount,
525+
textToSpeechCount
521526
});
522527
this.syncLifecycleControls();
523528
}

tools/workspace-manager-v2/js/controls/ToolTilesControl.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export class ToolTilesControl {
1818
assetCount: 0,
1919
canLaunch: false,
2020
manifestStatus: "Waiting for manifest",
21-
paletteSwatchCount: 0
21+
paletteSwatchCount: 0,
22+
textToSpeechCount: 0
2223
});
2324
}
2425

@@ -28,7 +29,8 @@ export class ToolTilesControl {
2829
dirtyByToolId = {},
2930
enabledToolIds = [],
3031
manifestStatus = "Waiting for manifest",
31-
paletteSwatchCount = 0
32+
paletteSwatchCount = 0,
33+
textToSpeechCount = 0
3234
} = {}) {
3335
const enabledToolIdSet = new Set(enabledToolIds);
3436
this.container.replaceChildren(...TOOL_GROUPS.map((group) => this.groupSection({
@@ -38,11 +40,12 @@ export class ToolTilesControl {
3840
enabledToolIdSet,
3941
group,
4042
manifestStatus,
41-
paletteSwatchCount
43+
paletteSwatchCount,
44+
textToSpeechCount
4245
})));
4346
}
4447

45-
detailForTool(tool, { assetCount, manifestStatus, paletteSwatchCount }) {
48+
detailForTool(tool, { assetCount, manifestStatus, paletteSwatchCount, textToSpeechCount }) {
4649
if (tool.id === "templates-v2") {
4750
return "Canonical V2 template";
4851
}
@@ -56,12 +59,12 @@ export class ToolTilesControl {
5659
return "";
5760
}
5861
if (tool.id === "text2speech-V2") {
59-
return "Speech synthesis ready";
62+
return `${textToSpeechCount} text to speech`;
6063
}
6164
return manifestStatus;
6265
}
6366

64-
groupSection({ assetCount, canLaunch, dirtyByToolId, enabledToolIdSet, group, manifestStatus, paletteSwatchCount }) {
67+
groupSection({ assetCount, canLaunch, dirtyByToolId, enabledToolIdSet, group, manifestStatus, paletteSwatchCount, textToSpeechCount }) {
6568
const section = document.createElement("section");
6669
section.className = "workspace-manager-v2__tool-group";
6770
section.setAttribute("aria-label", `${group} tools`);
@@ -82,6 +85,7 @@ export class ToolTilesControl {
8285
enabledToolIdSet,
8386
manifestStatus,
8487
paletteSwatchCount,
88+
textToSpeechCount,
8589
tool
8690
}));
8791
});
@@ -90,7 +94,7 @@ export class ToolTilesControl {
9094
return section;
9195
}
9296

93-
tile({ assetCount, canLaunch, dirtyByToolId, enabledToolIdSet, manifestStatus, paletteSwatchCount, tool }) {
97+
tile({ assetCount, canLaunch, dirtyByToolId, enabledToolIdSet, manifestStatus, paletteSwatchCount, textToSpeechCount, tool }) {
9498
const isEnabledForGame = canLaunch && enabledToolIdSet.has(tool.id);
9599
const dirtyStatus = dirtyByToolId[tool.id] || "unknown";
96100
const button = document.createElement("button");
@@ -113,7 +117,7 @@ export class ToolTilesControl {
113117
? "Ready to launch"
114118
: (canLaunch ? "Not enabled for game" : manifestStatus);
115119

116-
const detailText = this.detailForTool(tool, { assetCount, manifestStatus, paletteSwatchCount });
120+
const detailText = this.detailForTool(tool, { assetCount, manifestStatus, paletteSwatchCount, textToSpeechCount });
117121

118122
const actions = document.createElement("span");
119123
actions.className = "workspace-manager-v2__tool-tile-actions";

0 commit comments

Comments
 (0)