feat: configurable daily recap depth (Brief/Normal/Deep)
What changed and why
Closes #6137. Daily recaps were hard-coded to the short “be snappy” prompt. Users can now choose Brief, Normal, or Deep Reflection in macOS Settings → Alerts & Privacy → Daily Summary; the preference is stored on their account and used when the next recap is generated.
- Brief preserves the existing limits and is the default for older or malformed profiles.
- Normal allows up to eight useful items per section with context and related themes.
- Deep removes fixed item counts and uses bounded context from up to three earlier recaps and three active goals. It asks for grounded connections and never invents emotional states. The optional context reads fail soft with fallback telemetry.
- The GET/PATCH settings contract remains compatible with older callers. A depth PATCH echoes the saved value; the desktop picker reverts with an error if an older backend ignores it, instead of falsely appearing saved. The recap output JSON schema is unchanged and existing recaps are not rewritten. The app-client OpenAPI snapshot and generated Dart/TypeScript API clients are updated mechanically; a
kind: nonemobile fragment records that this is not a mobile feature.
Backend must deploy before the desktop picker is released. This PR does not deploy either service.
Product invariants affected
none (the new control uses the existing settings row and menu components; the desktop UX contract and brand ratchets pass).
How it was verified
backend/.venv/bin/python -m pytest backend/tests/unit/test_daily_summary_*.py backend/tests/unit/test_daily_notification_*.py backend/tests/unit/test_notifications_daily_summary_null_defaults.py backend/tests/unit/test_backfill_daily_summary_schedule_fields.py backend/tests/unit/test_users_error_sanitization.py -q→ 202 passed on the rebased branch. Includes a real FastAPI TestClient GET → PATCH → GET round trip and captured production-generator prompts for each depth../scripts/dev-feedback.py --once swift 'APIClientRoutingTests'→ 72 passed on the rebased branch. Covers PATCH wire body, backend acknowledgement, legacy response rejection, and GET decoding fallback.OMI_PR_BODY_FILE=/tmp/omi-6137-pr-body.md make preflight→ 46 checks passed after rebasing onto currentorigin/main(including the scoped line-count exception below).- Generated OpenAPI, Dart
usersDTO, and all four TypeScript API clients pass their freshness checks.web/apptests pass 601/601 withNODE_OPTIONS=--no-experimental-webstorageto avoid a Node 26/jsdom globallocalStoragecollision in this environment. - Built and launched isolated
com.omi.omi-6137-recap-depthon macOS. The named bundle passed dependency audit, loaded signed-in Settings, and the accessibility tree exposed the picker. I opened its menu and visually confirmed all three options without changing the live account preference. Visual proof. - No production LLM call or live account-setting mutation was made. Generated recap quality still needs human review on a development backend after deployment.
Tests
test_daily_summary_depth_settings.pytests HTTP persistence and invalid values.test_daily_summary_depth_prompt.pytests the real generator prompt, bounded prior context, non-Deep read avoidance, and optional-read degradation.- Firestore schedule-field tests prove changing depth preserves the existing daily-summary schedule.
APIClientRoutingTestscovers desktop wire compatibility and failure presentation preconditions.
Failure class (fixes)
Failure-Class: none
Scoped cleanups
The existing daily-summary PATCH returned {status: "ok"} while its Swift client attempted to decode the GET response shape; this change corrects that contract. An unrelated users error-sanitization test gained the missing quota mock so the broader hermetic suite does not attempt a metadata-network call.
Line-Count-Exception: backend/routers/users.py | 2525 -> 2541 | reason: extend the existing daily-summary settings endpoint in place; splitting this large router as part of a scoped account-preference feature would add unrelated routing churn.