The first cycle tracker built for partners.
Every period tracking app on the market (Flo, Clue, Natural Cycles) is designed for the person who menstruates. FlowState is designed for the person who loves them. It teaches you what's happening inside her body, why she feels the way she does, and gives you real things you can do about it.
One input. One date. Daily guidance that actually makes you a better partner.
This is a working MVP with the core daily experience fully implemented. Here's what exists and what doesn't.
- Onboarding flow with signal-based phase estimation (no period date needed)
- Daily Cards with full 28-day content (biology, feelings, actions, avoids, hormone levels)
- Cycle Insights screen with animated SVG cycle ring visualization
- Check-in system with daily observation logging, streaks, and milestones
- Category playbooks with 100+ actions across 6 categories (food, date, physical touch, words, logistics, gifts)
- Encyclopedia data with 7 phase-specific "vibe meters" per phase (Cuddle Factor, Romance Level, etc.)
- Observation mode with signal-to-phase estimation algorithm
- Non-28-day cycle support via proportional day mapping
- Dark theme with phase-specific color accents
- Animations via React Native Reanimated v4 (bars, counters, rings, transitions)
- Local storage via AsyncStorage (no backend, no account required)
- AI Coach (context-aware relationship advice via API, see
docs/AI_COACH.mdfor full architecture) - Smart Reminders (push notifications for phase transitions)
- Learn Mode (educational mini-modules on biology topics)
- Manual date input onboarding (only signal-based exists right now)
- Cycle Log ("Period started today" recalibration button)
- Data export / partner sharing
- Premium tier / monetization
| Layer | Choice |
|---|---|
| Framework | React Native + Expo SDK 54 |
| Routing | Expo Router 6 (file-based) |
| Language | TypeScript (strict) |
| Storage | AsyncStorage (local-first, no backend) |
| Animations | React Native Reanimated v4 |
| Gestures | React Native Gesture Handler |
| Visualization | react-native-svg |
| Date Picker | @react-native-community/datetimepicker |
No state management library (single custom hook). No UI component library. No backend.
flowstate/
├── app.json # Expo config (dark theme, scheme: flowstate)
├── package.json # main: expo-router/entry
├── tsconfig.json # strict, paths: @/* -> src/*
├── babel.config.js # babel-preset-expo + reanimated plugin
├── index.ts # expo-router entry point
│
├── docs/
│ ├── PRODUCT.md # Product vision, features, monetization
│ ├── ARCHITECTURE.md # Technical architecture overview
│ ├── AI_COACH.md # AI observation + coaching webhook design
│ └── CONTENT_GUIDE.md # How daily content is structured + hormone reference table
│
├── app/
│ ├── _layout.tsx # Root Stack layout, GestureHandler, SafeArea, StatusBar
│ ├── index.tsx # Router: checks onboarding state -> redirects
│ ├── onboarding.tsx # Signal-based onboarding (vibe cards)
│ ├── (tabs)/
│ │ ├── _layout.tsx # Tab bar: Today, Cycle, Check In (animated icons)
│ │ ├── index.tsx # Today screen (Daily Card, main experience)
│ │ ├── insights.tsx # Cycle Insights (SVG ring, phase legend, hormones)
│ │ └── checkin.tsx # Check-in flow (daily questions + streak tracking)
│ └── category/
│ └── [category].tsx # Category detail page (full action playbook)
│
├── src/
│ ├── types/
│ │ └── index.ts # All TypeScript types and interfaces
│ ├── constants/
│ │ ├── theme.ts # Colors, spacing, radius, typography
│ │ └── phases.ts # Phase metadata: names, colors, day ranges, emojis
│ ├── data/
│ │ ├── cycle-content.ts # 28 DayContent entries (803 lines of content)
│ │ ├── signals.ts # 24 vibe cards, 10 questions, signal-to-phase weights
│ │ └── encyclopedia.ts # Phase-specific indicators, actions, avoids (488 lines)
│ ├── utils/
│ │ ├── cycle.ts # getCycleDay, getPhase, getDayContent, mapTo28
│ │ ├── storage.ts # AsyncStorage wrapper (save/load/clear config + observations)
│ │ └── signals.ts # Phase estimation from behavioral signals
│ ├── hooks/
│ │ └── useCycleData.ts # Single hook: loads config, computes day/phase/content
│ └── components/
│ ├── HormoneChart.tsx # 5 animated horizontal bars (estrogen, progesterone, etc.)
│ ├── VibeMeters.tsx # 7 animated indicator bars with emoji + flavor text
│ ├── CycleRing.tsx # 280px SVG circular visualization with phase arcs
│ └── AnimatedCounter.tsx # Animated number display with prefix/suffix support
│
└── assets/
├── icon.png
├── adaptive-icon.png
├── splash-icon.png
└── favicon.png
# Install dependencies
npm install
# Start Expo dev server (all platforms)
npm start
# Platform-specific
npm run web # Web
npm run ios # iOS Simulator
npm run android # Android EmulatorFor Vercel web deployment, set:
- Build command:
npx expo export --platform web - Output directory:
dist
Two-step flow:
Step 0: Welcome
- Logo with ambient glow, tagline "understand her better"
- Three feature callouts
- "Get started" button
Step 1: Signal Selection
- Prompt: "What's going on with her?"
- 24 emoji-based vibe cards in a 2-column grid (multi-select)
- Examples: "She's on fire" 🔥, "Really happy" 😍, "Low energy" 😴, "Cramps" 🤕
- Cards use a rotating 12-color palette for selection feedback with checkmark badges
- Live phase estimate preview below the grid (shows estimated phase + confidence)
- On submit: estimates cycle phase from signals, calculates synthetic period start date, saves CycleConfig to AsyncStorage, redirects to tabs
Each vibe card maps to multiple signal tags. Signal tags have weighted probabilities per phase. The algorithm picks the highest-probability phase and derives a synthetic lastPeriodStart date.
What's missing: Manual date input path (enter period start date + cycle length directly). The docs/PRODUCT.md describes this as "Path A" but only Path B (observation) is built.
The main daily experience. Scrollable card with these sections (each animates in with staggered FadeInUp):
-
Hero: Phase name, animated cycle day counter ("Day 14/28"), pulsing countdown pill ("Period likely in 3 days"), confidence indicator if estimate is rough
-
The Vibe Right Now: 7 horizontal bar meters from
encyclopedia.ts:- Cuddle Factor, Romance Level, Adventure Readiness, Patience Meter, Spicy Meter, Nesting Energy, Social Battery
- Each has emoji, label, animated fill bar, and flavor text (e.g., "Maximum koala mode. She wants to be wrapped up.")
-
What's Happening: Plain-language biology explanation of current hormones and their effects (from
cycle-content.ts) -
Hormone Levels: 5 animated horizontal bars showing estrogen, progesterone, testosterone, serotonin, prostaglandins with color-coded fills
-
She Might Be Feeling: Bullet list of 4-5 emotional/physical feelings with parenthetical explanations
-
What You Can Do: 2x3 grid of 6 action category cards (Food, Date Ideas, Physical Touch, Words, Logistics, Gifts), each showing count and preview. Taps navigate to
/category/[category] -
Today's Picks: Top 5 prioritized actions across all categories with emoji, text, and effort indicator (Easy/Medium/Big with color dot)
-
Don't Do This: Red-tinted section with avoids and severity indicators (if any exist for the day)
-
CTA: "Quick check in" button linking to check-in tab
Design patterns: Ambient phase-color glow background (8% opacity), glass-morphism cards (rgba with 3% white overlay), staggered fade animations
- Cycle Ring: 280px SVG circular visualization with 5 colored phase arcs, each animated with staggered strokeDasharray. Pulsing glow dot on current day. Center shows cycle day number + phase pill.
- Phase Legend: Table of all 5 phases with color dot, name, day ranges
- Hormone Levels: Same chart as Today screen
- Period Countdown: "Estimated next period in X days"
- Low Confidence Warning: Shows if confidence < 0.7
Question Flow (if not checked in today):
- Animated progress bar at top
- 3 questions per session, randomly selected from pool of 10 (seeded by date for consistency)
- Sample: "How's her energy?", "What's her mood?", "Does she want to go out?", "Any physical complaints?"
- Each question has 3-4 multi-select emoji answer cards
- Slide transitions between questions (FadeInRight/FadeOutLeft)
- Answers map to signal tags which feed into the phase estimation algorithm
Completion Screen (shown after finishing or if already checked in today):
- Animated streak counter with spring entry
- Fire emoji that pulses at 7+ day streaks
- Streak messages ("Unstoppable. She is lucky to have you.")
- 4 milestone boxes (3-day, 7-day, 14-day, 30-day) with glow for achieved ones
- Next check-in reminder
- Accuracy improvement hint based on streak length
- Header with emoji and category name
- Scrollable list of all actions for that category in the current phase
- Each action card shows: bold title, detail explanation, optional pro tip (💡), effort label (Easy Win / Worth the Effort / Go All Out with color)
- "Things to Avoid" section at bottom with red styling (if avoids exist)
/ -> Splash (checks isOnboarded in AsyncStorage)
-> if no: /onboarding
-> if yes: /(tabs)
/onboarding -> Signal-based setup flow
/(tabs)/ -> Tab navigator (3 tabs)
index -> Today (💡)
insights -> Cycle (🔄)
checkin -> Check In (📝)
/category/[category] -> Category detail (back button returns to tabs)
Tab icons have spring scale animation on focus (damping 15, stiffness 300).
type Phase = 'menstruation' | 'follicular' | 'ovulation' | 'luteal_early' | 'luteal_late'
type ActionCategory = 'food' | 'date' | 'physical_touch' | 'words' | 'logistics' | 'gift'
type Effort = 'low' | 'medium' | 'high'
type SignalTag = 'tired' | 'energetic' | 'cramps' | 'flirty' | 'emotional' | 'bloated'
| 'hungry' | 'social' | 'withdrawn' | 'irritable' | 'happy' | 'anxious'
| 'confident' | 'acne' | 'headache' | 'backpain' | 'nauseous'
| 'cravings_sweet' | 'cravings_salty' | 'insomnia' | ... // ~20+ tags
interface CycleConfig {
lastPeriodStart: string // ISO date string
cycleLength: number // default 28
onboardedAt: string // ISO timestamp
source: 'observation' | 'manual'
confidence: number // 0-1, from signal estimation
}
interface DayContent {
biology: string // 150-200 word hormone explanation
feelings: string[] // 4-5 emotional/physical experiences
actions: Action[] // 5-8 suggestions across categories
avoid: string[] // 2-4 things not to do
hormones: {
estrogen: number // 0-1 relative levels
progesterone: number
testosterone: number
prostaglandins: number
serotonin: number
}
}
interface Action {
text: string
category: ActionCategory
effort: Effort
}
interface Observation {
id: string
date: string
signals: SignalTag[]
note?: string
}
interface PhaseEstimate {
phase: Phase
confidence: number // 0-1
estimatedDay: number
probabilities: Record<Phase, number>
}
interface PhaseIndicator {
label: string // e.g., "Cuddle Factor"
emoji: string // e.g., "🤗"
value: number // 0-1 for bar fill
flavor: string // e.g., "Maximum koala mode."
}
interface EncyclopediaAction {
id: string
text: string
detail: string
category: ActionCategory
effort: Effort
proTip?: string
}
interface AvoidItem {
text: string
why: string
severity: 'gentle' | 'serious' | 'nuclear'
}AsyncStorage wrapper with two keys:
@flowstate/cycle_configfor CycleConfig@flowstate/observationsfor Observation[]
Functions: saveCycleConfig, loadCycleConfig, clearCycleConfig, saveObservations, loadObservations, clearObservations
Auto-migration for old configs missing source or confidence fields.
useCycleData() is the only state hook. It:
- Loads config and observations from AsyncStorage on mount
- Computes
cycleDayfromlastPeriodStartusingdaysBetween - Maps to 28-day equivalent for non-28-day cycles
- Looks up DayContent from
cycle-content.ts - Returns
{ loading, isOnboarded, cycleDay, phase, content, daysUntilPeriod, confidence } addObservationcallback re-estimates phase if in observation mode
803 lines. 28 unique DayContent entries, one per cycle day. Each day includes:
- Biology: 150-200 word explanation of what hormones are doing TODAY
- Feelings: 4-5 specific experiences with "why" in parentheses
- Actions: 5-8 concrete suggestions (at least 2-3 low effort, at least 1 high effort, varied categories)
- Avoids: 2-4 specific behaviors to not do
- Hormones: 5 normalized values (0.0 to 1.0) for chart visualization
See docs/CONTENT_GUIDE.md for the full hormone reference table (all 28 days) and content writing rules.
488 lines. Per-phase expanded content:
- Phase Indicators: 7 "vibe meters" per phase with label, emoji, value (0-1), flavor text
- Actions by Category: Detailed actions with
id,text,detail,category,effort, optionalproTip - Avoids: Phase-specific with
text,why,severity
This is what powers the category detail pages and the vibe meters on the Today screen.
- 24 Vibe Cards: For onboarding. Each has emoji, label, and array of SignalTag mappings
- 10 Daily Questions: For check-in. Each has text, 3-4 emoji options, each option maps to signal tags
- Signal Phase Weights: Maps each of ~20 signal tags to phase probability distribution. Example:
cramps= 80% menstruation,flirty= 55% ovulation - Phase Midpoints: Estimated cycle day for each phase center (menstruation: 3, follicular: 10, ovulation: 15, luteal_early: 19, luteal_late: 25)
pickDailyQuestions(): Selects 3 random questions seeded by date for consistency
estimatePhaseFromSignals(signals): Takes signal array, returns PhaseEstimate with phase, confidence, day, probabilitiesestimateFromDatedObservations(observations): Temporal analysis using observation dates to fit best cycle start daysyntheticPeriodStart(estimatedDay): Calculates a lastPeriodStart date from estimated cycle day
cycleDay = ((daysSince(lastPeriodStart, today)) % cycleLength) + 1
Content is written for 28 days. For other lengths, proportional mapping:
fractionElapsed = (currentDay - 1) / (actualCycleLength - 1)
equivalentDay = Math.round(fractionElapsed * 27) + 1
A 35-day cycle's "day 20" maps to roughly the same phase as a 28-day cycle's "day 16."
| Phase | Days | Color |
|---|---|---|
| Menstruation | 1-5 | #C75B5B (warm red) |
| Follicular | 6-13 | #6BC7A3 (fresh green) |
| Ovulation | 14-16 | #E8C86B (golden) |
| Early Luteal | 17-21 | #7B8FD4 (calm blue) |
| Late Luteal | 22-28 | #9B7BC7 (muted purple) |
Background: #050506 (near black)
Surface: rgba(255,255,255,0.04) (glass)
Text Primary: #EDEDEF (off-white)
Text Secondary: #8A8F98 (gray)
Text Tertiary: #565B65 (darker gray)
Accent: #E8836B (warm coral/salmon)
Phase Colors: See table above.
Hormone Colors:
Estrogen: #E88BA3 (pink)
Progesterone: #B07BE8 (purple)
Testosterone: #E8B86B (orange)
Prostaglandins: #E86B6B (red)
Serotonin: #6BC7E8 (cyan)
Category Colors:
Food: #E8A86B (orange)
Date: #E86BA3 (pink)
Physical Touch: #B06BE8 (purple)
Words: #6BC7E8 (cyan)
Logistics: #6BE8A3 (green)
Gift: #E8C86B (yellow)
xs: 4, sm: 8, md: 16, lg: 24, xl: 32, xxl: 48
sm: 8, md: 12, lg: 16, xl: 24, full: 999
Sizes: xs (12), sm (14), md (16), lg (20), xl (28), xxl (36)
Weights: regular (400), medium (500), semibold (600), bold (700)
All animations use React Native Reanimated v4. Key patterns:
Entering animations: FadeIn, FadeInUp, FadeInRight, ZoomIn, BounceIn with staggered delays (100-300ms between sections)
Bar meters (HormoneChart, VibeMeters): useSharedValue for width, animated from 0 with Easing.out(Easing.cubic), staggered 80-100ms between bars
Cycle Ring (CycleRing.tsx): SVG arcs drawn with animated strokeDasharray, 150ms stagger between phases. Active phase has a wider stroke with pulsing opacity. Center dot pulses on a 1.5s sinewave cycle.
Animated Counter (AnimatedCounter.tsx): useAnimatedReaction to round intermediate values during transitions. Supports prefix/suffix ("Day " + "14" + " /28").
Tab icons: Spring scale on focus (damping 15, stiffness 300)
Check-in streaks: Spring entry for counter, pulse animation on fire emoji at 7+ day streaks, ZoomIn with springify for milestone boxes
Pulsing dot (countdown pill): Repeating sequence of scale 1.0 -> 1.4 -> 1.0, 1000ms each
Glass morphism: Cards use rgba(255,255,255,0.03) overlay on rgba(255,255,255,0.04) surface
Biology: Uterine lining sheds. Prostaglandins cause contractions. Estrogen + progesterone at floor. Iron drops from blood loss. Serotonin/dopamine low. Her experience: Cramps (mild to debilitating), fatigue, possible headaches/migraines, lower back pain, nausea, mood may improve partway through as PMS lifts. Partner mode: Comfort. Ibuprofen, heating pad, iron-rich food, handle chores, physical affection without pressure, stock the bathroom. Content tone: Warm, supportive, practical.
Biology: Pituitary releases FSH, follicles develop. Estrogen rises steadily, boosting serotonin/dopamine/endorphins. Testosterone climbing. Pain tolerance improves. Her experience: Energy climbing daily, mood brightening, increased sex drive, better workouts, skin clearing, more adventurous. Partner mode: Adventure. Active dates, gym together, new experiences, spontaneous plans, compliments, match her energy. Content tone: Energetic, encouraging, adventurous.
Biology: Estrogen peaks, LH surges to release the egg. Peak testosterone. Pheromone production increases, senses heighten. Her experience: Peak energy/mood/confidence, highest sex drive of the entire cycle, most social, skin glowing, some may feel mittelschmerz (ovulation pain). Partner mode: Date night. Plan something memorable, be present, physical intimacy at natural peak, match social energy. This phase is 2-3 days. Content tone: Celebratory, present, connected.
Biology: Corpus luteum forms, pumps progesterone. Second smaller estrogen peak. Body temperature rises. Her experience: Relatively stable mood, slightly introspective, mild bloating starting, appetite increasing, prefers smaller settings. Partner mode: Low-key. Cooking together, deep conversations, walks, comfortable evenings, proactively handle household tasks. Content tone: Calm, reflective, nurturing.
Biology: Corpus luteum breaks down. Progesterone and estrogen crash. Serotonin drops. GABA receptors disrupted. This is PMS and it's biochemical, not psychological. Her experience: Mood swings, bloating (2-5 lbs water), breast tenderness, cravings (body burns 100-300 extra cal/day), acne, disrupted sleep, difficulty concentrating, lower self-esteem. Partner mode: Patience. Validate feelings (don't fix), comfort food without commentary, reduce friction, heating pads, chocolate (magnesium + endorphins), give space or presence (ask which). NEVER say "Are you PMSing?" Content tone: Patient, validating, gentle.
Context-aware relationship coaching via webhook to a Claude API backend. User describes a situation ("we just had a fight"), AI responds with biology-informed advice knowing what cycle day she's on. Full architecture spec is in the doc including:
- Webhook endpoint design (
POST /api/flowstate) - Authentication (device UUID + shared secret)
- Rate limiting (20 req/hr, 100 req/day per device)
- Request/response payload schemas
- System prompt design
- Anti-abuse measures
- Privacy model (stateless server, all context sent per request)
Local push notifications for phase transitions:
- "Her period likely starts in 2 days. Stock up on ibuprofen and chocolate."
- "Ovulation window. Date night?"
- "PMS window starts Thursday. Keep the weekend low-key."
Short educational modules (2-3 min reads): "What actually causes cramps," "The estrogen-serotonin connection," "Why her sex drive changes," "Why cravings are real." Premium feature.
Path A: user enters last period start date + cycle length directly. Currently only signal-based onboarding exists.
"Period started today" button that recalibrates predictions over time for increasing accuracy.
- Free: Daily cards, cycle tracking, observation logging, check-in streaks
- Premium: AI coach, learn mode, expanded action library, smart reminders, multi-cycle insights
- One-time purchase option: $5-10 unlock everything (no subscription)
- All data stored locally on device (AsyncStorage)
- No account required, no cloud sync by default
- No PII sent anywhere
- App prompts users to tell their partner they're using it (not a secret surveillance tool)
- Optional: partner can share data from their own tracker
{
"expo": "~54.0.33",
"expo-router": "~6.0.23",
"react": "19.1.0",
"react-native": "0.81.5",
"react-native-reanimated": "~4.1.1",
"react-native-gesture-handler": "~2.28.0",
"react-native-svg": "15.12.1",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-native-community/datetimepicker": "8.4.4",
"react-native-safe-area-context": "~5.6.0",
"expo-linear-gradient": "~15.0.8",
"expo-status-bar": "~3.0.9",
"typescript": "~5.9.2",
"babel-preset-expo": "^55.0.13"
}Expo Config (app.json):
- Name: FlowState
- Slug/Scheme: flowstate
- Dark UI style
- Plugins: expo-router, @react-native-community/datetimepicker
- Android: edge-to-edge enabled, adaptive icon
- Web: metro bundler
Babel (babel.config.js):
- Preset:
babel-preset-expo - Plugin:
react-native-reanimated/plugin(must be last)
TypeScript (tsconfig.json):
- Strict mode
- Path alias:
@/*maps tosrc/* - Extends
expo/tsconfig.base
Vercel Web Deploy:
- Build command:
npx expo export --platform web - Output directory:
dist - Note: Reanimated and GestureHandler may need web-specific fallbacks
Built by publu