- Add KeychainService for encrypted token storage (auth, refresh, health JWT, API key) - Remove hardcoded email/password from HealthAPIService, store in Keychain - Move all tokens from UserDefaults to Keychain - API key sent via X-API-Key header instead of URL query parameter - Replace force unwrap URL(string:)! with guard let + throws - Fix force unwrap Calendar.date() in HealthKitService - Mark HealthKitService @MainActor for thread-safe @Published - Use withTaskGroup for parallel habit log fetching in TrackerView - Check notification permission before scheduling reminders - Add input validation (title max 200 chars) - Add privacy policy and terms links in Settings - Update CLAUDE.md with security section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.4 KiB
8.4 KiB
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
datenotcompleted_at), freezes, stats - Habits frequency: iOS uses daily/weekly/monthly/interval internally, sends
customto API for monthly/interval - Finance: transactions, categories (field is
emojinoticon), 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:
- Apple Watch → HealthKit on iPhone
- App collects from HealthKit → POST /api/health?key=API_KEY
- health-webhook stores JSON files → parses on GET requests
- 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:
{"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-isdistance→ as-isactiveEnergy→ 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]), backgroundColor(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-Keyheader, не в 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 —
.onTapGestureon text area for edit,Buttonwith.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.valuefrom API shows different step count than latest (different time periods)