From 7f933f3ca264fa7b233def945ba0161867d8a8c9 Mon Sep 17 00:00:00 2001 From: Jake Date: Fri, 15 May 2026 09:13:13 +0100 Subject: [PATCH] v0.2 UI capture: scripts/capture-v0.2-screenshots.mjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .gitignore | 4 + scripts/capture-v0.2-screenshots.mjs | 124 +++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 scripts/capture-v0.2-screenshots.mjs diff --git a/.gitignore b/.gitignore index d51bc0e..d956d1e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ reports/ test-results/ playwright-report/ +# Vite-loaded env overrides — local-only by design. +.env.local +.env.*.local + # Python bytecode (from release scripts under scripts/) __pycache__/ *.pyc diff --git a/scripts/capture-v0.2-screenshots.mjs b/scripts/capture-v0.2-screenshots.mjs new file mode 100644 index 0000000..84554ba --- /dev/null +++ b/scripts/capture-v0.2-screenshots.mjs @@ -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); +});