# Understand — full UI replacement plan

**Status:** In review (2026-08-27)
**Decision locked:** Flutter replacement. HTML is the **design-integration harness**; `os_tts_mobile` is the **ship target**.  
**Parents:** `attention-model.md` · `direction.md` · `gap-blast-decision-brief.md` · Wave 1 `proof/flow.html`  
**Story sources:** `os_tts_mobile/docs/USER_STORIES.md` + live presentation layer (Library-first IA supersedes stale “History tab” wording in places)

---

## Do you understand what we need to do?

**Yes.**

1. **Design every product surface** under ferrofluid × attention (not a mood slice of one happy path).
2. Use the HTML phone as the place we **resolve** chrome-last, rewrite-central, states, and sheets **before** burning Flutter cycles.
3. **Replace** the warm-paper / copper Flutter UI with that designed system — same providers, audio, rewrite, locks — new skin + attention choreography.
4. Ship only when a user can complete the real stories **in the app**, not in a mock.

Wave 1 proved the spine (state machine, rewrite, advancing highlight, honest library/back). Everything below is how we finish the product and land it in Flutter.

---

## Non-goals / keep intact

| Keep | Why |
| --- | --- |
| Playback / synthesis / models / Drift history | Engine work; UI rides on top |
| Locks (`pux-01` offline TTS, `pux-02` entitlement shape, `pux-03` rewrite commit-at-play, `fea-01` rewrite ≠ summary, etc.) | Product law |
| Library-first + keep-alive Reader (`app.dart` IndexedStack) | Shipped IA; brand must absorb it |
| Goldens as regression net | Update goldens as surfaces restyle; don’t delete coverage |

**Not** a rewrite of the TTS stack. **Not** a second app. One app; new face.

---

## Operating model

```
USER_STORIES + live widgets
        ↓
HTML harness (design every aspect, attention-legal)
        ↓  approve surface
Flutter theme + widgets (replace warm-paper)
        ↓
PARITY GATE (mechanical, per surface state)   ← added 2026-08-26
        ↓
Goldens + make run device proof
```

### Parity gate (added 2026-08-26 — after two false "complete" claims)

**The defect it closes:** the old model gated design work in HTML, then
verified the port with app-vs-app goldens and a manual look. Nothing ever
compared the Flutter render back to the approved harness surface, so the
port could drift arbitrarily with every gate green. Acceptance criteria
were qualitative ("no warm-paper", "feels finished") — unmeasurable.

**The bar (mechanical, no eyeballs):** a surface is done only when
`scripts/design/parity` is green for every state of that surface:

1. `render_design.py` renders the harness state to `goldens/<state>.png`
   (headless Chrome, drivers call the harness's own functions).
2. The same state is captured from the running app (seeded content, adb
   screencap) to `captures/<state>.png`.
3. `compare.py goldens/<state>.png captures/<state>.png reports/<state>.json`
   exits 0: no unexplained off-palette band (Material/warm-paper leak
detector), text renders with the brand face, overlay written for audit.

Rules are extracted to `docs/design/brand/flow-parity-rules.json` — code
must match that sheet; the rendered gate proves it. Minor HTML-vs-Flutter
and viewport differences are absorbed by relative-space normalization
(no pixel lock; must look right on common devices).

**Also versioned now:** the harness (`docs/design/mocks/brand-proof/`) lives
in-repo next to the code — the reference can no longer exist only as
untracked files in one checkout.

**Rule:** no Flutter skin for a surface that has no brand treatment in the harness (or an explicit “engine-only / OS chrome” exception).

**Chrome-last lock (from Wave 1):** matter/blob from gather; skip / scrub / segment / title settle after first live sentence on cold open; resume keeps chrome.

---

## Surface inventory

Legend for **Design:** `done` = Wave 1 harness · `next` = Wave 2/3 HTML · `later` · `os` = OS/system, document only · `engine` = no visual redesign  
Legend for **Flutter port phase:** F0 foundation · F1 primary path · F2 chrome · F3 depth · F4 polish

### A. Shell / navigation

| Surface | Stories / live | Brand treatment | Design | Flutter |
| --- | --- | --- | --- | --- |
| App shell (Library root, keep-alive Reader, system back) | live `app.dart` | Dark field; back preserves play; no bottom tabs | done | F0 |
| Now-playing when on Library | live gap (mini presence) | Glanceable matter + title; not a second full dock | done (pill) | F1 |
| Onboarding (2–3 pages) | US-5.1, US-5.2 | Dark-field; voice-first promise; ends in action (demo/paste) | next | F2 |
| Theme light/dark/system | US-7.1 | **Dark-first brand**; light = deferred or soft inversion later | later | F3* |

\*Brand direction is dark-field. System-light may stay as accessibility escape, not co-equal brand.

### B. Library / intake (Epic 1 + 4)

| Surface | Stories | Brand treatment | Design | Flutter |
| --- | --- | --- | --- | --- |
| Empty library + Paste CTA | US-1.1, primary-flow | Quiet field + one CTA + sample chips | done | F1 |
| Sample chips | live | Low-energy chips; tap → gather → listen | done | F1 |
| Continue hero | live | Memory of left-off; progress as thin field line (not skeuomorph bookmark theater) | done | F1 |
| Recent list + date buckets | US-4.1, US-4.4 | Title + **voice** meta (not persona-as-row-meta) | done (partial buckets) | F1 |
| Paste replace / archive-failed dialogs | live intake signals | Destructive confirm in dark sheet language | next | F2 |
| Share-from-OS intake | US-1.4 | Document in harness; real only in Flutter | os | F2 |
| Card expand (±15, play, open, voice) | US-4.2 | Expand = secondary; no fake library dock | next | F2 |
| Swipe delete / edit multi-select | US-4.3 | Restrained; undo toast | later | F3 |
| Banner ad (non-premium) | live / pux-02 | Contained; never competes with Continue | later | F3 |

### C. Reader / listen (Epic 2 + rewrite)

| Surface | Stories | Brand treatment | Design | Flutter |
| --- | --- | --- | --- | --- |
| Full scrollable text + live sentence | US-2.x, brief | Live / wake / quiet tiers; ignite after voice | done (short passage) | F1 |
| Tap-to-seek on sentence | live TextDisplay | Seek = handoff; don’t light ahead of audio | next | F1 |
| Play / pause / hold matter | US-2.1, US-2.2 | Blob = matter states; not ink Material icon | done (CSS stand-in) | F1 |
| −15 / +15 / scrub / timecode | US-2.3, US-2.4, US-7.3 | Chrome-last; ±15 matches live (fix icon bug separately) | done | F1 |
| Synthesizing / buffer label | live | Matter synthesizing; copy quiet | done | F1 |
| Error / download-on-play (129MB) | US-2.9, US-3.2 | Play presence becomes recovery — P1 truth | done (long-press demo) | F1 |
| Retelling \| Original | product core / pux-03 | Central segment; re-cast text+matter | done | F1 |
| First Retelling disclosure | live | One beat; dismissible | done | F1 |
| Retelling settings sheet | live goldens | Persona / style / fidelity / local-cloud | done (structure) | F1 |
| Speed control | US-2.5 | Settles with chrome or settings hub — not before voice | next | F2 |
| Voice row → voice sheet | US-3.1–3.6 | Casting a performer; download progress in-sheet | next | F1–F2 |
| AppBar / back / overflow | live ReaderAppBar | Late chrome; Contents / Edit / Clear / Report | next | F2 |
| Contents sheet | live | Dark sheet; chapter list energy quiet | next | F2 |
| Edit text / clear | US-1.2, US-1.3 | Edit is secondary; confirm clear | next | F2 |
| AppBar fade while playing | live chrome_orchestrator | Align with chrome-last / settle | next | F2 |
| AI content badge | live | Disclosure adjacent to rewrite | next | F2 |
| Text scale / a11y | US-7.2, goldens | Harness must gain large-text story before F3 claim | next | F2 |
| Background / lock screen | US-2.6–2.8 | os media session; brand title string only | os | F1 (wiring) |

### D. Voice / models (Epic 3)

| Surface | Stories | Brand treatment | Design | Flutter |
| --- | --- | --- | --- | --- |
| Voice selector sheet | US-3.5 | Field list; active check; preview = small matter pulse | next | F2 |
| Download progress + size | US-3.2 | Progress as field fill; size in meta | next | F2 |
| Preview voice | US-3.6 | Doesn’t steal main listen matter | next | F2 |
| Delete model (settings) | US-3.7 | Settings data section | later | F3 |

### E. Settings / commercial / system (Epics 6–7 + depth)

| Surface | Stories | Brand treatment | Design | Flutter |
| --- | --- | --- | --- | --- |
| Settings hub | US-7.x | Instrument panel, late; dark | next | F2 |
| Paywall / trial / restore | US-6.x, pux-02 | Cinematic but honest; no carnival | later | F3 |
| Quota / completion / error banners | live | Quiet banners; matter can signal error | next | F2 |
| Marks / notes / Ask / listening view | live Loop | Depth; after primary listen is solid | later | F3 |
| Report / add-ons / transform diffs | live | Overflow depth | later | F3 |
| True ferrofluid GLSL + audio FFT | extraordinary brief | Shared `.frag` HTML + Flutter FragmentProgram | later | F4 |

---

## Flutter replace order (execution)

### F0 — Foundation (unblocks everything)
1. Dark-field **theme tokens** (field / matter / live / wake / quiet / spike) replacing warm-paper copper as default listen theme.
2. Feature flag or theme mode: `BrandTheme.ferrofluid` vs legacy (rollback).
3. Document chrome-last + rewrite-central in Flutter-facing design note (link this file).

### F1 — Primary path (user can live here)
Wire brand UI to **existing providers** — no engine rewrite:
1. `HistoryScreen` / Library empty + Continue + Recent + Paste  
2. `PlayerStrip` → matter presence + transport chrome-last  
3. `TextDisplay` → live/wake/quiet tiers driven by playback position  
4. `RetellingViewControl` + settings sheet restyle  
5. Keep-alive back + Library now-playing affordance  
6. Error/download-on-play treatment on the play control  

**Exit criteria:** cold user can paste/sample → hear rewrite → flip Original → return to Library with audio still going — in **Flutter**, under brand theme.

### F2 — Chrome & casting
Voice sheet, speed, AppBar/overflow, Contents, intake replace dialogs, settings hub shell, onboarding restyle, banners/badge, text-scale pass.

### F3 — Depth & commercial
History expand/edit/delete, paywall, ads placement, marks/Ask, light theme escape if required.

### F4 — Living matter
Shared ferrofluid shader + FFT uniforms; HTML mock and Flutter share one `.frag`. Until then CSS/canvas stand-in is allowed in harness only; Flutter may use a restrained custom painter interim.

---

## HTML harness roadmap (keeps feeding Flutter)

| Wave | Scope | Status |
| --- | --- | --- |
| **1** | State machine, rewrite, highlight, dock, library honesty, back preserves play | **done** |
| **2** | Voice sheet, speed, AppBar/overflow, Contents, replace-confirm, date buckets, card expand, settings hub stub, text-scale | **integrated; parity in review** |
| **3** | Paywall, ads, edit/swipe, marks/Ask, error matrix completeness | later |
| **4** | Real ferrofluid field (WebGL) matching Flutter frag | later |

Keep `flow.html` as the frozen design harness while the integrated Flutter surfaces finish parity review; do not create a second harness until a real surface requires it.

---

## Story coverage matrix (epics → when “done” for brand replace)

| Epic | Brand-complete when… | Phase |
| --- | --- | --- |
| 1 Text input | Paste, replace confirm, edit, clear designed + in Flutter | F1–F2 (share = F2 os) |
| 2 Playback | Full transport + states + chrome-last in Flutter | F1 |
| 3 Voice/models | Selector + download + preview branded | F2 |
| 4 History/Library | Empty/Continue/Recent/delete designed | F1–F3 |
| 5 Onboarding | Dark-field action-ending onboarding | F2 |
| 6 Paywall | Branded paywall (copy+layout) | F3 |
| 7 Settings | Hub + text scale + skip interval | F2 |

---

## Risks

| Risk | Mitigation |
| --- | --- |
| Designing forever in HTML, never shipping Flutter | F0+F1 start as soon as this plan is approved — parallel to Wave 2 HTML |
| Skinning Flutter without attention choreography | Chrome-last + ignite are acceptance checks, not “nice polish” |
| Goldens mass-break | Update per surface; keep redacted CI goldens layout-stable where possible |
| Ferrofluid blocks F1 | F1 ships with restrained matter painter; F4 is shader |
| USER_STORIES drift (History tab vs Library) | Live IA wins; update stories in a follow-up doc pass |

---

## Immediate next actions (after you approve this plan)

1. **Approve** this inventory + F0→F4 order (edit anything wrong).  
2. Start **Wave 2 HTML** (voice sheet + AppBar + Contents + replace confirm) so F2 has pictures.  
3. Start **F0 Flutter tokens + flag** in `os_tts_mobile` (separate worktree / PR).  
4. F1 Library + PlayerStrip + TextDisplay + Retelling under flag.

---

## Approval

Reply with:

- **Approve as written**, or  
- **Approve with edits:** (list), or  
- **Reject:** (what’s wrong)

No Flutter skinning and no Wave 2 expansion until this plan is approved.
