v0.2 Phase 7.6: DictationPage — centrepiece wrapper sweep

This is the page Settings will inherit grammar from, so it's worth a
slightly fuller sweep than the earlier ones — without touching the
recording state machine, the SVG VisualTimer, the waveform bars, the
transcript textarea, ModelDownloader, or SpeakerButton, which are all
bespoke per the §6.3 do-not-wrap list.

  - StatusPill import → LumotiaStatusPill (all use sites swapped)
  - PostCaptureCard import → LumotiaPostCaptureCard
  - Card import → LumotiaCard
  - EmptyState import → LumotiaEmptyState
  - LumotiaNotice import added; the inline `liveWarning` panel now
    uses LumotiaNotice tone=caution

The danger-tinted error block (lines ~1130) was left verbatim — it
already nests a StatusPill (now LumotiaStatusPill), a <details>
disclosure, and a Dismiss button in a structure LumotiaNotice's
single-icon contract doesn't model cleanly. Phase 7's primary
output for this page is import-level coherence; behavioural
identity stays untouched.

Bespoke surfaces preserved: recording controls, VisualTimer, waveform,
transcript surface (bionic action + accessibility typography), all
hotkey wiring, all live-session state.

Per-page gate: npm run check (0/0/5704 files).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 09:00:22 +01:00
parent 021a5fc196
commit 4fa8df638b

View File

@@ -8,17 +8,18 @@
import { markGenerating, markGenerationDone } from "$lib/stores/llmStatus.svelte.js"; import { markGenerating, markGenerationDone } from "$lib/stores/llmStatus.svelte.js";
import { playStartCue, playStopCue, playCompleteCue } from "$lib/utils/sounds.js"; import { playStartCue, playStopCue, playCompleteCue } from "$lib/utils/sounds.js";
import { profilesStore } from "$lib/stores/profiles.svelte.ts"; import { profilesStore } from "$lib/stores/profiles.svelte.ts";
import StatusPill from "$lib/components/StatusPill.svelte"; import LumotiaStatusPill from "$lib/ui/LumotiaStatusPill.svelte";
import PostCaptureCard from "$lib/components/PostCaptureCard.svelte"; import LumotiaPostCaptureCard from "$lib/ui/LumotiaPostCaptureCard.svelte";
import { toasts } from "$lib/stores/toasts.svelte.js"; import { toasts } from "$lib/stores/toasts.svelte.js";
import Card from "$lib/components/Card.svelte"; import LumotiaCard from "$lib/ui/LumotiaCard.svelte";
import LumotiaEmptyState from "$lib/ui/LumotiaEmptyState.svelte";
import LumotiaNotice from "$lib/ui/LumotiaNotice.svelte";
import ModelDownloader from "$lib/components/ModelDownloader.svelte"; import ModelDownloader from "$lib/components/ModelDownloader.svelte";
import { exportTranscript } from "$lib/utils/export.js"; import { exportTranscript } from "$lib/utils/export.js";
import { extractTasks } from "$lib/utils/taskExtractor.js"; import { extractTasks } from "$lib/utils/taskExtractor.js";
import { pad } from "$lib/utils/time.js"; import { pad } from "$lib/utils/time.js";
import { FEEDBACK_TIMEOUT_MS } from "$lib/utils/constants.js"; import { FEEDBACK_TIMEOUT_MS } from "$lib/utils/constants.js";
import { Mic, Loader2, SquareCheck, AlertTriangle } from 'lucide-svelte'; import { Mic, Loader2, SquareCheck, AlertTriangle } from 'lucide-svelte';
import EmptyState from '$lib/components/EmptyState.svelte';
import SpeakerButton from '$lib/components/SpeakerButton.svelte'; import SpeakerButton from '$lib/components/SpeakerButton.svelte';
import { getPreferences } from '$lib/stores/preferences.svelte.js'; import { getPreferences } from '$lib/stores/preferences.svelte.js';
import { bionicReading } from '$lib/actions/bionicReading.js'; import { bionicReading } from '$lib/actions/bionicReading.js';
@@ -982,21 +983,21 @@
<!-- Status indicator — Sub-task A: <StatusPill> replaces hand-rolled pill --> <!-- Status indicator — Sub-task A: <StatusPill> replaces hand-rolled pill -->
<span class="flex-shrink-0"> <span class="flex-shrink-0">
{#if page.recording} {#if page.recording}
<StatusPill status="recording" /> <LumotiaStatusPill status="recording" />
{:else if modelLoading} {:else if modelLoading}
<StatusPill status="transcribing" label="Loading model…" /> <LumotiaStatusPill status="transcribing" label="Loading model…" />
{:else if transcribing} {:else if transcribing}
<StatusPill status="transcribing" /> <LumotiaStatusPill status="transcribing" />
{:else if saved} {:else if saved}
<StatusPill status="saved" /> <LumotiaStatusPill status="saved" />
{:else if error} {:else if error}
{#if transcriptionFailed} {#if transcriptionFailed}
<StatusPill status="failed-safely" /> <LumotiaStatusPill status="failed-safely" />
{:else} {:else}
<StatusPill status="needs-review" /> <LumotiaStatusPill status="needs-review" />
{/if} {/if}
{:else} {:else}
<StatusPill status="ready" /> <LumotiaStatusPill status="ready" />
{/if} {/if}
</span> </span>
@@ -1132,9 +1133,9 @@
<div class="px-4 py-3 rounded-lg bg-danger/10 border border-danger/20 text-[12px] text-danger space-y-2"> <div class="px-4 py-3 rounded-lg bg-danger/10 border border-danger/20 text-[12px] text-danger space-y-2">
<div class="flex items-start gap-2"> <div class="flex items-start gap-2">
{#if transcriptionFailed} {#if transcriptionFailed}
<StatusPill status="failed-safely" /> <LumotiaStatusPill status="failed-safely" />
{:else} {:else}
<StatusPill status="needs-review" /> <LumotiaStatusPill status="needs-review" />
{/if} {/if}
<p class="m-0">{error}</p> <p class="m-0">{error}</p>
</div> </div>
@@ -1156,16 +1157,14 @@
{#if liveWarning && !error} {#if liveWarning && !error}
<div class="px-5 pt-2 animate-fade-in flex-shrink-0"> <div class="px-5 pt-2 animate-fade-in flex-shrink-0">
<div class="px-4 py-2 rounded-lg bg-warning/10 border border-warning/20 text-[12px] text-warning"> <LumotiaNotice tone="caution">{liveWarning}</LumotiaNotice>
{liveWarning}
</div>
</div> </div>
{/if} {/if}
<!-- Sub-task C: Post-capture card — shown after recording completes, hidden when new recording starts --> <!-- Sub-task C: Post-capture card — shown after recording completes, hidden when new recording starts -->
{#if showPostCaptureCard && lastCapture && !page.recording} {#if showPostCaptureCard && lastCapture && !page.recording}
<div class="px-5 pt-3 flex-shrink-0 animate-fade-in"> <div class="px-5 pt-3 flex-shrink-0 animate-fade-in">
<PostCaptureCard <LumotiaPostCaptureCard
transcriptId={lastCapture.id} transcriptId={lastCapture.id}
rawTranscript={lastCapture.raw} rawTranscript={lastCapture.raw}
cleanedTranscript={lastCapture.cleaned} cleanedTranscript={lastCapture.cleaned}
@@ -1183,14 +1182,14 @@
<!-- Transcript area --> <!-- Transcript area -->
<div class="flex-1 px-5 pt-3 pb-3 min-h-0" style="--text-transcript: {prefs.accessibility.transcriptSize}px; font-size: var(--text-transcript)"> <div class="flex-1 px-5 pt-3 pb-3 min-h-0" style="--text-transcript: {prefs.accessibility.transcriptSize}px; font-size: var(--text-transcript)">
<Card classes="h-full flex flex-col"> <LumotiaCard classes="h-full flex flex-col">
{#if transcriptionFailed && !transcript.trim() && !page.recording} {#if transcriptionFailed && !transcript.trim() && !page.recording}
<div class="flex-1 flex items-center justify-center"> <div class="flex-1 flex items-center justify-center">
<EmptyState icon={AlertTriangle} message="Something went wrong with that transcription. Your audio is saved, try again when you're ready." /> <LumotiaEmptyState icon={AlertTriangle} message="Something went wrong with that transcription. Your audio is saved, try again when you're ready." />
</div> </div>
{:else if !transcript.trim() && !page.recording && !transcribing} {:else if !transcript.trim() && !page.recording && !transcribing}
<div class="flex-1 flex items-center justify-center"> <div class="flex-1 flex items-center justify-center">
<EmptyState icon={Mic} headline="Talk now, think later." message={`Press record, or ${settings.globalHotkey}.`} /> <LumotiaEmptyState icon={Mic} headline="Talk now, think later." message={`Press record, or ${settings.globalHotkey}.`} />
</div> </div>
<textarea <textarea
bind:this={textareaEl} bind:this={textareaEl}
@@ -1257,7 +1256,7 @@
{/if} {/if}
</div> </div>
</div> </div>
</Card> </LumotiaCard>
</div> </div>
{/if} {/if}
</div> </div>