# Lumotia v0.3 — Tactile Quietware

> Calm enough for overloaded brains, structured enough to recover thoughts, tactile enough to feel like a local desktop tool rather than SaaS.

The v0.3 release reframes Lumotia around one thesis: a low-cognitive-load capture instrument. Built on the WCAG 2.2 + W3C COGA + NN/G evidence base, validated against the four HMRC accessibility personas that map most directly onto a dictation-first desktop tool.

This document is the single source of truth for v0.3. Phase log, regression diary, palette spec and font stack all live here.

---

## Source brief

Locked 2026-05-15 from Jake's design brief and three rounds of capture-pipeline iteration.

- Palette: `inputs/inbox/2026-05-15-lumotia-colour-layout-changes.md` plus the colour-palette refinements at `inputs/inbox/2026-05-15-re-lumotia-v03-renders-revised-palette-typography-.md`.
- Personas: HMRC Virtual Empathy Hub, eight personas at `https://personas-prototype.herokuapp.com/`.
- Accessibility tools: DfE manual at `https://accessibility.education.gov.uk/tools-testing/tools`.

## Rule set

The five rules every v0.3 PR has to clear.

| Rule | Meaning |
|---|---|
| Texture = atmosphere | Static global grain. No per-panel noise. No motion. Default 6%, slider 5 to 8% in advanced. |
| Colour = status | Brand palette IS the semantic palette. Brand chroma earns its keep by signalling state. Decorative colour is forbidden. |
| Layout = calm | Every page uses the same skeleton. Files, Tasks, History and Dictation must not invent their own structure. |
| Motion = feedback only | Animation only for recording, loading, success and failure. Honour `prefers-reduced-motion` at root. |
| Settings = progressive disclosure | One sectional tab visible at a time. Eight tabs. No long scroll. |

---

## Palette

Locked 2026-05-15 from Jake's color.adobe.com primaries. Each primary is used literally where it clears WCAG AA on the relevant background. Where it does not, a text-safe sibling at the same hue and saturation is derived.

### Source primaries

| Role | Primary | Hue | Notes |
|---|---|---|---|
| `--danger` | `#FF0700` | 2deg | Pure red |
| `--info` | `#000AFF` | 238deg | Pure blue |
| `--success` | `#00FF56` | 140deg | Pure green |
| `--caution` | `#FFCD00` | 48deg | Pure yellow |

### Light mode

Cream paper base. Jake's blue passes AAA on cream and is used literally. The other three are darkened siblings for AA text contrast, with caution kept fill-only because any darkened yellow reads as olive or mustard ("looks like poop").

| Token | Hex | Contrast on `#FBF8F2` | Notes |
|---|---|---|---|
| `--color-bg` | `#FBF8F2` | Base | Cream paper |
| `--color-text` | `#1D1B18` | 14.43 AAA | Near-black |
| `--color-info` | `#000AFF` | 7.96 AAA | Jake literal |
| `--color-danger` | `#E60600` | 4.51 AA | Sibling derived from `#FF0700` |
| `--color-success` | `#00852D` | 4.51 AA | Sibling derived from `#00FF56` |
| `--color-caution` | `#FFCD00` | 1.42 (fill-only) | Jake literal, fill-only convention |

**Caution is fill-only on cream.** Pill backgrounds, dots, left-border accents and icons take `--color-caution`. Foreground text inside a caution-coloured pill stays `--color-text`. Matches Material, GOV.UK and IBM Carbon practice.

### Dark mode

Lifted Coffee Bean base. Jake's green and yellow primaries already clear AAA on the dark surface and are used literally. Red and blue need lifting because pure-saturated dark hues fail against a dark background.

| Token | Hex | Contrast on `#2A1620` | Notes |
|---|---|---|---|
| `--color-bg` | `#2A1620` | Base | Lifted Coffee Bean |
| `--color-text` | `#F3EFE7` | 13.85 AAA | |
| `--color-info` | `#7076FF` | 4.64 AA | Lifted from `#000AFF` |
| `--color-danger` | `#FF2A24` | 4.54 AA | Lifted from `#FF0700` |
| `--color-success` | `#00FF56` | 12.51 AAA | Jake literal |
| `--color-caution` | `#FFCD00` | 11.33 AAA | Jake literal |

### High contrast mode

Same four hues retained so role recognition does not flip between modes. Saturation 80%, lightness 70%. Zero texture, no translucent overlays.

| Token | Hex | Contrast on black |
|---|---|---|
| `--color-bg` | `#000000` | Base |
| `--color-text` | `#FFFFFF` | 21.00 AAA |
| `--color-info` | `#757AF0` | 5.80 AA |
| `--color-danger` | `#F07975` | 7.70 AAA |
| `--color-success` | `#75F09F` | 14.72 AAA |
| `--color-caution` | `#F0D875` | 14.78 AAA |

---

## Typography stack (V4 pairing, locked 2026-05-15)

Three production fonts. Self-hosted as WOFF2. No CDN dependency.

| Role | Font | Licence | Used for |
|---|---|---|---|
| Body and UI | Work Sans (variable) | OFL | Headers, labels, controls, sidebar, body, transcript surface |
| Brand moments | Young Serif | OFL | Wordmark, splash, empty-state hook copy, About modal |
| Mono | JetBrains Mono | Apache 2.0 | Logs, model IDs, file paths, hotkey strings |

Atkinson Hyperlegible Next and OpenDyslexic stay as opt-in accessibility fonts via Settings. Archivo, Lexend and Instrument Serif from v0.2 stay shipped during v0.3 transition; future release may remove them after persona testing.

---

## Persona test gates

Each phase must pass these four manual gates before merge. Tools and tasks from the DfE manual.

| Persona | Condition simulated | Primary risk for Lumotia | Gate |
|---|---|---|---|
| Pawel | Autistic | Sensory load, predictability, motion sensitivity | Empty states speak. No surprise motion. Same skeleton on every page. |
| Simone | Dyslexic | Transcript readability, dense paragraphs | Transcript body uses Work Sans at 16px minimum, line-height ≥ 1.6, comfortable measure. |
| Chris | Rheumatoid arthritis | Click target size, keyboard fallback for every action | All interactive targets ≥ 44 x 44 px. Every action reachable by keyboard with visible focus ring. |
| Ron | Older user, multiple conditions | Combined: vision + cognitive + motor | All three of the above hold simultaneously. |

Medium-priority personas (Claudia magnifier, Saleem deaf) get explicit checks at Phase 5. Lower-priority personas (Ashleigh screen reader, Helena dyscalculia) get checks at v0.4.

---

## Phase 0 — Accessibility tools checklist

Manual gov.uk DfE tools applied to every v0.3 phase before merge. Tools that map onto a Tauri webview tested via the same browser-preview surface already shipped in v0.2.

- [ ] Contrast Checker. All palette pairs verified against WCAG AA. Annotated in the relevant phase commit message.
- [ ] axe DevTools. Run on every page after the phase migrates it.
- [ ] Resize Text. 200% zoom does not break any layout.
- [ ] Text Spacing. Increased letter, word, line spacing per WCAG 1.4.12 does not clip content.
- [ ] Target Size. All interactive targets ≥ 44 x 44 px (WCAG 2.5.8).
- [ ] HeadingsMap. Settings sectional refactor preserves a sensible h1 → h6 hierarchy.
- [ ] Blur. Smoke check that primary actions remain legible under simulated low vision.
- [ ] Screen reader smoke test. NVDA or Orca on the Settings page after Phase 3.

A future-Phase CI step will automate axe-core + contrast checks. Out of scope for v0.3 per Jake's 2026-05-15 decision.

---

## Phase plan

Each phase ships as its own PR. Each is flag-gated by `html[data-design="quietware"]`. The current v0.2 user-facing surface is unaffected until Phase 6 promotes quietware to the default.

### Phase 1 — Tokens. (this PR)

- New file `src/design-system/v0.3-quietware-tokens.css` with three palette scopes (dark, light, high-contrast).
- Self-hosted Work Sans (variable), Work Sans Italic (variable), Young Serif (static) at `src/fonts/`.
- Import wired in `src/app.css` directly after the tailwind import. Inert until `data-design="quietware"` is present on `<html>`.
- No component changes. No behavioural changes.

### Phase 2 — Build flag wiring.

- `VITE_LUMOTIA_QUIETWARE=1` build-time env var sets `data-design="quietware"` on `<html>` via `src/routes/+layout.svelte`.
- Manual override path stays: dev tools, or a Settings toggle gated behind the same flag.

### Phase 3 — Status grammar primitives.

- `LumotiaStatusPill`, `LumotiaNotice` with icon + label + body + actions.
- Caution is fill-only across both. Foreground text uses `--color-text`.
- Component preview added to `src/routes/design-system-v2/`.

### Phase 4 — Settings sectional refactor.

- Split the 2 791 LOC `SettingsPage.svelte` across eight tabs: Start Here, Models, Output, Vocabulary, Tasks, Accessibility, Privacy, Advanced.
- Texture-opacity slider lives in Accessibility. Range 0.05 to 0.08. Default 0.06.
- High-contrast toggle in Accessibility. Toggle overrides system `prefers-contrast` query when set.
- One section visible at a time. No long-scroll.

### Phase 5 — Critical-path simplification.

- Dictation page: header + transcript surface + action rail + empty state. No cockpit furniture.
- Files, Tasks, History migrated to the shared page skeleton (left nav, header, optional toolbar, primary work surface, optional right drawer, status row).
- Empty states speak on all four pages.
- Persona-test gates for Pawel, Simone, Chris, Ron at this phase.

### Phase 6 — Icon and wordmark integration.

- Real Lumotia wordmark + mark lifted from brand-proposal v1 colophon. Replaces the placeholder swirl.
- Surfaces: window icon (.ico, .icns, multi-size .png), splash, About modal, empty-state anchor, status-pill leading glyph for "Ready".
- Removes any remaining Magnotia / Wyrdnote / Kon-era assets.

### Phase 7 — Motion audit. (final v0.3 polish)

- Inventory every animation, transition and decorative motion currently in the app.
- Keep: recording-active heartbeat, loading spinner, success / failure feedback.
- Strip: decorative pulse, animated background, sliding panels, attention shimmer.
- `prefers-reduced-motion` reducer set at the root selector for quietware (already in tokens CSS).

### Phase 8 — Promote to default. (post-release decision)

- Flip `<html data-design="quietware">` to be the default; remove the flag.
- Either lands as a major release (v1.0) or stays opt-in indefinitely.
- This decision is deferred to after Phase 6 user testing.

---

## Phase log

### Phase 1 — Tokens. Landed 2026-05-15.

- `src/design-system/v0.3-quietware-tokens.css` written. 5.7 KB.
- Three WOFF2 fonts added at `src/fonts/`: `work-sans-variable.woff2`, `work-sans-italic-variable.woff2`, `young-serif.woff2`. 287 KB total.
- `src/app.css` imports the new tokens file directly after `@import "tailwindcss"`.
- Inert without `data-design="quietware"` on `<html>`. v0.2 surface unaffected.
- Verified by visual inspection. Manual activation via dev tools toggling the attribute.
- Palette iterated three times this session (brand-as-semantic → cobalt-pegged square → color.adobe.com primaries). Final values locked in commit on amendment.

### Phase 2 — Build flag wiring. Landed 2026-05-15.

- `src/routes/+layout.svelte` extended with a `$effect` block that reads `import.meta.env.VITE_LUMOTIA_QUIETWARE` at runtime and sets `<html data-design="quietware">` when the flag is `"1"`.
- Hot-reload safety: if the env var is unset between dev rebuilds, the attribute is cleared.
- Activation: `VITE_LUMOTIA_QUIETWARE=1 npm run dev` (or `npm run build`) lights up the quietware tokens. Without the env var, app renders v0.2 unchanged.
- Combine with `data-theme="light"` and `data-contrast="high"` (already wired by v0.2) to reach the light and high-contrast quietware modes.
- No new dependencies. No bundle-size impact in the off path.

### Phase 3 — Status grammar primitives. Landed 2026-05-15.

Architectural finding: both `LumotiaStatusPill` and `LumotiaNotice` already shipped in v0.2 Phase 5. Phase 3 became an audit-and-adapt task rather than a new-component build.

- `LumotiaStatusPill` ([src/lib/components/StatusPill.svelte](../../src/lib/components/StatusPill.svelte)) audited. Pattern is already accessibility-correct: neutral pill background + neutral label text + role colour confined to a 6×6 px dot. Colour is supplementary, label is always perceivable. Works unchanged across all three quietware modes. No changes shipped.

- `LumotiaNotice` ([src/lib/ui/LumotiaNotice.svelte](../../src/lib/ui/LumotiaNotice.svelte)) refactored to the left-bar accent pattern. Old version put the role colour on the icon + outline; in quietware light mode that rendered a bright `#FFCD00` caution icon faded against cream paper. New version places the role signal on a 4-px solid left border (visible at any contrast level), with the rest of the surround as a soft tone-tinted background plus subtle neutral outer border. Icon, title and body text all use `--color-text`. Matches Material "banner with leading accent" and IBM Carbon "inline notification" guidance. ARIA semantics (`role="alert"` for danger, `role="status"` for the rest) preserved.

- Design-system-v2 preview route already imports `LumotiaNotice` and `LumotiaStatusPill`. The updated Notice renders correctly there with the existing preview content; no additional preview wiring needed.

- All four tones (info / caution / danger / success) verified by the pattern itself: the visible signal is the left bar, which is `border-l-{tone}` at 100% opacity, working at any colour intensity. Caution remains a fill-only convention on cream by design.

### Phase 4a — Settings tab shell. Landed 2026-05-15.

Architectural finding: the v0.2 `SettingsPage.svelte` (2 891 LOC after Phase 4a) was already structured as eight numbered sections that align almost 1:1 with the v0.3 plan. The cheapest path to "one section visible at a time" is therefore a tab-shell overlay rather than a file split. Behaviour stays identical to v0.2 when quietware is off; with quietware on, only the active tab's section renders.

- New `LumotiaSettingsTabs.svelte` ([src/lib/ui/LumotiaSettingsTabs.svelte](../../src/lib/ui/LumotiaSettingsTabs.svelte)) implements the WAI-ARIA tabs (automatic activation) pattern. Arrow / Home / End keyboard navigation. Active tab uses the brand left-bar accent (`border-l-caution`) so it carries the same grammar as the LumotiaNotice refactor.

- `SettingsPage.svelte` gained a small reactive `isQuietware` flag, a `MutationObserver` watching `<html data-design>`, and 8 `{#if !isQuietware || activeTab === 'X'}` wrappers around the existing eight top-level `<SettingsGroup>` sections. The settings tree is unchanged; only its top-level rendering gate changed.

- v0.2 fallback behaviour: when `data-design="quietware"` is absent the tablist does not render and all eight sections stack as before. The search filter still spans every section in v0.2 mode. This is the smallest-possible behavioural change for "one section at a time".

- Tab IDs in this commit map 1:1 to existing v0.2 section names (start-here / transcription / models / tasks / accessibility / privacy / advanced / help). Phase 4b will restructure to match the plan's canonical names (carving Output from Advanced and Vocabulary from Transcription).

### Phase 4b — Settings restructure to canonical tab names. Deferred to post-Phase-5.

Rationale: phase 4a already delivers "one section at a time" under quietware. Renaming the eight tabs to match the canonical plan (Output / Vocabulary carved out as top-level) is labelling polish, not user-value. Page-skeleton sweep (Phase 5) delivers more visible improvement per LOC. Phase 4b will return after Phase 5 lands.

### Phase 4d — Palette revision per 2026-05-15 round 4 feedback. Landed 2026-05-15.

The color.adobe.com primaries (#FF0700 / #000AFF / #00FF56 / #FFCD00) earned vividness at the cost of brand identity and accent-vs-caution discipline. Round-4 feedback pulled the system back toward muted-Material values with a clear architectural split:

```
Atmosphere = warm tactile surface
Status     = red / yellow / green / blue
Brand      = copper-clay amber accent (--color-accent, distinct from caution)
Accessibility = high-contrast override is a different rendering contract
```

Dark mode (revised, brown-charcoal instead of wine-aubergine):

| Token | Before (4c) | After (4d) |
|---|---|---|
| `--color-bg` | `#2A1620` | `#12100E` |
| `--color-bg-elevated` | `#34222B` | `#1A1713` |
| `--color-bg-card` | `#3E2A33` | `#211D18` |
| `--color-sidebar` | `#1F0F18` | `#0F0E0C` |
| `--color-accent` | (unset, fell through to v0.2) | `#C97845` |
| `--color-danger` | `#FF2A24` | `#FF8A8A` |
| `--color-caution` | `#FFCD00` | `#F2C94C` |
| `--color-success` | `#00FF56` | `#79D59B` |
| `--color-info` | `#7076FF` | `#8AB4F8` |

Light mode (revised, semantic values back to Material AA-friendly hues):

| Token | Before (4c) | After (4d) |
|---|---|---|
| `--color-bg-card` | `#FFFFFF` | `#FFFDF8` (warm white) |
| `--color-bg-elevated` | `#F3EEE6` | `#F6F1EA` |
| `--color-accent` | (unset) | `#9D5F32` |
| `--color-danger` | `#C54B1F` | `#B3261E` |
| `--color-caution` | `#FFCD00` (fill-only) | `#7A5D00` (passes as text) |
| `--color-success` | `#00852D` | `#1B6B3A` |
| `--color-info` | `#0047AB` | `#2457A6` |

The "caution fill-only on cream" rule retires under the muted palette. Ochre `#7A5D00` passes AA as foreground text against cream, so the same single-token-per-role convention applies across all four semantic colours in light mode.

LumotiaNotice opacity rebalance:

| Surface | Before | After |
|---|---|---|
| Background tint | role at 10% | role at 8% |
| Outer border | neutral subtle | role at 40% |
| Left bar | role full | role full (unchanged) |
| Icon | `--color-text` | role colour |
| Title / body | `--color-text` | `--color-text` (unchanged) |

### Phase 4f — Semantic source / derived token architecture. Landed 2026-05-15.

Round-4 feedback formalised the relationship between the brand-source palette and the theme-derived semantic tokens. Source tokens are stable across all themes (they encode the role's hue identity); derived tokens are theme-tuned to meet WCAG AA in context.

Added to `src/design-system/v0.3-quietware-tokens.css`:

```css
:root[data-design="quietware"] {
  --semantic-red-source:    #FF0700;  /* error / failed / destructive / blocked */
  --semantic-yellow-source: #FFCD00;  /* needs review / caution / pending */
  --semantic-green-source:  #00FF56;  /* ready / saved / complete / safe */
  --semantic-blue-source:   #000AFF;  /* info / processing / system guidance */
}
```

The existing `--color-{danger,caution,success,info}` tokens are documented as derivations of these sources, retuned per theme (muted-Material values in normal modes, clarity-first values in HC). New `--color-{role}-bg` subtle-fill tokens derived via `color-mix(in oklab, var(--color-{role}) 8%, transparent)` — used by Notice backgrounds, hover tints and focus halos. HC mode forces all `-bg` tokens to `transparent` so HC notices render as text + icon + 2-px border + left bar with no body tint.

### Phase 5a — Page skeleton primitive + slim notice variant. Landed 2026-05-15.

Foundation for the page-skeleton sweep without yet migrating any page. Two additions:

- **`LumotiaPageSkeleton.svelte`** ([src/lib/ui/](../../src/lib/ui/LumotiaPageSkeleton.svelte)) — six-zone layout used by every quietware page: header / optional slim notice / primary work surface / optional right rail / optional bottom action bar / optional mono metadata footer. Sidebar lives in AppChrome above the skeleton. Every snippet is optional except `primary`. `primaryBleed` prop drops the surface's inner card chrome for full-bleed transcript canvases.

- **`LumotiaNotice` `slim` prop** — collapses padding, drops the title block, smaller icon. For one-line ambient notices like browser-preview state.

### Phase 4g — Four-tier semantic tokens + notice usage layer. Landed 2026-05-15.

Round-5 feedback (2026-05-15) flagged the muddy result of forcing one `--color-caution` to be both bright yellow (signal) and accessible text (ochre). The single-token-per-role model collapsed the visible identity.

New architecture: four-tier per-role tokens with hand-tuned surfaces, plus usage-specific notice tokens on top.

**Tier model.** Each semantic role publishes four tier-specific tokens:

| Tier | Purpose | Example (light caution) |
|---|---|---|
| `signal` | Brand-true colour. Left bars, large icons, dots, focus rings | `#FFCD00` |
| `ink` | Accessible text. Used ONLY when text itself must carry the role colour (small chips, badges). AA 4.5:1 on surface | `#7A5D00` |
| `border` | Middle value. Visible chip/notice outline; does not need AA-text contrast | `#D9A900` |
| `bg` | Hand-tuned pale tint surface. NOT a generic `color-mix` derivation | `#FFF7D6` |

CSS:

```css
--color-{role}-signal
--color-{role}-ink
--color-{role}-border
--color-{role}-bg
```

`--color-{role}` retained as a backward-compat alias pointing at `-signal`. v0.2 callers keep working; new code reaches for the specific tier.

**Usage layer.** Components subscribe to usage-specific tokens, not role-tier tokens directly. This lets per-usage retuning happen in one place:

```css
--notice-{role}-bar     /* left bar background, signal-tier */
--notice-{role}-icon    /* signal in dark, ink in light, signal in HC */
--notice-{role}-border  /* outer border colour */
--notice-{role}-bg      /* hand-tuned pale surface tint */
```

LumotiaNotice now reads these via Tailwind arbitrary-value classes (`bg-[var(--notice-info-bg)]` etc). v0.2 fallbacks defined at `:root` (outside quietware blocks) so the v0.2 surface keeps a sensible Notice render without depending on quietware.

**HC contract.** Every `-bg` token forces to `transparent`. Notices in HC render as text + icon + 2-px border + left bar with no body tint. Brand atmosphere steps fully aside.

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.

**Scales.** New file [src/design-system/v0.3-quietware-scales.css](../../src/design-system/v0.3-quietware-scales.css) defines 11-step tonal scales (50–950) for the five source hues and their five complement sources. Generated by `scripts/generate-quietware-scales.py` (committed) so the values are reproducible. Saturation drops at the extremes (50–100 and 900–950) to avoid neon highs and washed-out lows. Wired into `app.css` directly before the tokens file.

```css
--red-50  ..  --red-950
--blue-50 ..  --blue-950
--green-50 .. --green-950
--yellow-50 .. --yellow-950
--orange-50 .. --orange-950
--red-complement-50  .. --red-complement-950
... and so on for the four other complements
```

**Component tokens consume scale references only.** All component-level tokens now read from `var(--{hue}-{step})`, never raw hex:

```css
--button-record-bg:     var(--red-600);
--button-primary-bg:    var(--blue-700);
--button-success-bg:    var(--green-800);   /* darkened so white text clears AA */
--button-caution-bg:    var(--yellow-400);
--button-neutral-bg:    var(--color-bg-elevated);
--button-disabled-bg:   var(--color-bg-elevated);
--brand-accent:         var(--orange-500);
```

**Progress component tokens.** New family covers download, transcribing, success, caution, danger, disk + disk-caution + disk-danger states. Each per-mode block tunes the shade step so the bar reads on its track:

| Token | Dark | Light | HC light | HC dark |
|---|---|---|---|---|
| `--progress-download-fill` | `blue-400` | `blue-700` | `blue-700` | `blue-300` |
| `--progress-success-fill` | `green-500` | `green-800` | `green-800` | `green-400` |
| `--progress-caution-fill` | `yellow-500` | `yellow-600` | `yellow-700` | `yellow-400` |
| `--progress-danger-fill` | `red-500` | `red-700` | `red-700` | `red-400` |

LumotiaProgress reworked to subscribe to the new tokens. New `tone="transcribing"` / `tone="disk"` / `tone="disk-caution"` / `tone="disk-danger"` values added so the progress role mapping in code matches the design grammar.

**Contrast check script.** New `scripts/check-colour-contrast.mjs` parses both v0.3 CSS files, builds per-scope token tables for all 4 modes (dark, light, HC-light, HC-dark), resolves scale references through `var()` chains and `color-mix(...)` expressions, then verifies the meaningful component pairs against WCAG 2.2 minima (4.5:1 text, 3:1 UI). Exit code 1 on any failure. Run with `node scripts/check-colour-contrast.mjs`.

Current state: **0 failures** across 4 modes for the 12 load-bearing pairs (record/primary/danger/success/caution/neutral button text, primary/success/danger progress fills on track, info notice border on card, body text on bg + card). Caution-on-cream (progress fill + notice border) is reported as a "warn" — known structural yellow limitation; role identity is carried by the left-bar + icon + label combo, not by raw surface contrast.

**Border ≠ Wireline ≠ Focus Ring.** Three distinct token families, each with its own purpose:

```css
--button-primary-border:  var(--blue-800)               /* structural edge */
--button-primary-wire:    var(--blue-complement-400)    /* identity detail */
--focus-ring-color:       var(--blue-700)               /* keyboard interaction */
```

### Phase 4j — Colour grammar correction (round-8 redirect). Landed 2026-05-15.

Major correction. Brown/copper accent was dragging the UI back into the warm-brutalist palette and competing with semantic yellow. Record button was about to become blue (wrong — record is universally red). Brand orange got conflated with primary action.

**New colour grammar:**

| Colour | Role |
|---|---|
| Red `#FF0700` | record / error / destructive / blocked |
| Blue `#000AFF` | primary app action / info / processing |
| Green `#00FF56` | ready / saved / success |
| Yellow `#FFCD00` | caution / needs review / pending |
| Orange `#F0620A` | Lumotia brand mark only, NOT default action |
| Neutral | atmosphere, panels, text, disabled |

**Token architecture:**

```css
/* Source tokens — identity-only, never used directly in components */
--source-red, --source-yellow, --source-green, --source-blue, --source-orange-brand
--red-complement-source, --yellow-complement-source, --green-complement-source,
--blue-complement-source, --orange-complement-source

/* Component tokens — what components actually subscribe to */
--button-record-bg/fg/border/wire     (red + cyan)
--button-primary-bg/fg/border/wire    (blue + yellow)
--button-danger-bg/fg/border/wire     (red + cyan)
--button-success-bg/fg/border/wire    (green + magenta)
--button-caution-bg/fg/border/wire    (yellow + blue)
--brand-accent + --brand-wire         (orange + blue)
```

Tonal scales (red-50..red-950 etc.) reserved for a focused future commit. Component tokens currently pick hand-tuned hex values verified at WCAG AA on their target foreground; the rename to scale-derived values stays invisible to callers.

**Visible changes:**

- `--color-accent` (primary action default) repointed to BLUE — was copper-clay, now `#4A7BFF` dark / `#1D4ED8` light. All buttons that read `bg-accent` (including LumotiaButton primary, LumotiaProgress default tone) inherit blue under quietware.
- New `--brand-accent` token at `#F0620A`, separate from `--color-accent`. Used only on Lumotia logo / brand surfaces.
- New LumotiaButton variant `record` (red, with cyan wireline complement). Dictation page's record button repointed to `--button-record-bg`. The existing recording-state still uses `--color-danger` which now resolves to red.
- v0.2 fallback tokens defined at `:root` so non-quietware app keeps rendering with v0.2 amber.

**Wirelines (renamed from counterlines):**

```
--wire-width: 1px        (Level 1 quiet)
--wire-width-active: 2px (Level 2 active)
--wire-width-focus: 2px  (Level 2 focus)
--wire-opacity: var(--wire-opacity-dark) / -light per mode
--wire-opacity-dark: 0.75    (was 0.32 — bumped for visibility)
--wire-opacity-light: 0.6    (was 0.22)
--wire-opacity-focus: 1
--wire-style: solid          (dotted reserved for design-preview / drag-drop)
```

Legacy `--counterline-*` tokens kept as aliases pointing at the new `--wire-*` set so Phase 4h/4i opt-in code still works.

HC contract zeroes `--wire-width` and `--wire-opacity-*` but keeps `--wire-width-focus: 2px`. HC focus ring is `--focus-ring-color: #005FCC` (strong blue), not a complement.

### Phase 4i — Counterlines as interaction-affordance identity. Landed 2026-05-15.

Round-7 redirect (Option D): move counterlines from notices (where they're invisible) to interactive controls (where they add identity). Three-level model:

| Level | Surface | Width | Opacity |
|---|---|---|---|
| 0 — none | panels, notices (default), content, disabled | — | — |
| 1 — quiet | controls, chips, active nav | 1 px | 48% dark / 34% light |
| 2 — focus | keyboard focus, recording state | 2 px | 85% |

Notice retires from default counterline rendering. Opt-in via `<LumotiaNotice counterline ... />` when a specific notice deliberately mimics control identity.

LumotiaButton primary + destructive variants gain a Level-1 inset counterline:

- primary: `--button-brand-counterline` (softened blue, complement of brand amber)
- destructive: `--button-danger-counterline` (softened blue, complement of red)
- secondary / tertiary: no counterline (clean neutral)
- All variants on `:focus-visible`: 2-px ring at `--focus-ring-color` (default `--color-accent`) at 85% opacity. HC overrides to a strong blue `#005FCC`.

Token deltas vs Phase 4h:

```css
--counterline-width: 1px                    /* unchanged */
--counterline-width-focus: 2px              /* new — Level-2 width */
--counterline-opacity-dark: 0.48            /* was 0.32 */
--counterline-opacity-light: 0.34           /* was 0.22 */
--counterline-opacity-focus: 0.85           /* new */
--role-brand-counterline: #3A6BFF           /* new — blue counter for orange brand */
--button-{role}-counterline                 /* new — per-role component tokens */
--focus-ring-color                          /* new — context-overridable focus tint */
```

HC contract: every counterline token forced transparent, `--counterline-width: 0`, focus width kept at 2 px (HC focus ring carries the tactile detail in that mode), `--focus-ring-color: #005FCC`.

### Phase 4h — Counterlines test (RETIRED 2026-05-15, superseded by Phase 4i). Landed 2026-05-15.

Test commit per Jake's round-6 spec. Subtle 1-px inner-shadow detail in a softened complementary hue, applied to LumotiaNotice only. Other test surfaces (Button.destructive, status pills, record ring, active nav) reserved for after evaluation.

Tokens added under quietware scope:

```css
--counterline-width: 1px;

--role-info-counterline:    var(--color-accent);   /* copper for blue */
--role-danger-counterline:  #6EA8FF;               /* soft blue for red */
--role-success-counterline: #B891FF;               /* soft violet for green */
--role-caution-counterline: #3A6BFF;               /* soft blue for yellow */

--notice-{role}-counterline: color-mix(in srgb, <role-counterline> {32% dark / 22% light}, transparent)
```

HC contract: `--counterline-width: 0`, all counterline tokens forced to `transparent`. The 2-px HC border IS the tactile detail in that mode.

LumotiaNotice applies the counterline via Tailwind arbitrary value:

```svelte
shadow-[inset_0_0_0_var(--counterline-width,0)_var(--notice-{tone}-counterline,transparent)]
```

**Honest evaluation.** At 1px / 22% light / 32% dark opacity the effect is whisper-quiet to the point of being almost invisible at standard viewing. Doesn't look noisy (which was the risk Jake flagged), but also doesn't add perceptible tactility. Pending Jake's call: keep with raised opacity (perhaps 45% dark / 35% light), keep at current opacity for the quietness, or revert. Phase 5c proceeds either way.

Targeted scope per Jake's round-4 feedback: the browser-preview state used to render as a red error block in `DictationPage.svelte`. That is an environment limitation, not a failed action. Reclassified as a slim info notice with friendlier copy:

```
Old: bg-danger/10 border-danger/20, red text, technical-details affordance
New: LumotiaNotice tone="info" slim, "You're in a browser preview. Local
     transcription only works in the Lumotia desktop app."
```

Other error states (transcription failed, needs review) still render through the original danger-tinted block with the technical-details `<details>` affordance preserved. Only the browser-preview path branches into the info-classed notice.

The larger Dictation page restructure (header recomposition, Template/Extract Tasks demotion to bottom action bar, empty-state typography) is reserved for a focused Phase 5c sitting — `DictationPage.svelte` is 1 262 LOC of state-heavy code and warrants surgical attention rather than a tail-end rush.

### Phase 4e — High-contrast rendering contract. Landed 2026-05-15.

High-contrast is not a colour variant — it's a different rendering contract that the entire UI subscribes to. The tokens block now publishes behavioural variables alongside the palette overrides, so components can opt into the contract by reading them.

Behavioural tokens (defaults + HC values):

| Token | Default | HC |
|---|---|---|
| `--grain-opacity` | 0.06 (dark) / 0.07 (light) | 0 |
| `--shadow-strength` | 1 | 0 |
| `--border-width-control` | 1px | 2px |
| `--focus-ring-width` | 2px | 3px |
| `--panel-radius` | 8px | 8px (unchanged for now) |

`--quietware-texture-opacity` retained as a legacy alias pointing at `--grain-opacity` so Phase 4c controls keep working through the rename.

HC palette tweaks (round-4 feedback):

- **HC light** is now true pure-white background (`#FFFFFF`) + true pure-black text/borders (`#000000`). Brand atmosphere steps aside completely. Single strong-focus blue `#005FCC` replaces the accent.
- **HC dark** stays pure black `#000000` with white borders, semantic muted hues that read clearly. Matches the v0.3 dark feel but at maximum legibility.
- Hue-based selectors: `[data-design="quietware"][data-contrast="high"]` matches both base themes; the dark-base variant is scoped via `:not([data-theme="light"])` so light-base HC and dark-base HC can diverge.

### Phase 4c — Texture-opacity slider + high-contrast toggle. Landed 2026-05-15.

- New static grain overlay defined in `src/design-system/v0.3-quietware-tokens.css`. Fixed-position `body::after`, `mix-blend-mode: overlay`, opacity driven by `--quietware-texture-opacity`. Pattern generated inline via SVG `feTurbulence` (`baseFrequency=0.85`, `numOctaves=2`, `stitchTiles=stitch`). Tile size 240×240, repeated across viewport. `pointer-events: none` so the overlay never intercepts clicks. Active only under `html[data-design="quietware"]` and only on the dark and light modes — high-contrast forces opacity to 0.
- New `QuietwareAccessibilityControls.svelte` ([src/lib/components/](../../src/lib/components/QuietwareAccessibilityControls.svelte)) ships two controls:
  - **High-contrast toggle**, wired through `LumotiaToggle`. Sets `<html data-contrast="high">` so the quietware HC palette block in the tokens CSS activates. Persists via `localStorage`. Overrides the OS `prefers-contrast: more` query when explicitly enabled.
  - **Texture-intensity slider**, range 0.05–0.08, step 0.005, default 0.06. Sets the `--quietware-texture-opacity` CSS variable inline on `<html>` so it overrides the per-mode default in the tokens CSS. Disabled when high-contrast mode is on. Reset button restores the 0.06 default.
- Component injected into `SettingsPage.svelte` inside the Accessibility section, gated by `{#if isQuietware}`. v0.2 fallback unaffected.
- Persistence path: localStorage keys `lumotia:quietware:high-contrast` and `lumotia:quietware:texture-opacity`. Full integration with the central `Preferences` store is a follow-up; localStorage keeps Phase 4c contained.
- Verified: `npm run check` reports 0 errors, 0 warnings across 5706 files.

---

## Regression diary

Empty. Update inline as findings emerge per phase.

---

## References

- Source brief: `inputs/inbox/2026-05-15-lumotia-colour-layout-changes.md`.
- Palette refinement round 1 (brand-as-semantic): `inputs/inbox/2026-05-15-re-lumotia-v03-renders-revised-palette-typography-.md`.
- Palette refinement round 2 (cobalt-square peg): chat decision 2026-05-15.
- Palette refinement round 3 (color.adobe.com primaries): `inputs/inbox/2026-05-15-re-lumotia-v03-phase-1-landed-cobalt-pegged-square.md` + attached `2.jpeg`. Locked.
- W3C COGA: `https://www.w3.org/TR/coga-usable/`.
- WCAG 2.2 Understanding docs: `https://www.w3.org/WAI/WCAG22/Understanding/`.
- HMRC accessibility personas: `https://personas-prototype.herokuapp.com/`.
- DfE accessibility tools manual: `https://accessibility.education.gov.uk/tools-testing/tools`.
- v0.2 release doc: `docs/release/v0.2-frontend-overhaul.md` (predecessor; reference patterns).
