Same gated-layout pattern as Phase 5c. v0.2 surface untouched;
quietware path renders FilesPage via LumotiaPageSkeleton with the
four zones the page actually needs:
Capture header "File transcription" + "Transcribe audio files locally."
Primary surface drop zone / progress / transcript states
Action dock Browse files / Copy / Export
Mono footer formats · model · Local only
State + handlers preserved.
fileTranscript, segments, isDragOver, progress, progressText,
fileName, error, transcribing, showExportMenu — all unchanged.
handleBrowse, transcribeFiles, copyAll, handleExport — all
unchanged. Tauri drag-drop listeners stay live on onMount; new
MutationObserver for data-design joins them.
Primary surface state machine.
transcribing Upload icon, "Transcribing" label, mono file
name, LumotiaProgress (tone="transcribing", so
the blue Phase 4k progress fill applies).
Progress text below.
empty Dashed drop zone using --button-primary-bg
accent when isDragOver, neutral subtle border
otherwise. Empty-state typography mirrors
Dictation: Young Serif italic "Drop an audio
file.", Work Sans support, format list in
JetBrains Mono.
has transcript Full-bleed editable textarea.
Action dock.
Browse files (primary when no transcript; secondary when transcript
exists — at that point Export becomes the primary action).
Copy (tertiary, disabled until transcript exists).
Export (LumotiaMenu trigger — primary when transcript exists,
secondary otherwise).
Disabled controls render neutral per the Phase 4k token contract.
Mono footer.
Single line: "MP3 · WAV · M4A · MP4 · FLAC · OGG · {model} · Local only"
Capture script updated.
scripts/capture-quietware-screenshots.mjs now navigates to the
Files page via the sidebar nav button after capturing the root
route, and screenshots both quietware modes.
Verified.
- npm run check: 0 errors, 0 warnings across 5707 files.
- Headless captures confirm the new layout in both modes:
drop zone + action dock + mono footer with the right Phase 4j
colour grammar applied. Browse Files blue primary, disabled
actions neutral, dashed border picks up --button-primary-bg
when isDragOver.
Phase 5e (Tasks) and 5f (History) follow same pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
108 lines
4.3 KiB
JavaScript
108 lines
4.3 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}`);
|
|
}
|
|
|
|
await browser.close();
|
|
console.log(`\nWrote screenshots to ${OUT}/`);
|