--- created: '2026-05-16' updated: '2026-05-16' tags: - project - dev - ios - swift --- # Reflect App — Dev Setup & Architecture > Путь: `~/Developer/reflect-app/` > Статус: BUILD SUCCEEDED, 30/30 тестов --- ## Структура проекта ``` reflect-app/ ├── project.yml ← XcodeGen spec (генерирует .xcodeproj) ├── proxy_server.py ← Local AI proxy server (Mac → iPhone) ├── ReflectApp/ │ ├── Models/ │ │ ├── CrisisKeywords.swift │ │ ├── Message.swift │ │ ├── Session.swift │ │ └── UserProfile.swift │ ├── Services/ │ │ ├── ClaudeService.swift ← AI бэкенд (cloud + local proxy) │ │ ├── AnalystBot.swift ← Бот 2 (Sonnet, CBT вопросы) │ │ ├── NarratorBot.swift ← Бот 1 (Haiku, 3rd-person reframe) │ │ ├── CrisisDetector.swift ← Hardcoded SI/SH детекция (RU+EN) │ │ ├── ProfileManager.swift ← Obsidian-like on-device vault │ │ └── SessionScheduler.swift ← Напоминания (UNUserNotificationCenter) │ ├── Views/ │ │ ├── OnboardingView.swift ← 3 слайда + 5 вопросов + инсайт │ │ ├── HomeView.swift │ │ ├── SessionView.swift │ │ ├── CrisisView.swift │ │ ├── ProfileView.swift ← Obsidian-like markdown viewer │ │ ├── SettingsView.swift ← Выбор бэкенда + API key + server URL │ │ ├── CardView.swift │ │ └── OrbCharacter.swift ← SwiftUI анимированный орб │ └── ViewModels/ │ ├── HomeViewModel.swift │ └── SessionViewModel.swift └── ReflectAppTests/ └── ReflectAppTests.swift ← 30 тестов ``` --- ## Генерация проекта ```bash # Установить XcodeGen (один раз) brew install xcodegen # Регенерировать .xcodeproj после изменения project.yml cd ~/Developer/reflect-app xcodegen generate ``` --- ## Сборка и тесты ```bash # Узнать ID симулятора xcrun simctl list devices | grep "iPhone 16 Pro" # Сборка xcodebuild -project ReflectApp.xcodeproj -scheme ReflectApp \ -destination "id=9FF2D6A8-E0DF-4157-9FA5-463E45BB5FAD" build # Тесты (30 тестов, ~0.1 сек) xcodebuild test -project ReflectApp.xcodeproj -scheme ReflectApp \ -destination "id=9FF2D6A8-E0DF-4157-9FA5-463E45BB5FAD" ``` --- ## Локальный AI сервер (режим без ключа) ### Запуск ```bash # claude-code — OAuth через Hermes (нет API ключа, читает CLAUDE_CODE_OAUTH_TOKEN из ~/.hermes/.env) cd ~/Developer/reflect-app python3 proxy_server.py --backend claude-code # Ollama — локальная модель python3 proxy_server.py --backend ollama --ollama-model qwen2.5:7b # Claude API key export ANTHROPIC_API_KEY=sk-ant-... python3 proxy_server.py --backend claude ``` **claude-code бэкенд** — приоритет источников токена: 1. `CLAUDE_CODE_OAUTH_TOKEN` из `~/.hermes/.env` (Hermes-managed, наиболее надёжный) 2. macOS Keychain (`Claude Code-credentials`) 3. `~/.claude/.credentials.json` (с авто-обновлением если есть refreshToken) ### Настройка на iPhone 1. Открыть приложение → шестерёнка (Settings) 2. AI Backend → "Локальный сервер (Mac)" 3. URL сервера: скопировать из вывода `proxy_server.py` 4. Нажать "Проверить" — должно показать "Доступен · qwen2.5:7b" ### Архитектура proxy ``` iPhone (ClaudeService) → POST /v1/messages → proxy_server.py (8765) → Anthropic API format → Ollama (11434) ← response ← Ollama response (конвертируется в Anthropic формат) ← response ← proxy_server.py ``` --- ## Архитектура двойного бота ``` User input (1-е лицо) ↓ CrisisDetector.detect() — hardcoded, <5ms ↓ (если .critical → CrisisView) NarratorBot (Haiku) — переформулировка в 3-е лицо ↓ AnalystBot (Sonnet) — читает profile.md + вопросы ↓ CardView с вопросами ``` --- ## On-device хранилище (Obsidian-like) ``` Documents/UserProfile/ ├── profile.md ← паттерны, наблюдения, прогресс ├── insights.md ← кристаллизованные инсайты └── sessions/ ├── 2026-05-16.md └── ... ``` --- ## Тест-результаты (2026-05-16) - ClaudeServiceTests: 4/4 - CrisisDetectorTests: 17/17 (включая RU+EN, нормализация) - ProfileManagerTests: 6/6 (включая onboarding init) - SessionSchedulerTests: 3/3 - **ИТОГО: 30/30, 0 failures** ## App Lock (Face ID / Touch ID / Passcode) **Файлы:** - `Services/AppLockService.swift` — `@MainActor ObservableObject`, `LAContext`, состояние - `Views/LockView.swift` — экран блокировки, автоматически запускает биометрию при появлении - `ReflectApp.swift` — `checkOnLaunch()` при старте, `scenePhase.background → lock()` - `SettingsView.swift` — секция "Безопасность" с Toggle **Поведение:** - Включение/выключение требует подтверждения биометрией - При уходе в фон (`scenePhase == .background`) — блокируется - При возврате — LockView показывается поверх всего, сразу запрашивает биометрию - Симулятор: биометрия эмулируется через Features → Face ID → Enrolled