Skip to content

Commit c7f8cfa

Browse files
author
DavidQ
committed
Fix V2 schema naming workspace launch and manifest save flow - PR_26126_116-v2-tool-schema-naming-and-workspace-launch-fix
1 parent e56a8e0 commit c7f8cfa

27 files changed

Lines changed: 818 additions & 107 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# PR_26126_116 Manual Validation Notes
2+
3+
- `npm run test:asset-manager-v2`: PASS, 8 tests.
4+
- `npm run test:workspace-v2`: PASS, 20 tests.
5+
- Verified Asset Manager V2 loads `asset-manager-v2.schema.json`.
6+
- Verified Workspace Manager V2 generated manifests use `tools.palette-manager-v2` and `tools.asset-manager-v2`.
7+
- Verified Save exports schema-valid Workspace Manager V2 manifest JSON.
8+
- Verified Save blocks when generated context fails schema validation and logs the exact failure.
9+
- Verified Asset Manager V2 launched from Workspace Manager V2 shows only `Return to Workspace` in workspace nav.
10+
- Verified `Return to Workspace` navigates back to Workspace Manager V2.
11+
- Verified `tools/workspace-v2` and sample JSON were not modified.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# PR_26126_116 Save Manifest Notes
2+
3+
- Added a Workspace Manager V2 `Save` button beside `Launch Asset Manager V2`.
4+
- Save is disabled until a valid game workspace context is selected.
5+
- Save validates the generated manifest against `tools/schemas/workspace.manifest.schema.json` and referenced V2 payload schemas before export.
6+
- Validation failures block export and log exact schema failures to Status.
7+
- Successful save exports `<manifest-id>.workspace.manifest.json`.
8+
- Playwright coverage validates successful export and blocked save behavior for schema-invalid generated context.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# PR_26126_116 V2 Schema Naming Audit Notes
2+
3+
- Added canonical V2 tool payload schemas:
4+
- `tools/schemas/tools/asset-manager-v2.schema.json`
5+
- `tools/schemas/tools/palette-manager-v2.schema.json`
6+
- Updated `tools/schemas/workspace.manifest.schema.json` so generated Workspace Manager V2 manifests require `tools.palette-manager-v2` and `tools.asset-manager-v2`.
7+
- Updated Asset Manager V2 schema loading and validation messages from `asset-browser.schema.json` to `asset-manager-v2.schema.json`.
8+
- Updated Workspace Manager V2 generated JSON and tests to reject old generated `palette-browser`/`asset-browser` tool keys.
9+
- Updated visible V2 registry/shell naming for Asset Manager V2 and Palette Manager V2 where this lane directly references them.
10+
- Preview Generator V2 remains under its existing `preview-generator-v2` tool ID and dedicated Playwright spec path; this lane only revalidated that reference through `test:workspace-v2`.
11+
- Deprecated `tools/workspace-v2` was not modified.
12+
- Sample JSON was not modified.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# PR_26126_116 Workspace Launch Fix Notes
2+
3+
- Workspace Manager V2 now builds a schema-valid workspace manifest as the launch context.
4+
- Asset Manager V2 production launch receives the manifest through sessionStorage using `hostContextId`; no `?workspace=prod` path is supported.
5+
- Asset Manager V2 launch validation now requires:
6+
- root workspace manifest shape
7+
- games-only `gameRoot`
8+
- matching `assetsPath`
9+
- `tools.palette-manager-v2.swatches`
10+
- `tools.asset-manager-v2.assets`
11+
- Workspace nav in Asset Manager V2 now shows only `Return to Workspace`.
12+
- `Return to Workspace` navigates back to `tools/workspace-manager-v2/index.html`.
13+
- Direct launch and `?workspace=prod` remain blocked by the launch guard overlay.

tests/playwright/tools/AssetManagerV2.spec.mjs

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ test.describe("Asset Manager V2", () => {
379379
await expect(page.locator("#jsonInput")).toHaveCount(0);
380380
await expect(page.locator("#validateJsonButton")).toHaveCount(0);
381381
await expect(page.locator('button[aria-controls="assetsContent"] span').first()).toHaveText("Assets");
382-
await expect(page.locator("#statusLog")).toHaveValue(/INFO Loaded asset-browser\.schema\.json/);
382+
await expect(page.locator("#statusLog")).toHaveValue(/INFO Loaded asset-manager-v2\.schema\.json/);
383383

384384
await page.locator("#assetKindAudio").check();
385385
await expect(page.locator("#assetFileInput")).toHaveAttribute("accept", /audio\/wav/);
@@ -1168,7 +1168,8 @@ test.describe("Asset Manager V2", () => {
11681168
gameRoot: "games/Asteroids/",
11691169
assetsPath: "games/Asteroids/assets",
11701170
tools: {
1171-
"palette-browser": {
1171+
"palette-manager-v2": {
1172+
"$schema": "tools/schemas/tools/palette-manager-v2.schema.json",
11721173
schema: "html-js-gaming.palette",
11731174
version: 1,
11741175
name: "Asteroids Palette",
@@ -1246,7 +1247,7 @@ test.describe("Asset Manager V2", () => {
12461247
}
12471248
});
12481249

1249-
test("launches Asset Manager V2 from Workspace Manager V2 and inserts only Workspace asset entries", async ({ page }) => {
1250+
test("launches Asset Manager V2 from Workspace Manager V2 with schema-valid context and workspace return nav", async ({ page }) => {
12501251
const server = await openWorkspaceManagerV2(page, {
12511252
assetFiles: [
12521253
{
@@ -1290,6 +1291,10 @@ test.describe("Asset Manager V2", () => {
12901291
await expect(page).not.toHaveURL(/gameId=Asteroids/);
12911292
await expect(page.locator(".asset-manager-v2__tool__menu")).toBeHidden();
12921293
await expect(page.locator(".asset-manager-v2__workspace__menu")).toBeVisible();
1294+
await expect(page.locator(".asset-manager-v2__workspace__menu button")).toHaveText(["Return to Workspace"]);
1295+
await expect(page.locator("#returnToWorkspaceButton")).toBeEnabled();
1296+
await expect(page.locator("#workspaceInsertAssetsButton")).toHaveCount(0);
1297+
await expect(page.locator("#workspaceCopyManifestButton")).toHaveCount(0);
12931298
await expect(page.locator("#statusLog")).toHaveValue(/Workspace Manager V2 loaded 0 validated assets from tools\.asset-manager-v2\.assets/);
12941299
await expect(page.locator("#statusLog")).toHaveValue(/Workspace Manager V2 loaded \d+ palette colors from active palette context/);
12951300
const hostContextId = await page.evaluate(() => new URL(window.location.href).searchParams.get("hostContextId"));
@@ -1397,53 +1402,20 @@ test.describe("Asset Manager V2", () => {
13971402
await expect(page.locator("#inspectorOutput")).toContainText("\"kind\": \"hex\"");
13981403
await expect(page.locator("#inspectorOutput")).toContainText("\"name\": \"HUD Blue\"");
13991404

1400-
await expect(page.locator("#workspaceInsertAssetsButton")).toBeEnabled();
1401-
await page.locator("#workspaceInsertAssetsButton").click();
1402-
await expect(page.locator("#statusLog")).toHaveValue(/OK Inserted 4 validated assets into Workspace Manager V2 tools\.asset-manager-v2\.assets/);
1403-
14041405
const storedContext = await page.evaluate((id) => JSON.parse(sessionStorage.getItem(id)), hostContextId);
14051406
expect(storedContext.documentKind).toBe("workspace-manifest");
14061407
expect(storedContext.toolId).toBeUndefined();
14071408
expect(storedContext.activePalette).toBeUndefined();
14081409
expect(storedContext.workspaceManifest).toBeUndefined();
14091410
expect(storedContext.tools["asset-browser"]).toBeUndefined();
1410-
expect(storedContext.tools["asset-manager-v2"].assets["assets.audio.sound.fire"]).toEqual({
1411-
path: "assets/audio/fire.wav",
1412-
type: "audio",
1413-
kind: "wav",
1414-
role: "sound",
1415-
source: "asset-manager-v2"
1416-
});
1417-
expect(storedContext.tools["asset-manager-v2"].assets["assets.font.ui.vector-battle"]).toEqual({
1418-
path: "assets/fonts/vector_battle.ttf",
1419-
type: "font",
1420-
kind: "ttf",
1421-
role: "ui",
1422-
source: "asset-manager-v2"
1423-
});
1424-
expect(storedContext.tools["asset-manager-v2"].assets["assets.image.sprite.preview"]).toEqual({
1425-
path: "assets/images/preview.png",
1426-
type: "image",
1427-
kind: "png",
1428-
role: "sprite",
1429-
source: "asset-manager-v2"
1430-
});
1431-
expect(storedContext.tools["asset-manager-v2"].assets["assets.color.hud.primary-hud.hud-blue"]).toEqual({
1432-
path: "palette://workspace/hud-blue",
1433-
type: "color",
1434-
kind: "hex",
1435-
role: "hud",
1436-
source: "asset-manager-v2",
1437-
color: {
1438-
hex: "#78B7FF",
1439-
name: "HUD Blue",
1440-
symbol: "*"
1441-
}
1442-
});
1443-
expect(storedContext.tools["palette-browser"].source).toBe("workspace-manager-v2");
1444-
expect(storedContext.tools["palette-browser"].swatches.length).toBeGreaterThan(0);
1411+
expect(storedContext.tools["palette-browser"]).toBeUndefined();
1412+
expect(storedContext.tools["asset-manager-v2"].assets).toEqual({});
1413+
expect(storedContext.tools["palette-manager-v2"].source).toBe("workspace-manager-v2");
1414+
expect(storedContext.tools["palette-manager-v2"].swatches.length).toBeGreaterThan(0);
14451415
expect(storedContext.tools["workspace-v2"]).toBeUndefined();
1446-
expect(Object.keys(storedContext.tools).sort()).toEqual(["asset-manager-v2", "palette-browser"]);
1416+
expect(Object.keys(storedContext.tools).sort()).toEqual(["asset-manager-v2", "palette-manager-v2"]);
1417+
await page.locator("#returnToWorkspaceButton").click();
1418+
await expect(page).toHaveURL(/workspace-manager-v2\/index\.html$/);
14471419

14481420
expect(pageErrors).toEqual([]);
14491421
} finally {

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect, test } from "@playwright/test";
2+
import { readFile } from "node:fs/promises";
23
import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs";
34
import { workspaceV2CoverageReporter as coverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs";
45

@@ -135,6 +136,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
135136
try {
136137
await expect(page.locator("body[data-tool-id='workspace-manager-v2']")).toBeVisible();
137138
await expect(page.locator("#launchAssetManagerV2Button")).toBeDisabled();
139+
await expect(page.locator("#saveWorkspaceManifestButton")).toBeDisabled();
138140
await expect(page.locator("#activeGameSelect option")).toHaveText([
139141
"Select a game",
140142
"Asteroids",
@@ -152,13 +154,27 @@ test.describe("Workspace Manager V2 bootstrap", () => {
152154
await expect(page.locator("#workspaceContextOutput")).toContainText('"assetsPath": "games/Asteroids/assets"');
153155
await expect(page.locator("#workspaceContextOutput")).toContainText('"source": "workspace-manager-v2"');
154156
await expect(page.locator("#workspaceContextOutput")).toContainText('"asset-manager-v2"');
157+
await expect(page.locator("#workspaceContextOutput")).toContainText('"palette-manager-v2"');
158+
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"palette-browser"');
159+
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"asset-browser"');
155160
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"activePalette"');
156161
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"toolId"');
157162
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"workspaceManifest"');
158163
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"workspaceMetadata"');
159164
await expect(page.locator("#workspaceContextOutput")).not.toContainText("samples/");
160-
await expect(page.locator("#workspaceContextOutput")).not.toContainText("tools/");
161165
await expect(page.locator("#launchAssetManagerV2Button")).toBeEnabled();
166+
await expect(page.locator("#saveWorkspaceManifestButton")).toBeEnabled();
167+
168+
const downloadPromise = page.waitForEvent("download");
169+
await page.locator("#saveWorkspaceManifestButton").click();
170+
const download = await downloadPromise;
171+
expect(download.suggestedFilename()).toBe("workspace-manager-v2-Asteroids.workspace.manifest.json");
172+
const savedManifest = JSON.parse(await readFile(await download.path(), "utf8"));
173+
expect(savedManifest.documentKind).toBe("workspace-manifest");
174+
expect(Object.keys(savedManifest.tools).sort()).toEqual(["asset-manager-v2", "palette-manager-v2"]);
175+
expect(savedManifest.tools["palette-manager-v2"].swatches.length).toBeGreaterThan(0);
176+
expect(savedManifest.tools["asset-manager-v2"].assets).toEqual({});
177+
await expect(page.locator("#statusLog")).toHaveValue(/OK Saved schema-valid Workspace Manager V2 manifest workspace-manager-v2-Asteroids\./);
162178

163179
await page.locator("#launchAssetManagerV2Button").click();
164180
await expect(page).toHaveURL(/asset-manager-v2\/index\.html.*launch=workspace/);
@@ -170,6 +186,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
170186
await expect(page.locator("#assetLaunchGuard")).toBeHidden();
171187
await expect(page.locator(".asset-manager-v2__tool__menu")).toBeHidden();
172188
await expect(page.locator(".asset-manager-v2__workspace__menu")).toBeVisible();
189+
await expect(page.locator(".asset-manager-v2__workspace__menu button")).toHaveText(["Return to Workspace"]);
173190
await expect(page.locator("#statusLog")).toHaveValue(/Workspace Manager V2 loaded 0 validated assets from tools\.asset-manager-v2\.assets/);
174191
await expect(page.locator("#statusLog")).toHaveValue(/Workspace Manager V2 loaded \d+ palette colors from active palette context/);
175192

@@ -197,20 +214,21 @@ test.describe("Workspace Manager V2 bootstrap", () => {
197214
expect(storedContext.gameId).toBe("Asteroids");
198215
expect(storedContext.gameRoot).toBe("games/Asteroids/");
199216
expect(storedContext.assetsPath).toBe("games/Asteroids/assets");
200-
expect(storedContext.tools["palette-browser"].swatches.length).toBeGreaterThan(0);
217+
expect(storedContext.tools["palette-manager-v2"].swatches.length).toBeGreaterThan(0);
201218
expect(storedContext.tools["asset-manager-v2"].assets).toEqual({});
202219
expect(storedContext.workspaceMetadata).toBeUndefined();
203220
expect(storedContext.tools["asset-browser"]).toBeUndefined();
221+
expect(storedContext.tools["palette-browser"]).toBeUndefined();
204222
expect(storedContext.tools["asset-manager-v2"]).toEqual({ assets: {} });
205223
const schemaValidation = await page.evaluate(async () => {
206224
const [workspaceSchema, paletteSchema, assetSchema] = await Promise.all([
207225
fetch("/tools/schemas/workspace.manifest.schema.json", { cache: "no-store" }).then((response) => response.json()),
208-
fetch("/tools/schemas/tools/palette-browser.schema.json", { cache: "no-store" }).then((response) => response.json()),
209-
fetch("/tools/schemas/tools/asset-browser.schema.json", { cache: "no-store" }).then((response) => response.json())
226+
fetch("/tools/schemas/tools/palette-manager-v2.schema.json", { cache: "no-store" }).then((response) => response.json()),
227+
fetch("/tools/schemas/tools/asset-manager-v2.schema.json", { cache: "no-store" }).then((response) => response.json())
210228
]);
211229
const url = new URL(window.location.href);
212230
const manifest = JSON.parse(sessionStorage.getItem(url.searchParams.get("hostContextId")));
213-
const palettePayload = manifest.tools["palette-browser"];
231+
const palettePayload = manifest.tools["palette-manager-v2"];
214232
const assetPayload = manifest.tools["asset-manager-v2"];
215233
const extraKeys = (value, schema) => Object.keys(value).filter((key) => !Object.hasOwn(schema.properties || {}, key));
216234
const missingKeys = (value, schema) => (schema.required || []).filter((key) => !Object.hasOwn(value, key));
@@ -242,7 +260,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
242260
paletteMissingKeys: [],
243261
swatchExtraKeys: [],
244262
swatchMissingKeys: [],
245-
toolKeys: ["asset-manager-v2", "palette-browser"],
263+
toolKeys: ["asset-manager-v2", "palette-manager-v2"],
246264
unsupportedToolKeys: []
247265
});
248266
expect(JSON.stringify(storedContext)).not.toMatch(/samples\//i);
@@ -253,6 +271,29 @@ test.describe("Workspace Manager V2 bootstrap", () => {
253271
}
254272
});
255273

274+
test("blocks Workspace Manager V2 save when the generated manifest fails schema validation", async ({ page }) => {
275+
const server = await openWorkspaceManagerV2(page);
276+
const pageErrors = [];
277+
278+
page.on("pageerror", (error) => {
279+
pageErrors.push(error.message);
280+
});
281+
282+
try {
283+
await page.locator("#activeGameSelect").selectOption("Asteroids");
284+
await expect(page.locator("#saveWorkspaceManifestButton")).toBeEnabled();
285+
await page.evaluate(() => {
286+
window.__workspaceManagerV2App.activeContext.tools["asset-manager-v2"].unexpected = true;
287+
});
288+
await page.locator("#saveWorkspaceManifestButton").click();
289+
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Save blocked: Generated Workspace Manager V2 manifest failed schema validation: root\.tools\.asset-manager-v2\.unexpected is not allowed/);
290+
expect(pageErrors).toEqual([]);
291+
} finally {
292+
await coverageReporter.stop(page);
293+
await server.close();
294+
}
295+
});
296+
256297
test("keeps direct Asset Manager V2 workspace prod launch blocked", async ({ page }) => {
257298
const server = await openAssetManagerV2(page, "?workspace=prod");
258299
const pageErrors = [];

tools/asset-manager-v2/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Asset Manager V2
22

3-
Asset Manager V2 is an asset-only First-Class Tool V2 surface. It creates and validates audio, color, data, font, image, localization, shader, and video entries against `tools/schemas/tools/asset-browser.schema.json`.
3+
Asset Manager V2 is an asset-only First-Class Tool V2 surface. It creates and validates audio, color, data, font, image, localization, shader, and video entries against `tools/schemas/tools/asset-manager-v2.schema.json`.
44

55
The Type radios choose the asset type before picking an asset. File-backed types use the selected type's accept filter, preserve the selected path filename exactly from the project root, default the role from the selected type, and persist only schema-valid `path`, `type`, `kind`, `role`, and `source` metadata. Color assets use only active Workspace Manager V2 palette swatches and persist schema-valid palette color metadata. The visible Assets list and Output Summary use compact `id`, `type`, `kind`, `role`, and `path` display fields. The tool has no dependency on the legacy asset browsing implementation and does not discover or enumerate all tools. In Workspace Manager V2 launch mode, validated entries are written only to the canonical `tools.asset-manager-v2.assets` schema location in the workspace manifest context.
66

tools/asset-manager-v2/index.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@ <h2 class="tools-platform-frame__eyebrow">Asset-only First-Class Tool V2</h2>
5151
</nav>
5252

5353
<nav class="asset-manager-v2__menu asset-manager-v2__workspace__menu" aria-label="Workspace asset actions" data-launch-mode-nav="workspace" hidden>
54-
<button id="workspaceInsertAssetsButton" type="button" disabled>Insert Assets</button>
55-
<button id="workspaceCopyManifestButton" type="button" disabled>Copy Manifest</button>
54+
<button id="returnToWorkspaceButton" type="button">Return to Workspace</button>
5655
</nav>
5756

5857
<main class="asset-manager-v2 app-shell" data-tool-id="asset-manager-v2">

0 commit comments

Comments
 (0)