Skip to content

Commit adb2bc2

Browse files
author
DavidQ
committed
Fix Preview Generator SVG hydration and workspace background rendering - PR_26127_009-preview-svg-selection-and-background-hydration
1 parent d29b93b commit adb2bc2

6 files changed

Lines changed: 307 additions & 42 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# PR_26127_009-preview-svg-selection-and-background-hydration
2+
3+
## Scope
4+
- Read `docs/dev/PROJECT_INSTRUCTIONS.md` and followed the single-purpose Workspace V2/tool lane.
5+
- Kept changes scoped to Preview Generator V2 runtime hydration/capture behavior and Workspace Manager V2 Playwright coverage.
6+
- Did not modify deprecated `tools/workspace-v2`.
7+
- Did not modify sample JSON.
8+
9+
## Preview Target Hydration
10+
- Preview Generator V2 now separates the manifest-selected preview source from the generated output target.
11+
- Workspace launch hydration reads the Asset Manager V2 image asset with role `preview`.
12+
- Before repo selection, the UI preview source remains the manifest-selected image, for example `games/Asteroids/assets/images/bezel.png`.
13+
- After Pick Repo Folder succeeds, the Output Summary preview target updates to the generated output path `games/Asteroids/assets/images/preview.svg`.
14+
- The generated output target uses `OUTPUT_NAME = "preview.svg"` and the manifest-selected preview asset folder, so it is not hardcoded to PNG.
15+
16+
## Manifest Asset Structure Notes
17+
- The Asteroids manifest already uses the normalized Asset Manager V2 preview asset structure:
18+
```json
19+
"assets.image.preview.bezel": {
20+
"path": "assets/images/bezel.png",
21+
"type": "image",
22+
"kind": "png",
23+
"role": "preview",
24+
"source": "manifest"
25+
}
26+
```
27+
- No manifest fallback asset shape was added.
28+
29+
## Background Hydration Notes
30+
- Preview Generator V2 now hydrates a background source from the Asset Manager V2 image asset with role `background`.
31+
- It hydrates a dark background color from the active Palette Manager V2 swatches by `background` or `black` name/tag.
32+
- Asteroids resolves `assets.image.background.deluxe` and `Space Black #020617`.
33+
- Capture output applies the hydrated color to generated SVG wrappers and html2canvas full-page capture.
34+
- The previous hidden/default white html2canvas background fallback was removed.
35+
- Status output logs the hydrated background asset path and palette color.
36+
37+
## Validation
38+
- `node --check tools/preview-generator-v2/PreviewGeneratorV2App.js` passed.
39+
- `node --check tools/preview-generator-v2/PreviewGeneratorV2Capture.js` passed.
40+
- `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs` passed.
41+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list` passed: 8 passed.
42+
- `npm run test:workspace-v2` passed: 24 passed.
43+
- Full samples smoke test skipped per PR instructions.

games/Asteroids/assets/images/preview.svg

Lines changed: 2 additions & 1 deletion
Loading

games/Asteroids/game.manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@
178178
"uniformEdgeStretchPx": 10
179179
}
180180
},
181-
"assets.image.preview.bezel": {
182-
"path": "assets/images/bezel.png",
181+
"assets.image.preview.preview": {
182+
"path": "assets/images/preview.png",
183183
"type": "image",
184184
"kind": "png",
185185
"role": "preview",

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,88 @@ async function openToolsIndex(page) {
2626

2727
async function installPreviewGeneratorRepoRootPicker(page) {
2828
await page.addInitScript(() => {
29+
class FakeFileHandle {
30+
constructor(name, parentPath, contents = "") {
31+
this.kind = "file";
32+
this.name = name;
33+
this.path = parentPath ? `${parentPath}/${name}` : name;
34+
this.contents = contents;
35+
}
36+
37+
async getFile() {
38+
return new File([this.contents], this.name, { type: "text/plain" });
39+
}
40+
41+
async createWritable() {
42+
return {
43+
write: async (contents) => {
44+
this.contents = String(contents || "");
45+
},
46+
close: async () => {
47+
window.__previewGeneratorV2Writes.push({
48+
contents: this.contents,
49+
path: this.path
50+
});
51+
}
52+
};
53+
}
54+
}
55+
2956
class FakeDirectoryHandle {
30-
constructor(name) {
57+
constructor(name, parentPath = "") {
3158
this.kind = "directory";
3259
this.name = name;
60+
this.path = parentPath ? `${parentPath}/${name}` : name;
3361
this.children = new Map();
3462
}
3563

3664
addDirectory(name) {
37-
const directory = new FakeDirectoryHandle(name);
65+
const directory = new FakeDirectoryHandle(name, this.path);
3866
this.children.set(name, directory);
3967
return directory;
4068
}
4169

42-
async getDirectoryHandle(name) {
70+
addFile(name, contents = "") {
71+
const file = new FakeFileHandle(name, this.path, contents);
72+
this.children.set(name, file);
73+
return file;
74+
}
75+
76+
async getDirectoryHandle(name, options = {}) {
4377
const child = this.children.get(name);
4478
if (child?.kind === "directory") {
4579
return child;
4680
}
81+
if (options.create) {
82+
return this.addDirectory(name);
83+
}
4784
throw new Error(`Missing directory: ${name}`);
4885
}
86+
87+
async getFileHandle(name, options = {}) {
88+
const child = this.children.get(name);
89+
if (child?.kind === "file") {
90+
return child;
91+
}
92+
if (options.create) {
93+
return this.addFile(name);
94+
}
95+
throw new Error(`Missing file: ${name}`);
96+
}
97+
98+
async *entries() {
99+
for (const entry of this.children.entries()) {
100+
yield entry;
101+
}
102+
}
49103
}
50104

51105
const repo = new FakeDirectoryHandle("HTML-JavaScript-Gaming");
52-
repo.addDirectory("games");
106+
const games = repo.addDirectory("games");
107+
const asteroids = games.addDirectory("Asteroids");
108+
asteroids.addFile("index.html", "<!doctype html><title>Asteroids</title>");
53109
repo.addDirectory("tools");
110+
window.__previewGeneratorV2Writes = [];
54111
window.showDirectoryPicker = async () => repo;
55112
});
56113
}
@@ -532,12 +589,27 @@ test.describe("Workspace Manager V2 bootstrap", () => {
532589
await expect(page.locator("#log")).toContainText("Repo selected from manifest repoRoot: HTML-JavaScript-Gaming.");
533590
await expect(page.locator("#log")).toContainText("Pick the matching actual repo root folder before writing preview output.");
534591
await expect(page.locator("#log")).toContainText("Asset folder: assets\\images");
535-
await expect(page.locator("#log")).toContainText("Manifest preview asset: assets.image.preview.bezel");
592+
await expect(page.locator("#log")).toContainText("Manifest preview asset: assets.image.preview.bezel (image/png)");
593+
await expect(page.locator("#log")).toContainText("Manifest preview source: games/Asteroids/assets/images/bezel.png");
594+
await expect(page.locator("#log")).toContainText("Generated preview target: games/Asteroids/assets/images/preview.svg");
536595
await expect(page.locator("#log")).toContainText("Preview target: games/Asteroids/assets/images/bezel.png");
537-
await expect(page.locator("#log")).toContainText("OK Workspace preview target is valid at games/Asteroids/assets/images/bezel.png.");
596+
await expect(page.locator("#log")).toContainText("Workspace background source: assets.image.background.deluxe -> games/Asteroids/assets/images/deluxe.png");
597+
await expect(page.locator("#log")).toContainText("Workspace background color: Space Black #020617 from palette-manager-v2 swatch.");
598+
await expect(page.locator("#log")).toContainText("OK Workspace manifest preview source is valid at games/Asteroids/assets/images/bezel.png.");
538599
await page.locator("#pickRepoBtn").click();
539600
await expect(page.locator("#repoSelectedValue")).toHaveText("HTML-JavaScript-Gaming");
601+
await expect(page.locator("#previewTargetValue")).toHaveText("games/Asteroids/assets/images/preview.svg");
602+
await expect(page.locator("#log")).toContainText("Preview target updated: games/Asteroids/assets/images/preview.svg");
540603
await expect(page.locator("#executeBtn")).toBeEnabled();
604+
await page.locator("#executeBtn").click();
605+
await expect(page.locator("#log")).toContainText("OUT games\\Asteroids\\assets\\images\\preview.svg", { timeout: 30000 });
606+
await expect(page.locator("#log")).toContainText("Done.", { timeout: 30000 });
607+
await expect(page.locator("#lastGeneratedImageMeta")).toHaveText("Last generated: Asteroids");
608+
const previewWrites = await page.evaluate(() => window.__previewGeneratorV2Writes);
609+
expect(previewWrites).toHaveLength(1);
610+
expect(previewWrites[0].path).toBe("HTML-JavaScript-Gaming/games/Asteroids/assets/images/preview.svg");
611+
expect(previewWrites[0].contents).toContain('fill="#020617"');
612+
expect(previewWrites[0].contents).not.toContain("#ffffff");
541613
await page.locator("#returnToWorkspaceButton").click();
542614
await expect(page).toHaveURL(/workspace-manager-v2\/index\.html\?hostContextId=workspace-manager-v2-/);
543615
expect(pageErrors).toEqual([]);

0 commit comments

Comments
 (0)