Skip to content

Commit e93eea5

Browse files
author
DavidQ
committed
Hydrate only game-relevant workspace tools for selected games - PR_26128_021-workspace-hydrate-game-tools-only
1 parent e450e37 commit e93eea5

6 files changed

Lines changed: 184 additions & 26 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Playwright Workspace Hydrate Game Tools Only
2+
3+
## Command
4+
`npm run test:workspace-v2`
5+
6+
## Result
7+
- Passed: 15/15
8+
- Runtime: about 1.4 minutes
9+
10+
## Targeted Coverage
11+
- Verified initial Workspace Manager V2 load still has no hydrated tool sessions.
12+
- Verified selecting Asteroids hydrates:
13+
- `workspace.tools.asset-manager-v2`
14+
- `workspace.tools.palette-manager-v2`
15+
- `workspace.tools.preview-generator-v2`
16+
- `workspace.tools.session-inspector-v2`
17+
- Verified selecting Asteroids does not hydrate `workspace.tools.templates-v2`.
18+
- Verified the hydration report lists hydrated tools and skipped tools with skip reasons.
19+
- Verified the `templates-v2` tile is disabled after Asteroids opens because it is not enabled for the selected game.
20+
- Verified Asset Manager V2 and Palette Manager V2 retain manifest-backed data hydration.
21+
- Verified dirty defaults remain clean for hydrated tools.
22+
- Verified Session Inspector V2 does not show an unrelated `workspace.tools.templates-v2` entry after selecting Asteroids.
23+
- Verified Preview Generator V2 still opens from Workspace Manager V2 and reaches enabled image generation state.
24+
- Verified imported Asteroids game manifests also skip `workspace.tools.templates-v2`.
25+
26+
## Skipped
27+
- Full samples smoke test was skipped by request. The relevant hydration, launch, Session Inspector V2, and Preview Generator V2 paths are covered by `tests/playwright/tools/WorkspaceManagerV2.spec.mjs`.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Workspace Hydrate Game Tools Only
2+
3+
## Scope
4+
- Updated Workspace Manager V2 session hydration to hydrate only tools relevant to the selected game/workspace.
5+
- Preserved the normalized per-tool session object:
6+
- `schema`
7+
- `workspace`
8+
- `data`
9+
- `dirty`
10+
- Preserved the existing clean dirty defaults:
11+
- `isDirty: false`
12+
- `reason: null`
13+
- `changedAt: null`
14+
- `changedKeys: []`
15+
- Did not change repo reference hydration at `workspace.repo.reference`.
16+
17+
## Hydration Rules
18+
- Hydrate a tool when its payload exists in the selected game workspace config.
19+
- Hydrate selected-game purpose launch-context tools that need the active game context.
20+
- Skip starter/dev-only tools when the selected game workspace does not explicitly provide tool data for them.
21+
- Disable skipped tool tiles after a game is opened so only hydrated tools are launchable.
22+
23+
## Asteroids Hydration Report
24+
| Tool | Result | Reason |
25+
| --- | --- | --- |
26+
| `asset-manager-v2` | Hydrated | Tool data is present in selected game workspace config. |
27+
| `palette-manager-v2` | Hydrated | Tool data is present in selected game workspace config. |
28+
| `preview-generator-v2` | Hydrated | Tool has a selected-game workspace launch purpose. |
29+
| `session-inspector-v2` | Hydrated | Tool has a selected-game workspace launch purpose. |
30+
| `templates-v2` | Skipped | Starter/dev-only tool is not enabled by the selected game workspace config. |
31+
32+
## Validation Notes
33+
- Selecting Asteroids does not create `workspace.tools.templates-v2`.
34+
- Asset Manager V2 and Palette Manager V2 hydrate with their manifest data when present.
35+
- Preview Generator V2 still opens from Workspace Manager V2 and can generate from hydrated repo/game session context.
36+
- Session Inspector V2 shows hydrated game-relevant `workspace.tools.*` entries and no unrelated `workspace.tools.templates-v2` entry.
37+
38+
## Guardrails
39+
- No cross-tool communication was added.
40+
- No sample JSON was modified.
41+
- No roadmap content was modified.
42+
- No schema/runtime contract changes were made.
43+
44+
## Skipped
45+
- Full samples smoke test was skipped by request. The changed surface is Workspace Manager V2 session hydration and Workspace Manager V2 launch/session behavior, covered by `npm run test:workspace-v2`.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,16 +1134,29 @@ test.describe("Workspace Manager V2 bootstrap", () => {
11341134
"workspace.tools.asset-manager-v2",
11351135
"workspace.tools.palette-manager-v2",
11361136
"workspace.tools.preview-generator-v2",
1137-
"workspace.tools.session-inspector-v2",
1138-
"workspace.tools.templates-v2"
1137+
"workspace.tools.session-inspector-v2"
11391138
]);
1139+
expect(selectedGameHydration.toolKeys).not.toContain("workspace.tools.templates-v2");
11401140
expect(selectedGameHydration.toolKeys.some((key) => key.endsWith(".schema") || key.endsWith(".state"))).toBe(false);
11411141
expect(Object.keys(selectedGameHydration.toolSessions).sort()).toEqual([
11421142
"asset-manager-v2",
11431143
"palette-manager-v2",
11441144
"preview-generator-v2",
1145-
"session-inspector-v2",
1146-
"templates-v2"
1145+
"session-inspector-v2"
1146+
]);
1147+
const selectedGameHydrationReport = await page.evaluate(() => window.__workspaceManagerV2App.activeSessionHydration.report);
1148+
expect(selectedGameHydrationReport.hydratedTools.map((tool) => tool.toolId)).toEqual([
1149+
"asset-manager-v2",
1150+
"palette-manager-v2",
1151+
"preview-generator-v2",
1152+
"session-inspector-v2"
1153+
]);
1154+
expect(selectedGameHydrationReport.skippedTools).toEqual([
1155+
{
1156+
reason: "starter/dev-only tool is not enabled by the selected game workspace config",
1157+
toolId: "templates-v2",
1158+
toolName: "Tool Starter V2"
1159+
}
11471160
]);
11481161
expect(Object.values(selectedGameHydration.toolSessions).every((session) => (
11491162
JSON.stringify(Object.keys(session).sort()) === JSON.stringify(["data", "dirty", "schema", "workspace"])
@@ -1172,12 +1185,11 @@ test.describe("Workspace Manager V2 bootstrap", () => {
11721185
});
11731186
expect(selectedGameHydration.toolSessions["asset-manager-v2"].state).toBeUndefined();
11741187
expect(Object.keys(selectedGameHydration.dataByTool["asset-manager-v2"].assets)).toHaveLength(14);
1175-
expect(selectedGameHydration.dataByTool["templates-v2"]).toBeNull();
1188+
expect(selectedGameHydration.toolSessions["templates-v2"]).toBeUndefined();
11761189
expect(Object.values(selectedGameHydration.dirtyByTool)).toEqual([
11771190
{ isDirty: false, reason: null, changedAt: null, changedKeys: [] },
11781191
{ isDirty: false, reason: null, changedAt: null, changedKeys: [] },
11791192
{ isDirty: false, reason: null, changedAt: null, changedKeys: [] },
1180-
{ isDirty: false, reason: null, changedAt: null, changedKeys: [] },
11811193
{ isDirty: false, reason: null, changedAt: null, changedKeys: [] }
11821194
]);
11831195
await page.evaluate(() => {
@@ -1210,8 +1222,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
12101222
const paletteTile = page.locator('[data-workspace-tool-id="palette-manager-v2"]');
12111223
const previewTile = page.locator('[data-workspace-tool-id="preview-generator-v2"]');
12121224
const sessionInspectorTile = page.locator('[data-workspace-tool-id="session-inspector-v2"]');
1213-
await expect(templateTile).toBeEnabled();
1225+
await expect(templateTile).toBeDisabled();
12141226
await expect(templateTile).toContainText("Tool Starter V2");
1227+
await expect(templateTile).toContainText("Not enabled for game");
12151228
await expect(templateTile).toContainText("Canonical V2 template");
12161229
await expect(assetTile).toBeEnabled();
12171230
await expect(assetTile).toContainText("Asset Manager V2");
@@ -1234,9 +1247,20 @@ test.describe("Workspace Manager V2 bootstrap", () => {
12341247
{ height: 142, width: 180 }
12351248
]);
12361249
await expect(page.locator("#statusLog")).toHaveValue(/OK Boundary contract: game\.gameData is runtime data; game\.workspace is editor\/tool state\. Runtime ignores game\.workspace; tools may read game\.gameData, write game\.workspace, and update game\.gameData only through explicit validated apply\/build\/export actions\./);
1237-
await expect(page.locator("#statusLog")).toHaveValue(/OK Hydrated workspace session for templates-v2, asset-manager-v2, palette-manager-v2, preview-generator-v2, session-inspector-v2\./);
1250+
await expect(page.locator("#statusLog")).toHaveValue(/OK Hydrated workspace session for asset-manager-v2, palette-manager-v2, preview-generator-v2, session-inspector-v2\./);
1251+
await expect(page.locator("#statusLog")).toHaveValue(/INFO Skipped workspace session hydration for templates-v2: starter\/dev-only tool is not enabled by the selected game workspace config\./);
12381252
await expect(page.locator("#statusLog")).toHaveValue(/OK Loaded Asteroids from \/games\/Asteroids\/game\.manifest\.json with 11 active palette colors and 14 managed assets\./);
12391253

1254+
await page.locator('[data-workspace-tool-id="session-inspector-v2"]').click();
1255+
await expect(page).toHaveURL(/session-inspector-v2\/index\.html.*launch=workspace/);
1256+
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id='sessionStorage:workspace.tools.asset-manager-v2']")).toHaveCount(1);
1257+
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id='sessionStorage:workspace.tools.palette-manager-v2']")).toHaveCount(1);
1258+
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id='sessionStorage:workspace.tools.preview-generator-v2']")).toHaveCount(1);
1259+
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id='sessionStorage:workspace.tools.session-inspector-v2']")).toHaveCount(1);
1260+
await expect(page.locator("#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id='sessionStorage:workspace.tools.templates-v2']")).toHaveCount(0);
1261+
await page.locator("#returnToWorkspaceButton").click();
1262+
await expect(page).toHaveURL(/workspace-manager-v2\/index\.html\?hostContextId=workspace-manager-v2-/);
1263+
12401264
const downloadPromise = page.waitForEvent("download");
12411265
await page.locator("#exportManifestButton").click();
12421266
const download = await downloadPromise;
@@ -1436,13 +1460,6 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14361460
await expect(page.locator("#exportManifestButton")).toBeEnabled();
14371461
await expect(page.locator("#statusLog")).toHaveValue(/OK Restored Asteroids workspace from session context workspace-manager-v2-/);
14381462

1439-
await page.locator('[data-workspace-tool-id="templates-v2"]').click();
1440-
await expect(page).toHaveURL(/templates-v2\/index\.html.*launch=workspace/);
1441-
await expect(page.locator('[data-launch-mode-nav="tool"]')).toBeHidden();
1442-
await expect(page.locator('[data-launch-mode-nav="workspace"]')).toBeVisible();
1443-
await expect(page.locator('[data-launch-mode-nav="workspace"]').getByRole("button")).toHaveText(["Return to Workspace"]);
1444-
await page.locator("#returnToWorkspaceButton").click();
1445-
await expect(page).toHaveURL(/workspace-manager-v2\/index\.html\?hostContextId=workspace-manager-v2-/);
14461463
await page.locator('[data-workspace-tool-id="palette-manager-v2"]').click();
14471464
await expect(page).toHaveURL(/palette-manager-v2\/index\.html.*launch=workspace/);
14481465
await expect(page.locator('[data-launch-mode-nav="tool"]')).toBeHidden();
@@ -1543,9 +1560,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15431560
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"id": "workspace-manager-v2-Asteroids-imported"/);
15441561
await expect(page.locator("#activeAssetRegistrySummary")).toHaveCount(0);
15451562
await expect(page.locator('[data-workspace-tool-id="asset-manager-v2"]')).toBeEnabled();
1546-
expect((await readWorkspaceSessionHydration(page)).toolKeys).toContain("workspace.tools.asset-manager-v2");
1563+
const importedHydration = await readWorkspaceSessionHydration(page);
1564+
expect(importedHydration.toolKeys).toContain("workspace.tools.asset-manager-v2");
1565+
expect(importedHydration.toolKeys).not.toContain("workspace.tools.templates-v2");
15471566
await expect(page.locator("#statusLog")).toHaveValue(/OK Boundary contract: game\.gameData is runtime data; game\.workspace is editor\/tool state\. Runtime ignores game\.workspace; tools may read game\.gameData, write game\.workspace, and update game\.gameData only through explicit validated apply\/build\/export actions\./);
1548-
await expect(page.locator("#statusLog")).toHaveValue(/OK Hydrated workspace session for templates-v2, asset-manager-v2, palette-manager-v2, preview-generator-v2, session-inspector-v2\./);
1567+
await expect(page.locator("#statusLog")).toHaveValue(/OK Hydrated workspace session for asset-manager-v2, palette-manager-v2, preview-generator-v2, session-inspector-v2\./);
1568+
await expect(page.locator("#statusLog")).toHaveValue(/INFO Skipped workspace session hydration for templates-v2: starter\/dev-only tool is not enabled by the selected game workspace config\./);
15491569
await expect(page.locator("#statusLog")).toHaveValue(/OK Imported schema-valid Workspace Manager V2 manifest workspace-manager-v2-Asteroids-imported\./);
15501570

15511571
const downloadPromise = page.waitForEvent("download");

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ export class WorkspaceManagerV2App {
215215
this.toolTiles.render({
216216
assetCount: result.assetCount,
217217
canLaunch: hydration.ok,
218+
enabledToolIds: hydration.ok ? hydration.hydratedToolIds : [],
218219
manifestStatus: "Schema-valid manifest",
219220
paletteSwatchCount: result.paletteSwatches.length
220221
});
@@ -230,6 +231,9 @@ export class WorkspaceManagerV2App {
230231
return;
231232
}
232233
this.statusLog.ok(`Hydrated workspace session for ${this.activeSessionHydration.hydratedToolIds.join(", ")}.`);
234+
(this.activeSessionHydration.skippedTools || []).forEach((tool) => {
235+
this.statusLog.info(`Skipped workspace session hydration for ${tool.toolId}: ${tool.reason}.`);
236+
});
233237
}
234238

235239
hasLaunchReadyContext() {
@@ -244,6 +248,10 @@ export class WorkspaceManagerV2App {
244248
this.statusLog.fail("Launch blocked: active game context and palette are required.");
245249
return;
246250
}
251+
if (!this.activeSessionHydration.hydratedToolIds.includes(toolId)) {
252+
this.statusLog.fail(`Launch blocked: ${toolId} is not enabled for ${this.activeGame.name}.`);
253+
return;
254+
}
247255
const validation = await this.contextService.validateGeneratedManifest(this.activeContext);
248256
if (!validation.ok) {
249257
this.statusLog.fail(`Launch blocked: ${validation.message}`);

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ export class ToolTilesControl {
2525
render({
2626
assetCount = 0,
2727
canLaunch = false,
28+
enabledToolIds = [],
2829
manifestStatus = "Waiting for manifest",
2930
paletteSwatchCount = 0
3031
} = {}) {
32+
const enabledToolIdSet = new Set(enabledToolIds);
3133
this.container.replaceChildren(...TOOL_GROUPS.map((group) => this.groupSection({
3234
assetCount,
3335
canLaunch,
36+
enabledToolIdSet,
3437
group,
3538
manifestStatus,
3639
paletteSwatchCount
@@ -53,7 +56,7 @@ export class ToolTilesControl {
5356
return manifestStatus;
5457
}
5558

56-
groupSection({ assetCount, canLaunch, group, manifestStatus, paletteSwatchCount }) {
59+
groupSection({ assetCount, canLaunch, enabledToolIdSet, group, manifestStatus, paletteSwatchCount }) {
5760
const section = document.createElement("section");
5861
section.className = "workspace-manager-v2__tool-group";
5962
section.setAttribute("aria-label", `${group} tools`);
@@ -70,6 +73,7 @@ export class ToolTilesControl {
7073
grid.append(this.tile({
7174
assetCount,
7275
canLaunch,
76+
enabledToolIdSet,
7377
manifestStatus,
7478
paletteSwatchCount,
7579
tool
@@ -80,12 +84,13 @@ export class ToolTilesControl {
8084
return section;
8185
}
8286

83-
tile({ assetCount, canLaunch, manifestStatus, paletteSwatchCount, tool }) {
87+
tile({ assetCount, canLaunch, enabledToolIdSet, manifestStatus, paletteSwatchCount, tool }) {
88+
const isEnabledForGame = canLaunch && enabledToolIdSet.has(tool.id);
8489
const button = document.createElement("button");
8590
button.type = "button";
8691
button.className = "workspace-manager-v2__tool-tile";
8792
button.dataset.workspaceToolId = tool.id;
88-
button.disabled = !canLaunch;
93+
button.disabled = !isEnabledForGame;
8994
button.addEventListener("click", () => {
9095
this.onLaunchTool(tool.id);
9196
});
@@ -96,7 +101,7 @@ export class ToolTilesControl {
96101

97102
const state = document.createElement("span");
98103
state.className = "workspace-manager-v2__tool-tile-state";
99-
state.textContent = canLaunch ? "Ready to launch" : "Waiting for manifest";
104+
state.textContent = isEnabledForGame ? "Ready to launch" : (canLaunch ? "Not enabled for game" : "Waiting for manifest");
100105

101106
const detailText = this.detailForTool(tool, { assetCount, manifestStatus, paletteSwatchCount });
102107

0 commit comments

Comments
 (0)