diff --git a/personal/projects/nolvu-reflect-skip.md b/personal/projects/nolvu-reflect-skip.md index a045568d..bf28337a 100644 --- a/personal/projects/nolvu-reflect-skip.md +++ b/personal/projects/nolvu-reflect-skip.md @@ -103,6 +103,86 @@ Vault путь для импорта: `personal/psychology/` (на Taiga). - Если на iOS-симуляторе не появляется `DevSetupView` (ввод токена) — значит `dev_setup_done = true` в UserDefaults. Удалить приложение с симулятора и запустить заново. - `ReflectApp/` в корне — legacy, не трогать. Реальные правки только в `Sources/ReflectApp/`. +## Plan: Session Pacing & Closing (не реализовано) + +### Проблема + +`SessionViewModel.endSession()` существует и реализует полный closing-флоу (инсайт → warm phrase → oneLastThing → vault save), но **нигде не вызывается из UI**. +`dismissSession()` (кнопка X) отменяет сессию без сохранения. +Пейсинга нет: агент не знает сколько оборотов прошло, нет правил завершения в systemPrompt, нет сигнала `session_complete` в `AnalystJSON`. + +### Решение: Вариант Б + hard limit + +**Шаг 1 — Quick-fix: X-кнопка вызывает `endSession()` (даёт рабочий closing уже сейчас)** + +Вместо `dismissSession()` при нажатии X — вызывать `endSession()`. +Пользователь сам решает когда нажать, но closing-флоу отработает корректно. + +**Шаг 2 — Turn count в SessionManager** + +Добавить `var userTurnCount: Int = 0`, инкрементировать в `processUserInput()`. +Передавать в `AnalystBot.analyze()` как параметр `turnCount`. + +**Шаг 3 — `session_complete` в AnalystJSON** + +```swift +struct AnalystJSON: Codable { + let questions: [String] + let observation: String? + let crisisSignal: Bool + let suggestedInsight: String? + let sessionComplete: Bool // новое поле +} +``` + +**Шаг 4 — Пейсинг-правила в systemPrompt AnalystBot** + +``` +Правила ведения сессии (параметр turn_count передаётся в каждом запросе): +- После 4+ оборотов: если пользователь пришёл к пониманию, тема исчерпана + или повторяется — установи session_complete: true +- Если открылась новая важная тема — продолжай (session_complete: false) +- После 7+ оборотов: session_complete: true независимо от темы +- session_complete: true — предложение закрыть, не принудительное завершение +``` + +**Шаг 5 — `.readyToClose` в ProcessingResult** + +```swift +enum ProcessingResult { + case questions([String], observation: String?) + case readyToClose([String]?, observation: String?) + case crisis(CrisisDetectionResult) + case error(String) +} +``` + +SessionManager возвращает `.readyToClose` когда `analystResponse.sessionComplete == true` ИЛИ `userTurnCount >= 8`. + +**Шаг 6 — UI карточка в SessionViewModel** + +При `.readyToClose`: показать вопросы (если есть) → после ответа добавить карточку +"Кажется, мы хорошо прошлись по теме. Завершить сессию?" +Кнопки: "Завершить" → `endSession()` | "Продолжить" → сбросить сигнал на текущий turn. + +**Шаг 7 — Интеграционные тесты (TDD: писать до реализации шагов 2-6)** + +- `testAnalystReturnsSessionComplete_afterExhaustedTopic()` +- `testSessionManager_readyToClose_onSessionCompleteSignal()` +- `testSessionManager_hardLimit_at8Turns()` +- `testSessionViewModel_showsCompletionCard_onReadyToClose()` +- `testSessionViewModel_continueAfterReadyToClose_resetsSignal()` + +### Порядок реализации + +1. Тесты (шаг 7) — все RED +2. Quick-fix X-кнопки (шаг 1) — немедленно даёт рабочий closing +3. Шаги 2-5 — данные + логика +4. Шаг 6 — UI карточка +5. Все тесты GREEN + +--- + ## Связанные заметки - [[personal/projects/psychologist-app/overview]]