Files
Lumotia/scripts/capture-quietware-screenshots.mjs
Jake 1d5e6d4c59 v0.3 Phase 5f: History page migrated to quietware skeleton
Calm local archive instead of SaaS table. Same gated-layout pattern
as Phases 5c/5d. v0.2 surface unchanged; quietware path renders
HistoryPage via LumotiaPageSkeleton.

Zones.

  Header             "History" title + "Search and reopen saved
                     transcripts." subtext + Live/Trash tab toggle
                     with live counts in mono.
  Primary surface    search input + scrollable list of items, or
                     empty-state typography when nothing saved.
  Mono footer        N saved · N in trash · Local only.

  Action dock omitted — actions live per-row (Open / Copy /
  Delete) so the dock doesn't add noise when the user is browsing
  a list of items each with their own context.

State + handlers preserved.

  history (store), trashItems, viewMode, searchQuery, filtered
  (derived), trashError, trashLoading, restoringId,
  compactPreviewText, copyItem, removeItem, openViewer,
  restoreFromTrash, formatTimestamp.

  Bulk actions, audio playback, tag chips, ClearAll modal and
  Phase 9 multi-select toolbar all stay accessible via the v0.2
  fallback path when quietware is off. The quietware view focuses
  on the core archive flow.

Per-item row.

  Mic icon if source=dictation, FileText icon if source=file.
  Title (or first 80 chars of preview if untitled).
  Two-line preview clamp.
  Mono timestamp via formatTimestamp.
  Open / Copy / Delete actions in tertiary buttons on the right.

Empty states.

  Live mode, no items:
    FileText icon + Young Serif italic "Nothing saved yet." +
    Work Sans support.

  Live mode, search returns nothing:
    same icon + "Nothing matched." + "Try different keywords or
    clear the search."

  Trash mode, no items:
    Clock icon + "Trash is empty." + 30-day retention copy.

Search input.

  Lives at the top of the primary surface. Standard search input
  with focus ring picking up --button-primary-bg.

Capture script.

  scripts/capture-quietware-screenshots.mjs now navigates to the
  History page via the sidebar nav button after Files, and
  screenshots both quietware modes.

Verified.

  - npm run check: 0 errors, 0 warnings across 5707 files.
  - Captures confirm both empty and populated states render
    correctly (populated state captured with temporary mock data
    in the history store, reverted before this commit).

Phase 5f is complete. Phase 5e (Tasks) is the last layout migration
before Phase 6 / 7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:03:44 +01:00

128 lines
5.0 KiB
JavaScript

// 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}`);
}
// Files page — SPA navigation via the sidebar item.
try {
console.log("\n=== files page (best effort) ===");
await page.evaluate(() => {
const items = Array.from(document.querySelectorAll('button, a, [role="button"], li'));
const target = items.find(el => (el.textContent || "").trim().toLowerCase() === "files");
if (target) target.click();
});
await page.waitForTimeout(1500);
for (const mode of MODES.slice(1, 3)) {
await setMode(mode);
await page.waitForTimeout(400);
const file = `${OUT}/files__${mode.id}.png`;
await page.screenshot({ path: file, fullPage: false });
console.log(` files ${mode.id}`);
}
} catch (e) {
console.error(`files page skipped: ${e.message}`);
}
// History page — SPA navigation via the sidebar item.
try {
console.log("\n=== history page (best effort) ===");
await page.evaluate(() => {
const items = Array.from(document.querySelectorAll('button, a, [role="button"], li'));
const target = items.find(el => (el.textContent || "").trim().toLowerCase() === "history");
if (target) target.click();
});
await page.waitForTimeout(1500);
for (const mode of MODES.slice(1, 3)) {
await setMode(mode);
await page.waitForTimeout(400);
const file = `${OUT}/history__${mode.id}.png`;
await page.screenshot({ path: file, fullPage: false });
console.log(` history ${mode.id}`);
}
} catch (e) {
console.error(`history page skipped: ${e.message}`);
}
await browser.close();
console.log(`\nWrote screenshots to ${OUT}/`);