Files
Reflect/README.md
Trey T 7683717240 Add full repo README with feature docs and CBT reflection deep dive
Covers every feature with how-it-works, selling-point framing, and
clickable source links — targeted at both developers and business
partners. Includes an in-depth section on the guided reflection flow
(Socratic templating, cognitive-distortion routing, evidence step).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 19:08:12 -05:00

477 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` (010). 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 | 04 (Horrible → Great), 5 = missing, 6 = placeholder |
| `forDate` | Date | The *logical* day being rated |
| `timestamp` | Date | When the entry was written |
| `weekDay` | Int | 17, 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).*