diff --git a/ClaudeQAPlan.md b/ClaudeQAPlan.md index 332590df..0e2d685f 100644 --- a/ClaudeQAPlan.md +++ b/ClaudeQAPlan.md @@ -97,6 +97,30 @@ confirms + enumerates this; the fix phase applies couple-shared everywhere. re-checks it cheaply instead of by hand. - **Test-data hygiene:** keep known test accounts; clean up artifacts (stray messages/reactions/sessions) between rounds so they don't masquerade as bugs. +- **Evidence standard:** every filed bug must be reproducible from text alone: build/commit, device, account, theme, + app/process state, screen/route, exact tap/input sequence, expected result, actual result, and whether logcat showed + a crash/ANR/permission denial. Screenshots/videos are helpful but never the only evidence because session artifacts + may not survive compaction. +- **Flake policy:** if something fails once and then passes, do not dismiss it. Repeat from a clean state, vary timing + (rapid tap / slow network / background-resume), inspect logs, and file it as intermittent if it cannot be made fully + deterministic. Intermittent routing, notification, encryption, duplicate-write, or crash behavior is still a bug. + +## Living discovery ritual (before each round, and whenever reality disagrees with the docs) +The app is allowed to grow; the QA plan must keep up. Before a pass or chunk, quickly inventory the current code/app +surface and reconcile it with `ClaudeQACoverage.md`: +- **Routes/screens:** inspect `core/navigation/AppRoute.kt`, navigation graph call sites, Settings sub-pages, dialogs, + bottom tabs, deep links, and any new composables reachable by buttons/cards. +- **Notifications:** inspect notification type enums/classes, Cloud Function triggers, Android intent/deep-link handling, + notification channels/actions, FCM token registration, and Android runtime notification permission paths. +- **Features/gates:** grep for premium checks, permission requests, media pickers, billing/paywall entry points, + destructive actions, account/couple lifecycle actions, and admin/server-only writes. +- **Assets/content:** inventory new drawables, `drawable-night*` variants, pack art, empty states, strings, feature flags, + remote config, and any debug-only screens that should not ship. +- **Backend/rules:** inspect Firestore rules, indexes/queries, Functions triggers/callables, Storage paths, scheduled + jobs, and migrations for new data shapes or access paths. +- **Docs update rule:** if the inventory finds a page, feature, notification, asset, state, backend path, or edge case + missing from the playbook/coverage, update `ClaudeQAPlan.md` and `ClaudeQACoverage.md` before marking the chunk done. + If it is product polish, also add it to `Future.md`; if it needs new artwork, add it to `ClaudeBrandingReview.md`. ## Multi-angle attack mandate (go DEEPER than "does the happy path work") A capability can pass via the UI yet fail when hit directly. Probe each meaningful capability (read/write a private @@ -139,6 +163,13 @@ State lives in **files**, not memory: every `fail`. Never renumber or reuse. - **Source of truth**: the two MD files are authoritative; the TodoWrite list is scratch for the current chunk only. Update the MD files + run-state header *before* ending a session. +- **Living playbook rule:** when QA discovers any new app surface or recurring lesson — a new page/route, feature, + setting, game state, notification type/action/channel, entry point, background/killed-state behavior, asset/art + placement, repeatable bug class, missed edge case, fragile route, confusing state, image/layout failure mode, + security angle, or anything else that should be checked every future round — update **this `ClaudeQAPlan.md`** in the + relevant pass before ending the chunk. Also add the matching row/cell to `ClaudeQACoverage.md` if it needs recurring + verification. Do this even after the immediate bug is filed/fixed so the lesson or newly discovered surface is not + lost to memory or git history. - **Commit cadence**: commit `ClaudeReport.md` + `ClaudeQACoverage.md` after each pass and each chunk. - **Chunking**: run small chunks (Pass C one screen-group; Pass A one feature), checkpoint after each. - **Session-start ritual**: (1) read run-state header + both MD files; (2) `adb devices` shows **both** emulators @@ -153,16 +184,22 @@ with a commit + run-state update. If a chunk starts overflowing, split it; if ch **Why:** in Round 1, A & D fit as single batches, but B/C/E were too large → got cut off → deferred. Sub-batching prevents half-done/lost work and gives cleaner per-chunk verification + revertable commits. +Default small: if a chunk requires two-device live driving, screenshots/montage review, logcat checks, or admin/API +verification, keep it to **one small route family, one game phase, or one notification type**. A chunk is too large if +it cannot produce a precise coverage update, issue log, and commit before context gets tight. Split before starting +rather than leaving a half-tested matrix behind. **Prefer Claude-friendly micro-batches**: smaller chunks let the agent +fully inspect screenshots, tap every CTA, vary app states, update files accurately, and avoid shallow "covered" rows. + | Pass | Chunk granularity | ~chunks | |---|---|---| -| A Premium | free-state gating sweep; then couple-shared verify (mostly code + a few live taps) | 1–2 | -| B Games | **one game per chunk** — full two-device playthrough + edges + commit | 7 | -| C Visual | **one screen-group per chunk** (both themes, ~6–10 screens, montage-reviewed + nav/back for that group) — never "all screens" at once (heaviest, image-bound) | 6–8 | -| D Security | D1 at-rest · D2 rules + D3 negative · D4 keys/recovery · D5–D7 appcheck/secrets/leaks/migration | ~4 | -| E Notifications | **3–5 types per chunk** × {foreground/background/killed} + tap-to-open; **game/join-game notification chunks** included; both clients (QA→Sam, Sam→QA) | ~5–6 | +| A Premium | one gated-feature family per chunk if live toggles are needed; otherwise free-state sweep → couple-shared verify | 2–4 | +| B Games | **one game per chunk max**; split complex games into lifecycle/playthrough chunk + join/resume/results/notification-entry chunk | 7–14 | +| C Visual | **one small route family per chunk** (both themes, ~2–3 screens/states, screenshots reviewed + nav/back + image-fit + all CTAs for that family) — never "all screens" or a broad tab at once | 16–25 | +| D Security | one security assertion group per chunk: D1 at-rest · D2 rules static · D3 live negative raw API · D4 keys/recovery · D5/D6 leaks · D7 migration | ~6 | +| E Notifications | **one notification type per chunk** with the full contract below; split a type into direction/state subchunks if needed, but do not mark the type pass until both clients + source screens + fg/bg/killed + stale/malformed + payload/back-stack are covered | 16–30 | | F Resilience | **one dimension per chunk** (concurrency · lifecycle/process-death · network · time · account-lifecycle) | ~5 | | G Account creation | **one creation/abuse dimension per chunk** (happy/validation · duplicate/conflict · fake-account abuse · lifecycle) | ~4 | -| H Branding | **one screen-group per chunk** (consumer brand walk → ready-to-paste art prompts) | ~4 | +| H Branding | **one small route family per chunk** (~2–3 screens/states) consumer brand walk + ready-to-paste art prompts + existing-image integration verdict | 8–14 | | I Performance | **one route-group per chunk** — gfxinfo/jank + read-count instrumentation (build the route smoke checklist) | ~3 | | J Accessibility | **one a11y setting per chunk** (font scale · TalkBack · contrast · targets · keyboard · reduce-motion) | ~5 | @@ -253,11 +290,21 @@ auth/onboarding/pairing (fresh acct); Home (solo + paired); Play + every game; T (inbox + conversation); Packs; Dates (Match/Builder/Matches/Bucket List); Wheel (picker/session/complete/history); Settings + all sub-pages (Account, Notifications, Appearance, Privacy, Subscription, Relationship, Security, Delete Account); Paywall; Your Progress/Activity; Recovery. +- **Images must belong to the screen:** during the UI sweep, visually inspect every illustration, glyph, banner, + empty-state image, pack art, celebration asset, and dark/light variant in context. It should feel intentionally + integrated with the page hierarchy, copy, spacing, and action area — not like a forgotten placeholder dropped into + an empty slot. Check crop, scale, padding, alignment, corner radius, background/tile treatment, theme variant, + loading/fallback state, and whether the image competes with or clarifies the primary task. If it is broken, + clipped, low-contrast, off-brand, stale, or placeholder-looking, file a bug in `ClaudeReport.md`; if the screen + works but would benefit from new/better art, log the prompt need in `ClaudeBrandingReview.md`. - **Probe:** `ui/theme/Theme.kt` hardcoded brand colors + chat's custom `closerBackgroundBrush` — verify dark mode truly adapts; grep screens for hardcoded `Color(0x...)`. - **States, not just happy path:** empty / loading / error / not-paired / locked-premium / signed-out / stale-or-deleted-target / populated-with-many where they exist; many need data setup (seeding is user-gated) — note unreachable states in coverage rather than skipping silently. +- **Text/data stress:** test long names, long relationship labels, long question/answer text, emoji, multiline content, + empty optional fields, many list items, and both partners having similar names. Verify no clipping, overlap, + confusing attribution, broken sorting, or hidden actions. - **Readability at scale:** default font size + spot-check largest system font scale on text-heavy screens. (The full accessibility sweep — large-font on every primary flow, TalkBack labels, touch targets, keyboard, reduce-motion — is **Pass J**; per-route performance/jank is **Pass I**.) @@ -265,6 +312,11 @@ Account); Paywall; Your Progress/Activity; Recovery. opens correctly each time — e.g. a conversation from the inbox AND from "Discuss" AND from a notification; a game from the Play hub AND from a notification; Paywall from each gated feature; Settings sub-pages; reveal from Today AND from history AND from `partner_answered`. A screen that works from one entry but breaks/duplicates from another = bug. +- **Every link, CTA, and mission must prove its destination:** actively hunt for dead buttons, wrong targets, generic + Home fallbacks, no-op taps, stale routes, and confusing affordances. Example class: a Reveal card saying + **"Tiny Mission: Send one flirty text"** must open the relevant Messages/conversation flow, not do nothing. For every + button/card/chip/row, record the expected destination before tapping, then verify the actual destination, state, + payload, and back stack. Broken/no-op/wrong-destination CTA = bug (usually P2; P1 if it blocks a core flow). - **All routes into a game / join-game state (verify each opens the correct game + session + partner-state + mode + premium/couple-entitlement + back stack):** Play-hub cards (incl. premium-gated), active-session banners, Home/Today game prompts, game history, replay/results, waiting screens, notification-opened screens, in-app banners, @@ -291,6 +343,11 @@ Account); Paywall; Your Progress/Activity; Recovery. back twice** (duplicate/stacked destinations on the back stack = bug). Bottom-tab reselection and deep-link/ notification entries must land with a sane back stack (back → Home, not off the app or a blank screen). Wrong/ double back or a dead-end = **P2** (P1 if it traps the user). +- **UI consistency / polish defects:** compare each screen against sibling patterns in the same area and across the + app. Headers, labels, status chips, partner names, connected-state copy, spacing, card treatments, and button + hierarchy should feel intentional and consistent. Awkward or out-of-place UI such as a Settings relationship row + where **"Connected with ..."** looks visually odd, cramped, misaligned, or unlike the rest of Settings is a finding: + file as a bug if it looks broken/inconsistent; log to `Future.md` only if it is purely a product/content improvement. - **D1 At-rest coverage:** admin-read RAW docs/objects, assert ciphertext for every private type — chat text + `lastMessagePreview` (`enc:v1:`), chat media bytes (Tink `01 69 59 51 f0…`), answers (`sealed:v1:`/`enc:v1:`), date plans + `date_swipes`, Memory Lane capsules, Bucket List. Also: **wrappedCoupleKey** + recovery material never @@ -348,10 +405,46 @@ Run the **complete** suite across **both clients** (QA→Sam AND Sam→QA). Each → delivered to the right partner (never self/non-member/ex-partner) → correct channel + copy with no private content → tap opens exactly the right item (loaded, not generic Home/dead-end) → sane back stack → privacy/authz re-checked on open**. No duplicates; rate limiter (20/day, 100/week) doesn't drop legit ones. +- **Notification chunk contract (small chunks, complete coverage):** each chunk owns **one notification type** (or one + explicit subchunk of that type, e.g. `chat_message QA→Sam foreground/source-screen sweep`, then + `chat_message Sam→QA background+killed+stale`). Before starting, write the chunk's matrix in `ClaudeQACoverage.md`; + after finishing, mark each cell `pass | fail→id | blocked→id | not implemented→Future.md`. A notification type is + not complete until all applicable cells below are covered: + - **Directions:** QA→Sam and Sam→QA; sender must not receive their own push unless intentionally designed. + - **Process states:** foreground, background/warm, killed/cold-start, force-stopped if deliverable, screen locked, + and resumed after rotation/process recreation when relevant. + - **Current screens:** Home, Play hub, active game/waiting/results, Today/reveal, Messages inbox, exact conversation, + Settings/sub-settings, Paywall, unrelated deep screen, logged-out, unpaired, and stale prior-partner context. + - **Entry surfaces:** foreground in-app banner/head, Android system tray tap, any push action button, crafted + deep-link/intent matching the payload, repeated/double tap, and tap after the target has changed. + - **Targets:** fresh target, already-open target, completed target, stale/expired/deleted target, unauthorized target, + wrong couple/session/item ID, malformed/missing extras, and no-network-on-open. + - **Assertions:** correct recipient, correct channel/priority/copy, no private payload/log content, exact destination, + membership/auth/entitlement re-check, no duplicate route/session, sane back stack, logcat clean, and coverage/docs + updated before the chunk ends. +- **Notification tap crash triage (mandatory):** never conclude "the notification didn't open" from UI behavior alone. + Before each notification/deep-link tap, clear or timestamp logcat; after the tap, inspect both devices for + `FATAL EXCEPTION`, ANR, ActivityTaskManager errors, `RuntimeException`, navigation/deep-link exceptions, + `PERMISSION_DENIED`, and swallowed repository/decryption errors. If the app returns Home, stays put, flashes, + restarts, or silently fails, classify whether it was wrong routing, missing extras, stale data, permission denial, or + a crash. Any notification tap that crashes (example class: tapping a game notification to open **Spin the Wheel**) + is a filed bug with stack trace + exact payload/session/game type, not a vague "didn't open" note. - **Both-client × app-state matrix (per type):** QA→Sam and Sam→QA, each in **foreground / background / killed (cold-start)**, plus **already on the target screen**, **on a different screen**, **logged out**, **unpaired**, with a **stale/expired/completed/deleted target**, and **both users opening around the same time**. Not a `pass` unless it works from both clients in every state that applies. +- **Current-screen/source-screen matrix (per type):** do not test notifications only from Home or only from a clean + launch. For each notification type, vary where the receiving client is when the notification arrives/taps: **Home, + Play hub, active game/waiting/results, Today/reveal, Messages inbox, exact conversation, Settings/sub-settings, + Paywall, an unrelated deep screen, app backgrounded from each major tab, and app fully closed/killed**. Foreground + banners, system-tray taps, warm-start `onNewIntent`, and cold-start launch must all route to the exact target. A tap + that lands on generic Home, stays on the old screen, opens the wrong tab, loses extras, duplicates the destination, + or needs a second tap is a bug. +- **Permission/token health:** cover Android `POST_NOTIFICATIONS` granted, denied, "don't ask again"/system-disabled, + and re-enabled states; Settings notification toggles; sign-out/sign-in token refresh; same account on two devices; + partner/account switch; stale token cleanup; app reinstall/update; and notification channel migration. Denied/system + disabled notifications should fail gracefully with in-app state still correct, never with lost data or broken routing + after permission is restored. - **Six assertions per notification:** (1) trigger fires correctly — right event, not early, not twice, sender doesn't get their own (unless intended), retry/idempotency doesn't duplicate; (2) delivered to the right person — correct token, old tokens unused after sign-out/account-switch; (3) copy + channel correct — friendly, right channel/ @@ -383,7 +476,9 @@ open**. No duplicates; rate limiter (20/day, 100/week) doesn't drop legit ones. → finish push opens the exact results/reveal → re-opening the push after completion opens replay/results (not a dead active session) → if A ends/quits, B is notified or shown a graceful ended state → a **stale** game push routes to results/history or a clear expired-session message → simultaneous start/join yields **one** session, neither stuck → - premium gate holds (neither-premium push must NOT bypass paywall; either-premium unlocks for both). + premium gate holds (neither-premium push must NOT bypass paywall; either-premium unlocks for both). For each game + type, including **Spin the Wheel**, notification taps must be paired with logcat review so crashes are caught even if + the visible symptom looks like a no-op or generic Home fallback. - **Join-game navigation suite:** every entry that leads to joining/resuming a game opens the correct game + session + partner-state + mode + entitlement + back stack — Play-hub card, active-game banner/card, Home active-game card, Today game prompt, notification tap, in-app foreground banner, game history/replay, partner waiting screen, results/ @@ -424,6 +519,10 @@ open**. No duplicates; rate limiter (20/day, 100/week) doesn't drop legit ones. - **Account/couple lifecycle:** brand-new (empty) account; unpaired state; pair → unpair → re-pair; partner leaves mid-session; account deletion cascade; same account on two devices; stale notifications after unpair/delete are graceful; invite accepted while already paired is rejected cleanly. No orphaned/broken state. +- **Install/update/migration lifecycle:** fresh install, update over an existing signed-in install, app data retained, + Room/DataStore/SharedPreferences migrations, notification channel migration, cached encryption/key material, + pending deep links/notifications across update, and version-skew between partners if one device updates first. No + sign-out loops, stale build routing, lost local state, broken permissions, or migration crashes. - **Crash reporting:** confirm crashes/ANRs are actually captured (Crashlytics) so field issues surface. ### Pass H — Branding & artwork (every screen: could it carry more of the brand? where would art help?) @@ -431,6 +530,10 @@ A consumer-mindset pass focused on **brand presence and delight**, not defects. ask: *does this feel like Closer (private, warm, equal, intentional — a ritual for two)? Could brand color, the heart mark, a brand message, or an illustration make it warmer or clearer without clutter?* Output is **artwork descriptions written as ready-to-paste ChatGPT image-generation prompts** — the user generates the images; we only describe them. +- **Existing art integration check:** judge the art as part of the whole page, not as a standalone asset. Confirm each + image supports the screen's job, aligns with the surrounding typography/actions, has enough breathing room, and uses + the right light/dark treatment. Art that looks generic, unfinished, randomly placed, or visually disconnected is a + finding even if the bitmap itself is technically valid. - **First, lock the house style (do this once per round, refresh if the art evolved):** read `docs/brand/visual-identity.md` + `docs/brand/asset-system.md` AND open 2–3 existing illustrations (`illustration_couple_onboarding`, `illustration_reveal_celebration`, `pack_art_*`) to capture the *actual* look. New screens/features since the last diff --git a/ClaudeReport.md b/ClaudeReport.md index 4a413d79..9f63d4a1 100644 --- a/ClaudeReport.md +++ b/ClaudeReport.md @@ -1,13 +1,13 @@ # Claude QA Report — Full-App QA (living report) -> **Verdict (2026-06-26, R9): 0 open P0–P2 (1 P3 J-OBS, non-blocking). I-001/I-002 confirmed + pruned. Security cornerstone clean. At the flawless bar.** +> **Verdict (2026-06-26): 0 open P0–P2 (1 P3 J-OBS). Fixed this session: E-GAME-001 (notifications now deep-link into the live game) + dark-theme illustrations wired. Nav/button/image QA sweep both themes = 0 FATAL. (Changes uncommitted — user commits.)** > > This report shows **current state only**. Fixed issues live here for **one** confirmation round, then they're pruned > to the archived-ID line below (full detail stays in git history). See **Report hygiene** in `ClaudeQAPlan.md`. ## Run-state (current) -`Round 9 — COMPLETE (clean confirmation round, 0 new findings) | 0 open P0–P2 (1 P3 J-OBS) | I-001/I-002 pruned; deferred Pass C + Pass F network swept | NEXT ACTION: FLAWLESS — optional P3 J-OBS fix + low-risk deferred (time-gated content, deletion-cascade) in a future round.` -- **Build:** client HEAD `23dd6a7`, Cloud Functions deployed. +`Post-R9 session (2026-06-26) | 0 open P0–P2 (1 P3 J-OBS) | Fixed E-GAME-001 (notif→game) + wired dark-theme art + nav/button/image QA sweep (0 FATAL) | NEXT ACTION: user to commit working tree; optional P3 J-OBS + low-risk deferred later.` +- **Build:** working tree ahead of HEAD `23dd6a7` — **uncommitted** (per user: I no longer commit). Pending: notification→game fix (AndroidManifest singleTop + QuestionSessionRepositoryImpl server-first read), `drawable-night-nodpi/` dark art (+ prior art-drop commits already in history). - **Devices / accounts:** emulator-5554 = QA (`Y05AKO2IlTPMa0JQW1BiNIM0uzK2`) · emulator-5556 = Sam (`imDjjO…`) · paired, coupleId `Xal3Kw3gjSdn0niERYKJ`, both free (baseline restored). - **Docs:** Playbook `ClaudeQAPlan.md` · Coverage `ClaudeQACoverage.md` · Ideas `Future.md` `## QA` · Branding `ClaudeBrandingReview.md`. @@ -34,6 +34,7 @@ A-001 · A-003 · A-OBS · B-001 · B-002 · B-003 · B-004 · C-CC-001 · C-DS- - **Robustness:** malformed/abusive deep-link intents (unknown type, missing extras, injection/path-traversal) → 0 crash; killed-state cold-start chat deep-link → conversation loads. ## Round history (one line each) +- **Notif→game fix + dark art + QA sweep (2026-06-26, uncommitted).** **E-GAME-001 (P1, FIXED+VERIFIED):** game notifications "led nowhere" — backgrounded/warm taps landed on Home (MainActivity was standard launch mode → `onNewIntent` never delivered the tap's extras → `pendingDeepLink` unset), and even when routed, the game screen showed *setup* instead of joining (one-shot `getActiveSessionForCouple` raced the post-push Firestore sync → returned stale-empty). Fixes: `AndroidManifest` `MainActivity launchMode=singleTop` + `QuestionSessionRepositoryImpl.getActiveSessionForCouple` now SERVER-first (cache fallback). **Verified live:** Sam backgrounded → taps partner_started_game → lands IN the active This-or-That (1/10), joined, no duplicate session; back-stack sane (game→back→Home→back→exit, C-NAV-001 holds). Generic across game types (shared routing + getActiveSession). **Dark-theme art:** 12 `_dark` variants → `drawable-night-nodpi/` (light names) so dark mode auto-swaps; verified live (Security shows the aubergine variant on dark; light unchanged). **QA sweep:** tabs both themes, deep-link back-stack, all 12 illustrations both themes — **0 FATAL**, baseline intact. - **Brand art drop (2026-06-26) — wired + QA-swept, 0 issues.** All 11 generated illustrations (A1–A12, source gitignored) wired into their screens via shared `EmptyState` + new `BrandIllustration` helper (commits `077a408`→`5868d06`). **Complete both-theme sweep:** in-context dark **and** light verified for Bucket List (A6), Quiet hours (A9), Security (A11), Delete account (A12) — all render as crisp rounded tiles, on-brand, no clipping/contrast issue; A1 (transparent), A3 (banner) + the empty-only states (A2/A4/A5/A8/A10, unreachable on the baseline couple) verified via the debug Art-preview gallery on both themes + the proven shared tile. **0 FATAL/ANR** both devices; baseline intact (0 sessions/outcomes). Process catch: 5556 was on a stale build mid-sweep → reinstalled current, both now on `768f511`. Details in `ClaudeBrandingReview.md`. - **R9** — clean confirmation round (**0 new findings**): confirmed + pruned I-001/I-002 (0 outcomes denials/CCE on the fixed build); swept deferred Pass C deep/list screens (Answer History, Activity, Bucket List, Date Match/Matches — both themes) + Pass F network (offline cache render + clean reconnect). 0 open P0–P2. - **R8** — F-RACE-001 re-confirmed + pruned; Passes I (perf) + J (a11y) run; found+fixed+verified **I-001 & I-002** (outcomes read: query rules-denied + Long/Int parse CCE → "Your Progress" was silently dead). 0 open P0–P2.