# Future — ideas & improvements backlog Non-blocking ideas: things that work today but could be better, plus feature ideas. Actual bugs (broken/incorrect behavior) live in `ClaudeReport.md`, not here. ## Backup, restore & E2EE (follow-ons to R24) - **Option B — relay-and-delete for messages.** Now that E2EE backup + restore exist (R24), make the Room `conversation_cache` the **source of truth** and flip Firestore `messages` to a transient relay (TTL / delete-after-delivery), plus **back up media into the snapshot** (since `chat_media` would then be relay-deleted). This is the payoff the R24 backup unblocks. Rewire the chat read to local-first + live-merge (additive, flag-guarded — dedupe Room∪Firestore by message id). - **Backup trigger hardening.** Backups currently run opportunistically from `HomeViewModel.loadHome` (throttled). Move to **WorkManager** (deferred, retried, network/battery-constrained) + an app-background trigger for reliability; add a **resumable, paginated initial backfill** for very large existing histories. - **Settings visibility + control.** "Last backed up: {time} · {N} messages" indicator + manual "Back up now" / "Restore history", and an **opt-out** toggle for users who want zero server retention. Include a dedicated **"Recent restore activity"** list — the R24-b `restore_self_alert` entries already land in `notification_queue` as the raw audit; this surfaces them so the owner can review restores on their account in one place. - **Email-verification challenge for partner-assisted restore (strongest anti-account-takeover control).** Before a restore request is honored, send a code to the account's **registered email** and require the recipient to enter it. A phished-password attacker who lacks inbox access can't complete it (the couple-email match that defeats the on-screen identity check does NOT defeat this). Needs mail infra (SendGrid / a Firebase Auth action) — deferred for that reason. R24-b shipped the on-screen identity + confirm + owner self-alert as the pragmatic interim. - **Restore-request lifecycle cleanup.** A `restore_requests` doc left at `READY` (partner wrapped the key but the recipient never completed) leaves an ECIES `keybox` — ciphertext sealed only to the recipient, useless to anyone else, but untidy. Add a **scheduled cleanup** of expired requests (and their keyboxes). R24-b already enforces expiry at fulfil time and deletes any stale request before a re-request. - **Owner-alert precision.** The R24-b "was this you?" self-alert also reaches the *requesting* device (harmless). Optionally exclude it via a client-written token hint on the request (would add a rules-allowed field). - **Couple-key rotation / forward secrecy.** A couple-key compromise exposes all history incl. backups (no FS today). Add rotation (both devices re-key) — hard but the right long-term hardening. - **Server-independent anti-rollback freshness.** A malicious server could serve a stale manifest to hide recent messages; today mitigated by the `generation` counter + a Phase-1 Firestore cross-check. Add a signed/monotonic freshness signal for the Option-B world. ## Today widget (Batch 3.2 follow-ons) The Glance "Today" widget shipped (R29): content-free daily-question state + streak, app-lock → generic mode, taps into the daily question via `MainActivity`'s `app_route` extra. Verified: provider registers, the `HomeViewModel` → `TodayWidgetUpdater` → `today_widget` DataStore bridge writes, headline logic unit-tested, no crash. Remaining: - **On-home-screen render check.** Binding a widget to the launcher isn't adb-scriptable; confirm the Glance composition renders (light/dark) on a device, or add an instrumented test that binds via `AppWidgetManager`. - **Remote Config kill switch (`widget_enabled`).** Deferred: the app has **no** RemoteConfig wrapper yet despite `firebase-config-ktx` being a dep. Build one shared wrapper once (fetch/activate + defaults), then apply the kill switches the plan wants across the widget, solo mode, and deep links. Until then the widget degrades safely on its own (generic card + taps into the app). - **Day-rollover refresh.** Covered today by `updatePeriodMillis=30m` + app-open updates; a WorkManager day-boundary trigger (plan's suggestion) would make the "Tonight's question is ready" flip crisper. ## Solo / pre-pairing (Batch 2.2 follow-ons) The core of the pre-pairing experience shipped (R29): unpaired users can answer the daily question solo (`DailyQuestionViewModel.submitAnswer` already saves to `LocalAnswerRepository` and skips the Firestore sync when there's no couple), reach it via the Home secondary card, and see an honest `UnpairedLockedCard` on the Play hub instead of a silent bounce to invite. On pairing, `SoloAnswerMigrator` offers a one-time "bring your answers along?" consent dialog (`PairingSuccessScreen`) that writes the solo answers into **Couple Lore** (encrypted) or discards them. Remaining, deliberately deferred: - **Read-only date-ideas browse when unpaired.** The Date Match / Plan Date tiles still route unpaired users to invite (they need a couple for swipes). A standalone read-only date-ideas list (the seed is client-side and `date_ideas` rules already allow any authed read) would let a solo user browse while they wait. Pairs well with the Firestore-backed catalog work (Batch 4.2). - **Remote Config kill switch `solo_mode_enabled`.** Not yet wired; `firebase-config-ktx` is already a dep. - **Convert the remaining silent bounces to `UnpairedLockedCard`.** Wheel / Messages / Answer-history / the daily-question "Discuss" button still redirect unpaired users to `CREATE_INVITE` without explanation; reuse the new `ui/components/UnpairedLockedCard.kt` on those surfaces for consistency. - **Migrate-on-pairing robustness.** `SoloAnswerMigrator` is best-effort per entry (a not-yet-ready couple key leaves the answer local for a retry), but there's no later retry trigger beyond the pairing-success screen — add one if telemetry shows keys aren't ready at that moment. ## UI _(No open UI defects. The P0 onboarding/auth crash filed here 2026-06-28 was fixed + verified live and moved to `ClaudeReport.md` as **O-ONBOARD-001** — root cause was `painterResource` on the `` `ic_launcher_foreground` (not the background, as originally guessed); fixed both `OnboardingScreen.CtaSlide` + `AuthVisuals.AuthLogoMark` to use the raster `closer_launcher_foreground`. The "Add to Bucket List mixed dark/light" item is also fully resolved — dialog tokens R16, add-FAB `Color(0xFFB98AF4)`→`primary` R18.)_ ## QA ### From R29 games review (2026-07-07) - **Seed the server daily-question pool** — `assignDailyQuestion` still assigns the `q_default_daily` rollout fallback for every couple (Firestore `questions` empty), so clients always use the local fallback. Seed real ids (synced with the asset DB) so the assignment actually pins the couple's question. (Client-side premium-independence fix landed in R29; this is the durable half.) - **Generic session-game engine** — ThisOrThat/HowWell/DesireSync embedded VMs are ~80% clones of one ~250-line state machine (load/create/join/submit/retry/observe/abandon) and their data sources are structural copies (`EncryptedAnswerMapDataSource`). Migrate one game first (ToT), full live regression, then the rest. - **Generic replay VM/screen** — 3× ~60-line clones (ToT/DS/HW replay). - **Shared waiting/length-picker/error composables** — each game re-implements WaitingForPartner/length chips/error screens despite shared components existing. - **Premium-check invocation helper** — CouplePremiumChecker is invoked 4 different ways across ~10 call sites with inconsistent failure fallback. - **GameConstants** — divergent ADVANCE_DELAY_MS (420 vs 380), QUESTION_COUNT, SPIN_DURATION_MS per file; follow SessionLength's pattern. - **DispatcherProvider injection** — Dispatchers.IO/Default hardcoded everywhere (testability). - **Error-surfacing standardization** — BucketList/MessagesInbox/PlayHub/AnswerHistory/WeeklyRecap/Onboarding VMs swallow load errors (blank screens); adopt uiState.error + ErrorState for reads, _events snackbar for writes (ConversationViewModel pattern). - **Retention (from ClaudeGamesReview.md):** HowWell role-swap rematch CTA · return-tomorrow beat on all results screens · per-couple seen-question memory for DS (102-item pool) + shuffle Date Match deck (deterministic first card for every couple) · exclude daily_fun_mc from HowWell prediction pool · hide Challenges day-N+1 prompt until its day · analytics: first-answer/reveal-viewed/waiting-abandoned/notif-tap events. - **Content retag** — `daily_fun_mc` rows carry `sex='neutral'` (benign today only via the binary filter; retag to keep Desire Sync's pool clean). 48/150 sexual_preferences items are non-binary configs (Wheel-only) — intended? - **npm audit** — 10 moderate (retry-request/teeny-request via @google-cloud/storage transitive); revisit on next functions dependency bump. - **Banner lifecycle** — GamePromptController results banner should dismiss once results are viewed (BANNER-LIFE-001). Improvement & feature ideas surfaced while QA-testing as a consumer (each works today — none are defects). - **Tier 3: Compose screenshot diff for visual regression.** The static scanner in `scripts/theme-scan.sh` catches ~80% of light/dark theme mismatches, but it cannot detect *compositional* failures: a theme-token color used on the wrong surface, a gradient with hardcoded light stops, or subtle contrast collapse. Implement a screenshot pipeline with Roborazzi / Shot / papAROS that renders every `AppRoute` in both light and dark, pixel-diffs them, and fails on unexpected white backgrounds or invisible text. When done, run it in CI against every UI PR. - **🟡 STARTED (R20) — Instrumented / on-device test coverage (was 0 androidTest).** First cut shipped: `app/src/androidTest/.../ui/FirstRunRenderSmokeTest.kt` — an on-device **Compose render smoke** of the first-run screens (`CtaSlide` + `AuthLogoMark`, light + dark), the exact `painterResource` logo sites that crashed every fresh install in **O-ONBOARD-001**. It's the net for the "composes fine, crashes on first paint" class the JVM unit tests + static scanners structurally can't catch. **Proven (R20):** passes green on-device, and FAILS with the original `IllegalArgumentException: Only VectorDrawables…` when the bug is reintroduced. Infra added: `testInstrumentationRunner` + `ui-test-junit4` (build.gradle.kts); also un-blocked the androidTest source set (the stale `CanonicalVectorCaptureInstrumentTest` couldn't compile against `private deriveKey` → made it `@VisibleForTesting internal`). Run: `./gradlew :app:connectedDebugAndroidTest`. **Still to grow:** it's Hilt-/Firebase-free leaf-composable rendering only — extend toward a fuller sign-in → pair → answer daily Q → open a game → send a message flow (needs a Hilt test runner + fakes), and wire `connectedDebugAndroidTest` into the per-round gate / CI alongside `qa/entrypoint_smoke.sh`. *Prompted by:* the QA-plan render-coverage gap (O-ONBOARD-001 escaped because nothing rendered a composable). - **✅ DONE — Consistent brand glyphs across game cards + waiting surfaces.** G-set + G2 (17 glyphs) in `res/drawable-nodpi/glyph_*.xml`; **13 wired + verified live:** every Play-hub card (This or That, How Well, Desire Sync, Connection Challenges, Memory Lane, Date Match, Plan Date, Question Packs, Bucket List, Past Games — Spin the Wheel keeps its full illustration), WaitingForPartner per-game glyph, and Settings (Subscription/Security/Privacy/ Delete). 4 unused have no clean slot (notif uses `ic_notification_closer`; Today uses hero art; quiet-hours uses its illustration; no export-data row exists). Full map in `ClaudeBrandingReview.md`. _(This-or-That backdrop redesign is Codex C-DARK-UI-001.)_ - **Minor proactive-notification gaps (low priority).** ~~No push when a partner *joins* your active game (`partner_joined_game`)~~ → **BUILT (2026-06-28, pending deploy):** the non-starter opening an active session writes `joinedByUsers` (rule-gated), `onGameSessionUpdate` notifies the starter " joined your game" with the joiner's avatar (one-time `joinNotifiedAt`); foreground shows the standardized in-app banner. **Still open:** no push when a partner *ends/abandons* a game (`game_ended`/`game_abandoned`) — the other partner sees it in-session / on WaitingForPartner, so nothing's broken, just less proactive. *Prompted by:* Pass E (R8) inventory. - **Clarify Connection Challenges day-progress when partners are out of step.** If one partner catches up a *missed* day ("Pick it back up") while the other doesn't, the two devices show different **"Day N of 7"** (seen R10: QA Day 4 vs Sam Day 3) even though the 🔥 streak stays in sync on both. Not broken (plausibly individual-pace-through-the-series by design), but two people in the same shared challenge seeing different day numbers is confusing — consider a shared "you're on Day N together" framing or a clearer caught-up/ahead indicator. *Prompted by:* Pass B (R10) Connection Challenges playthrough. ### Security hardening (defense-in-depth — not vulnerabilities; rules already hold) > **Canonical security doc: [`SECURITY.md`](SECURITY.md)** (2026-06-29) — full threat model, what's > protected vs. exposed (metadata), known caveats, and the **prioritized hardening roadmap**. The P0 > there (**enforce App Check on the backend**, independent audit, release hardening), P1 (encrypt > profile metadata, opt-in telemetry, recovery-phrase UX, biometric re-lock), and P2 (cert pinning, > multi-device keys, data export, key rotation) are the authoritative list; the two items below are the > older notes that fed into it. - **Enforce App Check on Firestore (currently OFF).** Round 7 raw-API test: an authenticated request with **no App Check token** (raw Firestore REST) returned `200` for a member — so rules are the *sole* gate. Rules correctly deny non-members/cross-couple (all `403`), so this is not a live hole, but enabling App Check enforcement on Firestore would block non-app clients entirely (defense-in-depth). *Prompted by:* R7 D3 raw-API angle. - **✅ DONE (R21) — Biometric app-lock now re-arms on background/timeout (was: only cold-start/process-death).** `MainActivity` observes the lifecycle: while the lock is on and the session is unlocked, it records when the app is backgrounded and **re-locks if it returns after >60s away** (`BIOMETRIC_RELOCK_GRACE_MS`) — so a picked-up, already-open phone re-prompts, not only on Activity recreation. The grace window avoids re-locking on quick task-switches (the biometric prompt, photo picker, share sheet). Code-complete + compiles; **live re-lock not yet driven** — emulators have no enrolled biometric/PIN, so verify on a physical device. (SECURITY.md rec #7.) > Artwork to generate (ChatGPT prompts, house-style-matched) lives in `ClaudeBrandingReview.md`, not here. ## Roadmap — features & strategy (consolidated from the old `FUTURE.md`, 2026-06-28) **Unbuilt games (Play Hub is live; these remain):** - **Would You Rather / Truth or Dare** — tiered sweet→spicy, consent-gated; reuses the deck + match engine. Strong **premium "spicy" tier** lever. - **Daily Sync / Rose-Bud-Thorn** — one-tap emotional check-in, see partner's; small/new, free retention driver. **"2026, not 2019" differentiators (strategic):** - **AI-personalized prompts** — server-side (latest Claude) generate/weight prompts from the couple's history/season/unexplored topics; add a `generatePrompts` callable (Cloud Functions already in place). Biggest modern lever; pairs with content-metadata routing below. - **Async + real-time hybrid** — partner-presence ("they're here now") for live co-play, everything still async-friendly (long-distance). - **Multi-modal answers** — voice/photo answers (esp. Memory Lane + daily check-ins). - **Home/lock-screen widgets / live updates** — glanceable today's-prompt + partner status (Glance/Wear surface). - **Gentle gamification** — forgiving "gentle streaks" (grace days) + shared wins, not loss-aversion/guilt. - **Consent-first spicy content** — Intimacy/Dare tiers opt-in, double-confirmed, reveal-only-on-overlap. **Cross-platform:** Android-first is fine for MVP; iOS is the strategic gap (couples split devices). Decision + plan live in `ClaudeiOSPlan.md` — don't start before release/trust polish. ## Product polish (consolidated from the old `FUTURE.md`) - **Skeleton/loading states over bare spinners (P8).** `LoadingState` exists but many screens still use a bare `CircularProgressIndicator`. Add skeletons for question lists, game histories, home modules, paywall offerings, sync/reveal waits. Acceptance: no primary route shows an isolated spinner on an otherwise blank screen. - **Paywall / store value framing (P12).** Paywall has benefits/restore/legal/RevenueCat; needs stronger value framing, real offering/trial clarity, screenshots/previews, and test coverage before release. (Overlaps Pass K/O.) - **Content metadata & personalization (P13).** The bank is clean; the next leap is *routing*, not more questions — tag mood/depth/relationship-stage/conflict-safe/intimacy-level/time-needed and extend the selection APIs so prompts adapt to skipped topics, relationship length, and recent answers. ## Release / pre-ship (consolidated + re-verified 2026-06-28) - **Real release config before any store submission.** Confirmed still open: `app/build.gradle.kts` `versionCode = 1` / `versionName = "0.1.0"` and `core/navigation/ExternalLinks.kt` legal URLs are placeholder TODOs (`https://closer.app/privacy|terms|subscription-terms`). _Already done:_ a release-blocking Gradle check now fails the build if `RC_API_KEY` is unset/placeholder (`build.gradle.kts:106–110`), so the old "add a gradle guard" sub-item is closed. Remaining: set real version, real legal/support URLs, real RC key + verify offerings/purchase/restore on internal testing. (Tracked by **Pass O** release-readiness.) ## Help & support surface (consolidated — old "Notes to Consider") A future Help/Support screen could include: contact support · report a bug · send feedback · FAQ · subscription/billing help · pairing help · recovery-phrase/account help · app version + build number · optional "copy diagnostics" button.