Skip to content

Commit 44132af

Browse files
author
DavidQ
committed
Hydrate Workspace Manager tool session data after explicit game selection - PR_26128_010-workspace-session-hydration
1 parent 6a97a54 commit 44132af

6 files changed

Lines changed: 366 additions & 2 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# PR_26128_010 Playwright Workspace Session Hydration
2+
3+
## Command
4+
`npm run test:workspace-v2`
5+
6+
## Result
7+
PASS: 12/12 tests passed.
8+
9+
## Targeted Coverage Added
10+
- Initial Workspace Manager V2 load clears stale active-game hydration keys.
11+
- Before repo selection, `workspace.repo.reference` is absent and no `workspace.tools.*` keys exist.
12+
- After successful repo selection, `workspace.repo.reference` is present and per-tool keys are still absent.
13+
- On repo load failure, repo reference and per-tool hydration remain absent.
14+
- On a repo with no valid game manifests, the repo reference is stored, Active Game remains disabled, and tool hydration remains absent.
15+
- After selecting Asteroids, all enabled launchable tools receive stable schema/state keys.
16+
- Asset Manager V2 receives its payload schema ref and state payload.
17+
- Preview Generator V2 and Tool Starter V2 receive workspace launch context schema refs.
18+
- Importing a schema-valid game manifest also hydrates per-tool session keys before tools enable.
19+
20+
## Existing Launch Coverage Confirmed
21+
- Workspace Manager V2 launches from the tools index.
22+
- Active Game starts empty/disabled with no Asteroids preselection.
23+
- Workspace Manager V2 discovers schema-valid `game.manifest.json` files and skips invalid manifests with path/reason log entries.
24+
- Asset Manager V2, Palette Manager V2, Tool Starter V2, and Preview Generator V2 still launch from Workspace Manager V2.
25+
- Preview Generator V2 still receives display-only workspace launch context.
26+
- Direct Asset Manager V2 production launch remains blocked.
27+
28+
## Skipped
29+
Full samples smoke test was skipped as requested because this PR is limited to Workspace Manager V2 session hydration and the targeted workspace V2 suite exercises the affected tool launch paths.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# PR_26128_010 Schema Verification
2+
3+
## Scope
4+
- Verified `tools/schemas/game.manifest.schema.json`.
5+
- Verified `tools/schemas/workspace.manifest.schema.json`.
6+
- Verified Workspace Manager V2 launchable tool schema references used for session hydration.
7+
8+
## Findings
9+
- `game.manifest.schema.json` remains the SSoT schema for `games/**/game.manifest.json`.
10+
- `game.gameData` remains runtime-owned and requires `launch`.
11+
- `game.gameData` continues to reject `workspace` and `tools` branches.
12+
- `game.workspace` remains editor/tool-owned workspace state.
13+
- `workspace.manifest.schema.json` remains the workspace/toolState container schema.
14+
- Workspace tool payload refs remain schema references, not inlined schemas.
15+
- Required workspace tool payloads remain `palette-manager-v2` and `asset-manager-v2`.
16+
- All 17 workspace tool schema references resolve to existing files.
17+
18+
## Workspace Manager V2 Launchable Tool Schema Basis
19+
- `asset-manager-v2`: `./tools/asset-manager-v2.schema.json`, hydrated as `tools/schemas/tools/asset-manager-v2.schema.json`.
20+
- `palette-manager-v2`: `./tools/palette-manager-v2.schema.json`, hydrated as `tools/schemas/tools/palette-manager-v2.schema.json`.
21+
- `preview-generator-v2`: workspace launch context only; hydrated against `tools/schemas/workspace.manifest.schema.json`.
22+
- `templates-v2`: workspace launch context only; hydrated against `tools/schemas/workspace.manifest.schema.json`.
23+
24+
## Verification
25+
- Parsed game manifest schema and workspace manifest schema with Node.
26+
- Confirmed game manifest schema id: `tools/schemas/game.manifest.schema.json`.
27+
- Confirmed workspace manifest schema id: `tools/schemas/workspace.manifest.schema.json`.
28+
- Confirmed unresolved workspace tool schema refs: `[]`.
29+
- Confirmed launchable tool schema mapping matches current Workspace Manager V2 usage.
30+
31+
Result: PASS.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# PR_26128_010 Workspace Session Hydration
2+
3+
## Summary
4+
Workspace Manager V2 now hydrates session storage only after explicit user progress through repo selection and valid game selection.
5+
6+
## Runtime Changes
7+
- Initial Workspace Manager V2 load clears stale `workspace.repo.reference` and `workspace.tools.*` hydration unless the page is explicitly returning with `hostContextId`.
8+
- Repo selection stores a serializable repo reference at `workspace.repo.reference` after repo discovery succeeds.
9+
- Repo load failure clears/disables Active Game and leaves no repo/tool hydration.
10+
- Valid game selection hydrates each enabled launchable tool with stable keys:
11+
- `workspace.tools.<tool-id>.schema`
12+
- `workspace.tools.<tool-id>.state`
13+
- Tool tiles and export stay disabled unless session hydration succeeds.
14+
- Game selection changes clear prior per-tool hydration before building the next context.
15+
16+
## Hydrated Tools
17+
- `templates-v2`
18+
- `asset-manager-v2`
19+
- `palette-manager-v2`
20+
- `preview-generator-v2`
21+
22+
## Boundary Notes
23+
- No cross-tool direct communication was added.
24+
- No repo write behavior was changed.
25+
- No sample JSON or roadmap content was modified.
26+
- Runtime behavior still ignores `game.workspace`; only Workspace Manager V2 reads it to build editor/tool context.
27+
- The repo value in session storage is a serializable reference, not a live `FileSystemDirectoryHandle`; live handle access remains user-selected in the active page.
28+
29+
## Validation
30+
- `npm run test:workspace-v2`: PASS, 12 tests passed.
31+
- Schema verification report: PASS.
32+
- Initial load has no active game session hydration: PASS.
33+
- Repo reference appears only after successful repo selection/discovery: PASS.
34+
- Tool session keys appear only after valid game selection/open: PASS.
35+
- Tools remain disabled until valid game selection and session hydration: PASS.
36+
- Tools enable after session hydration succeeds: PASS.
37+
- Default Asteroids selection did not return: PASS.
38+
39+
## Skipped
40+
Full samples smoke test was skipped as requested. This PR is scoped to Workspace Manager V2 session hydration and schema/reference verification; the targeted Workspace Manager V2 suite covers the affected repo selection, Active Game gating, launch, Preview Generator V2, Palette Manager V2, Asset Manager V2, and UAT paths.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,29 @@ async function expectWorkspaceToolsDisabled(page) {
140140
expect(await page.locator("#workspaceToolTiles [data-workspace-tool-id]").evaluateAll((tiles) => tiles.every((tile) => tile.disabled))).toBe(true);
141141
}
142142

143+
async function readWorkspaceSessionHydration(page) {
144+
return await page.evaluate(() => {
145+
const parseJson = (key) => {
146+
const value = window.sessionStorage.getItem(key);
147+
return value ? JSON.parse(value) : null;
148+
};
149+
const keys = Array.from({ length: window.sessionStorage.length }, (_, index) => window.sessionStorage.key(index))
150+
.filter(Boolean)
151+
.sort();
152+
const toolKeys = keys.filter((key) => key.startsWith("workspace.tools."));
153+
return {
154+
repoReference: parseJson("workspace.repo.reference"),
155+
schemaByTool: Object.fromEntries(toolKeys
156+
.filter((key) => key.endsWith(".schema"))
157+
.map((key) => [key.slice("workspace.tools.".length, -".schema".length), parseJson(key)])),
158+
stateByTool: Object.fromEntries(toolKeys
159+
.filter((key) => key.endsWith(".state"))
160+
.map((key) => [key.slice("workspace.tools.".length, -".state".length), parseJson(key)])),
161+
toolKeys
162+
};
163+
});
164+
}
165+
143166
test.describe("Workspace Manager V2 bootstrap", () => {
144167
test.afterAll(async () => {
145168
await coverageReporter.writeReport();
@@ -275,6 +298,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
275298
await page.addInitScript((manifest) => {
276299
window.sessionStorage.setItem("workspace-manager-v2-stale-context", JSON.stringify(manifest));
277300
window.sessionStorage.setItem("workspace-manager-v2-active-host-context-id", "workspace-manager-v2-stale-context");
301+
window.sessionStorage.setItem("workspace.repo.reference", JSON.stringify({ displayName: "StaleRepo" }));
302+
window.sessionStorage.setItem("workspace.tools.asset-manager-v2.schema", JSON.stringify({ toolId: "asset-manager-v2" }));
303+
window.sessionStorage.setItem("workspace.tools.asset-manager-v2.state", JSON.stringify({ toolId: "asset-manager-v2" }));
278304
}, staleGameManifest.game.workspace);
279305
const server = await openWorkspaceManagerV2(page);
280306

@@ -289,6 +315,10 @@ test.describe("Workspace Manager V2 bootstrap", () => {
289315
await expect(page.locator("#workspaceContextOutput")).toHaveValue("{}");
290316
await expectWorkspaceToolsDisabled(page);
291317
await expect(page.locator("#statusLog")).not.toHaveValue(/Restored Asteroids workspace/);
318+
expect(await readWorkspaceSessionHydration(page)).toMatchObject({
319+
repoReference: null,
320+
toolKeys: []
321+
});
292322
expect(pageErrors).toEqual([]);
293323
} finally {
294324
await coverageReporter.stop(page);
@@ -309,8 +339,23 @@ test.describe("Workspace Manager V2 bootstrap", () => {
309339
await expect(page.locator("#activeGameSelect option")).toHaveCount(0);
310340
await expect(page.locator("#activeGameSummary")).toHaveText("Pick a repo folder to discover schema-valid game manifests.");
311341
await expectWorkspaceToolsDisabled(page);
342+
expect(await readWorkspaceSessionHydration(page)).toMatchObject({
343+
repoReference: null,
344+
toolKeys: []
345+
});
312346

313347
await selectMockRepo(page);
348+
expect(await readWorkspaceSessionHydration(page)).toMatchObject({
349+
repoReference: {
350+
source: "workspace-manager-v2",
351+
kind: "file-system-directory-handle-reference",
352+
storageKey: "workspace.repo.reference",
353+
handleKind: "directory",
354+
handleName: "HTML-JavaScript-Gaming",
355+
displayName: "HTML-JavaScript-Gaming"
356+
},
357+
toolKeys: []
358+
});
314359
await expect(page.locator("#activeGameSummary")).toHaveText("Discovered 3 schema-valid game manifests from HTML-JavaScript-Gaming.");
315360
await expect(page.locator("#statusLog")).toHaveValue(/INFO SKIP games\/InvalidWorkspace\/game\.manifest\.json: Game manifest failed schema validation: root\.game is required/);
316361
await expect(page.locator("#statusLog")).toHaveValue(/INFO SKIP games\/MissingManifest\/game\.manifest\.json: game\.manifest\.json not found/);
@@ -359,6 +404,10 @@ test.describe("Workspace Manager V2 bootstrap", () => {
359404
await expectWorkspaceToolsDisabled(page);
360405
await expect(page.locator("#activeGameSummary")).toHaveText(/Selected repo is missing games\//);
361406
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Repo load failed: Selected repo is missing games\//);
407+
expect(await readWorkspaceSessionHydration(page)).toMatchObject({
408+
repoReference: null,
409+
toolKeys: []
410+
});
362411

363412
await page.evaluate(() => {
364413
window.__workspaceManagerV2MockRepoConfig = {
@@ -374,6 +423,17 @@ test.describe("Workspace Manager V2 bootstrap", () => {
374423
await expect(page.locator("#activeGameSummary")).toHaveText("No schema-valid game manifests were found in NoValidGamesRepo.");
375424
await expect(page.locator("#statusLog")).toHaveValue(/INFO SKIP games\/InvalidWorkspace\/game\.manifest\.json: Game manifest failed schema validation: root\.game is required/);
376425
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Repo load failed: No schema-valid game\.manifest\.json files were found in NoValidGamesRepo\./);
426+
expect(await readWorkspaceSessionHydration(page)).toMatchObject({
427+
repoReference: {
428+
source: "workspace-manager-v2",
429+
kind: "file-system-directory-handle-reference",
430+
storageKey: "workspace.repo.reference",
431+
handleKind: "directory",
432+
handleName: "NoValidGamesRepo",
433+
displayName: "NoValidGamesRepo"
434+
},
435+
toolKeys: []
436+
});
377437

378438
await selectMockRepo(page, { repoName: "SecondRepo" });
379439
await expect(page.locator("#activeGameSummary")).toHaveText("Discovered 3 schema-valid game manifests from SecondRepo.");
@@ -441,6 +501,14 @@ test.describe("Workspace Manager V2 bootstrap", () => {
441501
tiles.every((tile) => Array.from(tile.querySelectorAll(".workspace-manager-v2__tool-tile-action"), (action) => action.textContent.trim()).join("|") === "How To Use|Read Me")
442502
))).toBe(true);
443503
await selectMockRepo(page);
504+
expect(await readWorkspaceSessionHydration(page)).toMatchObject({
505+
repoReference: {
506+
displayName: "HTML-JavaScript-Gaming",
507+
handleName: "HTML-JavaScript-Gaming",
508+
kind: "file-system-directory-handle-reference"
509+
},
510+
toolKeys: []
511+
});
444512
const compactCenterLayout = await page.evaluate(() => {
445513
const getRect = (selector) => {
446514
const element = document.querySelector(selector);
@@ -485,6 +553,41 @@ test.describe("Workspace Manager V2 bootstrap", () => {
485553
await expect(page.locator("#workspaceContextOutput")).not.toHaveValue(/"workspaceManifest"/);
486554
await expect(page.locator("#workspaceContextOutput")).not.toHaveValue(/"workspaceMetadata"/);
487555
await expect(page.locator("#workspaceContextOutput")).not.toHaveValue(/samples\//);
556+
const selectedGameHydration = await readWorkspaceSessionHydration(page);
557+
expect(selectedGameHydration.toolKeys).toEqual([
558+
"workspace.tools.asset-manager-v2.schema",
559+
"workspace.tools.asset-manager-v2.state",
560+
"workspace.tools.palette-manager-v2.schema",
561+
"workspace.tools.palette-manager-v2.state",
562+
"workspace.tools.preview-generator-v2.schema",
563+
"workspace.tools.preview-generator-v2.state",
564+
"workspace.tools.templates-v2.schema",
565+
"workspace.tools.templates-v2.state"
566+
]);
567+
expect(selectedGameHydration.schemaByTool["asset-manager-v2"]).toMatchObject({
568+
source: "workspace-manager-v2",
569+
toolId: "asset-manager-v2",
570+
schemaRole: "workspace-tool-payload",
571+
schemaRef: "tools/schemas/tools/asset-manager-v2.schema.json",
572+
workspaceSchemaRef: "tools/schemas/workspace.manifest.schema.json"
573+
});
574+
expect(selectedGameHydration.schemaByTool["preview-generator-v2"]).toMatchObject({
575+
source: "workspace-manager-v2",
576+
toolId: "preview-generator-v2",
577+
schemaRole: "workspace-launch-context",
578+
schemaRef: "tools/schemas/workspace.manifest.schema.json"
579+
});
580+
expect(selectedGameHydration.stateByTool["asset-manager-v2"]).toMatchObject({
581+
source: "workspace-manager-v2",
582+
toolId: "asset-manager-v2",
583+
workspaceManifestId: "workspace-manager-v2-Asteroids",
584+
gameId: "Asteroids",
585+
gameRoot: "games/Asteroids/",
586+
assetsPath: "games/Asteroids/assets",
587+
repoReferenceKey: "workspace.repo.reference"
588+
});
589+
expect(Object.keys(selectedGameHydration.stateByTool["asset-manager-v2"].payload.assets)).toHaveLength(14);
590+
expect(selectedGameHydration.stateByTool["templates-v2"].payload).toBeNull();
488591
await page.evaluate(() => {
489592
Object.defineProperty(navigator, "clipboard", {
490593
configurable: true,
@@ -534,6 +637,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
534637
{ height: 142, width: 180 }
535638
]);
536639
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\./);
640+
await expect(page.locator("#statusLog")).toHaveValue(/OK Hydrated workspace session for templates-v2, asset-manager-v2, palette-manager-v2, preview-generator-v2\./);
537641
await expect(page.locator("#statusLog")).toHaveValue(/OK Loaded Asteroids from \/games\/Asteroids\/game\.manifest\.json with 11 active palette colors and 14 managed assets\./);
538642

539643
const downloadPromise = page.waitForEvent("download");
@@ -829,7 +933,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
829933
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"id": "workspace-manager-v2-Asteroids-imported"/);
830934
await expect(page.locator("#activeAssetRegistrySummary")).toHaveCount(0);
831935
await expect(page.locator('[data-workspace-tool-id="asset-manager-v2"]')).toBeEnabled();
936+
expect((await readWorkspaceSessionHydration(page)).toolKeys).toContain("workspace.tools.asset-manager-v2.state");
832937
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\./);
938+
await expect(page.locator("#statusLog")).toHaveValue(/OK Hydrated workspace session for templates-v2, asset-manager-v2, palette-manager-v2, preview-generator-v2\./);
833939
await expect(page.locator("#statusLog")).toHaveValue(/OK Imported schema-valid Workspace Manager V2 manifest workspace-manager-v2-Asteroids-imported\./);
834940

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

0 commit comments

Comments
 (0)