feat: major app overhaul — API fixes, glassmorphism UI, health dashboard, notifications

API Integration:
- Fix logHabit: send "date" instead of "completed_at"
- Fix FinanceCategory: "icon" → "emoji" to match API
- Fix task priorities: remove level 4, keep 1-3 matching API
- Fix habit frequencies: map monthly/interval → "custom" for API
- Add token refresh (401 → auto retry with new token)
- Add proper error handling (remove try? in save functions, show errors in UI)
- Add date field to savings transactions
- Add MonthlyPaymentDetail and OverduePayment models
- Fix habit completedToday: compute on client from logs (API doesn't return it)
- Filter habits by day of week on client (daily/weekly/monthly/interval)

Design System (glassmorphism):
- New DesignSystem.swift: Theme colors, GlassCard modifier, GlowIcon, GlowStatCard
- Custom tab bar with per-tab glow colors (VStack layout, not ZStack overlay)
- Deep dark background #06060f across all views
- Glass cards with gradient fill + stroke throughout app
- App icon: glassmorphism style with teal glow

Health Dashboard:
- Compact ReadinessBanner with recommendation text
- 8 metric tiles: sleep, HR, HRV, steps, SpO2, respiratory rate, energy, distance
- Each tile with status indicator (good/ok/bad) and hint text
- Heart rate card (min/avg/max)
- Weekly trends card (averages)
- Recovery score (weighted: 40% sleep, 35% HRV, 25% RHR)
- Tips card with actionable recommendations
- Sleep detail view with hypnogram (step chart of phases)
- Sleep segments timeline from HealthKit (deep/rem/core/awake with exact times)
- Line chart replacing bar chart for weekly data
- Collect respiratory_rate and sleep phases with timestamps from HealthKit
- Background sync every ~30min via BGProcessingTask

Notifications:
- NotificationService for local push notifications
- Morning/evening reminders with native DatePicker (wheel)
- Payment reminders: 5 days, 1 day, and day-of for recurring savings
- Notification settings in Settings tab

UI Fixes:
- Fix color picker overflow: HStack → LazyVGrid 5 columns
- Fix sheet headers: shorter text, proper padding
- Fix task/habit toggle: separate tap zones (checkbox vs edit)
- Fix deprecated onChange syntax for iOS 17+
- Savings overview: real monthly payments and detailed overdues from API
- Settings: timezone as Menu picker, removed Telegram/server notifications sections
- All sheets use .presentationDetents([.large])

Config:
- project.yml: real DEVELOPMENT_TEAM, HealthKit + BackgroundModes capabilities
- Info.plist: BGTaskScheduler + UIBackgroundModes
- Assets.xcassets with AppIcon
- CLAUDE.md project documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 23:15:36 +03:00
parent 1146965bcb
commit 28fca1de89
38 changed files with 3608 additions and 1031 deletions

146
CLAUDE.md Normal file
View File

@@ -0,0 +1,146 @@
# 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
## 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)