#!/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); });