Files
Lumotia/src/lib/ui/LumotiaPageSkeleton.svelte
Jake 48685f9dda v0.3 Phase 5c refinements: spacing, nested notice, conditional Save variant, paper edge
Final small refinements on Dictation per round-11 feedback before
moving to Files/Tasks/History.

1. Vertical rhythm tightened.

  LumotiaPageSkeleton header padding pt-5 pb-3 -> pt-3 pb-2.
  Notice padding pt-3 -> pt-2.
  Primary surface area pt-5 pb-3 -> pt-3 pb-2.
  DictationPage primary inner py-8 -> py-6.

  Net: about 16 px shaved off the top band. Transcript canvas
  begins higher, no longer feels like a large header above empty
  space.

2. Disabled actions go neutral when there is nothing to act on.

  Save button variant is now conditional:
    variant={transcript.trim() ? "primary" : "secondary"}

  Previously Save kept the blue primary fill even when disabled.
  Disabled blue under 50% opacity still read as a strong primary
  cue. Now: no transcript = neutral secondary; transcript exists
  = primary blue. Other actions (Copy / Extract / Template / Open
  Viewer) stay neutral regardless and become enabled when
  transcript exists.

3. Light-mode transcript surface gains a tactile paper edge.

  LumotiaPageSkeleton primary surface gains:
    shadow-[inset_0_1px_0_rgba(0,0,0,0.05)]

  Subtle 1-px inset top shadow at 5% opacity. Invisible in dark
  mode (against the dark surface), visible in light mode as a
  faint paper edge. Skeleton-wide because it costs nothing in
  dark and adds the warmth Jake asked for in light.

4. Ambient info notice nested inside transcript surface.

  Browser-preview notice + real-error notice now render INSIDE
  the transcript card via the primary snippet, not in the
  skeleton's notice slot. Renders contained at the top of the
  card rather than as a full-width page banner. The notice slot
  on LumotiaPageSkeleton stays available for blocking/error-
  banner cases on other pages.

5. Mock-ready screenshot delivered.

  Captured two new screenshot pairs:
    root_PREVIEW__quiet-{dark,light}.png  browser-preview state
    root_READY__quiet-{dark,light}.png    mocked Tauri-available

  Mock done by temporarily setting tauriRuntimeAvailable = true
  for the capture run, then reverted to hasTauriRuntime() at
  commit time. The headless context still has no real Tauri
  runtime so the model-check call fails — that surfaces as a
  danger-tone slim notice in the ready screenshots, which
  happens to demonstrate the danger-slim notice rendering
  alongside the enabled red Record button.

  In the real Tauri desktop app the danger notice would not
  appear; only the Record (red), empty-state typography, and
  bottom action dock with all-disabled neutral controls would.

Verified.

  - npm run check: 0 errors, 0 warnings across 5707 files.

No colour exploration. No new tokens. Same red/blue/green/yellow/
orange grammar from Phase 4k.

Phase 5c is now complete. Next focused commits: 5d (Files), 5e
(Tasks), 5f (History), then Phase 6 (icon — waiting on SVG) and
Phase 7 (motion audit).

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

93 lines
3.0 KiB
Svelte

<script lang="ts">
// v0.3 Phase 5a — shared page skeleton.
//
// Six-zone layout used by every quietware page so structure stays
// consistent across Dictation / Files / Tasks / History / Settings.
// Sidebar lives in AppChrome above this — the skeleton owns the
// right-of-sidebar column only.
//
// +----------------------------+
// | header | page title + status cluster
// |----------------------------|
// | notice (slim, optional) | one-line ambient state
// |----------------------------|
// | |
// | primary work surface | the page's centre of attention
// | |
// |----------------------------|
// | action bar (optional) | post-capture / per-row actions
// |----------------------------|
// | metadata (mono, tiny) | hotkey hint · backend · model
// +----------------------------+
//
// Every snippet is optional except primary. Pages compose the
// skeleton; the skeleton does not impose copy or controls.
import type { Snippet } from "svelte";
interface Props {
header?: Snippet;
notice?: Snippet;
primary: Snippet;
actionBar?: Snippet;
metadata?: Snippet;
/** Right rail / drawer — opt-in narrow column to the right of the primary surface. */
rightRail?: Snippet;
/** Drop the primary surface's outer padding for full-bleed content (eg. transcript canvas). */
primaryBleed?: boolean;
classes?: string;
}
let {
header,
notice,
primary,
actionBar,
metadata,
rightRail,
primaryBleed = false,
classes = "",
}: Props = $props();
</script>
<div class="flex flex-col h-full min-h-0 bg-bg {classes}">
{#if header}
<!-- v0.3 Phase 5c polish: tighter vertical rhythm. -->
<div class="shrink-0 px-7 pt-3 pb-2 border-b border-border-subtle">
{@render header()}
</div>
{/if}
{#if notice}
<div class="shrink-0 px-7 pt-2">
{@render notice()}
</div>
{/if}
<div class="flex-1 min-h-0 flex {rightRail ? 'gap-5' : ''} px-7 pt-3 pb-2">
<!-- v0.3 Phase 5c polish: subtle inset top-shadow gives the
transcript surface a faint paper edge in light mode. The
shadow's near-zero opacity makes it invisible against the
dark surface — light-only effect by accident of luminance. -->
<div class="flex-1 min-w-0 {primaryBleed ? '' : 'bg-bg-card border border-border-subtle rounded-lg overflow-hidden shadow-[inset_0_1px_0_rgba(0,0,0,0.05)]'}">
{@render primary()}
</div>
{#if rightRail}
<aside class="w-72 shrink-0 hidden lg:flex flex-col gap-3">
{@render rightRail()}
</aside>
{/if}
</div>
{#if actionBar}
<div class="shrink-0 px-7 py-3 border-t border-border-subtle bg-bg-elevated/60">
{@render actionBar()}
</div>
{/if}
{#if metadata}
<div class="shrink-0 px-7 py-2 text-[11px] font-mono text-text-tertiary">
{@render metadata()}
</div>
{/if}
</div>