Architecture · Part 03
User Flows
Every flow in the shipped product, documented from code — goals, entry points, decision points, branches, success and failure states. Screens are referenced S1–S23 from the canonical inventory in the Information Architecture.
Flow index
| # | Flow family | Frequency | Entry surface |
|---|---|---|---|
| F1 | Onboarding & account creation | once | S1 Splash |
| F2 | Daily experience (dashboard · check-in · insight review) | daily | S3 Today |
| F3 | Cycle tracking | cycle-paced | S10 Log Period |
| F4 | Training | per workout | S9 Log Workout |
| F5 | Sleep (manual · wearable · analysis) | daily / weekly | S11 / S19 / S21 |
| F6 | Nutrition | daily, passive | S3 FuelingCard |
| F7 | Weekly review | weekly | S4 Insights |
| F8 | Historical exploration | occasional | WeekStrip / S19 / S20 |
| F9 | Settings & profile | rare | S19 Profile drawer |
| F10 | Permissions | event-driven | splash / banners / drawers |
| F11 | Error recovery | event-driven | any |
F1 · Onboarding & account creation
Goal · Start using Peri — with an account, without one, or just look around.
Entry points · Unauthenticated launch → Splash.
Success · Dashboard reached; new users meet the learning-state stack — the threshold ladder is the onboarding's second act.
Failure · OAuth denied → stay on splash, retriable. Drive unreachable → 'working offline', app proceeds local-only.
Decision points
hasProfile || hasData) skips onboarding on a new device. Every step is skippable — an all-null profile is valid. The demo path never marks onboarding complete, so browsing leaves the real flow untouched. Google OAuth is in Testing mode: only allow-listed users can sign in.F2 · Daily experience
F2a — Dashboard read (0 taps). Engines run synchronously on render for navDate; the card stack is the priority order. Success is sub-10-second comprehension; no failure state exists — every card degrades to a learning form, never an error.
Goal · F2b — record today in seconds through the single write funnel.
Entry points · The floating + (only door); assumption actions from drawers.
Success · Toast confirms scope ('Drive ✓' / 'locally ✓'); dashboard recomputes immediately.
Failure · Drive write fails → entry persists locally, red dot, auto-retry. Data loss is structurally impossible.
F2c — Insight review. Card face → drawer → “based on…” → red assumptions → tapping an action closes the sheet and opens the fixing log screen after 320ms. After saving, the assumption line disappears — the explainability loop closes on itself.
F3 · Cycle tracking
Goal · Record bleeding and period symptoms; keep cycle context honest as cycles become irregular.
Entry points · + → Log Period; Cycle Overview assumption 'Log Period'.
Success · Correct cycle day everywhere; predictions re-anchor instantly.
Failure · Bleed >40d after last start: saves fine, cycle mode absorbs the gap — no error surfaced. Duplicates resolved by merge.
The anchor and the modes
cd = 1. The system then reacts to gaps, not just logs: ≥35 days since last bleed → irregular; ≥60 → late_peri (phase labels removed, “Xd since last bleed”); none → no_data. The product sheds its cycle layer gracefully as cycles fade.F4 · Training
Goal · Log a session and its perceived effort; understand its recovery cost.
Entry points · + → Log Workout; DailyFocus assumption 'Log Workout'.
Success · Seconds to log; tomorrow's recommendation visibly reflects the session.
Failure · None beyond storage; legacy ⚡ Hulk tags accepted as ⚡ Peak.
Three tag sections — type → body state (incl. “Trained in the heat”) → how it felt (Very easy → Had to stop, ⚡ Peak). The return loop: recovery strain rises, capacity may drop, DailyFocus may flip to “Train lighter”, Fueling floors carbs and protein, WhatNext matches similar past days, and weekly Training Insights accumulate playbook patterns (back-to-back cost, heat+sleep interactions, yoga/walks as recovery boosters).
F5 · Sleep
Goal · Get sleep's effect on capability — manual or imported.
Entry points · + → Log Sleep (manual); Profile drawer → Wearables → Sync (import); Sleep Overview (analysis).
Success · Stages and overnight recovery feed DailyFocus; 'sleep missing' notices disappear.
Failure · Per-step failure shown in the modal; save_failed keeps imported data in memory with explicit wording.
Merge rule
F6 · Nutrition
Goal · Know when fueling needs increase — without logging anything extra.
Entry points · Passive: FuelingCard is card 2 on Today.
Success · Glanceable dots matching today's reality; the sentence names the driver.
Failure · None possible — missing inputs reduce confidence and are listed (max 3), never errored.
Baselines 2/2/1; floors only raise. Signal priority: training load > sleep <6h > capacity <30% > crash risk ≥70 > fatigue/heavy bleeding > symptoms > appetite > cycle phase (lowest, skipped at low confidence).
F7 · Weekly review
Goal · Is anything changing? What has my data taught me?
Entry points · Insights tab, typically weekly.
Success · A narrative of direction plus at least one personally-earned insight.
Failure · Degrades only: <10 entries → low confidence labels; <14 → playbooks gated with the threshold named.
The designed reading order: Trends over time (1M/3M/6M or “Since [Treatment]”) → What My Data Says → the four domain overviews with playbooks → the stats row (Days logged · Patterns found · Insights learned). Range switches recompute in place; assumption actions hand off to log screens.
F8 · Historical exploration
Three entries by depth: WeekStrip/calendar pick — navDate changes and the same dashboard recomputes for that day (the calendar stays open for hopping); Profile drawer → Log History — full list → PastEntryModal for read/edit through the same merge-safe save; and the data coverage indicator — % of the last 90 days logged, color-banded (≥75% green / ≥40% orange / red). No failure states; future dates just show predictions.
F9 · Settings & profile
| Item | Path | Outcome |
|---|---|---|
| Edit Profile | drawer → Edit Profile view | onboarding answers editable; saves to peri-profile |
| Log History | drawer → History view | F8 — list → PastEntryModal |
| Wearables | drawer → Wearables view | F5b sync; manage devices; sync history sub-view |
| Privacy mode | toggle | blurs content text for the session |
| Legal | drawer → document views | read-only, back returns |
| Log out | button | signed-in → revoke token → splash; demo → clear demo → splash |
| Delete logged data / account data | double-confirmed modals | destructive: wipes local → splash. Drive copy from last sync may persist (documented limitation) |
F10 · Permissions
| Permission | Asked | Granted | Denied / absent |
|---|---|---|---|
| Google sign-in | Splash; quiet auth banner when signed out | Drive sync, photo, cross-device | full app local-only; 'Saved locally ✓' |
| Drive (appdata + readonly) | bundled with sign-in | data file + Health Connect export readable | red dot; local persistence continues |
| Wearable / Health Connect | 'Sync' assumption actions, in context | import pipeline | absent-but-invited; no nagging |
| Demo consent | 'I just want to browse' | read-only tour, real engines | exit returns to splash |
Re-ask policy
F11 · Error recovery
Goal · Never lose data, never be blocked.
Entry points · Any failure, anywhere.
Success · Every branch ends in an automatic or one-tap recovery.
Failure · None terminal — the worst case is 'saved locally, will sync later', and the UI says exactly that.
The structural guarantees
Divergence touched by this part
Ruling #10 — Treatments export
peri-{category}-export.csv) with a confirmation toast. App canonical (CSV); the PDF flow is visual intent for a future iteration. Recorded in the divergence register.