Skip to content

tsushanth/SunflowerCompanion

Repository files navigation

Sunflower Companion

A working SwiftUI prototype of three shipping-ready iOS surfaces for Sunflower (YC F25): a hands-free "Whisper to Sam" voice mode for late-night urges, a Lock Screen Live Activity for the sober day count, and a Home Screen widget with milestone progress.

Sunflower Companion home screen — 47 days sober, urge button, Whisper to Sam, widget, Live Activity

Built unsolicited. Each surface maps to a specific pattern across the 5-star App Store reviews — not a guess at what users might want.


Why these three surfaces

The current Sunflower iOS app does the heavy lifting beautifully (Sam, day counter, journal). The gaps users implicitly reach for are off-screen, not in-screen:

Surface Gap it closes Evidence
Whisper to Sam (hands-free voice mode) "Late-night when I can't talk to humans, I open Sam" — but text-chatting in bed with shaky hands is exactly the wrong UX for a peak urge. Voice is. Apple_Ann08: "AI chat for late-night crisis support when unable to contact others." Northwest Darling, snicklelove, Carxxsatf200 all cite the same 24/7-late-night use case.
Lock Screen Live Activity (sober days + days to next milestone) Reviewers obsess over the day counter, but checking it requires opening the app. A glance on the lock screen turns the streak from a destination into ambient reinforcement. JonDA., brooklynbabe718, Highley92 all describe the counter as their primary motivator. None mention a widget exists — because none does yet.
Home Screen widget (3 sizes + Lock Screen accessory) Same — passive visibility, no taps needed. Sober-day apps live or die on streak salience. Same review cluster.

The fourth surface (a 60-second 5-4-3-2-1 grounding flow for sub-Sam urges) is also included as a lower-friction alternative when the user doesn't have AirPods in or the moment is too brief to start a conversation.


What's in the box

SunflowerCompanion/
├── SunflowerCompanion/             # Main app target
│   ├── SunflowerCompanionApp.swift
│   ├── Models/
│   │   ├── SobrietyState.swift       # Milestone ladder + UrgeEvent
│   │   └── SobrietyStore.swift       # App-Group-backed streak state
│   ├── Audio/
│   │   └── WhisperToSamEngine.swift  # SFSpeechRecognizer + AVSpeechSynthesizer voice loop
│   ├── Intelligence/
│   │   └── SamResponder.swift        # Local mock + Remote stub (single integration point)
│   └── Views/
│       ├── ContentView.swift         # Home
│       ├── WhisperToSamView.swift    # Voice mode UI
│       └── GroundingView.swift       # 60-second 5-4-3-2-1 flow
├── SunflowerWidget/                # WidgetKit + Live Activity extension
│   └── SunflowerWidget.swift         # 6 widget families + Live Activity + Dynamic Island
└── SunflowerLiveActivity/          # Shared attrs between app + extension
    └── SobrietyAttributes.swift

Whisper to Sam — WhisperToSamEngine.swift

  • Tap once → loop runs hands-free. Designed for AirPods + locked phone on the nightstand.
  • AVAudioSession configured for .playAndRecord / .voiceChat so it ducks music and routes through AirPods.
  • SFSpeechRecognizer with on-device opt-in when supported. End-of-turn detected on 1.4s of silence after the most recent partial transcript update.
  • AVSpeechSynthesizer for Sam's voice, slightly slowed (0.95×) and pitched up subtly so it doesn't read as robotic.
  • Auto-continues — after Sam finishes speaking, the engine flips back to listening without user input. Turn count tracked so the responder can shape replies differently in the opening vs. mid-conversation.

Swap point for Sunflower's existing Sam pipeline: SamResponder.swift has a RemoteSamResponder stub. Drop your endpoint URL + bearer token there and the rest of the engine works unchanged. Production replies use the same prompt + the same physician-reviewed eval suite you already run; nothing about the voice surface forces a model change.

Live Activity — SunflowerWidget.swift

  • Lock Screen: 🌻 + "47 days sober" + "→ Two months in 13"
  • Dynamic Island compact: 🌻 + day count
  • Dynamic Island expanded: day count, current milestone target, and days remaining
  • Stale-date 24h (the day count is the only thing that needs to advance, and it only advances at midnight)

Widget — 6 families

systemSmall / systemMedium / systemLarge (Home Screen), plus accessoryRectangular, accessoryCircular, accessoryInline (Lock Screen + StandBy). Refresh policy is pinned to local midnight so the day count flips on time without burning timeline budget.

Grounding view — GroundingView.swift

The lower-friction sibling to Whisper to Sam. 60-second 5-4-3-2-1 walkthrough, no audio, single-thumb. Logs an UrgeEvent on completion so the home screen can show "your urges pass in 9 minutes (median)" — the data point that fights the "but this one feels different" cognitive distortion.


How this hooks into Sunflower's existing stack

  1. SobrietyStore — replace the startDate seed with a fetch from your user-state endpoint. Keep the App-Group write-through so the widget + Live Activity stay accurate.
  2. SamResponder — swap LocalSamResponder for RemoteSamResponder pointed at your Sam endpoint. The endpoint contract in the stub is POST { user_text, turn_index, surface: "voice" } → { reply } — adapt as needed.
  3. Activity<SobrietyAttributes> updates — when the user hits a new milestone, push an update to the Live Activity to flip the next-milestone label. Helper not included; happy to add as a follow-up.
  4. Anthropic Claude usageSamResponder is model-agnostic on purpose. Same Claude prompt, same evals.

Required Xcode capabilities

  • App Groupsgroup.sunflowersober.companion (change to your team's group) on both targets
  • Background Modes → Audio (for the Whisper-to-Sam loop with phone locked)
  • Info.plist keys (already set in project.yml):
    • NSMicrophoneUsageDescription
    • NSSpeechRecognitionUsageDescription
    • NSSupportsLiveActivities

Generate the .xcodeproj with xcodegen generate. Built clean on Xcode 26.4.1 / iOS 17 deployment target.


What I'd measure before claiming retention impact

A few cohort comparisons I'd want before this gets put behind a flag for everyone:

  • D7 / D30 retention for users who add the widget vs. matched users who don't. Streak-app conventional wisdom is that widget installers retain ~2x.
  • Sam conversation count per user per week — does voice mode cannibalize text, or add net new sessions? Hypothesis: net new — voice happens in moments text doesn't (driving, walking, in bed with lights off).
  • Urge-event log rate — does the grounding flow get used? Time-to-pass median per intensity bucket is the data point that fights the cognitive distortion behind a relapse.
  • Conversion to paid — voice mode is a natural premium gate (compute + Claude tokens cost more per minute than chat). Hypothesis: voice mode lifts trial→paid conversion by 30%+. Worth A/B-testing as Premium-only after a free first session.

What I deliberately did not build

  • Apple Watch app — would extend the Live Activity to the wrist (WKInterfaceController + Workout-style heart-rate context). Smaller scope than it sounds; happy to add.
  • Haptic-only urge response — Apple Watch crown haptic taps matching breath cadence, for moments when the user can't make audio.
  • Voice-cloned Sam — pick from a small set of voices (parent, sponsor, "future you"). I run a voice-cloning service (OpenVoice v2) that drops in here; intentionally left out so this PR is reviewable as plain SwiftUI.

Built by Sushanth Tiruvaipati. Reach me at t.sushanth@gmail.com.

About

iOS prototype for Sunflower (YC F25): hands-free Whisper to Sam voice mode, Lock Screen Live Activity, Home Screen widget, 60-second grounding flow.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages