# Pulse Health — iOS App ## Overview iOS-приложение для управления жизнью: привычки, задачи, финансы, накопления, здоровье. Интегрируется с Apple Watch через HealthKit и серверным API. - **Bundle ID:** com.daniil.pulsehealth - **Platform:** iOS 17+, SwiftUI, Swift 5.9 - **Build:** XcodeGen (project.yml → .xcodeproj) - **Team ID:** V9AG8JTFLC - **Язык интерфейса:** Русский ## Architecture ### Project Structure ``` PulseHealth/ ├── App.swift # Entry point, AuthManager, BGTask registration ├── Models/ │ ├── AuthModels.swift # Login/Register/Refresh requests & responses │ ├── UserModels.swift # UserProfile, UpdateProfileRequest │ ├── HealthModels.swift # Readiness, Latest health, Heatmap, SleepSegment │ ├── TaskModels.swift # PulseTask, CreateTask, UpdateTask (priorities 1-3) │ ├── HabitModels.swift # Habit, HabitLog, HabitFreeze, HabitStats, HabitFrequency enum │ ├── FinanceModels.swift # Transaction, Category (emoji field, not icon), Summary, Analytics │ └── SavingsModels.swift # Category, Transaction, Stats, RecurringPlan, MonthlyPaymentDetail, OverduePayment ├── Services/ │ ├── APIService.swift # Main REST client (api.digital-home.site), auto token refresh on 401 │ ├── HealthAPIService.swift # Health API (health.digital-home.site), separate JWT auth │ ├── HealthKitService.swift # HealthKit data collection & sync, sleep segments timeline │ └── NotificationService.swift # Local push notifications (morning/evening, task deadlines) ├── Views/ │ ├── DesignSystem.swift # Theme colors, GlassCard modifier, GlowIcon, GlowStatCard │ ├── MainTabView.swift # Custom tab bar with glow effects (VStack layout, not ZStack) │ ├── LoginView.swift # Auth + ForgotPassword │ ├── Dashboard/ │ │ └── DashboardView.swift # Home: progress, stats, habits, tasks, FAB │ ├── Health/ │ │ ├── HealthView.swift # Full health dashboard: readiness, metrics, HR, trends, recovery, tips │ │ ├── MetricCardView.swift # SleepCard, StepsCard, SleepPhasesCard, InsightsCard, GradientIcon │ │ ├── WeeklyChartView.swift # Line chart (not bar), animated │ │ ├── SleepDetailView.swift # Detailed sleep timeline with phases from HealthKit │ │ ├── ReadinessCardView.swift # (empty, replaced by ReadinessBanner in HealthView) │ │ └── ToastView.swift # Toast notification modifier │ ├── Tracker/ │ │ └── TrackerView.swift # Tabs: Habits, Tasks, Statistics. Separate tap zones for edit vs toggle │ ├── Tasks/ │ │ ├── TasksView.swift │ │ ├── AddTaskView.swift │ │ ├── EditTaskView.swift │ │ └── TaskRowView.swift │ ├── Habits/ │ │ ├── HabitsView.swift │ │ ├── AddHabitView.swift │ │ ├── EditHabitView.swift │ │ └── HabitRowView.swift │ ├── Finance/ │ │ ├── FinanceView.swift # Overview, Transactions, Analytics, Categories tabs │ │ └── AddTransactionView.swift │ ├── Savings/ │ │ ├── SavingsView.swift # Overview (monthly payments, overdues), Categories, Operations │ │ └── EditSavingsCategoryView.swift │ ├── Settings/ │ │ └── SettingsView.swift # Appearance, Profile, Timezone (Menu picker) │ └── Profile/ │ └── ProfileView.swift # ChangePasswordView ``` ## API Integration ### Main API: `https://api.digital-home.site` - Auth: JWT Bearer token with auto-refresh via `/auth/refresh` - AuthManager stores token + refreshToken in UserDefaults - APIService.authManager weak ref enables transparent 401 → refresh → retry **Endpoints used:** - Auth: login, register, me, refresh - Profile: GET/PUT /profile - Tasks: CRUD + complete/uncomplete, priorities 1-3 (not 4!) - Habits: CRUD + log (sends `date` not `completed_at`), freezes, stats - Habits frequency: iOS uses daily/weekly/monthly/interval internally, sends `custom` to API for monthly/interval - Finance: transactions, categories (field is `emoji` not `icon`), summary, analytics - Savings: categories, transactions (date required!), stats (includes monthly_payment_details, overdues), recurring plans ### Health API: `https://health.digital-home.site` - Separate JWT auth (hardcoded credentials in HealthAPIService) - API key for data sync: `health-cosmo-2026` - Server code: `/Users/daniilklimov/Personal/health-webhook` (Node.js + SQLite) **Data flow:** 1. Apple Watch → HealthKit on iPhone 2. App collects from HealthKit → POST /api/health?key=API_KEY 3. health-webhook stores JSON files → parses on GET requests 4. App displays from GET /api/health/latest, /readiness, /heatmap **HealthKit metrics collected:** - step_count, heart_rate, resting_heart_rate, heart_rate_variability - active_energy (kcal→kJ), blood_oxygen_saturation, walking_running_distance - respiratory_rate, sleep_analysis (with phase breakdown: deep/rem/core/awake + timestamps) **Sleep format for webhook:** ```json {"totalSleep": 7.5, "deep": 1.2, "rem": 2.0, "core": 4.3, "awake": 0.5, "inBedStart": "...", "sleepEnd": "...", "source": "Apple Watch"} ``` **API response field mapping (CodingKeys):** - `spo2` → `bloodOxygen` (BloodOxygenData) - `respiratoryRate` → as-is - `distance` → as-is - `activeEnergy` → as-is ## Design System - **Background:** `#06060f` (deep dark) - **Accent:** `#0D9488` (teal) - **Glass cards:** `.glassCard()` modifier — ultraThinMaterial + gradient fill + gradient stroke - **Glow icons:** `GlowIcon` — circle with blur glow behind - **Tab bar:** Custom VStack-based (not standard TabView), each tab has its own glow color - **All sheets:** `.presentationDetents([.large])`, background `Color(hex: "06060f")` - **Color pickers:** LazyVGrid 5 columns (not HStack — overflow on small screens) - **App icon:** Glassmorphism style, Assets.xcassets/AppIcon.appiconset ## Security - **Keychain** — все токены (auth, refresh, health JWT, API key) хранятся в iOS Keychain через `KeychainService.swift`, не в UserDefaults - **Health credentials** — email/password для health API хранятся в Keychain, устанавливаются один раз при первом запуске - **API key** — передаётся в `X-API-Key` header, не в URL query parameter - **No force unwraps** — URL создаются через guard/optional binding - **HealthKitService** — помечен `@MainActor` для thread-safe @Published - **Privacy policy** — ссылки в Settings (pulse.digital-home.site/privacy, /terms) ## Key Design Decisions & Gotchas - **Buttons in ScrollView/List MUST have `.buttonStyle(.plain)`** — otherwise taps get swallowed - **Tracker rows:** Separate tap zones — `.onTapGesture` on text area for edit, `Button` with `.buttonStyle(.plain)` for checkbox - **`try?` is avoided in save functions** — errors are shown in UI via `@State errorMessage` - **Tab bar uses VStack, not ZStack** — prevents content overlap - **onChange uses iOS 17+ syntax:** `{ }` not `{ _ in }` - **XcodeGen:** All capabilities must be in project.yml, manual Xcode changes get reset ## Background Sync - BGProcessingTask: `com.daniil.pulsehealth.healthsync` - Scheduled every ~30 minutes - Collects HealthKit data and POSTs to health-webhook - Registered in App.init(), scheduled in .onAppear ## External Services & Paths - **Pulse API source:** `/Users/daniilklimov/digital-home/pulse-api` (Go) - **Pulse Web source:** `/Users/daniilklimov/digital-home/pulse-web` (React) - **Health webhook:** `/Users/daniilklimov/Personal/health-webhook` (Node.js) - **Infrastructure docs:** `~/Obsidian/daniil/Инфраструктура` ## Known Issues / TODO - Finance tab is owner-only (user.id === 1) in web, no such restriction in iOS - Savings members endpoints (multi-user) not implemented in iOS - Auth: password change works via direct URLRequest, not through APIService - Health readiness `activity.value` from API shows different step count than latest (different time periods)