Skip to content

Commit 3fc7bbc

Browse files
author
DavidQ
committed
Write back updated workspace manifests and load Asteroids assets from manifest - PR_26126_118-workspace-manifest-session-writeback-and-vector-map
1 parent 452f653 commit 3fc7bbc

10 files changed

Lines changed: 308 additions & 15 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
PR_26126_118 Asteroids Asset Loading Notes
2+
3+
Scope:
4+
- Asteroids managed assets are loaded from games/Asteroids/game.manifest.json.
5+
- No Asteroids asset registry entries are constructed or injected by Workspace Manager V2 or Asset Manager V2 runtime code in this PR.
6+
7+
Behavior:
8+
- Workspace Manager V2 reports the manifest asset count from tools.asset-manager-v2.assets.
9+
- If the Asteroids manifest asset map is empty, Workspace Manager V2 logs a visible Status warning and keeps the asset count at 0.
10+
- Empty Asteroids assets do not silently fall back to hardcoded asset entries.
11+
12+
Validation:
13+
- npm run test:workspace-v2 passed.
14+
- Workspace Manager V2 Playwright coverage intercepts games/Asteroids/game.manifest.json with an empty assets map and validates the Status warning plus 0 managed assets.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
PR_26126_118 Manifest Writeback Notes
2+
3+
Scope:
4+
- Workspace Manager V2 now tracks the active session hostContextId after launching or restoring a workspace session.
5+
- Workspace Manager V2 Save/export refreshes from that active session before validating and downloading the workspace manifest.
6+
- Asset Manager V2 writes validated asset add, update, delete, undo, redo, import, and return-to-workspace state back into the Workspace Manager V2 session manifest.
7+
8+
Behavior:
9+
- Save/export uses the latest session manifest when Asset Manager V2 has changed tools.asset-manager-v2.assets.
10+
- Manual game selection without an active session still saves the loaded game manifest.
11+
- Session writeback preserves the existing workspace manifest wrapper and only replaces tools.asset-manager-v2.assets.
12+
13+
Validation:
14+
- npm run test:workspace-v2 passed.
15+
- Asset Manager V2 Playwright coverage validates a tool-updated session manifest returns to Workspace Manager V2 and Save/export includes the new session assets.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
PR_26126_118 Manual Validation Notes
2+
3+
Validation command:
4+
- npm run test:workspace-v2
5+
6+
Result:
7+
- PASS: 21 Playwright tests passed in the workspace-v2 gate.
8+
- PASS: git diff --check completed without whitespace errors.
9+
10+
Manual review checklist:
11+
- Confirmed games/Asteroids/game.manifest.json remains valid JSON after adding tools.vector-map-editor.
12+
- Confirmed deprecated tools/workspace-v2 was not modified.
13+
- Confirmed sample JSON files were not modified.
14+
- Confirmed no mp4 or mkv files were modified or added.
15+
- Confirmed Asset Manager V2 direct ?workspace=prod remains blocked by launch guard coverage.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
PR_26126_118 Vector Map Manifest Notes
2+
3+
Scope:
4+
- The Asteroids vector map reference now lives in games/Asteroids/game.manifest.json under tools.vector-map-editor.
5+
- The payload uses the existing tools/schemas/tools/vector-map-editor.schema.json structure with a vectorMapDocument root.
6+
- Workspace Manager V2 loads the vector map entry as part of the game manifest context.
7+
8+
Manifest entry:
9+
- tools.vector-map-editor.vectorMapDocument.schema = html-js-gaming.vector-map
10+
- tools.vector-map-editor.vectorMapDocument.source = manifest
11+
- tools.vector-map-editor.vectorMapDocument.assetsPath = games/Asteroids/assets
12+
- Included vector IDs: vector.asteroids.ship, vector.asteroids.asteroid.large, vector.asteroids.asteroid.medium, vector.asteroids.asteroid.small, vector.asteroids.ui.title.
13+
14+
Validation:
15+
- npm run test:workspace-v2 passed.
16+
- Workspace Manager V2 Playwright coverage validates vector-map-editor is present in the manifest context, saved export, and session context.

games/Asteroids/game.manifest.json

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,69 @@
175175
}
176176
}
177177
}
178+
},
179+
"vector-map-editor": {
180+
"vectorMapDocument": {
181+
"schema": "html-js-gaming.vector-map",
182+
"version": 1,
183+
"name": "Asteroids Vector Map",
184+
"source": "manifest",
185+
"assetsPath": "games/Asteroids/assets",
186+
"vectors": [
187+
{
188+
"id": "vector.asteroids.ship",
189+
"label": "Ship",
190+
"viewBox": "-24 -24 48 48",
191+
"paths": [
192+
"M 0 -18 L 14 16 L 0 8 L -14 16 Z",
193+
"M -6 14 L 0 6 L 6 14"
194+
],
195+
"stroke": true,
196+
"fill": false
197+
},
198+
{
199+
"id": "vector.asteroids.asteroid.large",
200+
"label": "Large Asteroid",
201+
"viewBox": "-40 -40 80 80",
202+
"paths": [
203+
"M -28 -12 L -10 -30 L 20 -26 L 32 -8 L 26 18 L 6 32 L -22 24 L -34 2 Z"
204+
],
205+
"stroke": true,
206+
"fill": false
207+
},
208+
{
209+
"id": "vector.asteroids.asteroid.medium",
210+
"label": "Medium Asteroid",
211+
"viewBox": "-28 -28 56 56",
212+
"paths": [
213+
"M -16 -10 L -2 -18 L 16 -14 L 20 2 L 8 18 L -10 16 L -20 4 Z"
214+
],
215+
"stroke": true,
216+
"fill": false
217+
},
218+
{
219+
"id": "vector.asteroids.asteroid.small",
220+
"label": "Small Asteroid",
221+
"viewBox": "-18 -18 36 36",
222+
"paths": [
223+
"M -10 -6 L 0 -12 L 10 -6 L 8 8 L -6 10 L -12 0 Z"
224+
],
225+
"stroke": true,
226+
"fill": false
227+
},
228+
{
229+
"id": "vector.asteroids.ui.title",
230+
"label": "Asteroids Title",
231+
"viewBox": "0 0 220 48",
232+
"paths": [
233+
"M 6 40 L 24 6 L 42 40 Z",
234+
"M 58 40 L 58 8 L 88 8 L 88 18 L 70 18 L 70 22 L 86 22 L 86 32 L 70 32 L 70 40 Z"
235+
],
236+
"stroke": true,
237+
"fill": false
238+
}
239+
]
240+
}
178241
}
179242
}
180243
}

tests/playwright/tools/AssetManagerV2.spec.mjs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,8 @@ test.describe("Asset Manager V2", () => {
12821282
await expect(page.locator("#workspaceContextOutput")).toContainText('"gameRoot": "games/Asteroids/"');
12831283
await expect(page.locator("#workspaceContextOutput")).toContainText('"assetsPath": "games/Asteroids/assets"');
12841284
await expect(page.locator("#workspaceContextOutput")).toContainText('"asset-manager-v2"');
1285+
await expect(page.locator("#workspaceContextOutput")).toContainText('"vector-map-editor"');
1286+
await expect(page.locator("#workspaceContextOutput")).toContainText('"vector.asteroids.ship"');
12851287
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"activePalette"');
12861288
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"workspaceManifest"');
12871289
await expect(page.locator("#launchAssetManagerV2Button")).toBeEnabled();
@@ -1401,6 +1403,7 @@ test.describe("Asset Manager V2", () => {
14011403
await expect(page.locator("#inspectorOutput")).toContainText("\"type\": \"color\"");
14021404
await expect(page.locator("#inspectorOutput")).toContainText("\"kind\": \"hex\"");
14031405
await expect(page.locator("#inspectorOutput")).toContainText("\"name\": \"HUD Blue\"");
1406+
await expect(page.locator("#statusLog")).toHaveValue(/OK Workspace Manager V2 session manifest now has 17 validated assets\./);
14041407

14051408
const storedContext = await page.evaluate((id) => JSON.parse(sessionStorage.getItem(id)), hostContextId);
14061409
expect(storedContext.documentKind).toBe("workspace-manifest");
@@ -1409,24 +1412,67 @@ test.describe("Asset Manager V2", () => {
14091412
expect(storedContext.workspaceManifest).toBeUndefined();
14101413
expect(storedContext.tools["asset-browser"]).toBeUndefined();
14111414
expect(storedContext.tools["palette-browser"]).toBeUndefined();
1412-
expect(Object.keys(storedContext.tools["asset-manager-v2"].assets)).toHaveLength(13);
1415+
expect(Object.keys(storedContext.tools["asset-manager-v2"].assets)).toHaveLength(17);
14131416
expect(storedContext.tools["asset-manager-v2"].assets["assets.audio.sound.fire"]).toEqual({
14141417
path: "assets/audio/fire.wav",
14151418
type: "audio",
14161419
kind: "wav",
14171420
role: "sound",
14181421
source: "manifest"
14191422
});
1420-
expect(storedContext.tools["asset-manager-v2"].assets["assets.audio.sound.laser"]).toBeUndefined();
1423+
expect(storedContext.tools["asset-manager-v2"].assets["assets.audio.sound.laser"]).toEqual({
1424+
path: "assets/audio/laser.wav",
1425+
type: "audio",
1426+
kind: "wav",
1427+
role: "sound",
1428+
source: "asset-manager-v2"
1429+
});
1430+
expect(storedContext.tools["asset-manager-v2"].assets["assets.font.ui.score"]).toEqual({
1431+
path: "assets/fonts/score.ttf",
1432+
type: "font",
1433+
kind: "ttf",
1434+
role: "ui",
1435+
source: "asset-manager-v2"
1436+
});
1437+
expect(storedContext.tools["asset-manager-v2"].assets["assets.image.sprite.preview"]).toEqual({
1438+
path: "assets/images/preview.png",
1439+
type: "image",
1440+
kind: "png",
1441+
role: "sprite",
1442+
source: "asset-manager-v2"
1443+
});
1444+
expect(storedContext.tools["asset-manager-v2"].assets["assets.color.hud.primary-hud.hud-blue"]).toEqual({
1445+
path: "palette://workspace/hud-blue",
1446+
type: "color",
1447+
kind: "hex",
1448+
role: "hud",
1449+
source: "asset-manager-v2",
1450+
color: {
1451+
hex: "#78B7FF",
1452+
name: "HUD Blue",
1453+
symbol: "*"
1454+
}
1455+
});
14211456
expect(storedContext.tools["palette-manager-v2"].source).toBe("manifest");
14221457
expect(storedContext.tools["palette-manager-v2"].swatches.length).toBeGreaterThan(0);
1458+
expect(storedContext.tools["vector-map-editor"].vectorMapDocument.vectors.map((vector) => vector.id)).toContain("vector.asteroids.ship");
14231459
expect(storedContext.tools["workspace-v2"]).toBeUndefined();
1424-
expect(Object.keys(storedContext.tools).sort()).toEqual(["asset-manager-v2", "palette-manager-v2"]);
1460+
expect(Object.keys(storedContext.tools).sort()).toEqual(["asset-manager-v2", "palette-manager-v2", "vector-map-editor"]);
14251461
await page.locator("#returnToWorkspaceButton").click();
14261462
await expect(page).toHaveURL(/workspace-manager-v2\/index\.html\?hostContextId=workspace-manager-v2-/);
14271463
await expect(page.locator("#activeGameSelect")).toHaveValue("Asteroids");
1428-
await expect(page.locator("#activeAssetRegistrySummary")).toHaveText("Schema-ready Asset Manager V2 manifest payload contains 13 managed assets.");
1464+
await expect(page.locator("#activeAssetRegistrySummary")).toHaveText("Schema-ready Asset Manager V2 manifest payload contains 17 managed assets.");
14291465
await expect(page.locator("#launchAssetManagerV2Button")).toBeEnabled();
1466+
await expect(page.locator("#saveWorkspaceManifestButton")).toBeEnabled();
1467+
const downloadPromise = page.waitForEvent("download");
1468+
await page.locator("#saveWorkspaceManifestButton").click();
1469+
const download = await downloadPromise;
1470+
expect(download.suggestedFilename()).toBe("workspace-manager-v2-Asteroids.workspace.manifest.json");
1471+
const savedManifest = JSON.parse(await readFile(await download.path(), "utf8"));
1472+
expect(Object.keys(savedManifest.tools["asset-manager-v2"].assets)).toHaveLength(17);
1473+
expect(savedManifest.tools["asset-manager-v2"].assets["assets.audio.sound.laser"]).toEqual(storedContext.tools["asset-manager-v2"].assets["assets.audio.sound.laser"]);
1474+
expect(savedManifest.tools["asset-manager-v2"].assets["assets.color.hud.primary-hud.hud-blue"]).toEqual(storedContext.tools["asset-manager-v2"].assets["assets.color.hud.primary-hud.hud-blue"]);
1475+
expect(savedManifest.tools["vector-map-editor"].vectorMapDocument.vectors.map((vector) => vector.id)).toContain("vector.asteroids.ship");
14301476

14311477
expect(pageErrors).toEqual([]);
14321478
} finally {

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
155155
await expect(page.locator("#workspaceContextOutput")).toContainText('"source": "manifest"');
156156
await expect(page.locator("#workspaceContextOutput")).toContainText('"asset-manager-v2"');
157157
await expect(page.locator("#workspaceContextOutput")).toContainText('"palette-manager-v2"');
158+
await expect(page.locator("#workspaceContextOutput")).toContainText('"vector-map-editor"');
159+
await expect(page.locator("#workspaceContextOutput")).toContainText('"vector.asteroids.ship"');
158160
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"palette-browser"');
159161
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"asset-browser"');
160162
await expect(page.locator("#workspaceContextOutput")).not.toContainText('"activePalette"');
@@ -174,11 +176,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
174176
const asteroidsManifest = await page.evaluate(async () => await fetch("/games/Asteroids/game.manifest.json", { cache: "no-store" }).then((response) => response.json()));
175177
expect(savedManifest).toEqual(asteroidsManifest);
176178
expect(savedManifest.documentKind).toBe("workspace-manifest");
177-
expect(Object.keys(savedManifest.tools).sort()).toEqual(["asset-manager-v2", "palette-manager-v2"]);
179+
expect(Object.keys(savedManifest.tools).sort()).toEqual(["asset-manager-v2", "palette-manager-v2", "vector-map-editor"]);
178180
expect(savedManifest.tools["palette-manager-v2"].swatches.length).toBeGreaterThan(0);
179181
expect(Object.keys(savedManifest.tools["asset-manager-v2"].assets)).toHaveLength(13);
180182
expect(savedManifest.tools["asset-manager-v2"].source).toBe("manifest");
181183
expect(savedManifest.tools["asset-manager-v2"].schema).toBe("html-js-gaming.asset-manager-v2");
184+
expect(savedManifest.tools["vector-map-editor"].vectorMapDocument.vectors.map((vector) => vector.id)).toContain("vector.asteroids.ship");
182185
await expect(page.locator("#statusLog")).toHaveValue(/OK Saved schema-valid Workspace Manager V2 manifest workspace-manager-v2-Asteroids\./);
183186

184187
await page.locator("#launchAssetManagerV2Button").click();
@@ -221,6 +224,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
221224
expect(storedContext.assetsPath).toBe("games/Asteroids/assets");
222225
expect(storedContext.tools["palette-manager-v2"].swatches.length).toBeGreaterThan(0);
223226
expect(Object.keys(storedContext.tools["asset-manager-v2"].assets)).toHaveLength(13);
227+
expect(storedContext.tools["vector-map-editor"].vectorMapDocument.vectors.map((vector) => vector.id)).toContain("vector.asteroids.ship");
224228
expect(storedContext.tools["asset-manager-v2"].assets["assets.font.ui.vector-battle"]).toEqual({
225229
path: "assets/fonts/vector_battle.ttf",
226230
type: "font",
@@ -233,15 +237,17 @@ test.describe("Workspace Manager V2 bootstrap", () => {
233237
expect(storedContext.tools["palette-browser"]).toBeUndefined();
234238
expect(storedContext.tools["asset-manager-v2"].schema).toBe("html-js-gaming.asset-manager-v2");
235239
const schemaValidation = await page.evaluate(async () => {
236-
const [workspaceSchema, paletteSchema, assetSchema] = await Promise.all([
240+
const [workspaceSchema, paletteSchema, assetSchema, vectorMapSchema] = await Promise.all([
237241
fetch("/tools/schemas/workspace.manifest.schema.json", { cache: "no-store" }).then((response) => response.json()),
238242
fetch("/tools/schemas/tools/palette-manager-v2.schema.json", { cache: "no-store" }).then((response) => response.json()),
239-
fetch("/tools/schemas/tools/asset-manager-v2.schema.json", { cache: "no-store" }).then((response) => response.json())
243+
fetch("/tools/schemas/tools/asset-manager-v2.schema.json", { cache: "no-store" }).then((response) => response.json()),
244+
fetch("/tools/schemas/tools/vector-map-editor.schema.json", { cache: "no-store" }).then((response) => response.json())
240245
]);
241246
const url = new URL(window.location.href);
242247
const manifest = JSON.parse(sessionStorage.getItem(url.searchParams.get("hostContextId")));
243248
const palettePayload = manifest.tools["palette-manager-v2"];
244249
const assetPayload = manifest.tools["asset-manager-v2"];
250+
const vectorPayload = manifest.tools["vector-map-editor"];
245251
const extraKeys = (value, schema) => Object.keys(value).filter((key) => !Object.hasOwn(schema.properties || {}, key));
246252
const missingKeys = (value, schema) => (schema.required || []).filter((key) => !Object.hasOwn(value, key));
247253
const swatchExtraKeys = palettePayload.swatches.flatMap((swatch, index) => (
@@ -260,7 +266,10 @@ test.describe("Workspace Manager V2 bootstrap", () => {
260266
swatchExtraKeys,
261267
swatchMissingKeys,
262268
toolKeys: Object.keys(manifest.tools).sort(),
263-
unsupportedToolKeys: Object.keys(manifest.tools).filter((key) => !Object.hasOwn(workspaceSchema.properties.tools.properties, key))
269+
unsupportedToolKeys: Object.keys(manifest.tools).filter((key) => !Object.hasOwn(workspaceSchema.properties.tools.properties, key)),
270+
vectorExtraKeys: extraKeys(vectorPayload, vectorMapSchema),
271+
vectorMissingKeys: missingKeys(vectorPayload, vectorMapSchema),
272+
vectorIds: vectorPayload.vectorMapDocument.vectors.map((vector) => vector.id)
264273
};
265274
});
266275
expect(schemaValidation).toEqual({
@@ -272,8 +281,17 @@ test.describe("Workspace Manager V2 bootstrap", () => {
272281
paletteMissingKeys: [],
273282
swatchExtraKeys: [],
274283
swatchMissingKeys: [],
275-
toolKeys: ["asset-manager-v2", "palette-manager-v2"],
276-
unsupportedToolKeys: []
284+
toolKeys: ["asset-manager-v2", "palette-manager-v2", "vector-map-editor"],
285+
unsupportedToolKeys: [],
286+
vectorExtraKeys: [],
287+
vectorIds: [
288+
"vector.asteroids.ship",
289+
"vector.asteroids.asteroid.large",
290+
"vector.asteroids.asteroid.medium",
291+
"vector.asteroids.asteroid.small",
292+
"vector.asteroids.ui.title"
293+
],
294+
vectorMissingKeys: []
277295
});
278296
expect(JSON.stringify(storedContext)).not.toMatch(/samples\//i);
279297
await page.locator("#returnToWorkspaceButton").click();
@@ -313,6 +331,40 @@ test.describe("Workspace Manager V2 bootstrap", () => {
313331
}
314332
});
315333

334+
test("warns instead of injecting hardcoded Asteroids assets when manifest assets are empty", async ({ page }) => {
335+
const server = await startRepoServer();
336+
const pageErrors = [];
337+
const asteroidsManifest = JSON.parse(await readFile("games/Asteroids/game.manifest.json", "utf8"));
338+
asteroidsManifest.tools["asset-manager-v2"].assets = {};
339+
340+
page.on("pageerror", (error) => {
341+
pageErrors.push(error.message);
342+
});
343+
344+
await page.route("**/games/Asteroids/game.manifest.json", async (route) => {
345+
await route.fulfill({
346+
body: JSON.stringify(asteroidsManifest),
347+
contentType: "application/json",
348+
status: 200
349+
});
350+
});
351+
await coverageReporter.start(page);
352+
await page.goto(`${server.baseUrl}/tools/workspace-manager-v2/index.html`, { waitUntil: "networkidle" });
353+
354+
try {
355+
await page.locator("#activeGameSelect").selectOption("Asteroids");
356+
await expect(page.locator("#activeAssetRegistrySummary")).toHaveText("Schema-ready Asset Manager V2 manifest payload contains 0 managed assets.");
357+
await expect(page.locator("#workspaceContextOutput")).toContainText('"assets": {}');
358+
await expect(page.locator("#workspaceContextOutput")).toContainText('"vector-map-editor"');
359+
await expect(page.locator("#statusLog")).toHaveValue(/INFO Warning: \/games\/Asteroids\/game\.manifest\.json has no Asteroids Asset Manager V2 assets; Workspace Manager V2 did not inject hardcoded assets\./);
360+
await expect(page.locator("#statusLog")).toHaveValue(/OK Loaded Asteroids from \/games\/Asteroids\/game\.manifest\.json with 11 active palette colors and 0 managed assets\./);
361+
expect(pageErrors).toEqual([]);
362+
} finally {
363+
await coverageReporter.stop(page);
364+
await server.close();
365+
}
366+
});
367+
316368
test("keeps direct Asset Manager V2 workspace prod launch blocked", async ({ page }) => {
317369
const server = await openAssetManagerV2(page, "?workspace=prod");
318370
const pageErrors = [];

0 commit comments

Comments
 (0)