v0.3 Phase 5d: Files page migrated to quietware skeleton

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>
This commit is contained in:
2026-05-15 18:03:45 +01:00
parent 48685f9dda
commit f311fdc47f
2 changed files with 154 additions and 1 deletions

View File

@@ -83,5 +83,25 @@ try {
console.error(`root route skipped: ${e.message}`); 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(); await browser.close();
console.log(`\nWrote screenshots to ${OUT}/`); console.log(`\nWrote screenshots to ${OUT}/`);

View File

@@ -11,6 +11,8 @@
import LumotiaNotice from "$lib/ui/LumotiaNotice.svelte"; import LumotiaNotice from "$lib/ui/LumotiaNotice.svelte";
import LumotiaProgress from "$lib/ui/LumotiaProgress.svelte"; import LumotiaProgress from "$lib/ui/LumotiaProgress.svelte";
import LumotiaMenu from "$lib/ui/LumotiaMenu.svelte"; import LumotiaMenu from "$lib/ui/LumotiaMenu.svelte";
// v0.3 Phase 5d — quietware layout for Files page.
import LumotiaPageSkeleton from "$lib/ui/LumotiaPageSkeleton.svelte";
import { exportTranscript } from "$lib/utils/export.js"; import { exportTranscript } from "$lib/utils/export.js";
import { hasTauriRuntime } from "$lib/utils/runtime"; import { hasTauriRuntime } from "$lib/utils/runtime";
import { toasts } from "$lib/stores/toasts.svelte.js"; import { toasts } from "$lib/stores/toasts.svelte.js";
@@ -30,7 +32,25 @@
let unlistenEnter = null; let unlistenEnter = null;
let unlistenLeave = null; let unlistenLeave = null;
// v0.3 Phase 5d — quietware layout switch. MutationObserver on
// <html data-design> mirrors the SettingsPage / DictationPage
// pattern. Without quietware the existing v0.2 layout renders.
let isQuietware = $state(false);
let quietwareObserver: MutationObserver | null = null;
onMount(async () => { onMount(async () => {
if (typeof document !== "undefined") {
const sync = () => {
isQuietware = document.documentElement.dataset.design === "quietware";
};
sync();
quietwareObserver = new MutationObserver(sync);
quietwareObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-design"],
});
}
unlistenDrop = await listen("tauri://drag-drop", async (event) => { unlistenDrop = await listen("tauri://drag-drop", async (event) => {
isDragOver = false; isDragOver = false;
if (event.payload?.paths?.length > 0) { if (event.payload?.paths?.length > 0) {
@@ -45,6 +65,8 @@
if (unlistenDrop) unlistenDrop(); if (unlistenDrop) unlistenDrop();
if (unlistenEnter) unlistenEnter(); if (unlistenEnter) unlistenEnter();
if (unlistenLeave) unlistenLeave(); if (unlistenLeave) unlistenLeave();
quietwareObserver?.disconnect();
quietwareObserver = null;
}); });
async function handleBrowse() { async function handleBrowse() {
@@ -175,7 +197,117 @@
} }
</script> </script>
<div class="flex flex-col h-full overflow-y-auto animate-fade-in"> <div class="flex flex-col h-full {isQuietware ? '' : 'overflow-y-auto'} animate-fade-in">
{#if isQuietware}
<!-- =================================================================
v0.3 Phase 5d — quietware Files layout. Uses LumotiaPageSkeleton
with the same five-zone language as Dictation:
Capture header title + description
Slim notice nested inside the primary surface for errors
Primary surface drop zone / progress / transcript states
Action dock Browse Files / Copy / Export
Mono footer formats · model · Local only
All existing state and handlers preserved.
================================================================= -->
<LumotiaPageSkeleton>
{#snippet header()}
<div>
<h1 class="text-[18px] font-medium text-text leading-tight">File transcription</h1>
<p class="text-[12px] text-text-secondary mt-0.5">Transcribe audio files locally.</p>
</div>
{/snippet}
{#snippet primary()}
<div class="h-full flex flex-col">
{#if error}
<div class="px-6 pt-4">
<LumotiaNotice tone="danger" slim dismissible onDismiss={() => error = ""}>
{error}
</LumotiaNotice>
</div>
{/if}
<div class="flex-1 flex flex-col px-8 py-6 min-h-0 overflow-hidden">
{#if transcribing}
<!-- Progress state -->
<div class="flex-1 flex flex-col items-center justify-center gap-4 px-12 text-text-tertiary">
<Upload size={48} class="opacity-50" aria-hidden="true" />
<p class="text-[18px] font-medium text-text">Transcribing</p>
{#if fileName}
<p class="text-[13px] text-text-secondary text-center font-mono break-all">{fileName}</p>
{/if}
<div class="w-full max-w-md">
<LumotiaProgress value={progress} max={100} tone="transcribing" ariaLabel="Transcription progress" showValue />
{#if progressText}
<p class="text-[12px] text-text-tertiary mt-2 text-center">{progressText}</p>
{/if}
</div>
</div>
{:else if !fileTranscript}
<!-- Empty state: drop zone -->
<div
class="flex-1 flex flex-col items-center justify-center text-center gap-3 rounded-xl border-2 border-dashed transition-colors duration-150
{isDragOver
? 'border-[var(--button-primary-bg)] bg-[var(--color-info-bg)]'
: 'border-border-subtle text-text-tertiary'}"
role="region"
aria-label="Audio file drop zone"
>
<Upload size={56} class="opacity-50" aria-hidden="true" />
<p class="font-display italic text-[28px] text-text">Drop an audio file.</p>
<p class="text-[14px] text-text-secondary">Or use Browse files in the action bar.</p>
<p class="text-[12px] text-text-tertiary mt-2 font-mono">MP3 · WAV · M4A · MP4 · FLAC · OGG</p>
</div>
{:else}
<!-- Transcript display -->
<textarea
class="font-transcript flex-1 w-full bg-transparent text-text resize-none placeholder:text-text-tertiary leading-relaxed"
placeholder="Transcribed text will appear here..."
bind:value={fileTranscript}
data-no-transition
aria-label="File transcript"
aria-live="polite"
></textarea>
{/if}
</div>
<!-- Action dock attached to the surface. -->
<div class="flex items-center gap-2 justify-end px-6 py-3 border-t border-border-subtle bg-bg-elevated/40">
<LumotiaButton
variant={fileTranscript ? "secondary" : "primary"}
size="sm"
disabled={transcribing}
onclick={handleBrowse}
>Browse files</LumotiaButton>
<LumotiaButton variant="tertiary" size="sm" onclick={copyAll} disabled={!fileTranscript}>Copy</LumotiaButton>
<LumotiaMenu
align="start"
items={[
{ value: "txt", label: "Plain text", onSelect: () => handleExport("txt") },
{ value: "md", label: "Markdown", onSelect: () => handleExport("md") },
{ value: "csv", label: "CSV", onSelect: () => handleExport("csv") },
{ value: "html",label: "HTML", onSelect: () => handleExport("html") },
{ value: "srt", label: "SRT subtitles", onSelect: () => handleExport("srt") },
{ value: "vtt", label: "WebVTT", onSelect: () => handleExport("vtt") },
]}
>
{#snippet trigger()}
<LumotiaButton variant={fileTranscript ? "primary" : "secondary"} size="sm" disabled={!fileTranscript}>Export</LumotiaButton>
{/snippet}
</LumotiaMenu>
</div>
</div>
{/snippet}
{#snippet metadata()}
<div class="flex items-center justify-end">
MP3 · WAV · M4A · MP4 · FLAC · OGG · {settings.modelSize} · Local only
</div>
{/snippet}
</LumotiaPageSkeleton>
{:else}
<!-- Title --> <!-- Title -->
<h2 class="font-display text-[26px] italic text-text px-7 pt-6 pb-4">File Transcription</h2> <h2 class="font-display text-[26px] italic text-text px-7 pt-6 pb-4">File Transcription</h2>
@@ -274,4 +406,5 @@
{/snippet} {/snippet}
</LumotiaMenu> </LumotiaMenu>
</div> </div>
{/if}
</div> </div>