diff --git a/docs/release/v0.3-tactile-quietware.md b/docs/release/v0.3-tactile-quietware.md index c6a6c0c..be2d7d1 100644 --- a/docs/release/v0.3-tactile-quietware.md +++ b/docs/release/v0.3-tactile-quietware.md @@ -351,6 +351,72 @@ LumotiaNotice now reads these via Tailwind arbitrary-value classes (`bg-[var(--n Per Jake's round-5 spec: yellow stays yellow (visible signal), ochre only appears as small-text ink, and brown/khaki never represents the caution role's primary identity again. +### Phase 5c — Dictation layout migration. Landed 2026-05-15. + +The big layout migration. DictationPage's existing 1 282-LOC structure stays untouched for v0.2; under quietware a new layout renders via `LumotiaPageSkeleton` with five zones matching the mockup: + +``` +[Capture header] large Record + status text + secondary support + timer + status pill +[Slim notice] info-classed browser-preview or danger-classed real errors +[Primary surface] calm transcript canvas with empty-state typography +[Action bar] Copy / Save / Extract Tasks / Template / Open Viewer +[Mono metadata] Smart · model · Local-only or Browser-preview +``` + +Implementation pattern: gated `{#if isQuietware}` branch inside the existing `{#if needsDownload}{:else}` structure. The new layout subscribes to ALL existing state bindings (`page.recording`, `page.timerText`, `transcript`, `error`, `tauriRuntimeAvailable`, `modelLoading`, `settings.*`, etc.) and reuses every handler (`toggleRecording`, `copyAll`, `saveTypedText`, `manualExtractTasks`, `applyTemplate`, `invoke("open_viewer_window")`). v0.2 surface is unchanged. + +Capture-header content: + +- **Record button** (72 px circular) carries the role-correct fill via `--button-record-bg` (red). Recording state pulses; modelLoading state shows the spinner; non-Tauri state goes neutral-disabled. Hover swaps to `--button-record-border`. Focus-visible ring uses `--button-record-bg` at `--wire-opacity-focus`. +- **Status text** uses Young Serif italic via `font-display`. Four states: Ready to capture / Loading model / Desktop preview mode / Recording… +- **Secondary support text** in Work Sans body weight. Adapts per state. +- **Timer** in JetBrains Mono tabular-nums, right aligned. +- **Status pill** appears when there is something useful to report (recording / transcribing / ready). + +Slim notice: + +- Browser-preview message renders as `LumotiaNotice tone="info" slim dismissible` with the friendly Phase 5b copy. +- Real errors render as `LumotiaNotice tone="danger" slim dismissible`. +- Notice slot stays empty otherwise — no permanent banner clutter. + +Transcript surface: + +- Empty state: 56 px Mic icon, Young Serif italic "Talk now, think later.", Work Sans support "Press record, or Ctrl+Shift+R." Centred, low-emphasis tertiary text on the calm card. +- Populated: scrollable transcript at the user's accessibility transcript size. + +Bottom action bar: + +- Copy (tertiary), Save (primary blue), Extract Tasks (secondary), Template (secondary, applies templates[0] when present), Open Viewer (secondary, gated on Tauri). +- All disabled until transcript has content; disabled controls render neutral with no wireline. + +Mono metadata footer: + +- Left: `Smart · {model} · Local only` or `Browser preview`. +- Right: `{formatMode} · {activeProfile}`. +- JetBrains Mono, text-tertiary, single line. + +What's preserved (no behaviour change): + +- Recording events, hotkey listeners (`lumotia:toggle-recording`). +- Tauri runtime branching (`tauriRuntimeAvailable`, browser-preview state). +- Live preview hooks (`emit("preview-listening")` etc.). +- Task extraction (`manualExtractTasks` → `extractTasksForTranscript`). +- Template apply (`applyTemplate(template)`). +- Copy / Save paths (`copyAll`, `saveTypedText`). +- All existing state bindings on `page.*`, `settings.*`, `prefs.*`. + +What is NOT done in this commit (out of scope for Phase 5c): + +- Files / Tasks / History page migrations (Phases 5d-5f). +- Real Lumotia wordmark / icon integration (Phase 6, blocked on SVG). +- Motion audit + decorative animation strip (Phase 7). +- Template-picker menu in the bottom action bar (currently auto-applies first template; full menu reserved for a follow-up). + +Verified: + +- `npm run check`: 0 errors, 0 warnings across 5707 files. +- Browser-preview screenshots show the new layout rendering correctly with the disabled-record state, slim info notice, and empty-state typography. + ### Phase 4k — Palette architecture hardening (round-9 redirect). Landed 2026-05-15. Token-hardening pass. No new visual decisions — just plumbing the tokens correctly so the grammar locked in Phase 4j has a system underneath instead of hand-tuned hex values. diff --git a/src/lib/pages/DictationPage.svelte b/src/lib/pages/DictationPage.svelte index 296ee1f..09713b3 100644 --- a/src/lib/pages/DictationPage.svelte +++ b/src/lib/pages/DictationPage.svelte @@ -14,6 +14,9 @@ import LumotiaCard from "$lib/ui/LumotiaCard.svelte"; import LumotiaEmptyState from "$lib/ui/LumotiaEmptyState.svelte"; import LumotiaNotice from "$lib/ui/LumotiaNotice.svelte"; + // v0.3 Phase 5c — quietware layout uses the shared page skeleton. + import LumotiaPageSkeleton from "$lib/ui/LumotiaPageSkeleton.svelte"; + import LumotiaButton from "$lib/ui/LumotiaButton.svelte"; import ModelDownloader from "$lib/components/ModelDownloader.svelte"; import { exportTranscript } from "$lib/utils/export.js"; import { extractTasks } from "$lib/utils/taskExtractor.js"; @@ -72,9 +75,28 @@ // Global hotkey listener let hotkeyHandler = () => toggleRecording(); + // v0.3 Phase 5c — quietware layout switch. Mirrors the SettingsPage + // pattern: reactive flag driven by a MutationObserver on + // . When unset (v0.2), the page renders its + // existing layout unchanged. + let isQuietware = $state(false); + let quietwareObserver: MutationObserver | null = null; + onMount(async () => { window.addEventListener("lumotia:toggle-recording", hotkeyHandler); + 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"], + }); + } + if (!tauriRuntimeAvailable) { error = browserPreviewMessage; return; @@ -87,6 +109,8 @@ onDestroy(() => { window.removeEventListener("lumotia:toggle-recording", hotkeyHandler); clearInterval(timerInterval); + quietwareObserver?.disconnect(); + quietwareObserver = null; if (page.recording) { page.recording = false; page.status = "Ready"; @@ -915,6 +939,134 @@ onComplete={onModelDownloaded} /> {:else} + {#if isQuietware} + + + {#snippet header()} +
+ +
+

+ {#if page.recording}Recording… + {:else if modelLoading}Loading model + {:else if !tauriRuntimeAvailable}Desktop preview mode + {:else}Ready to capture + {/if} +

+

+ {#if page.recording}Press to stop, or use the global hotkey. + {:else if modelLoading}One moment. + {:else if !tauriRuntimeAvailable}Local transcription needs the Lumotia desktop app. + {:else}Press record, or Ctrl+Shift+R. + {/if} +

+
+
+ {page.timerText || "00:00"} +
+ {#if page.recording} + + {:else if modelLoading} + + {:else if tauriRuntimeAvailable} + + {/if} +
+ {/snippet} + + {#snippet notice()} + {#if error === browserPreviewMessage} + { error = ""; errorDetail = ""; }}> + {browserPreviewSlim} + + {:else if error} + { error = ""; errorDetail = ""; }}> + {error} + + {/if} + {/snippet} + + {#snippet primary()} +
+ {#if !transcript.trim()} +
+
+ {:else} +
{transcript}
+ {/if} +
+ {/snippet} + + {#snippet actionBar()} +
+ Copy + Save + Extract Tasks + { if (templates.length > 0) applyTemplate(templates[0]); }} + title={templates.length === 0 ? "No templates configured" : `Apply ${templates[0].name}`} + >Template + invoke("open_viewer_window").catch(() => {})} + >Open Viewer +
+ {/snippet} + + {#snippet metadata()} +
+ Smart · {settings.modelSize} · {tauriRuntimeAvailable ? "Local only" : "Browser preview"} + {settings.formatMode} · {page.activeProfile === "None" ? "No profile" : page.activeProfile} +
+ {/snippet} +
+ {:else}