# Understand flow and copy review

**Status:** Review only. No Flutter files or browser artifacts were changed.
**Review target:** `consolidated-state-package-04.html`, the latest of the four package previews.
**Scope:** Information architecture, state transitions, labels, action meaning, recovery paths, and local/cloud truth.

## Verdict

The package contains 78 state definitions in 13 groups, and its broad shape matches the current IA: Library is the durable root, Reader is an entered listening surface, and the dock is not a bottom tab bar. The package is not ready as an approval contract yet.

The cards are an inspectable gallery, not a replayable flow. Several `path` values name a concept rather than a state ID, some action states have no target, and a few labels reverse the actual local/cloud behavior. The largest risks are the `Local quota reached` state, the missing local rewrite model setup, the blocking model gate versus `Not now`, and report copy that does not disclose the text included in a report.

Use `docs/DESIGN.md` and `docs/design/brand/INFORMATION-ARCHITECTURE.md` as the current IA and copy anchors. The locked product promises are `pux-01` for offline local listening, `pux-02` for one Premium entitlement, `pux-03` for next-play mode changes, `fea-01` for Local-first faithful retelling, `arc-01` for production cloud-voice limits, and `dat-02` plus `dat-03` for safe history and resume behavior.

## Review basis

I read:

- `docs/DESIGN.md`
- `docs/design/extraordinary-ux-brief.md`
- `docs/design/brand/MISSING-STATES-BRIEF.md`
- `docs/design/brand/INFORMATION-ARCHITECTURE.md`
- `docs/design/brand/STATE-PACKAGE-REVIEW.md`
- `.superpowers/brainstorm/81813-1787986487/content/consolidated-state-package-01.html` through `04.html`
- `docs/design/mocks/brand-proof/flow.html`, pinned at `30503a4`
- `docs/maps/product/user-flow.md`
- `os_tts_mobile/lib/app.dart`
- `os_tts_mobile/lib/presentation/providers/navigation_provider.dart`
- `os_tts_mobile/docs/understand-feature-design.md`
- `os_tts_mobile/docs/understand-ux-design-thread-2026-05-09.md`
- `os_tts_mobile/docs/understand-ux-animation-spec.md`
- `os_tts_mobile/docs/reader-companion-ia.md`
- `os_tts_mobile/docs/USER_STORIES.md`
- `os_tts_mobile/lib/presentation/providers/text_transform_provider.dart`
- `os_tts_mobile/lib/presentation/widgets/quota_banner.dart`
- `os_tts_mobile/lib/presentation/screens/model_gate_screen.dart`
- `os_tts_mobile/lib/presentation/widgets/intake_sheet.dart`
- `os_tts_mobile/lib/presentation/screens/report_output_screen.dart`
- `os_tts_mobile/lib/data/services/output_report_sink.dart`
- `os_tts_mobile/lib/data/models/text_transform/transform_persona.dart`
- `os_tts_mobile/lib/tts_engine/voice_registry.dart`

The package has exactly 78 `state(...)` declarations. Package 04 is the latest visual revision. Its group and state controls work, but its rendered action buttons and rows do not transition to another state.

## Severity

- `[P0]` Resolve before owner approval. The copy breaks a locked promise or creates a privacy or trust risk.
- `[P1]` Resolve before implementation. The flow can dead-end, take the user to the wrong place, or contradict the current runtime contract.
- `[P2]` Resolve before copy freeze. The state is understandable only with context, or the package and source docs drift.
- `[P3]` Polish after the state contract is stable.

## 78-state coverage audit

| Group | Count | State IDs |
| --- | ---: | --- |
| Library | 4 | `library-empty`, `library-continue`, `library-expanded`, `library-after-paste` |
| Add text | 6 | `reader-empty`, `intake-sheet`, `paste-replace`, `paste-success`, `share-return`, `imports-unavailable` |
| First run | 9 | `welcome`, `how-it-works`, `model-idle`, `model-downloading`, `model-verifying`, `model-ready`, `model-error`, `model-cancelled`, `local-disclosure` |
| Reader | 14 | `reader-idle-original`, `reader-cold-gather`, `reader-synthesizing`, `reader-live`, `reader-paused`, `reader-seeking`, `reader-completed`, `reader-error`, `reader-large`, `reader-retelling-loading`, `reader-retelling-ready`, `reader-retelling-disclosure`, `reader-quota`, `reader-retry` |
| Voice | 6 | `voice-ready`, `voice-preview`, `voice-unavailable`, `voice-downloading`, `voice-verifying`, `voice-error` |
| Retelling | 4 | `retelling-clean`, `retelling-dirty`, `retelling-cloud-gate`, `retelling-loading` |
| Contents | 3 | `contents-available`, `contents-current`, `contents-unavailable` |
| More actions | 8 | `overflow-open`, `edit-stub`, `report-confirm`, `report-sent`, `report-failed`, `clear-current-confirm`, `clear-current-success`, `clear-current-failure` |
| Settings | 4 | `settings-default`, `settings-large`, `settings-reduced-motion`, `settings-account-disabled` |
| Premium | 6 | `premium-choice`, `premium-checkout`, `premium-cancelled`, `premium-success`, `premium-error`, `premium-local-fallback` |
| Data and deletion | 8 | `history-delete-confirm`, `history-delete-success`, `history-delete-failure`, `model-delete-confirm`, `model-delete-progress`, `model-delete-success`, `model-delete-failure`, `data-delete-success` |
| Platform contracts | 3 | `share-handoff`, `notification-contract`, `permission-denied` |
| Recovery | 3 | `recovery-model`, `recovery-cloud`, `recovery-data` |
| **Total** | **78** | |

The count is complete, but the inventory is not the same as flow coverage. The IA map names `Listening view -> Reply listening -> Back to Reader`; no package state contains `reply` or `listening`. Either add that future state package or remove it from the current IA. The complete-redesign contract calls Ask, marks, and Loop depth out of scope, so the current package must make that boundary explicit.

## State transition map

The map below uses state IDs, not the package's informal `path` labels. `Prior Reader` means the exact `reader-idle-original`, `reader-live`, `reader-paused`, or retelling state that opened the sheet. The implementation and test fixture should carry that return state instead of guessing it.

### First run and voice readiness

| From | Action or event | Next state | Flow rule |
| --- | --- | --- | --- |
| `welcome` | `Continue` | `how-it-works` | The welcome action advances one onboarding beat. |
| `how-it-works` | `Set up local listening` | `model-idle` | This is the 129 MB voice setup, not the local rewrite model. |
| `model-idle` | `Download voice` | `model-downloading` | The app downloads the recommended local TTS voice. |
| `model-downloading` | Download completes | `model-verifying` | Keep the text or current context intact while the file is checked. |
| `model-downloading` | `Cancel setup` | `model-cancelled` | The package needs an explicit cancel target. |
| `model-verifying` | Verification succeeds | `model-ready` | The package currently has no failure target. Add `model-error`. |
| `model-ready` | `Start listening` | `library-empty` or `library-continue` | Select the destination from persisted data. A first run normally reaches `library-empty`. |
| `model-ready` | `Choose another voice` | `voice-ready` | The voice picker must return to the same prior context. |
| `model-error` | `Try again` | `model-downloading` | Retry the same voice download. |
| `model-error` | `Keep browsing` | `library-empty` only if no-voice browsing is supported | The current `ModelGateScreen` blocks Library until the voice is ready. |
| `model-cancelled` | `Resume setup` | `model-downloading` | Resume the same setup operation. |
| `model-cancelled` | `Keep browsing` | `library-empty` only if no-voice browsing is supported | Otherwise remove this action. |
| `local-disclosure` | `Got it` | `reader-retelling-loading` | This disclosure is reached from Original when the first local retelling starts. `Disclosure -> Reader` is too vague and can skip the rewrite operation. |

The current app has a blocking `ModelGateScreen` with Retry but no skip or cancel. The package must either remove `Not now`, `Cancel setup`, and the no-voice browsing branches, or design a real unavailable playback state with an installed-voice fallback. Do not show a reachable action that the app cannot honor.

### Intake, paste, share, and replacement

| From | Action or event | Next state | Flow rule |
| --- | --- | --- | --- |
| `library-empty` | `Paste text` | `paste-success` after clipboard read, or `intake-sheet` if this CTA opens the sheet | Pick one meaning. The current Library code reads the clipboard directly; the package path says Add text. |
| `reader-empty` | `Paste text` | `paste-success` | Library remains the primary intake root. Keep this Reader action secondary if that is the IA decision. |
| `reader-empty` | `More ways to add` | `intake-sheet` | Open the focused Add text sheet. |
| `intake-sheet` | `Paste from clipboard` | `paste-success` or `paste-replace` | Choose by whether the Reader already has text. |
| `intake-sheet` | `Share from another app` | `share-handoff` | This is an inbound OS handoff, not an in-app share button. |
| `intake-sheet` | `PDF or EPUB` | `imports-unavailable` | Keep the row disabled and state why. |
| `paste-replace` | `Cancel` | Prior Reader state | Keep the current text and playback position. |
| `paste-replace` | `Paste and replace` | `paste-success` | Stop playback, archive the current text, then open the new text. |
| `paste-replace` | Archive fails | Missing `paste-archive-failure` state | Preserve both current and pending text. Offer Retry save, Replace without saving, and Keep current. |
| `paste-success` | `Listen now` | `reader-idle-original` | New text opens as Original. Play then enters the cold-start path. |
| `paste-success` | `Open settings` | `settings-default` | Return to the text after closing Settings. |
| `share-handoff` | User chooses Understand in the OS share sheet | `share-return` | The app cannot open the other app's share sheet itself. |
| `share-handoff` | `Cancel` or no payload | Prior context or missing `share-empty` state | Do not imply a text was received. |
| `share-return` | `Listen now` | `reader-idle-original` | The passage is saved locally before the user starts playback. |
| `share-return` | `Back to Library` | `library-after-paste` or Library with the new Recent row | Use a concrete Library destination. |
| `imports-unavailable` | `Paste text` | `paste-success` or `intake-sheet` | The label must say whether it reads the clipboard or opens Add text. |
| `imports-unavailable` | `Close` | Prior intake context | Keep the user in Add text, not at a blank dead end. |
| `library-after-paste` | `Listen now` | `reader-idle-original` | Same target as `paste-success`; the two success states need distinct contexts. |
| `library-after-paste` | `Keep browsing` | Library | Keep the new Recent row visible. |
| `library-expanded` | `Play` | `reader-cold-gather` for a new item, or `reader-live` / `reader-paused` for the current item | The action must not always mean the same visual state if the selected item is already playing. Store the item ID. |
| `library-expanded` | `Open` | `reader-idle-original` or the item's saved view | Open without silently changing the saved mode. |
| `library-expanded` | `Voice` | `voice-ready` | Return to the expanded row or Reader after selection. |
| `library-expanded` | `-15 seconds` | Same Library state with the item's position changed | This is a transport action, not navigation. |

The runtime already has an empty or whitespace clipboard result (`PasteStatus.empty`) and an archive failure result (`PasteStatus.archiveFailed`). Those are not represented in the 78 cards. The package must add them or state that the package covers only visual states while the acceptance map covers these recovery states too.

### Reader playback and rewrite

| From | Action or event | Next state | Flow rule |
| --- | --- | --- | --- |
| `reader-idle-original` | Matter `Play` | `reader-cold-gather` | Gather before the first voice. |
| `reader-cold-gather` | Gather completes | `reader-synthesizing` | No user action is needed; expose failure as `reader-error`. |
| `reader-synthesizing` | First voice arrives | `reader-live` | The live sentence can appear after audio starts, then chrome settles. |
| `reader-synthesizing` | Synthesis fails | `reader-error` | Preserve the text and position. |
| `reader-live` | Matter `Pause` | `reader-paused` | The matter is the one playback control. |
| `reader-paused` | Matter `Play` | `reader-live` | Resume without restarting unless the saved position says otherwise. |
| `reader-live` or `reader-paused` | Scrubber or sentence jump | `reader-seeking` | Carry the prior playback state. |
| `reader-seeking` | Seek completes while playing | `reader-live` | The target sentence becomes live. |
| `reader-seeking` | Seek completes while paused | `reader-paused` | Do not resume audio as a side effect of seeking. |
| `reader-seeking` | Seek times out | `reader-paused` | The acceptance contract calls for a non-error recovery after 10 seconds. |
| `reader-live` | End of text | `reader-completed` | Keep the session in Recent. |
| `reader-completed` | `Listen again` | `reader-cold-gather` | Reset to the beginning. |
| `reader-completed` | `Back to Library` | `library-continue` or Library | The body says the user can choose another passage, so say that Library is where they do it. |
| `reader-error` | `Download voice` | `voice-unavailable` or `voice-ready` | The target depends on whether a downloadable voice is known. |
| `reader-error` | `Use another voice` | `voice-ready` | After selection, return to `reader-cold-gather` or the preserved paused state. |
| `reader-large` | Text-size setting changes | `settings-large` or `settings-default` | Treat Large text as a Reader variant. Preserve playback state and reserve dock space. |
| Any Original Reader state | User selects Retelling for the first time | `local-disclosure` or the Retelling onboarding sheet | The disclosure must precede local rewrite setup. |
| Any Original Reader state | Retelling selected after disclosure | `reader-retelling-loading` | Original remains available while the rewrite is prepared. |
| `reader-retelling-loading` | `Use original` | Original equivalent, usually `reader-idle-original` or `reader-paused` | Preserve the existing playback state. |
| `reader-retelling-loading` | Rewrite succeeds | `reader-retelling-ready` | Do not replace current audio mid-sentence. |
| `reader-retelling-loading` | Rewrite fails | `reader-retry` or a named local-rewrite error | Explain whether Retry, Original, or Cloud rewrite is available. |
| `reader-retelling-ready` | User selects Original | `reader-idle-original` or the preserved Original playback state | Mode selection takes effect on the next play. |
| `reader-retelling-ready` | Matter `Play` | `reader-cold-gather` | The content view is Retelling; playback state is separate. |
| `reader-retelling-disclosure` | `Got it` | `reader-retelling-ready` | Use this only for the brief in-Reader explanation. |
| `reader-retelling-disclosure` | `Use original` | `reader-idle-original` | Keep the source text visible and playable. |
| `reader-quota` | Any action | Rename and split by cause first | The current state is labeled Local quota but the runtime quota comes from the cloud key path. See F-01. |
| `reader-retry` | `Try again` | `reader-retelling-loading` | Retry the same persona and location. |
| `reader-retry` | `Use original` | `reader-idle-original` or `reader-paused` | Preserve place and make the mode change next-play safe. |

The reader data currently overloads `mode` with both content view and playback state. `original`, `retelling`, `live`, `paused`, and `seeking` are not one dimension. Use separate concepts in the canonical fixture: `view: original|retelling` and `playback: idle|gather|synthesizing|playing|paused|seeking|completed|error`.

### Voice and Retelling settings

| From | Action or event | Next state | Flow rule |
| --- | --- | --- | --- |
| `voice-ready` | Preview an installed voice | `voice-preview` | Preview is inside the Voice sheet and is not Reader playback. |
| `voice-ready` | Select an installed voice | Prior Reader state | Apply the selected voice to the next playable sentence. |
| `voice-ready` | Select an unavailable voice | `voice-unavailable` | Show size and local availability before download. |
| `voice-preview` | `Stop preview` or preview ends | `voice-ready` | The package needs an explicit preview return target. |
| `voice-unavailable` | `Download voice` | `voice-downloading` | Use local voice language only for downloadable on-device voices. |
| `voice-unavailable` | `Keep current voice` | Prior Reader state | Keep the current performer. |
| `voice-downloading` | Download completes | `voice-verifying` | Preserve current voice until verification succeeds. |
| `voice-downloading` | `Cancel download` | `voice-unavailable` or Prior Reader state | The package path currently says Verify even for Cancel. |
| `voice-verifying` | Verification succeeds | `voice-ready` | Add a failure target, normally `voice-error`. |
| `voice-error` | `Try again` | `voice-downloading` | Retry the same voice. |
| `voice-error` | `Keep current voice` | Prior Reader state | Do not leave the Reader without a performer. |
| `retelling-clean` | `Close` | Prior Reader state | There are no changes to apply. |
| `retelling-clean` | `Apply` | Remove this action or make its effect explicit | Applying a clean sheet has no distinct meaning. |
| `retelling-dirty` | `Close` | Prior Reader state | Discard the draft. |
| `retelling-dirty` | `Apply` | `reader-retelling-loading` | Start the chosen persona and location, preserving the source text. |
| `retelling-cloud-gate` | `See Premium` | `premium-choice` or `premium-checkout` | Name the exact Premium entry point. |
| `retelling-cloud-gate` | `Close` | Prior Reader state with local choice unchanged | A locked Cloud choice must not silently apply. |
| `retelling-loading` | `Close` | Prior Reader state | The copy must say Local rewrite, not local voice. |
| `retelling-loading` | Rewrite completes | `reader-retelling-ready` | Keep the old rendering available until ready. |
| `retelling-loading` | Rewrite fails | `reader-retry` or named local-rewrite error | Give Retry and Original a concrete target. |

### Contents and More actions

| From | Action or event | Next state | Flow rule |
| --- | --- | --- | --- |
| `contents-available` | Chapter row selected | `contents-current`, then Reader at the selected sentence | A chapter row is a seek action, not a dead-end label. |
| `contents-current` | `Close Contents` | Reader with the new position | `Current chapter` is a selected variant, not a separate destination unless the jump is persisted. |
| `contents-unavailable` | `Close` | Prior Reader state | Say that chapter metadata is unavailable, not that contents are still loading. |
| `overflow-open` | `Contents` | `contents-available` or `contents-unavailable` | Choose by actual chapter metadata. |
| `overflow-open` | `Edit text` | `edit-stub` | The stub must have one exit. |
| `overflow-open` | `Report output` | `report-confirm` | Label the thing being reported. `Output` is technical. |
| `overflow-open` | `Clear this text` | `clear-current-confirm` | `Clear` applies only to the current Reader text. |
| `edit-stub` | One named exit | Prior Reader state | `Close` and `Back to Reader` currently mean the same thing. |
| `report-confirm` | `Send report` | `report-sent` renamed `report-saved` or `report-queued` | Disclose the reported text and actual delivery behavior. |
| `report-confirm` | `Cancel` | Prior Reader state | Keep the current retelling and playback. |
| `report-sent` | `Use original` | Original Reader state | Preserve the place. |
| `report-sent` | `Close` | Prior Reader state | A saved report does not need to change the rendering. |
| `report-failed` | `Try again` | `report-confirm` or the report form | The failure must identify whether saving or delivery failed. |
| `report-failed` | `Close` | Prior Reader state | Do not imply a network requirement if the queue is local. |
| `clear-current-confirm` | `Cancel` | Prior Reader state | No side effects. |
| `clear-current-confirm` | `Clear text` | `clear-current-success` or `clear-current-failure` | Keep the saved session in Recent. |
| `clear-current-success` | `Back to Library` | Library | The current Reader is empty; the saved session remains. |
| `clear-current-failure` | `Try again` | `clear-current-confirm` or retry operation | Keep the text open. |
| `clear-current-failure` | `Close` | Prior Reader state | Nothing was deleted. |

### Settings, Premium, and data

| From | Action or event | Next state | Flow rule |
| --- | --- | --- | --- |
| `settings-default` | Text size row | `settings-large` | Keep the setting in Settings, then open the Reader variant when the user closes the sheet. |
| `settings-default` | Reduced motion row | `settings-reduced-motion` | Preserve the rest of the settings context. |
| `settings-default` | Voice row | `voice-ready` | Return to Settings or Reader after selection. |
| `settings-default` | Account and Premium row | `settings-account-disabled` or Premium entry | Do not show `Coming later` beside live Premium purchase states. |
| `settings-large` | Text size row | `settings-default` or `reader-large` | Use one explicit next destination. |
| `settings-reduced-motion` | Close | Prior context | Keep all other settings rows visible or clearly mark this as a focused preference view. |
| `settings-account-disabled` | `Close settings` | Prior context | Explain what is unavailable and what local use still supports. |
| `premium-choice` | `See Premium` | `premium-checkout` | Explain Cloud rewrite before any text leaves the device. |
| `premium-choice` | `Keep local` | Prior Reader state | Use the canonical local fallback label. |
| `premium-checkout` | `Continue` | Missing purchase-pending or store-return state, then `premium-success` or `premium-error` | `Continue` has no concrete outcome in the package. |
| `premium-checkout` | `Not now` | `premium-cancelled` renamed `purchase-dismissed` | `No purchase was made` is not the same as a subscription being cancelled. |
| `premium-success` | `Back to Reader` | Reader | Cloud rewrite is selected only when the user chooses it. |
| `premium-error` | `Try again` | `premium-checkout` | Keep the local fallback visible. |
| `premium-error` | `Keep local` | Prior Reader state | Make the safe fallback primary if that is the intended choice. |
| `premium-cancelled` | `Keep local` | Prior Reader state | Use a purchase-dismissed label if this is the Not now outcome. |
| `premium-local-fallback` | `Back to Reader` | Reader with local path | Qualify this with required local model readiness and device support. |
| `history-delete-confirm` | `Cancel` | Settings storage | No history changes. |
| `history-delete-confirm` | `Delete history` | `history-delete-success` or `history-delete-failure` | Preserve the open Reader text as stated. |
| `history-delete-failure` | `Try again` | `history-delete-confirm` or retry operation | Keep history intact. |
| `history-delete-failure` | `Close` | Settings storage | State what remains. |
| `model-delete-confirm` | `Cancel` | Settings storage | Keep the voice model. |
| `model-delete-confirm` | `Delete model` | `model-delete-progress` | Name the selected voice model and consequence. |
| `model-delete-progress` | Delete completes | `model-delete-success` | Add the missing failure target and a busy or non-cancellable explanation. |
| `model-delete-success` | `Choose a voice` | `voice-ready` | This is the safe primary path. |
| `model-delete-success` | `Back to Library` | Library with visible no-voice recovery | Do not strand the user with rows that appear playable but cannot play. |
| `model-delete-failure` | `Try again` | `model-delete-confirm` or retry operation | Keep the model installed. |
| `model-delete-failure` | `Close` | Settings storage | Nothing changed. |
| `data-delete-success` | `Start again` | `welcome` or the actual onboarding entry | Add the missing confirmation, progress, and failure states before calling this complete. |

### Platform contracts and recovery

| From | Action or event | Next state | Flow rule |
| --- | --- | --- | --- |
| `notification-contract` | `Close` | Prior Reader state | This is a platform contract, not a fake lock screen. |
| `permission-denied` | `Keep listening` | Prior Reader state | Name the denied permission and keep local playback available. |
| `permission-denied` | `Open system settings` | OS Settings, then Prior Reader state | The return path must be explicit. |
| `recovery-model` | `Try again` | `model-downloading` or `voice-downloading` | The recovery card must carry the operation ID. |
| `recovery-model` | `Choose installed voice` | `voice-ready` | The summary currently mentions Keep browsing but exposes no such action. |
| `recovery-cloud` | `Keep using local` | Original Reader state or local Retelling state | Keep the source available. |
| `recovery-cloud` | `Try cloud again` | `premium-choice` or `reader-retry` | Name the exact target and reason. |
| `recovery-data` | `Try again` | The failed operation's confirmation or progress state | Generic Recovery cannot retry without an operation ID. |
| `recovery-data` | `Close` | Prior context | Confirm that no partial deletion occurred. |

## States missing from the package or needing an explicit boundary

These states are reachable in the product docs or runtime, but the 78-card package does not name them. Add them to the package, or write that they are implementation-only and map them to a canonical existing state.

| Missing state or contract | Why it matters | Recommended user-facing path |
| --- | --- | --- |
| Empty or whitespace clipboard | `PasteStatus.empty` is a real result and `USER_STORIES.md` promises `Nothing to paste`. | Stay on the current context and say `Nothing to paste. Make sure you copied text first.` |
| Archive failure during replacement | The runtime preserves current and pending text and offers three choices. | Add `Retry save`, `Replace without saving`, and `Keep current`. |
| Empty or failed share payload | A share can return no usable text. | Keep the current text and explain that no text was received. |
| Local rewrite model setup | `TransformStatus.modelSetup` downloads Qwen 3 0.6B at about 600 MB on first local retelling use. | Add idle, downloading, ready, and failure states, or show a named inline setup state before `reader-retelling-loading`. |
| Local rewrite unsupported device | The backend can report unsupported device, model not installed, temporary failure, and failed transform. | Offer Original and a truthful reason. Do not call this a quota. |
| Cloud daily limit or missing key | `DailyLimitReachedException` and `KeyUnavailableException` are separate Cloud errors. | Name Cloud rewrite and offer Original or Local where ready. |
| Premium purchase pending or store return | `premium-checkout.Continue` has no pending state or concrete return. | Add pending, success, cancel, and failure outcomes with one entitlement. |
| Account or all-data deletion confirmation and failure | The app has a typed DELETE confirmation and network failure paths. | Separate local-data deletion from account deletion, then add progress and failure recovery. |
| Reply listening | IA and post-vc25 reader-companion docs name it, while the current package has no ID. | Add it to a future package or remove it from the current IA. |

## Terminology corrections

| Current package or source wording | Use instead | Reason |
| --- | --- | --- |
| `Understand toggle` as the Reader control | `Retelling` and `Original` | `Understand` names the product mode. The current Reader control chooses the source rendering. Keep `Understand mode` in technical docs. |
| `Cloud processing` | `Cloud rewrite` | This is the exact IA term. It says what changes. |
| `Local quota reached` | `Cloud rewrite limit reached` for the daily cloud limit, or `Local rewrite unavailable` for an on-device failure | The runtime quota comes from the OpenRouter key service. The current label reverses the fallback. |
| `See cloud option` | `See Premium` or `Choose Cloud rewrite` | Say whether the next step is a purchase explanation or a mode choice. |
| `local voice is working` in `retelling-loading` | `Local rewrite is being prepared` | Voice means TTS performer. Retelling means wording transformation. |
| `Model setup idle`, `Model ready`, `Model deleted` in user-facing copy | `Voice setup`, `Voice ready`, `Voice removed` | Use `model` only when explaining download or setup mechanics. |
| `voice model` when the user is choosing a performer | `Voice` | The voice picker is a casting choice. Keep `voice model` in storage and download detail. |
| `Report sent` and `The report did not send` | `Report saved` or `Report queued`, then `Report could not be saved` | The current sink writes an encrypted local record and sends metadata only. Do not claim delivery that did not happen. |
| `Send a short report ... The original text stays on this device` | `Save a report about this retelling. It includes the text shown here and your reason. Comments and text stay encrypted on this device; only a metadata summary is sent for triage.` | The report includes the displayed retelling. Saying only the Original stays is incomplete. |
| `Premium cancelled` after `Not now` | `Purchase dismissed` | A dismissed checkout is not a subscription cancellation. |
| `Cloud · Premium` in a location selector | `Cloud rewrite · Premium` | Make the feature and entitlement visible together. |
| `Account & premium: Coming later` beside live Premium states | One truthful state | Either expose a real Premium entry or label the whole account and Premium surface unavailable. |
| `Controls` in the package's primary flow bar | `Settings` | The group and IA use Settings. |
| `overflow` in user-facing copy | `More` | Keep `overflow` only in code or state IDs. |
| `Report output` | `Report this retelling` | Name the content the user sees. |
| `Keep using local` and `Keep local` mixed across paths | `Keep local` for the fallback action | One action label should mean one choice. |
| `Open share sheet` in the app | `Share from another app` | The other app owns the system share sheet. |
| `No contents yet` | `Contents are not available for this text` | Distinguish unavailable metadata from loading. |
| `Back to Reader` and `Close` on the same `edit-stub` dialog | One exit, preferably `Back to Reader` | Two buttons currently dismiss the same dialog and create no distinct outcome. |
| `Warm narrator`, `Calm guide`, `Bright reader` as canonical voice names | Actual catalog names such as `Emma`, or mark these as fixture-only role labels | `VoiceRegistry` exposes named voices. Do not make illustrative labels look like ship-ready catalog entries. |
| `No account or phone verification is required` on data deletion success | `Local data on this device was deleted` | Account deletion, local data deletion, and phone verification are different concepts. Scope the local promise to local listening and local retelling. |

## Findings

### F-01 [P0] The quota state reverses the local and cloud paths

`reader-quota` is labeled `Local quota reached` and offers `Use original` plus `See cloud option`. In the runtime, `TransformStatus.quotaReached` is set after `DailyLimitReachedException`, which comes from the cloud OpenRouter key service. Local mode does not resolve that key. The state therefore tells the user that Local failed and points them toward Cloud when the actual failure is a Cloud daily limit.

Rename and split the state by cause. For a cloud daily limit, say `Today's Cloud rewrite limit is used` and offer Original or a local path when its model is ready. For an on-device failure, say `Local rewrite is unavailable` and offer Original plus Retry. This is required by `fea-01` and `pux-02`.

### F-02 [P0] Report copy makes an incomplete privacy claim

`report-confirm` says the Original stays on the device, but the report is opened with the displayed Retelling text. `ReportOutputScreen` submits that text and the selected reason. `OutputReportSink` stores the report text and comment in an encrypted local queue and sends a metadata summary, including a hash and length, to Sentry. The state label `Report sent` also claims delivery even though the current flow queues the record locally.

State exactly what the report contains, what stays encrypted locally, and what metadata leaves the device. Rename the success and failure states to match the current queue behavior. Do not use `Report sent` unless a real delivery acknowledgement exists.

### F-03 [P1] Model setup actions contradict the blocking app gate

The package exposes `Not now`, `Cancel setup`, `Keep browsing`, and `Choose another voice` around first-run model setup. The current `ModelGateScreen` explicitly blocks Library and Reader until Supertonic is verified and exposes no skip or cancel. If the package allows browsing without a voice, it must show how the user hears text with an installed fallback. Otherwise remove the bypass actions from the approval package.

### F-04 [P1] The package hides the second local model setup

The first-run cards cover a 129 MB voice model. First local Retelling use can separately download the on-device Qwen 3 0.6B model at about 600 MB. Runtime state already exposes `modelSetup`, progress, a Wi-Fi recommendation, and a Cancel action. The package goes directly from the local disclosure to Retelling loading and never names this setup.

Add the local rewrite setup states or explicitly fold them into `reader-retelling-loading` with the size, network expectation, readiness, unsupported-device, and failure branches. `Local rewrite stays on this device` is true only after this model is ready on a supported device.

### F-05 [P1] The browser artifact shows buttons but does not replay actions

Package 04 wires group navigation, state navigation, and the text-scale demo. Its generated state buttons and rows have no transition handlers. A reviewer can inspect all cards, but cannot prove `Paste -> Reader`, `Retry -> ready`, `Cloud -> Premium`, or any recovery outcome in the artifact.

Keep the package as an inspectable gallery if that is intentional, but attach the state transition matrix above to it as the canonical contract. The approval gate should not call the package a complete flow until every primary action has a target state ID and the highest-value paths replay.

### F-06 [P1] `mode` has two meanings and Original renders as Retelling

The Reader data uses `mode:'original'` for one state, `mode:'retelling'` for another, and `mode:'live'`, `mode:'paused'`, and `mode:'seeking'` for playback states. In the package renderer, any mode other than `original` leaves Retelling selected. The `reader-idle-original` card therefore displays the Retelling segment as selected even though its state ID says Original.

Separate content view from playback phase. Render the selected segment from the view field, not from a playback state. Add a deterministic transition for every transport action.

### F-07 [P1] The pinned Flow conflicts with the next-play lock

`pux-03`, the IA map, and the feature design say mode changes take effect on the next play. The pinned Flow handler instead changes mode while playing, stops the clock, recasts the text, and starts synthesis again. It also defaults new listening to Retelling, while the current IA says a new Reader opens with Original selected.

Before approval, choose the current rule and align the package, pinned Flow, and Flutter behavior. The locked rule is next-play and Original-first. The transition map above follows that rule.

### F-08 [P1] Async and recovery states do not identify their outcomes

The package has no explicit target for verification failure (`model-verifying`, `voice-verifying`), no failure or cancel target for `model-delete-progress`, and no target after `reader-seeking` settles. Downloading states point to verification even when the visible action is Cancel. `recovery-cloud` and `recovery-data` also point to generic `local` or `retry` concepts instead of state IDs.

Every async state needs success, failure, and cancellation semantics. Non-cancellable deletion can be valid, but the state must say it is busy and must still have a failure route. Carry an operation ID into reusable Recovery states.

### F-09 [P1] Share handoff assigns the wrong actor

`share-handoff` has an `Open share sheet` button, but the app cannot open another app's share sheet. The current intake tile is informational and disabled because the person starts in the source app and shares into Understand. The only actionable event is the OS returning a payload.

Make this a platform contract with no fake in-app primary button. Explain `Choose Share in the other app, then select Understand`, handle Cancel, and show `share-return` only after usable text is saved.

### F-10 [P1] Input and archive failures are absent

The product already has `PasteStatus.empty` for whitespace or empty clipboard content and `PasteStatus.archiveFailed` when the current text cannot be saved before replacement. `USER_STORIES.md` promises `Nothing to paste`; the runtime archive failure dialog offers Retry save, Replace without saving, and Keep current. None of these paths has a state ID in the package.

Add named states or include them in the canonical transition contract. A Snackbar can remain the presentation in Flutter, but the state map must still say where the person stays and what action is safe.

### F-11 [P1] Premium availability and copy disagree

`settings-account-disabled` says Account and Premium are coming later, while the package offers Premium checkout, success, cancellation, and error states. `premium-checkout` advertises Hosted voices, but `arc-01` keeps cloud voices out of production builds until account ownership is reviewed. The current paywall removes cloud voices from public benefits when that flag is off.

Choose one product state. If Premium Cloud rewrite is live, give Settings a live Premium entry and remove `Coming later`. If account and Premium depth is still a stub, keep only the truthful entry state and mark purchase and hosted voice outcomes as future. Never advertise hosted voices in the public package while production compile gates them out.

### F-12 [P1] Disclosure actions do not have one meaning

The package has both `local-disclosure` and `reader-retelling-disclosure`, with different action sets. The runtime first-use sheet offers `Not now` and `Got it`, then the Reader banner offers `Got it`. The Retelling selector currently ignores the first sheet's Boolean result, so `Not now` still proceeds to Retelling.

Use one disclosure gate with a real `Not now -> Original` return, then keep the brief Reader banner informational. Route `Got it` to the local rewrite setup and retain Original while it loads.

### F-13 [P1] Reply listening is in the IA but not in the package

`INFORMATION-ARCHITECTURE.md` includes Listening view, Reply listening, and Back to Reader. The missing-state brief also names the listening-view transition. The 78 package has no matching state. Post-vc25 reader-companion documents define replies as a later loop, while complete redesign marks Ask and Loop depth out of scope.

Do not leave this as an accidental omission. Either add a separate future package for reply listening or remove it from the current IA and mark the boundary as post-vc25.

### F-14 [P2] Several states contain duplicate or misleading actions

`retelling-clean` shows `Close` and `Apply` even though no draft changed. `edit-stub` shows `Close` and `Back to Reader`, which dismiss the same dialog. `reader-completed` says the user can choose another passage but exposes no such action. `premium-cancelled` describes a dismissed checkout as a cancelled Premium subscription.

Use one action per outcome. Make the completed copy say `Listen again or return to Library to choose another passage`. Rename purchase dismissal and remove duplicate dialog exits.

### F-15 [P2] Some state cards are variants presented as destinations

`contents-available` and `contents-current` are usually the same sheet with a different selected row. `reader-large` is a text-size variant, not a separate playback phase. `settings-reduced-motion` drops Playback and Voice rows, so the person cannot tell whether those settings remain. The package's primary flow bar calls Settings `Controls`, while the IA calls it Settings.

Keep variants in the state schema, but record the base context and selected value. Use one user-facing Settings label and show the complete focused settings context.

### F-16 [P2] Voice fixtures look like catalog promises

The package uses `Warm narrator`, `Calm guide`, and `Bright reader`. The actual voice registry uses names such as Emma, Sarah, Olivia, and James for Supertonic, plus named cloud voices. The persona labels `NPR thoughtful` and `Therapist` do exist in `TransformPersona`, so those are not the same problem.

Use actual voice catalog names in approved fixtures, or label role names as illustrative. Do not conflate a voice performer with a Retelling persona.

### F-17 [P2] Empty-state priority and sample entry drift from the current design

The IA says Library owns primary intake and Empty Reader is secondary. The package gives Reader `Paste text` the same primary weight as Library, and the package's `library-empty` card omits the sample entry that complete redesign and the running Library support. The current shell tooltip directs an empty Reader person back to Library or paste there.

Keep Library as the primary intake root. If Reader can still paste, make its role secondary and give it the same target as the Library action. Include samples where the approved empty Library state promises them.

### F-18 [P2] Older product docs still describe a different navigation model

`understand-ux-design-thread-2026-05-09.md` and its animation spec describe Reader and History as persistent bottom tabs. The current `app.dart` and `navigation_provider.dart` implement Library-first navigation with a keep-alive Reader and no bottom tab bar. `reader-companion-ia.md` is explicitly post-vc25 and should not silently override the current redesign.

Mark those docs as future or historical for this review. Keep one current navigation glossary: Library root, Reader surface, Settings sheet, More menu, and focused sheets.

## Local and Cloud truth checklist

The package should pass all of these before approval:

- Local TTS requires the local voice model once, then plays without a network. This is the `pux-01` promise.
- Local Retelling may require a separate on-device language model download. State its size and readiness before claiming offline rewrite.
- Local Retelling is free and on device on supported hardware. Unsupported devices need an Original fallback and a named reason.
- Cloud rewrite is an explicit Premium choice. Say `Cloud rewrite`, not `Cloud processing`.
- Cloud rewrite must disclose that the selected text leaves the device before the user confirms the choice.
- A daily Cloud limit is not a Local quota. Keep the fallback direction correct.
- Production cloud voices are compile-gated out under `arc-01`. Do not list hosted voices as available benefits until that lock changes.
- Local listening and local Retelling do not need an account, phone verification, or payment after the required local models are ready. Scope this promise to local use.
- A report is a separate privacy path. Say what the report contains and whether it is saved locally, queued, or sent.

## Approval gate

Before owner approval, require:

- [ ] A transition matrix uses canonical state IDs for every primary action, row action, and async outcome.
- [ ] The model gate decision is consistent. Remove or implement `Not now`, Cancel, and no-voice browsing.
- [ ] Reader fixtures separate content view from playback phase and render Original as Original.
- [ ] Next-play mode semantics match `pux-03` in the package, pinned Flow, and Flutter.
- [ ] Local rewrite model setup and unsupported-device recovery are represented or explicitly scoped out.
- [ ] Quota copy distinguishes Cloud daily limit, missing key, local model setup, and local transform failure.
- [ ] Report copy names the displayed retelling, local encrypted queue, and metadata-only triage path.
- [ ] Premium state copy matches the production hosted-voice gate and one-entitlement rule.
- [ ] Empty clipboard, archive failure, share-empty, purchase-pending, and account-data recovery are mapped.
- [ ] The IA decision about reply listening is explicit and does not conflict with the post-vc25 boundary.
- [ ] All user-facing labels use the terminology table above and direct verbs.
