diff --git a/scripts/capture-quietware-screenshots.mjs b/scripts/capture-quietware-screenshots.mjs new file mode 100644 index 0000000..a2c378c --- /dev/null +++ b/scripts/capture-quietware-screenshots.mjs @@ -0,0 +1,87 @@ +// Drives the dev server with Playwright bundled chromium. +// Captures /design-system-v2 in 3 quietware modes (dark/light/HC) + a fallback. +import { chromium } from "playwright"; +import { mkdir } from "node:fs/promises"; + +const OUT = "/tmp/lumotia-v0.3-screenshots"; +await mkdir(OUT, { recursive: true }); + +const browser = await chromium.launch({ headless: true }); +const ctx = await browser.newContext({ + viewport: { width: 1440, height: 1024 }, + deviceScaleFactor: 2, +}); +const page = await ctx.newPage(); + +// Capture console for diagnosis +page.on("pageerror", e => console.error("[page error]", e.message)); +page.on("console", m => { if (m.type() === "error") console.error("[console]", m.text()); }); + +async function setMode({ design, theme, contrast }) { + await page.evaluate(({ design, theme, contrast }) => { + const h = document.documentElement; + if (design) h.setAttribute("data-design", design); else h.removeAttribute("data-design"); + if (theme) h.setAttribute("data-theme", theme); else h.removeAttribute("data-theme"); + if (contrast) h.setAttribute("data-contrast", contrast); else h.removeAttribute("data-contrast"); + }, { design, theme, contrast }); +} + +const CAPTURES = [ + // route, modes + { route: "/design-system-v2", name: "design-system" }, +]; + +const MODES = [ + { id: "v02-dark", design: null, theme: null, contrast: null, label: "v0.2 baseline (no quietware)" }, + { id: "quiet-dark", design: "quietware", theme: null, contrast: null, label: "Quietware dark" }, + { id: "quiet-light", design: "quietware", theme: "light", contrast: null, label: "Quietware light" }, + { id: "quiet-hc-dark", design: "quietware", theme: null, contrast: "high", label: "Quietware high-contrast (dark base)" }, + { id: "quiet-hc-light", design: "quietware", theme: "light", contrast: "high", label: "Quietware high-contrast (light base)" }, +]; + +const BASE = "http://localhost:1420"; + +for (const capture of CAPTURES) { + console.log(`\n=== ${capture.name} (${capture.route}) ===`); + try { + await page.goto(BASE + capture.route, { waitUntil: "networkidle", timeout: 30000 }); + } catch (e) { + console.error(`navigation failed: ${e.message}`); + continue; + } + // Wait a beat for Svelte hydration + font load + await page.waitForTimeout(1500); + + for (const mode of MODES) { + await setMode(mode); + await page.waitForTimeout(400); // CSS transitions + const file = `${OUT}/${capture.name}__${mode.id}.png`; + await page.screenshot({ path: file, fullPage: true }); + const stats = await page.evaluate(() => ({ + h: document.documentElement.dataset.design || "(none)", + t: document.documentElement.dataset.theme || "(default)", + c: document.documentElement.dataset.contrast || "(default)", + })); + console.log(` ${mode.id} design=${stats.h} theme=${stats.t} contrast=${stats.c}`); + } +} + +// Also try the root route — may render the Dictation/FirstRun page if Tauri APIs +// gracefully degrade in browser context. Worth attempting; if it crashes we just skip. +try { + console.log("\n=== root route (best effort) ==="); + await page.goto(BASE + "/", { waitUntil: "domcontentloaded", timeout: 15000 }); + await page.waitForTimeout(2000); + for (const mode of MODES.slice(0, 3)) { + await setMode(mode); + await page.waitForTimeout(400); + const file = `${OUT}/root__${mode.id}.png`; + await page.screenshot({ path: file, fullPage: false }); + console.log(` root ${mode.id}`); + } +} catch (e) { + console.error(`root route skipped: ${e.message}`); +} + +await browser.close(); +console.log(`\nWrote screenshots to ${OUT}/`); diff --git a/vite.config.js b/vite.config.js index 95d2758..67a81fb 100644 --- a/vite.config.js +++ b/vite.config.js @@ -43,6 +43,12 @@ export default defineConfig(async () => ({ watch: { ignored: ["**/src-tauri/**"], }, + fs: { + // v0.3 Phase 4a screenshot capture: when dev server runs inside a + // git worktree (.worktrees/v0.3-tactile-quietware/) the parent + // node_modules sits one extra dir up. Allow the worktree root. + allow: ["..", "../.."], + }, }, test: { // jsdom gives storage-shim tests a real localStorage / DOMException /