seaislee1209 78fd7ee13d feat(core/frontend): P1 pixel restoration (settings/messages/wizard/product-create faithful; ai-tools draft)
- settings.tsx + settings-page.css: restore to settings.html (left-nav + sections), real user/team data
- messages.tsx + messages-page.css: rich inbox restore (filters/detail/props grid) on real notifications
- projects.tsx ProjectWizardPage + project-wizard-page.css: restore to projects-new.html
- products.tsx ProductCreateUploadPage + product-create-page.css: restore to product-create-v2 baseline
- ai-tools.tsx (AssetFactory/ImageWorkbench) + ai-tools-page.css: DRAFT unified studio shell;
  deviates from per-page baselines (image-optimize should be chat-stream; model-photo product+person picker)
  -> pending rework alongside P3 standalone image-gen decision
- shot-p1.mjs: playwright visual-parity capture (react vs exact baseline; uses 127.0.0.1 not localhost)
- verified: tsc --noEmit clean; screenshots confirm settings/wizard/product-create/messages faithful

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:25:19 +08:00

63 lines
2.4 KiB
JavaScript

// shot-p1.mjs · P1 像素还原验收截图(React 实页 vs public/exact 基线)
// 用法: node shot-p1.mjs [outDir]
// 前置: 后端 8010 + 前端 5173 在跑;playwright 已装;chromium 已缓存。
import { chromium } from "playwright";
import { mkdirSync } from "node:fs";
const BASE = "http://127.0.0.1:5180";
const API = "http://127.0.0.1:8010";
const OUT = process.argv[2] || "shots-p1";
mkdirSync(OUT, { recursive: true });
// [name, React 路由, 基线 html]
const PAGES = [
["settings", "/settings", "/exact/settings.html"],
["messages", "/messages", "/exact/messages.html"],
["asset-factory", "/asset-factory", "/exact/asset-factory.html"],
["image-optimize", "/image-optimize", "/exact/image-optimize.html"],
["model-photo", "/model-photo", "/exact/model-photo.html"],
["platform-cover", "/platform-cover", "/exact/platform-cover.html"],
["product-create", "/products/new", "/exact/product-create-upload.html"],
["project-wizard", "/projects/new", "/exact/projects-new.html"]
];
const res = await fetch(`${API}/api/auth/login/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: "airshelf", password: "Restraint2026" })
});
const data = await res.json();
const token = data.token;
if (!token) {
console.error("login failed:", JSON.stringify(data));
process.exit(1);
}
console.log("token ok");
const browser = await chromium.launch();
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 });
await ctx.addInitScript((t) => localStorage.setItem("airshelf_token", t), token);
const page = await ctx.newPage();
page.on("pageerror", (e) => console.error(" pageerror:", e.message));
for (const [name, route, baseline] of PAGES) {
try {
await page.goto(BASE + route, { waitUntil: "networkidle", timeout: 30000 });
await page.waitForTimeout(1400);
await page.screenshot({ path: `${OUT}/${name}.react.png`, fullPage: true });
console.log("react ", name);
} catch (e) {
console.error("FAIL react", name, e.message);
}
try {
await page.goto(BASE + baseline, { waitUntil: "networkidle", timeout: 30000 });
await page.waitForTimeout(900);
await page.screenshot({ path: `${OUT}/${name}.baseline.png`, fullPage: true });
console.log("baseline", name);
} catch (e) {
console.error("FAIL baseline", name, e.message);
}
}
await browser.close();
console.log("DONE ->", OUT);