Skip to content

Commit 208d8c5

Browse files
author
DavidQ
committed
Bootstrap Workspace Manager V2 as the games-only workspace owner - PR_26126_111-workspace-manager-v2-bootstrap
1 parent 68ba12a commit 208d8c5

19 files changed

Lines changed: 1098 additions & 1 deletion

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ docs/dev/reports/codex_review.diff
2626
docs/dev/reports/coverage_changed_js_guardrail.txt
2727
docs/dev/reports/playwright_v8_coverage_report.txt
2828

29+
# Ignore media files
2930
*.mp4
3031
*.mkv
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# PR_26126_111 Manual Validation Notes
2+
3+
## Automated Validation
4+
- `npm run test:workspace-v2` passed.
5+
- Result: 17 Playwright tests passed.
6+
7+
## Manual Checks
8+
1. Open `tools/index.html`.
9+
- Expected: Workspace Manager V2 appears as a first-class tool card.
10+
- Expected: Workspace Manager V2 opens `/tools/workspace-manager-v2/index.html`.
11+
2. Open `tools/workspace-manager-v2/index.html`.
12+
- Expected: Launch Asset Manager V2 is disabled before selecting a game.
13+
- Expected: Active Game selector lists games-only workspaces.
14+
3. Select `Asteroids`.
15+
- Expected: Active Palette summary shows the Asteroids palette and color count.
16+
- Expected: Asset Registry summary shows a schema-ready empty Asset Manager V2 registry context.
17+
- Expected: Context output uses `games/Asteroids/` and `games/Asteroids/assets`.
18+
4. Click Launch Asset Manager V2.
19+
- Expected: Asset Manager V2 opens with `launch=workspace`, `fromTool=workspace-manager-v2`, and a `hostContextId`.
20+
- Expected: URL does not contain `workspace=prod`, `workspace=UAT`, or `gameId=Asteroids`.
21+
- Expected: Asset Manager V2 launch guard is hidden.
22+
- Expected: Asset Manager V2 workspace actions are visible.
23+
5. Open `tools/asset-manager-v2/index.html?workspace=prod`.
24+
- Expected: Launch guard overlay remains visible.
25+
- Expected: Reason states temporary workspace `prod` is not supported.
26+
27+
## Out Of Scope
28+
- Old `tools/workspace-v2/` behavior was not modified.
29+
- Legacy game manifest asset entries were not converted.
30+
- Sample JSON was not modified.
31+
- Full samples smoke test was skipped because this PR is limited to Workspace Manager V2 bootstrap and Workspace V2 tool Playwright coverage.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# PR_26126_111 Workspace Manager V2 Bootstrap Notes
2+
3+
## Scope
4+
- Added `tools/workspace-manager-v2/` as a first-class Workspace Manager V2 surface.
5+
- Registered Workspace Manager V2 through `tools/toolRegistry.js`; `tools/index.html` renders it from the shared registry.
6+
- Classified Workspace Manager V2 as a utilities tool in `tools/renderToolsIndex.js`.
7+
- Added Workspace Manager V2 Playwright launch coverage under `tests/playwright/tools/WorkspaceManagerV2.spec.mjs`.
8+
- Updated `test:workspace-v2` to include the dedicated Workspace Manager V2 Playwright spec.
9+
10+
## Workspace Ownership
11+
- Workspace Manager V2 owns a games-only context:
12+
- active game id
13+
- game root under `games/<game>/`
14+
- assets path under `games/<game>/assets`
15+
- active palette swatches read from the selected game manifest
16+
- schema-ready `tools.asset-browser.assets` registry context
17+
- The bootstrap context does not include sample or tools workspace roots.
18+
- Asset registry context starts as an empty schema-ready Asset Manager V2 registry so old Workspace V2 and legacy manifest asset records are not patched or remapped in this PR.
19+
20+
## Asset Manager V2 Launch
21+
- Production launch uses sessionStorage host context only:
22+
- `launch=workspace`
23+
- `fromTool=workspace-manager-v2`
24+
- `hostContextId=<session key>`
25+
- Workspace Manager V2 does not generate or support `?workspace=prod`.
26+
- Asset Manager V2 direct `?workspace=prod` remains blocked by the launch guard overlay.
27+
- Temporary `?workspace=UAT` behavior remains isolated to Asset Manager V2 UAT testing.
28+
29+
## Playwright Coverage
30+
- Validates Workspace Manager V2 registration from `tools/index.html`.
31+
- Validates game selection, palette summary, asset registry summary, session context output, and launch button enablement.
32+
- Validates Asset Manager V2 launch through Workspace Manager V2 session context.
33+
- Validates the direct `?workspace=prod` launch guard remains enforced.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"codex:review-artifacts": "node ./scripts/write-codex-review-artifacts.mjs",
1515
"test:asset-manager-v2": "playwright test tests/playwright/tools/AssetManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list",
1616
"test:preview-generator-v2": "playwright test tests/playwright/tools/PreviewGeneratorV2Baseline.spec.mjs --project=playwright --workers=1 --reporter=list",
17-
"test:workspace-v2": "playwright test tests/playwright/tools/AssetManagerV2.spec.mjs tests/playwright/tools/PreviewGeneratorV2Baseline.spec.mjs --project=playwright --workers=1 --reporter=list",
17+
"test:workspace-v2": "playwright test tests/playwright/tools/AssetManagerV2.spec.mjs tests/playwright/tools/PreviewGeneratorV2Baseline.spec.mjs tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list",
1818
"test:launch-smoke": "node ./tests/runtime/LaunchSmokeAllEntries.test.mjs",
1919
"test:launch-smoke:games": "node ./tests/runtime/LaunchSmokeAllEntries.test.mjs --games",
2020
"test:workspace-manager:games": "node ./tests/runtime/GamesIndexWorkspaceManagerOpen.test.mjs",

tests/helpers/playwrightV8CoverageReporter.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ export class PlaywrightV8CoverageReporter {
327327
{ name: "Palette Manager V2", prefix: "tools/palette-manager-v2/" },
328328
{ name: "Tool Template V2", prefix: "tools/templates-v2/" },
329329
{ name: "Workspace V2", prefix: "tools/workspace-v2/" },
330+
{ name: "Workspace Manager V2", prefix: "tools/workspace-manager-v2/" },
330331
{ name: "Workspace Manager", prefix: "tools/workspace-manager/" }
331332
];
332333
return toolEntryPoints.map(({ name, prefix }) => {
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { expect, test } from "@playwright/test";
2+
import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs";
3+
import { workspaceV2CoverageReporter as coverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs";
4+
5+
async function openWorkspaceManagerV2(page) {
6+
const server = await startRepoServer();
7+
await coverageReporter.start(page);
8+
await page.goto(`${server.baseUrl}/tools/workspace-manager-v2/index.html`, { waitUntil: "networkidle" });
9+
return server;
10+
}
11+
12+
async function openAssetManagerV2(page, query = "") {
13+
const server = await startRepoServer();
14+
await coverageReporter.start(page);
15+
await page.goto(`${server.baseUrl}/tools/asset-manager-v2/index.html${query}`, { waitUntil: "networkidle" });
16+
return server;
17+
}
18+
19+
async function openToolsIndex(page) {
20+
const server = await startRepoServer();
21+
await coverageReporter.start(page);
22+
await page.goto(`${server.baseUrl}/tools/index.html`, { waitUntil: "networkidle" });
23+
return server;
24+
}
25+
26+
test.describe("Workspace Manager V2 bootstrap", () => {
27+
test.afterAll(async () => {
28+
await coverageReporter.writeReport();
29+
});
30+
31+
test("registers Workspace Manager V2 from the tools index", async ({ page }) => {
32+
const server = await openToolsIndex(page);
33+
const pageErrors = [];
34+
35+
page.on("pageerror", (error) => {
36+
pageErrors.push(error.message);
37+
});
38+
39+
try {
40+
const card = page.locator(".tools-platform-card", { has: page.locator("h3", { hasText: "Workspace Manager V2" }) });
41+
await expect(card).toBeVisible();
42+
await expect(card.locator("a", { hasText: "Workspace Manager V2" })).toHaveAttribute("href", "/tools/workspace-manager-v2/index.html");
43+
await expect(card).toContainText("First-Class Tool V2 workspace surface");
44+
await expect(card).toContainText("Workspace");
45+
expect(pageErrors).toEqual([]);
46+
} finally {
47+
await coverageReporter.stop(page);
48+
await server.close();
49+
}
50+
});
51+
52+
test("launches Asset Manager V2 through Workspace Manager V2 session context", async ({ page }) => {
53+
const server = await openWorkspaceManagerV2(page);
54+
const pageErrors = [];
55+
56+
page.on("pageerror", (error) => {
57+
pageErrors.push(error.message);
58+
});
59+
60+
try {
61+
await expect(page.locator("body[data-tool-id='workspace-manager-v2']")).toBeVisible();
62+
await expect(page.locator("#launchAssetManagerV2Button")).toBeDisabled();
63+
await expect(page.locator("#activeGameSelect option")).toHaveText([
64+
"Select a game",
65+
"Asteroids",
66+
"Gravity Well",
67+
"Pong"
68+
]);
69+
70+
await page.locator("#activeGameSelect").selectOption("Asteroids");
71+
await expect(page.locator("#activeGameSummary")).toContainText("games/Asteroids/");
72+
await expect(page.locator("#activePaletteSummary")).toContainText("Asteroids Palette");
73+
await expect(page.locator("#activePaletteSummary")).toContainText("active colors");
74+
await expect(page.locator("#activeAssetRegistrySummary")).toHaveText("Schema-ready Asset Manager V2 registry context contains 0 managed assets.");
75+
await expect(page.locator("#launchContextSummary")).toHaveText("Session launch context is ready for Asteroids.");
76+
await expect(page.locator("#workspaceContextOutput")).toContainText('"gameRoot": "games/Asteroids/"');
77+
await expect(page.locator("#workspaceContextOutput")).toContainText('"assetsPath": "games/Asteroids/assets"');
78+
await expect(page.locator("#workspaceContextOutput")).toContainText('"owner": "workspace-manager-v2"');
79+
await expect(page.locator("#workspaceContextOutput")).not.toContainText("samples/");
80+
await expect(page.locator("#workspaceContextOutput")).not.toContainText("tools/");
81+
await expect(page.locator("#launchAssetManagerV2Button")).toBeEnabled();
82+
83+
await page.locator("#launchAssetManagerV2Button").click();
84+
await expect(page).toHaveURL(/asset-manager-v2\/index\.html.*launch=workspace/);
85+
await expect(page).toHaveURL(/fromTool=workspace-manager-v2/);
86+
await expect(page).toHaveURL(/hostContextId=workspace-manager-v2-/);
87+
await expect(page).not.toHaveURL(/workspace=prod/i);
88+
await expect(page).not.toHaveURL(/workspace=UAT/i);
89+
await expect(page).not.toHaveURL(/gameId=Asteroids/);
90+
await expect(page.locator("#assetLaunchGuard")).toBeHidden();
91+
await expect(page.locator(".asset-manager-v2__tool__menu")).toBeHidden();
92+
await expect(page.locator(".asset-manager-v2__workspace__menu")).toBeVisible();
93+
await expect(page.locator("#statusLog")).toHaveValue(/Workspace mode loaded 0 validated assets from tools\.asset-browser\.assets/);
94+
await expect(page.locator("#statusLog")).toHaveValue(/Workspace mode loaded \d+ palette colors from tools\.palette-browser\.swatches/);
95+
96+
const workspacePreviewContext = await page.evaluate(async () => {
97+
const { WorkspaceBridge } = await import("/tools/asset-manager-v2/js/services/WorkspaceBridge.js");
98+
return new WorkspaceBridge({ windowRef: window }).readWorkspacePreviewContext();
99+
});
100+
expect(workspacePreviewContext).toEqual({
101+
workspaceMode: true,
102+
workspaceGameId: "Asteroids",
103+
workspaceGameRoot: "games/Asteroids/"
104+
});
105+
expect(JSON.stringify(workspacePreviewContext)).not.toMatch(/samples|tools/i);
106+
107+
const storedContext = await page.evaluate(() => {
108+
const url = new URL(window.location.href);
109+
const hostContextId = url.searchParams.get("hostContextId");
110+
return JSON.parse(sessionStorage.getItem(hostContextId));
111+
});
112+
expect(storedContext.version).toBe("workspace-manager-v2");
113+
expect(storedContext.gameRoot).toBe("games/Asteroids/");
114+
expect(storedContext.assetsPath).toBe("games/Asteroids/assets");
115+
expect(storedContext.workspaceManifest.tools["palette-browser"].swatches.length).toBeGreaterThan(0);
116+
expect(storedContext.workspaceManifest.tools["asset-browser"].assets).toEqual({});
117+
expect(JSON.stringify(storedContext)).not.toMatch(/samples\//i);
118+
expect(JSON.stringify(storedContext.workspaceManifest.workspaceMetadata)).not.toMatch(/tools\//i);
119+
expect(pageErrors).toEqual([]);
120+
} finally {
121+
await coverageReporter.stop(page);
122+
await server.close();
123+
}
124+
});
125+
126+
test("keeps direct Asset Manager V2 workspace prod launch blocked", async ({ page }) => {
127+
const server = await openAssetManagerV2(page, "?workspace=prod");
128+
const pageErrors = [];
129+
130+
page.on("pageerror", (error) => {
131+
pageErrors.push(error.message);
132+
});
133+
134+
try {
135+
await expect(page.locator("#assetLaunchGuard")).toBeVisible();
136+
await expect(page.locator("#assetLaunchGuardMessage")).toHaveText("Asset Manager V2 is only available through Workspace Manager with a game workspace and palette.");
137+
await expect(page.locator("#assetLaunchGuardReason")).toContainText("Temporary workspace prod is not supported.");
138+
await expect(page.locator("body")).toHaveClass(/asset-manager-v2--launch-blocked/);
139+
expect(pageErrors).toEqual([]);
140+
} finally {
141+
await coverageReporter.stop(page);
142+
await server.close();
143+
}
144+
});
145+
});

tools/renderToolsIndex.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ function classifyToolGroup(toolId) {
8282
"asset-browser",
8383
"asset-manager-v2",
8484
"asset-pipeline",
85+
"workspace-manager-v2",
8586
"tile-model-converter",
8687
"physics-sandbox",
8788
"3d-json-payload"

tools/toolRegistry.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,29 @@ export const TOOL_REGISTRY = Object.freeze([
215215
],
216216
visibleInToolsList: true
217217
},
218+
{
219+
id: "workspace-manager-v2",
220+
name: "Workspace Manager V2",
221+
displayName: "Workspace Manager V2",
222+
shortDescription: "Owns active game, palette, asset registry, and Asset Manager V2 launch context.",
223+
shortLabel: "Workspace",
224+
path: "workspace-manager-v2",
225+
folderName: "workspace-manager-v2",
226+
entryPoint: "workspace-manager-v2/index.html",
227+
description: "First-Class Tool V2 workspace surface for games-only context selection and Asset Manager V2 session launch.",
228+
showcaseTag: "Workspace",
229+
showcaseStatus: "Context Bootstrap",
230+
active: true,
231+
legacy: false,
232+
order: 6.75,
233+
sampleEntryPoints: [
234+
{
235+
label: "README",
236+
path: "workspace-manager-v2/README.md"
237+
}
238+
],
239+
visibleInToolsList: true
240+
},
218241
{
219242
id: "palette-manager-v2",
220243
name: "Palette Manager V2",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Workspace Manager V2
2+
3+
Workspace Manager V2 is the first-class games-only context surface for Asset Manager V2 launch.
4+
5+
It owns the active game, active palette, schema-ready asset registry context, and sessionStorage launch handoff used by Asset Manager V2. Direct production launch uses session/state context only; URL query access remains limited to Asset Manager V2 temporary `?workspace=UAT` testing.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>Workspace Manager V2</title>
7+
<link rel="stylesheet" href="../../src/engine/theme/main.css">
8+
<link rel="stylesheet" href="../../src/engine/ui/hubCommon.css">
9+
<link rel="stylesheet" href="../../src/engine/theme/accordionV2/accordionV2.css">
10+
<link rel="stylesheet" href="./styles/workspaceManagerV2.css">
11+
<script type="module" src="./js/bootstrap.js"></script>
12+
</head>
13+
<body class="tools-platform-tool-page tools-platform-surface hub-page-tools" data-tool-id="workspace-manager-v2">
14+
<details class="is-collapsible" open>
15+
<summary class="is-collapsible__summary" data-workspace-manager-v2-summary>Hide Header and Details</summary>
16+
<div class="is-collapsible__content">
17+
<div id="shared-theme-header"></div>
18+
<header class="workspace-manager-v2__header" data-workspace-manager-v2-header>
19+
<section class="tools-platform-frame workspace-manager-v2__local-shell-frame">
20+
<div class="tools-platform-frame__accordion-content">
21+
<div class="tools-platform-frame__accordion-summary">
22+
<div class="tools-platform-frame__summary-copy">
23+
<h1 class="tools-platform-frame__title" data-tool-id="workspace-manager-v2">Workspace Manager V2</h1>
24+
<h2 class="tools-platform-frame__eyebrow">Games-only launch context</h2>
25+
</div>
26+
<div class="tools-platform-frame__summary-meta">
27+
<div class="tools-platform-frame__meta">Owns active game, palette, asset registry, and Asset Manager V2 session launch context.</div>
28+
</div>
29+
</div>
30+
</div>
31+
</section>
32+
</header>
33+
</div>
34+
</details>
35+
36+
<nav class="workspace-manager-v2__menu" aria-label="Workspace Manager V2 actions">
37+
<button id="launchAssetManagerV2Button" type="button" disabled>Launch Asset Manager V2</button>
38+
</nav>
39+
40+
<main class="workspace-manager-v2 app-shell" data-tool-id="workspace-manager-v2">
41+
<aside class="workspace-manager-v2__panel workspace-manager-v2__panel--left" aria-label="Workspace setup">
42+
<section class="accordion-v2 workspace-manager-v2__accordion is-open" data-accordion-v2-open="true">
43+
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="activeGameContent">
44+
<span>Active Game</span>
45+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
46+
</button>
47+
<div id="activeGameContent" class="accordion-v2__content">
48+
<label class="workspace-manager-v2__field" for="activeGameSelect">
49+
<span>Game</span>
50+
<select id="activeGameSelect"></select>
51+
</label>
52+
<p id="activeGameSummary" class="workspace-manager-v2__hint">Select a game workspace.</p>
53+
</div>
54+
</section>
55+
</aside>
56+
57+
<section class="workspace-manager-v2__panel workspace-manager-v2__panel--center" aria-label="Workspace context">
58+
<section class="accordion-v2 workspace-manager-v2__accordion workspace-manager-v2__accordion--fill is-open" data-accordion-v2-open="true">
59+
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="workspaceContextContent">
60+
<span>Workspace Context</span>
61+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
62+
</button>
63+
<div id="workspaceContextContent" class="accordion-v2__content">
64+
<div class="workspace-manager-v2__summary-grid" aria-label="Active workspace context summary">
65+
<section class="workspace-manager-v2__summary-card" aria-label="Active palette summary">
66+
<h2>Active Palette</h2>
67+
<p id="activePaletteSummary">No active palette selected.</p>
68+
</section>
69+
<section class="workspace-manager-v2__summary-card" aria-label="Active asset registry summary">
70+
<h2>Asset Registry</h2>
71+
<p id="activeAssetRegistrySummary">No asset registry context created.</p>
72+
</section>
73+
<section class="workspace-manager-v2__summary-card" aria-label="Launch context summary">
74+
<h2>Launch Context</h2>
75+
<p id="launchContextSummary">Asset Manager V2 launch is waiting for a game and palette.</p>
76+
</section>
77+
</div>
78+
<pre id="workspaceContextOutput" class="workspace-manager-v2__output">{}</pre>
79+
</div>
80+
</section>
81+
</section>
82+
83+
<aside class="workspace-manager-v2__panel workspace-manager-v2__panel--right" aria-label="Workspace status">
84+
<section class="accordion-v2 workspace-manager-v2__accordion workspace-manager-v2__accordion--status is-open" data-accordion-v2-open="true">
85+
<div class="accordion-v2__header workspace-manager-v2__status-accordion-header" role="button" tabindex="0" aria-expanded="true" aria-controls="statusLogContent">
86+
<span>Status</span>
87+
<div class="workspace-manager-v2__status-header-actions">
88+
<button id="clearStatusButton" class="workspace-manager-v2__status-clear-button" type="button">Clear</button>
89+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
90+
</div>
91+
</div>
92+
<div id="statusLogContent" class="accordion-v2__content">
93+
<textarea id="statusLog" readonly rows="10" aria-label="Workspace Manager V2 status log"></textarea>
94+
</div>
95+
</section>
96+
</aside>
97+
</main>
98+
<script type="module" src="../../src/engine/theme/mount-shared-header.js"></script>
99+
</body>
100+
</html>

0 commit comments

Comments
 (0)