# Reflect A private, on-device iOS mood tracker that turns a five-second daily check-in into durable self-knowledge. Users rate their day on a 5-point scale, and the app turns that signal into calendars, trends, Apple-Intelligence-powered insights, guided CBT reflections, watch complications, widgets, Live Activities, and shareable summaries. > **Platform:** iOS 16+ · iPadOS · watchOS · macOS (Catalyst) > **Language:** Swift / SwiftUI > **Data:** SwiftData + CloudKit (private database) > **AI:** Apple Foundation Models (on-device, iOS 26+) > **Monetization:** 30-day free trial → monthly / yearly subscription (StoreKit 2) --- ## Table of Contents - [Elevator pitch](#elevator-pitch) - [Feature map](#feature-map) - [Core features in depth](#core-features-in-depth) - [Daily mood logging](#1-daily-mood-logging) - [Day / Month / Year views](#2-day--month--year-views) - [Insights (AI)](#3-insights-ai) - [Weekly Digest (AI)](#4-weekly-digest-ai) - [Auto-tagging (AI)](#5-auto-tagging-ai) - [AI Reports + PDF export](#6-ai-reports--pdf-export) - [Guided Reflection (CBT / ACT / BA)](#7-guided-reflection--deep-dive) - [Widgets](#8-widgets) - [Live Activities](#9-live-activities) - [Watch app + complications](#10-watch-app--complications) - [Control Center, Siri, App Intents, Shortcuts](#11-control-center-siri-app-intents-shortcuts) - [HealthKit State of Mind](#12-healthkit-state-of-mind) - [WeatherKit correlation](#13-weatherkit-correlation) - [Photos on entries](#14-photos-on-entries) - [Face ID / Touch ID lock](#15-face-id--touch-id-lock) - [Sharing templates](#16-sharing-templates) - [Customization](#17-customization) - [Notifications + personality packs](#18-notifications--personality-packs) - [Export / Import](#19-export--import) - [Subscriptions](#20-subscriptions) - [Guided Reflection — deep dive](#guided-reflection--deep-dive) - [Architecture](#architecture) - [Data model](#data-model) - [Directory map](#directory-map) - [Build, run, test](#build-run-test) - [Localization](#localization) - [Privacy & security](#privacy--security) - [Configuration reference](#configuration-reference) --- ## Elevator pitch Most mood trackers collect data. **Reflect converts it into insight.** The five-second daily tap is only the entry point — behind it sits a full behavioral-health stack: - **On-device Apple Intelligence** (Foundation Models) writes your weekly digest, surfaces patterns, and runs a cognitive-behavioral-therapy-aligned reflection flow. Nothing leaves the phone. - **CloudKit sync** keeps iPhone and Apple Watch in step without an account. - **HealthKit State of Mind** writes your rating back into Apple Health and reads activity / sleep / HRV so the app can correlate mood with the rest of your life. - **A CBT-aligned Guided Reflection** detects cognitive distortions in your automatic thoughts and rewrites the follow-up question to match the specific distortion — the actual mechanism of change in evidence-based CBT. - **Four surfaces to log from**: app, home/lock-screen widget, Control Center widget, Apple Watch complication. One tap, anywhere. The thesis: a mood tracker is only useful if the patterns it surfaces can drive behavior change. Reflect is designed from the top down around the CBT/ACT evidence base, not screen time. --- ## Feature map | Area | What it is | Why it sells | Source | |---|---|---|---| | 5-point mood log | Horrible → Great daily rating | The lowest-friction signal that still captures variance | [`Shared/Models/Mood.swift`](Shared/Models/Mood.swift) | | Day view | Chronological list with notes, photos, weather | Diary + heatmap in one scroll | [`Shared/Views/DayView/`](Shared/Views/DayView) | | Month view | Calendar heatmap | "What kind of month was this?" in one glance | [`Shared/Views/MonthView/`](Shared/Views/MonthView) | | Year view | 12-month heatmap + filters | See multi-month seasonality | [`Shared/Views/YearView/`](Shared/Views/YearView) | | AI Insights | Patterns, predictions, advice | Turns data into behavior change | [`Shared/Views/InsightsView/`](Shared/Views/InsightsView) · [`FoundationModelsInsightService.swift`](Shared/Services/FoundationModelsInsightService.swift) | | Weekly Digest | AI-written summary of the last 7 days | Sunday-night ritual, builds habit | [`FoundationModelsDigestService.swift`](Shared/Services/FoundationModelsDigestService.swift) | | Auto-tagging | AI extracts themes (work, sleep, family…) | Enables mood↔theme correlation | [`FoundationModelsTagService.swift`](Shared/Services/FoundationModelsTagService.swift) | | AI Reports + PDF | Clinical multi-week reports | Shareable with therapist | [`ReportPDFGenerator.swift`](Shared/Services/ReportPDFGenerator.swift) | | Guided Reflection | CBT Thought Record / ACT Defusion / Behavioral Activation | Evidence-based, not a mood journal | [`GuidedReflectionView.swift`](Shared/Views/GuidedReflectionView.swift) · [`GuidedReflection.swift`](Shared/Models/GuidedReflection.swift) | | Cognitive distortion detector | Routes reframes to specific distortion | The mechanism of change in CBT | [`CognitiveDistortionDetector.swift`](Shared/Services/CognitiveDistortionDetector.swift) | | Widgets | 6 widget families | Logging from the home screen | [`ReflectWidget/`](ReflectWidget) | | Live Activities | Lock Screen + Dynamic Island streak | Keeps the habit present | [`ReflectLiveActivity.swift`](ReflectWidget/ReflectLiveActivity.swift) · [`MoodStreakActivity.swift`](Shared/MoodStreakActivity.swift) | | Watch app | Log and view on wrist | Frictionless logging | [`Reflect Watch App/`](Reflect%20Watch%20App) | | Complications | Streak / last mood on watch face | Ambient nudge to log | [`ReflectComplication.swift`](Reflect%20Watch%20App/ReflectComplication.swift) | | Control Center | iOS 18 Control Center widget | One-tap log from anywhere | [`ReflectMoodControlWidget.swift`](ReflectWidget/ReflectMoodControlWidget.swift) | | Siri / Shortcuts | App Intents for "Log mood great" | Hands-free logging | [`AppShortcuts.swift`](Shared/AppShortcuts.swift) · [`SharedMoodIntent.swift`](Shared/SharedMoodIntent.swift) | | HealthKit | State of Mind write + activity read | Deep Apple Health integration | [`HealthKitManager.swift`](Shared/HealthKitManager.swift) · [`HealthService.swift`](Shared/Services/HealthService.swift) | | WeatherKit | Attach weather to each entry | Mood↔weather correlation in Insights | [`WeatherManager.swift`](Shared/Services/WeatherManager.swift) | | Photos | Attach a photo per day | Visual journal | [`PhotoManager.swift`](Shared/Services/PhotoManager.swift) | | Biometric lock | Face ID / Touch ID / Optic ID | Private data stays private | [`BiometricAuthManager.swift`](Shared/Services/BiometricAuthManager.swift) | | Sharing templates | Mood-style social cards | Organic growth channel | [`Shared/Views/Sharing/`](Shared/Views/Sharing) · [`Shared/Views/SharingTemplates/`](Shared/Views/SharingTemplates) | | Custom icon widget | User-designed widget layout | Personalization as retention | [`Shared/Views/CustomWidget/`](Shared/Views/CustomWidget) | | Theming | Color sets, icon packs, shapes, personality | Make it feel like yours | [`Shared/Views/CustomizeView/`](Shared/Views/CustomizeView) | | Export | CSV / JSON / PDF | No lock-in — builds trust | [`ExportService.swift`](Shared/Services/ExportService.swift) | | Subscription | Monthly / yearly + 30-day trial | StoreKit 2, offer codes | [`IAPManager.swift`](Shared/IAPManager.swift) | --- ## Core features in depth ### 1. Daily mood logging - **What:** A 5-point ordinal scale — Horrible, Bad, Average, Good, Great — defined in [`Shared/Models/Mood.swift`](Shared/Models/Mood.swift). - **How:** Every mood entry — from the app, a widget, the watch, Siri, the lock-screen Live Activity, a push-notification action, or an imported CSV row — funnels through a single choke point: **[`MoodLogger.shared.logMood(...)`](Shared/MoodLogger.swift)**. That one method runs the write to SwiftData, the HealthKit sync, streak recomputation, widget-timeline reload, Watch Connectivity nudge, TipKit parameter update, Live Activity refresh, and analytics event. - **Why 5 points:** Enough to capture variance, few enough to fit on a widget. The ordinal scale is what lets us run [`MoodMetrics.swift`](Shared/Models/MoodMetrics.swift) over it without arguing about arithmetic on emoji. - **Source-of-truth rule:** never insert into `modelContext` directly — always go through `MoodLogger`, or side effects silently break. This invariant is enforced in [`CLAUDE.md`](CLAUDE.md#mutation--write-patterns). ### 2. Day / Month / Year views Three zoom levels over the same data, selected via the tab bar in [`MainTabView.swift`](Shared/Views/MainTabView.swift). - **[Day](Shared/Views/DayView)** — Chronological list of entries with notes, photo, weather card, and the "open reflection" CTA. Backed by [`DayViewViewModel`](Shared/Views/DayView). - **[Month](Shared/Views/MonthView)** — Classic calendar grid, each cell tinted by its mood. Tap to drill into that date. Shape is user-configurable (circle / square / rounded / diamond). - **[Year](Shared/Views/YearView)** — 12 mini calendars stacked for seasonality. Users can filter by weekday (e.g. "only show Mondays") to isolate work-week patterns. ### 3. Insights (AI) - **What:** An on-device AI pass over your mood history that returns a short, human-readable list of patterns, predictions, advice, and achievements, tinted to the dominant mood. - **How:** [`FoundationModelsInsightService.swift`](Shared/Services/FoundationModelsInsightService.swift) uses Apple Foundation Models (`LanguageModelSession` with a `@Generable` schema defined in [`AIInsight.swift`](Shared/Models/AIInsight.swift)). The prompt is built by [`MoodDataSummarizer.swift`](Shared/Services/MoodDataSummarizer.swift), which precomputes mood distribution, day-of-week patterns, streaks, weather correlations, and HealthKit correlations so the model only has to narrate facts we already have. - **Caching:** 1-hour result cache — regenerating is one tap. - **Selling point:** Zero network, zero account, zero data leaves the device. ### 4. Weekly Digest (AI) - **What:** Every week the app writes you a one-paragraph summary with a headline, a highlight ("your best day was Thursday — walk after work"), and an intention for next week. - **How:** [`FoundationModelsDigestService.swift`](Shared/Services/FoundationModelsDigestService.swift) generates against the [`AIWeeklyDigest`](Shared/Models/AIWeeklyDigest.swift) schema. Scheduled by [`BGTask.runWeeklyDigestTask`](Shared/BGTask.swift) so it's ready on Sunday night without the user opening the app. Delivered via a local notification; rendered in [`WeeklyDigestCardView.swift`](Shared/Views/InsightsView). - **Why it sells:** A weekly "here's what happened" ritual is how habits become durable. ### 5. Auto-tagging (AI) - **What:** Themes like *work / sleep / family / exercise / stress* are extracted automatically from each entry's notes and reflection. - **How:** [`FoundationModelsTagService.swift`](Shared/Services/FoundationModelsTagService.swift) returns an [`AIEntryTags`](Shared/Models/AIEntryTags.swift). Insights cross-reference the tag with the mood to answer questions like "what happens to your mood on days you write about sleep?". - **Why it sells:** Users don't have to tag manually — the signal is free. ### 6. AI Reports + PDF export - **What:** A multi-week clinical-style report with overview stats, week-by-week breakdowns, and visualizations, exportable as a PDF to share with a therapist or doctor. - **How:** [`ReportPDFGenerator.swift`](Shared/Services/ReportPDFGenerator.swift) renders an HTML template through WKWebView and rasterizes to PDF. Data shape is [`AIReport`](Shared/Models/AIReport.swift); UI lives in [`Shared/Views/InsightsView/ReportsView.swift`](Shared/Views/InsightsView). - **Why it sells:** Unlocks clinical use cases — a real moat vs. tap-to-log competitors. ### 7. Guided Reflection — deep dive See the dedicated [Guided Reflection section below](#guided-reflection--deep-dive). ### 8. Widgets All six live in [`ReflectWidget/`](ReflectWidget) and are bundled by [`WidgetBundle.swift`](ReflectWidget/WidgetBundle.swift). | Widget | File | Purpose | |---|---|---| | Vote | [`ReflectVoteWidget.swift`](ReflectWidget/ReflectVoteWidget.swift) | Interactive 5-button logger — small/medium, no app launch | | Timeline | [`ReflectTimelineWidget.swift`](ReflectWidget/ReflectTimelineWidget.swift) | Recent-days strip; pre-vote shows buttons, post-vote shows stats | | Graphic | [`ReflectGraphicWidget.swift`](ReflectWidget/ReflectGraphicWidget.swift) | Large mood art widget | | Icon | [`ReflectIconWidget.swift`](ReflectWidget/ReflectIconWidget.swift) | User-customized widget (see [CustomWidget](Shared/Views/CustomWidget)) | | Mood Control | [`ReflectMoodControlWidget.swift`](ReflectWidget/ReflectMoodControlWidget.swift) | **Control Center** widget — iOS 18 | | Live Activity | [`ReflectLiveActivity.swift`](ReflectWidget/ReflectLiveActivity.swift) | Lock Screen + Dynamic Island | Widgets can't reach CloudKit or HealthKit from their process, so they read through [`ExtensionDataProvider.swift`](Shared/Persisence/ExtensionDataProvider.swift), which talks to the App Group container directly. When the widget writes a vote via an `AppIntent`, the main app picks it up and runs the deferred `MoodLogger` side effects next launch. ### 9. Live Activities - **What:** A streak card on the Lock Screen and in the Dynamic Island, updating in real time when you log or when the voting window opens. - **How:** [`MoodStreakActivity.swift`](Shared/MoodStreakActivity.swift) starts the `ActivityKit` activity; [`ReflectLiveActivity.swift`](ReflectWidget/ReflectLiveActivity.swift) is the view. [`LiveActivityScheduler`](Shared/MoodStreakActivity.swift) decides when the activity should be on-screen (typically after the daily reminder fires until the day is logged). - **Why it sells:** Continuous visible nudge without being a notification — drives logging consistency. ### 10. Watch app + complications - **[`Reflect Watch App/ContentView.swift`](Reflect%20Watch%20App/ContentView.swift)** — Full voting UI on the wrist. Writes go through the shared CloudKit database; UI refresh for the phone happens over [`WatchConnectivityManager`](Shared/Services/WatchConnectivityManager.swift). - **[`ReflectComplication.swift`](Reflect%20Watch%20App/ReflectComplication.swift)** — Circular / rectangular / corner complications showing streak, last mood, or graphic. - **Selling point:** the watch is the lowest-friction surface — users raise their wrist, tap, done. ### 11. Control Center, Siri, App Intents, Shortcuts - **[`AppShortcuts.swift`](Shared/AppShortcuts.swift)** — Exposes "Log a mood" and "Open Reflect" as App Intents so Siri and Shortcuts can call them. - **[`SharedMoodIntent.swift`](Shared/SharedMoodIntent.swift)** — The intent shared across widget buttons and voice actions. - **[`ReflectMoodControlWidget.swift`](ReflectWidget/ReflectMoodControlWidget.swift)** — iOS 18 Control Center widget that opens the app pre-scrolled to the log screen. ### 12. HealthKit State of Mind - **[`HealthKitManager.swift`](Shared/HealthKitManager.swift)** — Writes each mood into Apple Health's State of Mind store (valence mapped from the 5-point scale). - **[`HealthService.swift`](Shared/Services/HealthService.swift)** — Reads activity, exercise time, heart rate, HRV, and sleep and folds them into the Insights prompt for mood↔body correlation. - **Why it sells:** Reflect becomes the primary State-of-Mind logger for users already bought into Apple Health. ### 13. WeatherKit correlation - **[`WeatherManager.swift`](Shared/Services/WeatherManager.swift)** — Pulls current conditions from WeatherKit using a one-shot location from [`LocationManager.swift`](Shared/Services/LocationManager.swift) (10-min cache, 15 s timeout) and attaches a [`WeatherData`](Shared/Models/WeatherData.swift) snapshot to the entry. - **Retry:** failures queue for [`BGTask.runWeatherRetryTask`](Shared/BGTask.swift) so weather fills in overnight. ### 14. Photos on entries - **[`PhotoManager.swift`](Shared/Services/PhotoManager.swift)** — JPEG at 0.8, stored in the App Group container, 200×200 thumbnail cached for list rendering. - **Picker:** [`PhotoPickerView.swift`](Shared/Views/PhotoPickerView.swift) / [`ImagePickerGridView.swift`](Shared/Views/ImagePickerGridView.swift). ### 15. Face ID / Touch ID lock - **[`BiometricAuthManager.swift`](Shared/Services/BiometricAuthManager.swift)** — Detects Face ID / Touch ID / Optic ID, gracefully falls back to the device passcode, and gates the whole app behind a biometric unlock if the user opts in. ### 16. Sharing templates - **[`Shared/Views/Sharing/`](Shared/Views/Sharing)** — The sharing flow. - **[`Shared/Views/SharingTemplates/`](Shared/Views/SharingTemplates)** — Visual templates (monthly recap, streak brag, mood pie). - **[`SharingScreenshotExporter.swift`](Shared/Services/SharingScreenshotExporter.swift)** — Debug tool that renders every template for marketing. - **Why it sells:** Shareable cards are a zero-cost organic-growth channel — each user who shares is a free ad. ### 17. Customization [`Shared/Views/CustomizeView/`](Shared/Views/CustomizeView) lets users pick: | Axis | Protocol | Options | |---|---|---| | Color set | [`MoodTintable`](Shared/Models/MoodTintable.swift) | Default, Pastel, Neon, Ocean, Forest, Sunset | | Icon / emoji | [`MoodImagable`](Shared/Models/MoodImagable.swift) | FontAwesome, Emoji, Hand Emoji | | Shape | [`Shapes.swift`](Shared/Models/Shapes.swift) | Circle, Square, Diamond, Rounded | | Theme | [`Theme.swift`](Shared/Models/Theme.swift) | System / Light / Dark / iFeel gradient | | Personality | [`PersonalityPackable`](Shared/Models/PersonalityPackable.swift) | Default, Coach, Zen, Best Friend, Data Analyst | | App icon | [`Shared/Views/CustomIcon/`](Shared/Views/CustomIcon) | Multiple home-screen icons | ### 18. Notifications + personality packs - **[`LocalNotification.swift`](Shared/LocalNotification.swift)** — Daily reminder with inline mood buttons; notification copy is written by the selected [`PersonalityPackable`](Shared/Models/PersonalityPackable.swift) so the "Nice" pack asks gently and the "Rude" pack heckles you. - **[`AppDelegate.swift`](Shared/AppDelegate.swift)** — Handles the action buttons on the notification to route the vote through `MoodLogger`. ### 19. Export / Import - **[`ExportService.swift`](Shared/Services/ExportService.swift)** — CSV, JSON, and PDF export of the full entry history with notes, weather, reflections, photos, and entry source preserved. - **Why it sells:** No lock-in. Users know their data can leave, which makes them trust it going in. ### 20. Subscriptions - **[`IAPManager.swift`](Shared/IAPManager.swift)** — StoreKit 2, monthly (`com.88oakapps.reflect.IAP.subscriptions.monthly`) and yearly, 30-day free trial counted from `firstLaunchDate`, paywall UI in [`ReflectSubscriptionStoreView.swift`](Shared/Views/ReflectSubscriptionStoreView.swift). - **Gated behind paywall:** AI Insights, Weekly Digest, AI Reports, Guided Reflection AI feedback, HealthKit correlation insights, Apple Watch premium complications. Core logging + history are free forever. --- ## Guided Reflection — deep dive > **Source files:** > — Model: [`Shared/Models/GuidedReflection.swift`](Shared/Models/GuidedReflection.swift) > — Distortion detector: [`Shared/Services/CognitiveDistortionDetector.swift`](Shared/Services/CognitiveDistortionDetector.swift) > — AI feedback: [`Shared/Services/FoundationModelsReflectionService.swift`](Shared/Services/FoundationModelsReflectionService.swift) > — Feedback model: [`Shared/Models/AIReflectionFeedback.swift`](Shared/Models/AIReflectionFeedback.swift) > — UI: [`Shared/Views/GuidedReflectionView.swift`](Shared/Views/GuidedReflectionView.swift) > — Feedback UI: [`Shared/Views/ReflectionFeedbackView.swift`](Shared/Views/ReflectionFeedbackView.swift) > — Info sheet: [`Shared/Views/GuidedReflectionInfoView.swift`](Shared/Views/GuidedReflectionInfoView.swift) > — Plan doc: [`docs/guided-reflection-cbt-plan.md`](docs/guided-reflection-cbt-plan.md) > — Flowchart: [`docs/guided-reflection-flowchart.html`](docs/guided-reflection-flowchart.html) ### Why this exists Most mood trackers stop at logging. Reflect's reflection flow is a short, mood-adaptive, **CBT-aligned** guided exercise. The goal is not to make the user journal more — it's to run a known therapeutic mechanism that has evidence for changing the relationship with a thought. Three mood tiers → three different evidence-based frameworks: | Mood | Framework | Mechanism | Question count | |---|---|---|---| | **Great / Good** | Behavioral Activation (BA) | Savor + plan to repeat | 3 | | **Average** | ACT Cognitive Defusion | Notice thought, loosen its grip, re-orient to values | 4 | | **Bad / Horrible** | CBT Thought Record | Identify situation, surface automatic thought, check for distortion, examine evidence, generate a balanced reframe | 5 | These are routed from the mood value via [`MoodCategory(from:)`](Shared/Models/GuidedReflection.swift) and rendered as a stepped sheet from [`GuidedReflectionView.swift`](Shared/Views/GuidedReflectionView.swift). ### The Socratic template system The defining feature of Socratic questioning in CBT is that **each question builds on the user's previous answer**. Static question lists are not Socratic. We implemented this with [`QuestionTemplate`](Shared/Models/GuidedReflection.swift) — each question has a `text` with an optional `%@` placeholder and a `placeholderRef: Int?` pointing at which earlier question's answer to inject. Example (negative path): ``` Q1: "What happened today that affected your mood?" → user: "My boss criticized my presentation in front of the team" Q2: "What thought kept coming back about 'My boss criticized my presentation…'?" → user: "I'm not cut out for this job" Q3 (distortion-specific, see below): "Is 'I'm not cut out for this job' something you are, or something you did?" Q4 (evidence): "What evidence supports 'I'm not cut out for this job', and what challenges it?" Q5 (reframe): "Looking at 'I'm not cut out for this job' again — what's a more balanced way to see it?" ``` Injections are truncated to a sentence boundary or 60 characters by [`GuidedReflection.truncatedForInjection`](Shared/Models/GuidedReflection.swift) so long answers never break grammar. Templates are fully localized — each language controls its own `%@` position so grammar stays natural. ### Cognitive distortion detection [`CognitiveDistortionDetector.swift`](Shared/Services/CognitiveDistortionDetector.swift) classifies the Q2 answer into one of seven types using localized keyword lists (deterministic, offline, privacy-preserving — **not ML**). This was a deliberate product choice: rule-based is inspectable, predictable, and works with zero latency. | Distortion | Example phrasing | Q3 reframe prompt | |---|---|---| | `overgeneralization` | "always", "never", "everyone" | "Can you think of one counter-example to '%@'?" | | `shouldStatement` | "should", "must", "have to" | "Where did the rule 'I should …' come from? Is it still serving you?" | | `labeling` | "I am [trait]" | "Is '%@' something you *are*, or something you *did*?" | | `personalization` | "my fault", "because of me" | "What other factors, besides you, contributed to this?" | | `catastrophizing` | "ruined", "can't recover" | "What's the worst case? What's the most likely case?" | | `mindReading` | "thinks I'm", "hates me" | "What evidence do you have for that interpretation?" | | `unknown` | (no match) | Falls back to the generic perspective check | Keywords live in [`Localizable.xcstrings`](Reflect/Localizable.xcstrings) under `distortion_*_keywords` keys so each locale tunes its own detection rules. Priority order is specific → general so that "I always ruin everything" classifies as catastrophizing first, overgeneralization second. ### Evidence examination The negative path explicitly inserts a dedicated evidence step (Q4) — supporting and contradicting evidence both. This is the single most load-bearing step in a real CBT Thought Record and was previously missing. ### Intensity tracking [`GuidedReflection`](Shared/Models/GuidedReflection.swift) carries optional `preIntensity` and `postIntensity` (0–10). CBT literature emphasizes measuring the emotion's intensity before and after the thought work — the delta is the efficacy signal. The AI feedback stage references the shift ("you moved from an 8 to a 5") when it narrates the reflection. ### AI feedback stage On completion [`FoundationModelsReflectionService`](Shared/Services/FoundationModelsReflectionService.swift) generates an [`AIReflectionFeedback`](Shared/Models/AIReflectionFeedback.swift) with three slots — **affirmation**, **observation**, **takeaway** — rendered by [`ReflectionFeedbackView.swift`](Shared/Views/ReflectionFeedbackView.swift). Tone is driven by the user's selected [`PersonalityPackable`](Shared/Models/PersonalityPackable.swift) (Coach / Zen / Best Friend / Data Analyst / Default). This is gated behind the subscription + iOS 26 Apple Intelligence — when unavailable, the reflection still saves normally and the feedback view degrades gracefully. ### Back-compat Old reflections saved with 4 negative-path responses (pre-evidence step) still decode and still count as complete. [`GuidedReflection.isComplete`](Shared/Models/GuidedReflection.swift) detects the legacy shape so users never lose completed work across updates. ### Full flow diagram Open [`docs/guided-reflection-flowchart.html`](docs/guided-reflection-flowchart.html) in a browser for the visual flow. The plan doc in [`docs/guided-reflection-cbt-plan.md`](docs/guided-reflection-cbt-plan.md) walks through the phased rationale — phase 1 (templates + intensity), phase 2 (distortion detection + evidence), phase 3 (AI-generated final question). --- ## Architecture **Pattern:** MVVM + SwiftUI, singletons for cross-cutting concerns. **Entry point:** [`Shared/ReflectApp.swift`](Shared/ReflectApp.swift) ### Layers ``` ┌─────────────────────────────────────────────────────────────────┐ │ Views (SwiftUI) Shared/Views/ │ │ ViewModels (@MainActor) colocated with views │ ├─────────────────────────────────────────────────────────────────┤ │ Services (singletons) Shared/Services/ │ │ MoodLogger AnalyticsManager IAPManager │ │ HealthKitManager WeatherManager LocationManager │ │ FoundationModels{Insight,Digest,Reflection,Tag}Service │ │ CognitiveDistortionDetector │ ├─────────────────────────────────────────────────────────────────┤ │ Persistence Shared/Persisence/ │ │ DataController (GET/ADD/UPDATE/DELETE) │ │ SharedModelContainer — SwiftData + CloudKit + App Group │ │ ExtensionDataProvider — widget/watch read path │ ├─────────────────────────────────────────────────────────────────┤ │ Models Shared/Models/ │ │ MoodEntryModel (@Model) Mood GuidedReflection │ │ AIInsight AIWeeklyDigest AIReflectionFeedback AIReport │ └─────────────────────────────────────────────────────────────────┘ ``` ### Data flow for a single mood log ``` tap → DayViewViewModel.add() └─► MoodLogger.shared.logMood() ← ALL entry points converge here ├─► DataController.shared.add() — SwiftData insert + save ├─► HealthKitManager.write() — State of Mind ├─► streak recompute ├─► WidgetCenter.reloadAllTimelines() ├─► WatchConnectivityManager.nudge() ├─► LiveActivityScheduler.update() ├─► TipKit parameter update └─► AnalyticsManager.shared.track() ``` Widgets and the watch bypass `MoodLogger` when the main app isn't running and write through `ExtensionDataProvider`; `MoodLogger.applySideEffects()` catches up on next launch. Detailed rules: [`CLAUDE.md`](CLAUDE.md). --- ## Data model Primary entity: [`MoodEntryModel`](Shared/Models/MoodEntryModel.swift) — a SwiftData `@Model` with: | Field | Type | Notes | |---|---|---| | `moodValue` | Int | 0–4 (Horrible → Great), 5 = missing, 6 = placeholder | | `forDate` | Date | The *logical* day being rated | | `timestamp` | Date | When the entry was written | | `weekDay` | Int | 1–7, denormalized for fast filtering | | `entryType` | Int | listView / widget / watch / shortcut / filledInMissing / notification / header / siri / controlCenter / liveActivity | | `notes` | String? | Free text | | `photoID` | String? | Path into App Group container | | `weatherJSON` | String? | [`WeatherData`](Shared/Models/WeatherData.swift) | | `reflectionJSON` | String? | [`GuidedReflection`](Shared/Models/GuidedReflection.swift) | | `tagsJSON` | String? | [`AIEntryTags`](Shared/Models/AIEntryTags.swift) | All fields have defaults — required by CloudKit. Sync is automatic via SwiftData's built-in CloudKit integration. --- ## Directory map | Path | Contents | |---|---| | [`Shared/`](Shared) | All cross-platform code | | [`Shared/Models/`](Shared/Models) | Domain types, SwiftData models, AI schemas | | [`Shared/Views/`](Shared/Views) | SwiftUI views, grouped by feature | | [`Shared/Services/`](Shared/Services) | Singletons for AI, HealthKit, Weather, Export, etc. | | [`Shared/Persisence/`](Shared/Persisence) | SwiftData layer *(note: directory name has a typo — intentional, for historic reasons)* | | [`Shared/Onboarding/`](Shared/Onboarding) | First-launch flow | | [`Shared/Utilities/`](Shared/Utilities) | Small helpers | | [`ReflectWidget/`](ReflectWidget) | Widget + Control Center + Live Activity extension | | [`Reflect Watch App/`](Reflect%20Watch%20App) | watchOS app + complications | | [`Reflect/`](Reflect) | iOS app target assets, Info.plist, Localizable.xcstrings | | [`Tests iOS/`](Tests%20iOS) | XCUITest suites — see [`docs/XCUITest-Authoring.md`](docs/XCUITest-Authoring.md) | | [`ReflectTests/`](ReflectTests) | XCTest unit tests | | [`Tests macOS/`](Tests%20macOS) | macOS target tests | | [`landing_page/`](landing_page) | Marketing site | | [`docs/`](docs) | Design docs, ASO, QA plan, CBT plan, competitors | | [`scripts/`](scripts) | Build/dev utilities | | [`ads/`](ads) | Marketing creative | --- ## Build, run, test ```bash # Build xcodebuild -project Reflect.xcodeproj \ -scheme "Reflect (iOS)" \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build # Run all tests xcodebuild -project Reflect.xcodeproj \ -scheme "Reflect (iOS)" \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test # Run one UI suite xcodebuild -project Reflect.xcodeproj \ -scheme "Reflect (iOS)" \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ -only-testing:"Tests iOS/Tests_iOS" test ``` UI test conventions are mandatory — see [`docs/XCUITest-Authoring.md`](docs/XCUITest-Authoring.md), [`Tests iOS/Helpers/BaseUITestCase.swift`](Tests%20iOS/Helpers/BaseUITestCase.swift), and [`Shared/AccessibilityIdentifiers.swift`](Shared/AccessibilityIdentifiers.swift). --- ## Localization Format: **String Catalog** at [`Reflect/Localizable.xcstrings`](Reflect/Localizable.xcstrings). Shipping languages: English · German · Spanish · French · Japanese · Korean · Portuguese (Brazil). All user-facing strings use `String(localized:)`. "Reflect" is a brand name — untranslated. Distortion-detection keywords are per-locale (see [Cognitive distortion detection](#cognitive-distortion-detection)). --- ## Privacy & security - **No account, no backend, no third-party server** ever sees mood data. Data lives in SwiftData, synced through the user's own private CloudKit database. - **AI is 100% on-device** — Foundation Models runs locally; nothing is sent to Anthropic, OpenAI, Apple servers, or us. - **Analytics** (PostHog via [`AnalyticsManager`](Shared/Analytics.swift)) captures UI events, never mood content, and the user can opt out in settings. - **Optional biometric lock** ([`BiometricAuthManager`](Shared/Services/BiometricAuthManager.swift)) gates the app behind Face ID / Touch ID / Optic ID. - **HealthKit** read/write is permission-gated and only triggered on explicit user opt-in. --- ## Configuration reference | Setting | Value | |---|---| | Bundle ID (iOS) | `com.88oakapps.reflect` | | App Group (prod) | `group.com.88oakapps.reflect` | | App Group (debug) | `group.com.88oakapps.reflect.debug` | | CloudKit (prod) | `iCloud.com.88oakapps.reflect` | | CloudKit (debug) | `iCloud.com.88oakapps.reflect.debug` | | Subscription group | `21951685` | | Monthly product ID | `com.88oakapps.reflect.IAP.subscriptions.monthly` | | Yearly product ID | `com.88oakapps.reflect.IAP.subscriptions.yearly` | | Free trial | 30 days from `firstLaunchDate` | | URL scheme | `reflect://` (e.g. `reflect://subscribe`) | | BGTask — missing dates | `com.88oakapps.reflect.dbUpdateMissing` | | BGTask — weather retry | `com.88oakapps.reflect.weatherRetry` | | BGTask — weekly digest | `com.88oakapps.reflect.weeklyDigest` | | Logger subsystem | `com.88oakapps.reflect` | --- *For deeper architectural rules, concurrency constraints, data-access invariants, and edge-case gotchas, see [`CLAUDE.md`](CLAUDE.md). For ASO strategy and screenshot plans see [`docs/`](docs).*