chore: checkpoint web Blender feature-gap work, docs and tests
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

- update native web_engine sources and rebuilt wasm artifacts
- add task cards, status notes and tooling scripts
- add web e2e specs, tutorial UI sources and tutorial assets
- sync golden task manifests and audit records

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
mes123456
2026-11-11 10:46:55 +08:00
parent f3e21c6b98
commit d1e6b63d54
665 changed files with 59149 additions and 39194 deletions

View File

@@ -20,14 +20,14 @@ test("modifier stack panel commits only changed native fields", async ({ page },
const workflow = new NativeUIWorkflow(page, testInfo.outputPath("panel"));
await page.goto("/");
await workflow.load(path.join(root, fixturePath), "StackTarget");
const section = page.locator(".modifier-stack-authoring");
const bevel = section.locator(".modifier-row").filter({ hasText: "Stack Bevel" });
const section = page.locator("fieldset").filter({ hasText: "StackTarget: Modifiers" }).first();
const bevel = section.locator('[data-modifier-row][data-modifier-name="Stack Bevel"]');
const count = await page.evaluate(() => (window as unknown as { __nativeWorkflowRequests: unknown[] }).__nativeWorkflowRequests.length);
await bevel.getByLabel("width", { exact: true }).fill("0.35");
await bevel.getByLabel("Amount", { exact: true }).fill("0.35");
expect(await page.evaluate(() => (window as unknown as { __nativeWorkflowRequests: unknown[] }).__nativeWorkflowRequests.length)).toBe(count);
await workflow.command(() => bevel.getByLabel("width", { exact: true }).press("Enter"));
await workflow.command(() => bevel.getByLabel("Amount", { exact: true }).press("Enter"));
await expect(page.getByRole("treeitem").filter({ hasText: "StackTarget" })).toHaveAttribute("aria-selected", "true");
await workflow.command(async () => { await bevel.getByLabel("segments", { exact: true }).fill("4"); await bevel.getByLabel("segments", { exact: true }).press("Enter"); });
await workflow.command(async () => { await bevel.getByLabel("Segments", { exact: true }).fill("4"); await bevel.getByLabel("Segments", { exact: true }).press("Enter"); });
await bevel.screenshot({ path: testInfo.outputPath("modifier-fields-desktop.png") });
await page.setViewportSize({ width: 960, height: 844 });
await bevel.screenshot({ path: testInfo.outputPath("modifier-fields-compact.png") });
@@ -38,10 +38,10 @@ test("modifier stack panel commits only changed native fields", async ({ page },
expect(geometry.every((item) => item.fits && item.width >= 60)).toBe(true);
await page.setViewportSize({ width: 1280, height: 720 });
await workflow.click(page.getByRole("button", { name: "Move Stack Bevel up", exact: true }));
await page.getByLabel("Modifier type", { exact: true }).fill("SOLIDIFY");
await workflow.click(section.getByRole("button", { name: "Add", exact: true }));
await workflow.measured("remove-array", () => workflow.click(page.getByRole("button", { name: "Remove Stack Array", exact: true })));
await expect(section.locator(".modifier-row").filter({ hasText: "Stack Array" })).toHaveCount(0);
await page.getByLabel("Properties modifier type", { exact: true }).selectOption("Solidify");
await workflow.click(section.getByRole("button", { name: "Add Modifier", exact: true }));
await workflow.measured("remove-array", () => workflow.click(section.locator('[data-modifier-row][data-modifier-name="Stack Array"]').getByRole("button", { name: "Remove", exact: true })));
await expect(section.locator('[data-modifier-row][data-modifier-name="Stack Array"]')).toHaveCount(0);
await workflow.save("after");
const output = path.join(workflow.directory, "main.json");
const request = path.join(workflow.directory, "request.json");
@@ -63,7 +63,9 @@ for (const { route, modifierType } of scenarios) {
const directory = process.env.WBV2_RECEIPT_DIR ?? testInfo.outputPath("receipts");
const scenario = route === "parameters" ? `parameters-${modifierType.toLowerCase()}` : route;
fs.mkdirSync(directory, { recursive: true });
await page.goto("/");
// These scenarios own the only engine client; the app page would already
// hold the single-writer lock. The blank manual host keeps the contract.
await page.goto("/?engine=manual");
const result = await page.evaluate(async (input) => {
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
const clients: InstanceType<typeof WebEngineClient>[] = [];
@@ -145,7 +147,7 @@ for (const { route, modifierType } of scenarios) {
BEVEL: [{ segments: 0 }, { width: -0.1 }, { segments: 2.5 }],
ARRAY: [{ count: 0 }, { relative_offset_displace: [1, 2] }, { use_relative_offset: 1 }],
MIRROR: [{ use_axis: [1, 1, 0] }, { use_clip: "true" }],
SOLIDIFY: [{ offset: 2 }], SUBSURF: [{ levels: 12 }],
SOLIDIFY: [{ offset: 200000 }], SUBSURF: [{ levels: 12 }],
}[input.modifierType];
const badCommands: WebEngineEditCommand[] = input.route === "add"
? [{ type: "addModifier", meshId: "missing:99999", modifierType: "PARTICLE_SYSTEM", name: "Invalid target" }]
@@ -159,9 +161,11 @@ for (const { route, modifierType } of scenarios) {
await save(client, "undo");
await measured("redo", () => client.applyCommand({ type: "redo" }));
const saved = await save(client, "redo");
client.terminate();
const reopened = create();
await measured("reopen", () => reopened.openBlend(saved.slice(0)));
await save(reopened, "reopened");
reopened.terminate();
const replay = create();
await measured("replay-open", () => replay.openBlend(Uint8Array.from(atob(files.before), (char) => char.charCodeAt(0)).buffer));
await measured("replay-write", () => replay.applyCommand(commands[input.route]));