Skip to content

Commit 12ee16a

Browse files
author
DavidQ
committed
Add manifest repoPath support for Preview Generator direct writes - PR_26127_016-preview-generator-manifest-repo-path
1 parent 65f4ee8 commit 12ee16a

11 files changed

Lines changed: 142 additions & 48 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# PR_26127_016-preview-generator-manifest-repo-path
2+
3+
## Summary
4+
- Added optional `repoPath` support to `tools/schemas/workspace.manifest.schema.json`.
5+
- Added `repoPath` to `games/Asteroids/game.manifest.json` as the absolute filesystem repo path for direct preview writes.
6+
- Kept `repoRoot` as the display label.
7+
- Workspace Manager V2 now reads `repoPath`, surfaces `Select Repo` when it is missing, and passes manifest/session context through to launched tools.
8+
- Preview Generator V2 now hides `Pick Repo Folder` during Workspace Manager launches and uses manifest `repoPath` for direct writes.
9+
10+
## Manifest Repo Path Notes
11+
- `repoPath` is optional in the schema so existing manifests without it can still validate.
12+
- When `repoPath` is present and absolute, Preview Generator V2 enables Generate Preview after preview target validation.
13+
- When `repoPath` is missing or invalid, Preview Generator V2 opens, logs an actionable status message, and keeps Generate Preview disabled.
14+
- `/__workspace-manager-v2/repo-root` is not called or restored.
15+
16+
## Preview Generator Notes
17+
- Workspace launch logs now distinguish:
18+
- workspace launch hydrated
19+
- `repoRoot` display label available
20+
- `repoPath` available or missing
21+
- resolved absolute preview output path
22+
- direct write success/failure
23+
- Asteroids Workspace Manager launch writes generated preview output to `games/Asteroids/assets/images/preview.svg` using `repoPath`.
24+
25+
## Workspace Manager Notes
26+
- Preview Generator V2 tile shows `Schema-valid manifest` when `repoPath` is available.
27+
- Preview Generator V2 tile shows `Select Repo` when `repoPath` is missing.
28+
- Asset Manager V2 workspace context validation accepts the new root-level `repoPath` manifest field.
29+
30+
## Validation
31+
- `npm run test:workspace-v2`
32+
- Result: PASS, 10 tests passed.
33+
- Validated manifest `repoPath`, Preview Generator workspace launch with direct write enabled, missing `repoPath` disabled/actionable state, hidden Pick Repo Folder, and no `/__workspace-manager-v2/repo-root` dependency.
34+
35+
## Out Of Scope
36+
- Deprecated `tools/workspace-v2` was not modified.
37+
- Sample JSON was not modified.
38+
- Full samples smoke test was skipped because this PR is manifest repo path and Preview Generator launch scoped.

games/Asteroids/game.manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"gameRoot": "games/Asteroids/",
1010
"assetsPath": "games/Asteroids/assets",
1111
"repoRoot": "HTML-JavaScript-Gaming",
12+
"repoPath": "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming",
1213
"tools": {
1314
"palette-manager-v2": {
1415
"$schema": "tools/schemas/tools/palette-manager-v2.schema.json",

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

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

@@ -234,6 +235,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
234235
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"gameRoot": "games\/Asteroids\/"/);
235236
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"assetsPath": "games\/Asteroids\/assets"/);
236237
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"repoRoot": "HTML-JavaScript-Gaming"/);
238+
expect(JSON.parse(await page.locator("#workspaceContextOutput").inputValue()).repoPath).toBe(server.repoRoot);
237239
await expect(page.locator("#workspaceContextOutput")).not.toHaveValue(/"previewImagePath"/);
238240
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"assets.image.bezel.bezel"/);
239241
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"assets.image.preview.preview"/);
@@ -311,6 +313,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
311313
expect(savedManifest.documentKind).toBe("workspace-manifest");
312314
expect(Object.keys(savedManifest.tools).sort()).toEqual(["asset-manager-v2", "palette-manager-v2", "vector-map-editor"]);
313315
expect(savedManifest.repoRoot).toBe("HTML-JavaScript-Gaming");
316+
expect(savedManifest.repoPath).toBe(server.repoRoot);
314317
expect(savedManifest.tools["palette-manager-v2"].swatches.length).toBeGreaterThan(0);
315318
expect(Object.keys(savedManifest.tools["asset-manager-v2"].assets)).toHaveLength(14);
316319
expect(savedManifest.tools["asset-manager-v2"].previewImagePath).toBeUndefined();
@@ -387,6 +390,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
387390
expect(storedContext.gameRoot).toBe("games/Asteroids/");
388391
expect(storedContext.assetsPath).toBe("games/Asteroids/assets");
389392
expect(storedContext.repoRoot).toBe("HTML-JavaScript-Gaming");
393+
expect(storedContext.repoPath).toBe(server.repoRoot);
390394
expect(storedContext.tools["palette-manager-v2"].swatches.length).toBeGreaterThan(0);
391395
expect(Object.keys(storedContext.tools["asset-manager-v2"].assets)).toHaveLength(14);
392396
expect(storedContext.tools["asset-manager-v2"].previewImagePath).toBeUndefined();
@@ -521,8 +525,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
521525
await expect(page.locator('[data-launch-mode-nav="workspace"]')).toBeVisible();
522526
await expect(page.locator('[data-launch-mode-nav="workspace"] button')).toHaveText(["Generate Image", "Return to Workspace"]);
523527
await expect(page.locator("#executeBtn")).toBeVisible();
524-
await expect(page.locator("#executeBtn")).toBeDisabled();
525-
await expect(page.locator("#repoSelectedValue")).toHaveText("HTML-JavaScript-Gaming");
528+
await expect(page.locator("#executeBtn")).toBeEnabled();
529+
await expect(page.locator("#repoSelectedValue")).toHaveText(server.repoRoot);
530+
await expect(page.locator("#pickRepoBtn")).toBeHidden();
526531
await expect(page.locator("#workspaceContextValue")).toHaveCount(0);
527532
await expect(page.locator("#repoDestinationContent")).not.toContainText("Workspace launch");
528533
await expect(page.locator("#targetTypeGames")).toBeChecked();
@@ -534,10 +539,13 @@ test.describe("Workspace Manager V2 bootstrap", () => {
534539
await expect(page.locator("#previewTargetValue")).toHaveText("games/Asteroids/assets/images/preview.svg");
535540
await expect(page.locator("#lastGeneratedImagePreview")).toBeVisible();
536541
await expect(page.locator("#lastGeneratedImageMeta")).toHaveText("Preview target: games/Asteroids/assets/images/preview.png");
542+
const absoluteAsteroidsPreviewPath = path.join(server.repoRoot, "games", "Asteroids", "assets", "images", "preview.svg");
537543
await expect(page.locator("#log")).toContainText("OK Workspace launch context hydrated for Asteroids.");
538544
await expect(page.locator("#log")).toContainText("Workspace repoRoot display label available: HTML-JavaScript-Gaming.");
539-
await expect(page.locator("#log")).toContainText("WARN Absolute repoRoot missing for workspace launch; manifest repoRoot is display-only: HTML-JavaScript-Gaming.");
540-
await expect(page.locator("#log")).toContainText("Direct preview write unavailable until a real writable repo root is selected.");
545+
await expect(page.locator("#log")).toContainText(`Workspace repoPath available: ${server.repoRoot}.`);
546+
await expect(page.locator("#log")).toContainText(`Resolved repoPath: ${server.repoRoot}`);
547+
await expect(page.locator("#log")).toContainText(`Resolved absolute preview output path: ${absoluteAsteroidsPreviewPath}`);
548+
await expect(page.locator("#log")).not.toContainText("Direct preview write unavailable");
541549
await expect(page.locator("#log")).not.toContainText("Unable to resolve absolute repoRoot");
542550
await expect(page.locator("#log")).not.toContainText("/__workspace-manager-v2/repo-root");
543551
await expect(page.locator("#log")).toContainText("Asset folder: assets\\images");
@@ -552,9 +560,21 @@ test.describe("Workspace Manager V2 bootstrap", () => {
552560
const previewStatusHeaderOrder = await page.locator(".preview-generator-v2__status-accordion-header").evaluate((header) => Array.from(header.querySelectorAll(":scope > span, :scope > div > span, :scope > div > button"), (element) => element.textContent.trim()));
553561
expect(previewStatusHeaderOrder).toEqual(["Status", "+", "Clear"]);
554562
await page.locator("#baseUrl").fill(server.baseUrl);
555-
await expect(page.locator("#executeBtn")).toBeDisabled();
556-
expect(server.previewWrites.size).toBe(0);
557-
expect(server.previewAbsoluteWrites.size).toBe(0);
563+
await expect(page.locator("#executeBtn")).toBeEnabled();
564+
let previewDownloadOpened = false;
565+
page.on("download", () => {
566+
previewDownloadOpened = true;
567+
});
568+
await page.locator("#executeBtn").click();
569+
await expect(page.locator("#log")).toContainText("Workspace launch direct preview write target: games/Asteroids/assets/images/preview.svg.", { timeout: 20000 });
570+
await expect(page.locator("#log")).toContainText(`Workspace launch absolute preview output path: ${absoluteAsteroidsPreviewPath}.`, { timeout: 20000 });
571+
await expect(page.locator("#log")).toContainText("Direct preview write target: games/Asteroids/assets/images/preview.svg", { timeout: 20000 });
572+
await expect(page.locator("#log")).toContainText(`Direct preview absolute path: ${absoluteAsteroidsPreviewPath}`, { timeout: 20000 });
573+
await expect(page.locator("#log")).toContainText(`OK Direct preview write completed: ${absoluteAsteroidsPreviewPath}`, { timeout: 20000 });
574+
await expect(page.locator("#log")).toContainText("OK Asteroids", { timeout: 20000 });
575+
expect(previewDownloadOpened).toBe(false);
576+
expect(server.previewWrites.get("games/Asteroids/assets/images/preview.svg")).toContain("<svg");
577+
expect(server.previewAbsoluteWrites.get(absoluteAsteroidsPreviewPath)).toContain("<svg");
558578
await page.locator("#returnToWorkspaceButton").click();
559579
await expect(page).toHaveURL(/workspace-manager-v2\/index\.html\?hostContextId=workspace-manager-v2-/);
560580
expect(pageErrors).toEqual([]);
@@ -607,6 +627,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
607627
const hostContextId = "workspace-manager-v2-display-root-context";
608628
const displayRootManifest = JSON.parse(await readFile("games/Asteroids/game.manifest.json", "utf8"));
609629
displayRootManifest.repoRoot = "HTML-JavaScript-Gaming";
630+
delete displayRootManifest.repoPath;
610631

611632
page.on("pageerror", (error) => {
612633
pageErrors.push(error.message);
@@ -620,12 +641,14 @@ test.describe("Workspace Manager V2 bootstrap", () => {
620641

621642
try {
622643
await expect(page.locator("#repoSelectedValue")).toHaveText("HTML-JavaScript-Gaming");
644+
await expect(page.locator("#pickRepoBtn")).toBeHidden();
623645
await expect(page.locator("#executeBtn")).toBeDisabled();
624646
await expect(page.locator("#previewTargetValue")).toHaveText("games/Asteroids/assets/images/preview.svg");
625647
await expect(page.locator("#log")).toContainText("OK Workspace launch context hydrated for Asteroids.");
626648
await expect(page.locator("#log")).toContainText("Workspace repoRoot display label available: HTML-JavaScript-Gaming.");
627-
await expect(page.locator("#log")).toContainText("WARN Absolute repoRoot missing for workspace launch; manifest repoRoot is display-only: HTML-JavaScript-Gaming.");
628-
await expect(page.locator("#log")).toContainText("Direct preview write unavailable until a real writable repo root is selected.");
649+
await expect(page.locator("#log")).toContainText("Workspace repoPath missing.");
650+
await expect(page.locator("#log")).toContainText("WARN Manifest repoPath is missing or invalid for workspace launch; repoRoot is display-only: HTML-JavaScript-Gaming.");
651+
await expect(page.locator("#log")).toContainText("Direct preview write unavailable until manifest repoPath is an absolute filesystem path.");
629652
await expect(page.locator("#log")).not.toContainText("/__workspace-manager-v2/repo-root");
630653
await expect(page.locator("#log")).not.toContainText("FAIL Workspace launch context hydration");
631654
expect(server.previewWrites.size).toBe(0);
@@ -664,13 +687,15 @@ test.describe("Workspace Manager V2 bootstrap", () => {
664687

665688
await page.locator("#activeGameSelect").selectOption("GravityWell");
666689
await expect(page.locator("#activeGameSummary")).toContainText("games/GravityWell/");
690+
await expect(page.locator("#activeGameSummary")).toContainText("Select Repo");
667691
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"gameRoot": "games\/GravityWell\/"/);
668692
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"assetsPath": "games\/GravityWell\/assets"/);
669693
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"assets.image.preview.preview"/);
670694
await expect(page.locator("#workspaceContextOutput")).not.toHaveValue(/"assets.image.background.preview"/);
671695
await expect(page.locator("#workspaceContextOutput")).not.toHaveValue(/"asset-browser"|"palette-browser"|"vector-map-editor"/);
672696
await expect(page.locator('[data-workspace-tool-id="asset-manager-v2"]')).toContainText("1 managed assets");
673697
await expect(page.locator('[data-workspace-tool-id="palette-manager-v2"]')).toContainText("10 palette swatches");
698+
await expect(page.locator('[data-workspace-tool-id="preview-generator-v2"]')).toContainText("Select Repo");
674699
const gravityManifest = JSON.parse(await page.locator("#workspaceContextOutput").inputValue());
675700
expect(Object.keys(gravityManifest.tools).sort()).toEqual(["asset-manager-v2", "palette-manager-v2"]);
676701
expect(gravityManifest.tools["asset-manager-v2"].assets["assets.image.preview.preview"]).toEqual({
@@ -684,13 +709,15 @@ test.describe("Workspace Manager V2 bootstrap", () => {
684709

685710
await page.locator("#activeGameSelect").selectOption("Pong");
686711
await expect(page.locator("#activeGameSummary")).toContainText("games/Pong/");
712+
await expect(page.locator("#activeGameSummary")).toContainText("Select Repo");
687713
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"gameRoot": "games\/Pong\/"/);
688714
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"assetsPath": "games\/Pong\/assets"/);
689715
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"assets.image.preview.preview"/);
690716
await expect(page.locator("#workspaceContextOutput")).not.toHaveValue(/"assets.image.background.preview"/);
691717
await expect(page.locator("#workspaceContextOutput")).not.toHaveValue(/"asset-browser"|"palette-browser"|"vector-map-editor"/);
692718
await expect(page.locator('[data-workspace-tool-id="asset-manager-v2"]')).toContainText("1 managed assets");
693719
await expect(page.locator('[data-workspace-tool-id="palette-manager-v2"]')).toContainText("8 palette swatches");
720+
await expect(page.locator('[data-workspace-tool-id="preview-generator-v2"]')).toContainText("Select Repo");
694721
const pongManifest = JSON.parse(await page.locator("#workspaceContextOutput").inputValue());
695722
expect(Object.keys(pongManifest.tools).sort()).toEqual(["asset-manager-v2", "palette-manager-v2"]);
696723
expect(pongManifest.tools["asset-manager-v2"].assets["assets.image.preview.preview"]).toEqual({
@@ -792,7 +819,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
792819
await expect(page.locator('[data-workspace-tool-id="asset-manager-v2"]')).toContainText("0 managed assets");
793820
await expect(page.locator('[data-workspace-tool-id="workspace-manager-v2"]')).toHaveCount(0);
794821
await expect(page.locator('[data-workspace-tool-id="palette-manager-v2"]')).toContainText("3 palette swatches");
795-
await expect(page.locator('[data-workspace-tool-id="preview-generator-v2"]')).toContainText("Schema-valid manifest");
822+
await expect(page.locator('[data-workspace-tool-id="preview-generator-v2"]')).toContainText("Select Repo");
796823
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"id": "workspace-manager-v2-UAT-template"/);
797824
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"gameRoot": "games\/_template\/"/);
798825
await expect(page.locator("#workspaceContextOutput")).toHaveValue(/"assetsPath": "games\/_template\/assets"/);

tools/asset-manager-v2/js/services/WorkspaceBridge.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class WorkspaceBridge {
7474
return { ok: false, message: "Workspace Manager V2 launch no longer accepts wrapper context JSON." };
7575
}
7676
const unsupportedManifestKeys = Object.keys(workspaceManifest)
77-
.filter((key) => !["$schema", "documentKind", "schema", "version", "id", "name", "gameId", "gameRoot", "assetsPath", "repoRoot", "tools"].includes(key));
77+
.filter((key) => !["$schema", "documentKind", "schema", "version", "id", "name", "gameId", "gameRoot", "assetsPath", "repoRoot", "repoPath", "tools"].includes(key));
7878
if (unsupportedManifestKeys.length) {
7979
return { ok: false, message: `Workspace Manager V2 manifest includes fields not allowed by the workspace manifest schema: ${unsupportedManifestKeys.join(", ")}.` };
8080
}

0 commit comments

Comments
 (0)