v0.2 UI capture: scripts/capture-v0.2-screenshots.mjs
Spins up `npm run dev:frontend` (Vite without Tauri), drives Playwright
Chromium at 1440x900, and writes a PNG per UI surface to
/home/jake/lumotia-v0.2-screenshots/.
Surfaces captured (16 total):
01 Dictation (default)
02 Files
03 Tasks
04 History
05 Settings
06 Dictation × dark/light × cave/energy/reset (6 surface sets)
07 /float — frame-less task panel
08 /viewer — transcript viewer
09 /preview — Wayland-hardened transcription overlay
10 /design-system-v2 — internal primitives showcase
The design-system-v2 route is gated by VITE_LUMOTIA_DESIGN_SYSTEM_V2=1
per Phase 5; the screenshot run picks this up via a local-only
`.env.local` (now gitignored). shootRoute() waits on a Lumotia
text node before screenshotting so the 11-primitive showcase has time
to hydrate.
Browser-preview-only console noise ("transformCallback") is expected:
some Tauri-only code paths still call invoke() during initial render
even with hasTauriRuntime() gates upstream. No effect on the
captured surfaces.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -14,6 +14,10 @@ reports/
|
|||||||
test-results/
|
test-results/
|
||||||
playwright-report/
|
playwright-report/
|
||||||
|
|
||||||
|
# Vite-loaded env overrides — local-only by design.
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
# Python bytecode (from release scripts under scripts/)
|
# Python bytecode (from release scripts under scripts/)
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|||||||
124
scripts/capture-v0.2-screenshots.mjs
Normal file
124
scripts/capture-v0.2-screenshots.mjs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// v0.2 frontend-overhaul UI capture.
|
||||||
|
//
|
||||||
|
// Spins up `npm run dev:frontend` with the design-system-v2 env flag,
|
||||||
|
// drives Playwright Chromium at 1440x900, walks every reachable UI
|
||||||
|
// surface, and writes a PNG per surface to /home/jake/lumotia-v0.2-screenshots/.
|
||||||
|
//
|
||||||
|
// The dev server runs without Tauri, so any Tauri-only surface (record
|
||||||
|
// flow, model download, model loading state) renders the graceful
|
||||||
|
// browser-preview fallback rather than the production state. This is
|
||||||
|
// intentional and matches what the Phase 1 e2e smoke baseline captures.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// node scripts/capture-v0.2-screenshots.mjs
|
||||||
|
|
||||||
|
import { chromium } from "playwright";
|
||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { mkdir, rm } from "node:fs/promises";
|
||||||
|
import { existsSync } from "node:fs";
|
||||||
|
import { setTimeout as sleep } from "node:timers/promises";
|
||||||
|
|
||||||
|
const OUT_DIR = "/home/jake/lumotia-v0.2-screenshots";
|
||||||
|
const BASE = "http://localhost:1420";
|
||||||
|
const VIEWPORT = { width: 1440, height: 900 };
|
||||||
|
|
||||||
|
async function waitForUrl(url, timeoutMs = 60_000) {
|
||||||
|
const started = Date.now();
|
||||||
|
while (Date.now() - started < timeoutMs) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, { method: "GET" });
|
||||||
|
if (res.ok) return;
|
||||||
|
} catch { /* not up yet */ }
|
||||||
|
await sleep(500);
|
||||||
|
}
|
||||||
|
throw new Error(`dev server did not come up on ${url} within ${timeoutMs}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startDevServer() {
|
||||||
|
console.log("Starting Vite dev server …");
|
||||||
|
const env = { ...process.env, VITE_LUMOTIA_DESIGN_SYSTEM_V2: "1" };
|
||||||
|
const proc = spawn("npm", ["run", "dev:frontend"], { env, stdio: "ignore" });
|
||||||
|
await waitForUrl(BASE);
|
||||||
|
console.log("Vite up at", BASE);
|
||||||
|
return proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shoot(page, slug, label, action) {
|
||||||
|
console.log(` ${slug}: ${label}`);
|
||||||
|
await page.goto(BASE + "/");
|
||||||
|
await page.locator("main").waitFor({ state: "visible", timeout: 20_000 }).catch(() => {});
|
||||||
|
if (action) {
|
||||||
|
try { await action(page); } catch (err) {
|
||||||
|
console.warn(` action failed for ${slug}:`, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await page.waitForTimeout(700);
|
||||||
|
await page.screenshot({ path: `${OUT_DIR}/${slug}.png` });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shootRoute(page, route, slug, label) {
|
||||||
|
console.log(` ${slug}: ${label} (${route})`);
|
||||||
|
await page.goto(BASE + route);
|
||||||
|
// Wait for at least one Lumotia text node to appear; the design-system-v2
|
||||||
|
// route in particular hydrates a lot of primitives and needs longer than
|
||||||
|
// the previous flat 1.2 s sleep.
|
||||||
|
await page.waitForSelector("text=Lumotia", { timeout: 10_000 }).catch(() => {});
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
|
await page.screenshot({ path: `${OUT_DIR}/${slug}.png` });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
if (existsSync(OUT_DIR)) await rm(OUT_DIR, { recursive: true, force: true });
|
||||||
|
await mkdir(OUT_DIR, { recursive: true });
|
||||||
|
|
||||||
|
const dev = await startDevServer();
|
||||||
|
let proc = dev;
|
||||||
|
let browser;
|
||||||
|
try {
|
||||||
|
browser = await chromium.launch();
|
||||||
|
const ctx = await browser.newContext({ viewport: VIEWPORT });
|
||||||
|
const page = await ctx.newPage();
|
||||||
|
page.on("pageerror", (e) => console.warn(" pageerror:", e.message));
|
||||||
|
|
||||||
|
// Five sidebar-driven pages — click via aria-label.
|
||||||
|
await shoot(page, "01-dictation", "Dictation (default)", null);
|
||||||
|
await shoot(page, "02-files", "Files", (p) => p.click("[aria-label='Files']"));
|
||||||
|
await shoot(page, "03-tasks", "Tasks", (p) => p.click("[aria-label='Tasks']"));
|
||||||
|
await shoot(page, "04-history", "History", (p) => p.click("[aria-label='History']"));
|
||||||
|
await shoot(page, "05-settings", "Settings", (p) => p.click("[aria-label='Settings']"));
|
||||||
|
|
||||||
|
// Theme + zone variants of the default Dictation page so reviewers
|
||||||
|
// can sanity-check the warm-brutalist tokens hold up across all six
|
||||||
|
// (3 zones × 2 themes) surface sets per docs/release §15 mitigation.
|
||||||
|
for (const theme of ["dark", "light"]) {
|
||||||
|
for (const zone of ["cave", "energy", "reset"]) {
|
||||||
|
await shoot(page, `06-dictation-${theme}-${zone}`, `Dictation ${theme}/${zone}`, async (p) => {
|
||||||
|
await p.evaluate(({ t, z }) => {
|
||||||
|
document.documentElement.dataset.theme = t;
|
||||||
|
document.documentElement.dataset.zone = z;
|
||||||
|
}, { t: theme, z: zone });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standalone windows.
|
||||||
|
await shootRoute(page, "/float", "07-float", "Float — task panel");
|
||||||
|
await shootRoute(page, "/viewer", "08-viewer", "Viewer — transcript");
|
||||||
|
await shootRoute(page, "/preview", "09-preview", "Preview — transcription overlay");
|
||||||
|
|
||||||
|
// Internal preview surface (gated by VITE_LUMOTIA_DESIGN_SYSTEM_V2=1).
|
||||||
|
await shootRoute(page, "/design-system-v2", "10-design-system-v2", "Design system v0.2 preview");
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
console.log(`Screenshots written to: ${OUT_DIR}`);
|
||||||
|
} finally {
|
||||||
|
if (browser) await browser.close();
|
||||||
|
proc?.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user