Closer/docs/qa/private-mvp-checklist.md

417 lines
20 KiB
Markdown
Raw Normal View History

# Closer — Private MVP QA Checklist
> Manual testing checklist for the internal MVP build. Covers every top-level flow in the app and notes known gaps discovered during the 2025-06 QA pass.
> Last updated: 2026-06-21 — reflects pairing security hardening, notification preferences, appearance screen, and email invite removal.
---
## Environment & Setup
- [ ] Clean install on test device (API 26+).
- [ ] Test device/emulator has network connectivity.
- [ ] Check non-network build: `./gradlew :app:compileDebugKotlin` passes.
- [ ] Verify `RC_API_KEY` in `local.properties` (or a placeholder) so billing build does not fail.
- [ ] Google-services JSON is present for the selected build variant.
---
## 1. Onboarding & First Launch
### 1.1 Splash / App launch
- [ ] App launches to `MainActivity` without crash.
- [ ] `AppNavigation` starts at `AppRoute.ONBOARDING` on fresh install.
- [ ] Theme applies (`CloserTheme`) and status bar / navigation bar insets handled correctly.
- [ ] App name in launcher is "Closer".
### 1.2 Onboarding screen (`OnboardingScreen`)
- [ ] Value proposition visible and not placeholder text.
- [ ] CTA navigates to `CREATE_PROFILE` or `LOGIN` as expected.
- [ ] Secondary sign-in link works.
- [ ] No dead-end: user can always reach either create profile or login.
### 1.3 Create profile (`CreateProfileScreen`)
- [ ] Name input accepts text and reflects in UI.
- [ ] Validation prevents empty submission.
- [ ] Profile creation succeeds and navigates forward (home or pairing flow).
- [ ] Loading / error states handled.
### 1.4 Login / Sign up / Forgot password
- [ ] `LoginScreen`: email/password fields, sign-in call, error shown on failure, success navigates to `HOME`.
- [ ] `SignUpScreen`: account creation, weak-password validation, navigates to profile creation.
- [ ] `ForgotPasswordScreen`: email reset flow, success/error messaging.
- [ ] Back navigation from auth screens returns to onboarding.
### 1.5 Known onboarding gaps (from code scan)
- `AccountScreen` shows "Local profile" and disables "Sign in or create account" and "Export your data" rows. These need real wiring before public release.
- Home header text changes based on `partnerName` presence; confirm both states render.
---
## 2. Pairing
### 2.1 Create invite (`CreateInviteScreen`)
- [ ] Generates 6-character code.
- [ ] Code displays with chunked formatting (e.g., "ABC 123").
- [ ] Copy button copies raw code to clipboard and shows snackbar.
- [ ] Share button opens system share sheet with correct message.
- [ ] "Partner already has a code? Accept instead" navigates to `ACCEPT_INVITE`.
- [ ] Code expiry note shown ("expires in 24 hours").
### 2.2 Accept invite (`AcceptInviteScreen`)
- [ ] 6-character code entry field with visual chunking and uppercase auto-capitalization.
- [ ] Continue enabled only when code length == 6.
- [ ] Invalid/expired code shows error.
- [ ] Valid code navigates to `INVITE_CONFIRM/{inviteCode}`.
- [ ] "Need to create an invite instead?" link works.
- [ ] Keyboard Done triggers lookup.
### 2.3 Invite confirm (`InviteConfirmScreen`)
- [ ] Inviter name loaded and displayed.
- [ ] Pairing confirmation button shows loading spinner.
- [ ] Success navigates to home.
- [ ] "Not right — enter a different code" returns to accept screen.
- [ ] Error surfaced via snackbar.
- [ ] Recovery phrase is **not** prompted during confirm — it is returned automatically from the server and stored silently.
- [ ] No recovery phrase input field visible on this screen.
### 2.4 Share invite (was: Email invite — removed)
> `EmailInviteScreen` and the `EMAIL_INVITE` route have been deleted. Sharing is now handled entirely via the system share sheet in `CreateInviteScreen` / `CreateInviteView`. There is no separate email flow or backend email sending.
- [ ] **Android**: "Share" button on `CreateInviteScreen` opens system share sheet with invite message.
- [ ] **iOS**: "Share" button on `CreateInviteView` opens `UIActivityViewController` with the code.
- [ ] Share sheet offers SMS, email, Signal, WhatsApp, etc. — no Closer-specific channel required.
- [ ] Settings → Connection "Invite Partner" row navigates to `CreateInviteView` (not email view).
### 2.5 Relationship settings
- [ ] `SettingsScreen` partner card opens `RELATIONSHIP_SETTINGS` when paired.
- [ ] Leave couple confirmation dialog shown.
- [ ] Leave action calls repository and navigates to `CREATE_INVITE` on success.
- [ ] Error shown on failure.
---
## 3. Home
### 3.1 Home screen (`HomeScreen`)
- [ ] Loads without crash; loading, error, and success states all tested.
- [ ] Header shows correct subtitle for paired vs. unpaired user.
- [ ] Streak pill appears when `streakCount > 0`.
- [ ] Primary action card responds to tap.
- [ ] Secondary action feed renders and navigates.
- [ ] "More doorways" grid shows up to 2 categories; tap navigates to category.
- [ ] "All packs" button navigates to `QUESTION_PACKS`.
- [ ] Pull/refresh or retry on error works.
### 3.2 Partner home (`PartnerHomeScreen`)
- [ ] Renders `PlaceholderScreen` with correct copy.
- [ ] Actions navigate to invite flow or home.
- [ ] **Gap**: screen is a placeholder; not a functional partner dashboard yet.
### 3.3 Moment cue / special dates section
- [ ] `SpecialDatesSection` previews render.
- [ ] Home moment cue card text not placeholder.
- [ ] Hardcoded names ("Jessica", "Mark") and dates in `SpecialDatesSection` must be replaced with real data before public release.
---
## 4. Daily Question
### 4.1 Daily question screen (`DailyQuestionScreen`)
- [ ] Question text loads.
- [ ] Answer input accepts text and respects private/public toggle.
- [ ] Save/submit works, shows feedback.
- [ ] Navigates to answer reveal or answer history appropriately.
- [ ] Discussion section handles un-revealed state gracefully.
### 4.2 Answer input components
- [ ] `QuestionAnswerInput`: empty-state handling, formatting, IME actions.
- [ ] `QuestionHelpExpandable`: expand/collapse works.
- [ ] `QuestionDiscussionThread`: disabled state copy is clear.
- [ ] `QuestionNavigationBar`: previous/next navigation and disabled states.
### 4.3 Question thread (`QuestionThreadScreen`)
- [ ] Loads question and optional `prevId`/`nextId` args.
- [ ] Previous / next navigation works when ids provided.
- [ ] Both partner answers visible after reveal.
- [ ] Comments can be added (if unlocked).
- [ ] Back button returns to prior screen.
---
## 5. Question Packs & Categories
### 5.1 Question pack library (`QuestionPackLibraryScreen`)
- [ ] Loading, error, empty states tested.
- [ ] Categories/packs list scrolls.
- [ ] Free and premium locks indicated.
- [ ] Tap on premium pack navigates to `PAYWALL`.
- [ ] Tap on free/mixed pack navigates to `QUESTION_CATEGORY/{categoryId}`.
### 5.2 Question category (`QuestionCategoryScreen`)
- [ ] Loads questions for `categoryId`.
- [ ] Format and depth filters apply.
- [ ] Question cards navigate to `QUESTION_THREAD`.
- [ ] Locked premium packs route to paywall.
- [ ] Loading/error handled.
### 5.3 Question composer (`QuestionComposerScreen`)
- [ ] User can create a custom question.
- [ ] Category/depth/format options available.
- [ ] Save creates question and returns.
- [ ] Validation prevents empty submission.
---
## 6. Answers & Reveal
### 6.1 Answer reveal (`AnswerRevealScreen`)
- [ ] Loads answer for `questionId`.
- [ ] Private answer shown only to author until reveal.
- [ ] Reveal action updates state and reveals both answers.
- [ ] Loading, error, empty states tested.
- [ ] Navigation to discussion or history works.
### 6.2 Answer history (`AnswerHistoryScreen`)
- [ ] List of answered/revealed questions loads.
- [ ] Empty state shown when no answers.
- [ ] Remove action (if available) works and updates list.
- [ ] Tap navigates to thread/reveal.
---
## 7. Spin Wheel
### 7.1 Category picker (`CategoryPickerScreen`)
- [ ] Categories load; loading, error, empty states tested.
- [ ] Locked categories show lock icon and "Premium" pill.
- [ ] Locked tap routes to `PAYWALL`.
- [ ] Free tap routes to `SPIN_WHEEL/{categoryId}`.
### 7.2 Spin wheel (`SpinWheelScreen`)
- [ ] Category name displayed.
- [ ] Spin action triggers selection of `SpinWheelViewModel.SESSION_SIZE` questions.
- [ ] Visual spinning animation runs.
- [ ] Error surfaced.
- [ ] Ready state shows count and enables "Start session".
- [ ] "Spin again" re-rolls selection.
### 7.3 Wheel session (`WheelSessionScreen`)
- [ ] Loads session via `sessionId`.
- [ ] Progress indicator updates.
- [ ] Current question displays centered.
- [ ] Next / skip / end session buttons work.
- [ ] Empty session state shown when no active session.
- [ ] Last question button label reads "Finish".
- [ ] Navigates to `WHEEL_COMPLETE/{sessionId}` on finish.
### 7.4 Wheel complete (`WheelCompleteScreen`)
- [ ] Shows category name and answered/total count.
- [ ] Saves session to repository on init.
- [ ] "Back home" navigates to `HOME`.
- [ ] "Spin again" navigates to `CATEGORY_PICKER`.
- [ ] Handles 0-question edge case gracefully.
### 7.5 Wheel history (`WheelHistoryScreen`)
- [ ] Premium lock card shown for non-premium users.
- [ ] Premium users see list of completed sessions.
- [ ] Empty state with "Spin now" action.
- [ ] Error/retry works.
- [ ] Date formatting uses default locale.
---
## 8. Dates
### 8.1 Date match (`DateMatchScreen`)
- [ ] Swipe cards render.
- [ ] Match state shows correctly.
- [ ] Premium gating tested.
- [ ] Error/loading handled.
### 8.2 Date matches (`DateMatchesScreen`)
- [ ] List of matched date ideas loads.
- [ ] Empty state shown.
- [ ] Tap navigates to detail/builder.
### 8.3 Date builder (`DateBuilderScreen`)
- [ ] Fields render: date, time, budget, duration.
- [ ] **Gap**: date and time fields show "TODO: Date picker dialog" / "TODO: Time picker dialog"; they are not interactive yet.
- [ ] Budget input accepts digits only.
- [ ] Duration chips selectable.
- [ ] Save button calls view model.
- [ ] Back navigation works.
### 8.4 Bucket list (`BucketListScreen`)
- [ ] Items load; empty state shown.
- [ ] Category filter chips scroll and filter list.
- [ ] Add item FAB opens dialog.
- [ ] Add dialog: title required, description optional, category selectable.
- [ ] Toggle complete updates card style.
- [ ] Delete removes item.
- [ ] Back navigation works.
---
## 9. Settings
### 9.1 Settings home (`SettingsScreen`)
- [ ] Profile card shows display name / email or local profile note.
- [ ] Partner card shows paired state and partner name, or invite prompt.
- [ ] Tapping profile card opens `ACCOUNT`.
- [ ] Tapping partner card opens `RELATIONSHIP_SETTINGS` (paired) or `CREATE_INVITE` (unpaired).
- [ ] **Appearance** row (palette icon) present and opens `APPEARANCE` screen.
- [ ] Notifications, Subscription, Privacy & Terms rows open correct screens.
- [ ] No "Email Invite" or "Invite by Email" row present anywhere in settings.
- [ ] Legal links open external URLs.
- [ ] Delete account row opens `DELETE_ACCOUNT`.
- [ ] Sign out button works and shows loading state.
### 9.2 Account (`AccountScreen`)
- [ ] "Local profile" card shown for signed-out state.
- [ ] Disabled rows visually greyed out.
- [ ] **Recovery phrase card** shown when paired (key icon, monospaced phrase, copy button).
- [ ] Copy button copies phrase to clipboard and shows "Recovery phrase copied" snackbar.
- [ ] Recovery phrase card absent when not paired (no phrase to show).
- [ ] Delete account row navigates to `DELETE_ACCOUNT`.
- [ ] Back navigation works.
### 9.3 Notifications (`NotificationSettingsScreen`)
- [ ] **Five toggles** present: Daily question, Partner answered, New chat message, Shared rhythm reminder, Quiet hours.
- [ ] All toggles reflect persisted `AppStorage` / DataStore state on load.
- [ ] Toggling "Partner answered" or "New chat message" writes `notifPartnerAnswered` / `notifChatMessage` to Firestore user doc (verify in Firebase console).
- [ ] Daily reminder and streak reminder toggles persist locally only (no Firestore write required).
- [ ] Quiet hours description accurate (10 PM 8 AM).
- [ ] **iOS parity**: same two server-synced toggles (Partner answered, New chat message) present in iOS Notification Settings and sync to Firestore.
### 9.4 Appearance (`AppearanceScreen`)
- [ ] Three theme options: Device default, Light, Dark.
- [ ] Selecting a theme applies immediately across the app — no restart required.
- [ ] Selection persists after closing and reopening the app.
- [ ] Back navigation returns to Settings.
- [ ] "Device default" follows system dark/light mode correctly.
### 9.4 Privacy (`PrivacyScreen`)
- [ ] External links open in browser.
- [ ] No browser available case handled via `ExternalLinks.openUrl` Toast fallback.
- [ ] Back navigation works.
- [ ] Support URL resolves correctly — now `https://closer.app/support`.
### 9.5 Subscription (`SubscriptionScreen`)
- [ ] **Free state**: star icon, "Unlock Premium" header, benefits list, Upgrade button navigates to `PAYWALL`, Restore link.
- [ ] **Premium state**: "You're Premium" card, renewal date (when available), benefits list, "Manage subscription" opens Play Store, Restore link.
- [ ] Restore purchases shows snackbar on success; error surfaces via snackbar.
- [ ] Reads entitlement reactively — upgrading mid-session reflects immediately without restart.
- [ ] **Note**: Requires real RevenueCat API key + active product to fully test both states.
### 9.6 Relationship settings / Delete account
- [ ] See pairing section for leave-couple flow.
- [ ] Delete account confirmation dialog requires acknowledgment checkbox.
- [ ] Delete action calls `AuthRepository.deleteAccount()` and navigates to onboarding.
- [ ] Loading and error states shown.
---
## 10. Paywall
### 10.1 Paywall screen (`PaywallScreen`)
- [ ] Loads RevenueCat packages; loading/error handled.
- [ ] Benefits list accurate.
- [ ] Selecting plan updates selection state.
- [ ] Purchase button enabled only when plan selected.
- [ ] Purchase call uses current activity context.
- [ ] Restore purchases button works.
- [ ] Legal links open externally.
- [ ] Thank-you overlay shown on success; dismiss returns to previous screen.
- [ ] **Note**: Requires real RevenueCat API key and product configuration to fully test.
---
## 11. Notifications
### 11.1 FCM service (`AppMessagingService`)
- [ ] App receives push when in foreground and background.
- [ ] Quiet hours suppression works (10 PM 8 AM).
- [ ] Notification channel created on Android O+.
- [ ] Tap action navigates to relevant screen.
### 11.2 Token registrar
- [ ] Token updates sent to backend/user document.
- [ ] Handles sign-out / re-sign-in correctly.
### 11.3 Partner answered notification (`onAnswerWritten` CF)
- [ ] Push received on partner's device when you submit a daily question answer.
- [ ] No push received when "Partner answered" toggle is off in Notification Settings.
- [ ] Push not sent to the answering user themselves — only the partner.
### 11.4 Chat message notification (`onMessageWritten` CF)
- [ ] Push received on partner's device when a message is sent in a question thread.
- [ ] No push received when "New chat message" toggle is off in Notification Settings.
- [ ] Push not sent to the message author themselves.
- [ ] **Deployment note**: `onMessageWritten` is a new function — must run `firebase deploy --only functions` before this test is possible.
---
## 12. General UX / Edge Cases
### 12.1 Navigation
- [ ] Bottom nav shows on top-level routes only.
- [ ] System back from top-level routes does not exit unexpectedly (navigate back within app).
- [ ] Shell top bar appears on `shellBackRoutes` and uses route title.
- [ ] Deep links / invite-confirm route handled: `invite_confirm/{inviteCode}`.
### 12.2 Input / Accessibility
- [ ] Text fields have proper keyboard options (email, ASCII, capitalization).
- [ ] Labels / placeholders readable.
- [ ] Touch targets >= 48 dp for all clickable surfaces.
- [ ] Max lines and ellipses used to prevent overflow clipping.
### 12.3 Theming
- [ ] Light theme renders consistently (current primary theme).
- [ ] Dark theme colors defined but not wired to system dark mode (`isSystemInDarkTheme()` not used). Verify this is intentional for MVP.
- [ ] No stale Next.js / React Native references in resources or manifests.
### 12.4 Data / Offline
- [ ] App does not crash when offline (graceful error cards).
- [ ] Local answer repository persists data.
- [ ] Firebase calls fail cleanly and retry available where exposed.
---
## 13. Release Blockers Logged from This QA Pass
These findings came from the static review and should be fixed before public or store release. Do **not** block internal MVP on these unless explicitly required.
| # | Area | Issue | Severity | Status |
| --- | --- | --- | --- | --- |
| 1 | Date builder | Date/time picker TODOs — fields are not interactive | High | **Not an issue**`DatePickerDialog` and `TimeInput` are already implemented and wired |
| 2 | Special dates | Hardcoded names/dates in `SpecialDatesSection` | High | **Not an issue**`SpecialDatesSection` is dead code, never rendered; home shows honest placeholder copy |
| 3 | Email invite | Placeholder screen with hardcoded `ABC123` code | Medium | **Fixed** — screen deleted; share sheet is the flow |
| 4 | Subscription | Placeholder screen, not real management | Medium | **Fixed** — real `SubscriptionViewModel` reads `EntitlementChecker.isPremium()` + `CustomerInfo`; free state shows benefits + Upgrade button; premium state shows renewal date + "Manage subscription" → Play Store + Restore |
| 5 | Partner home | Placeholder screen only | Medium | **Fixed** — real `PartnerHomeViewModel` + screen with partner identity card, today activity status, send-nudge button, and navigation wired from HomeScreen streak card tap |
| 6 | Settings | Account rows disabled ("Auth coming soon", "Export coming soon") | Medium | **Not an issue**`AccountScreen` no longer has those rows; only Delete account row present |
| 7 | External links | Support URL points to `couplesconnect.app/support` | Low | **Fixed** — updated to `https://closer.app/support` in `ExternalLinks.kt` |
| 8 | Strings | 100+ hardcoded display strings; should move to `strings.xml` for localization | Low | **Partial**`strings.xml` built with 90+ entries; settings cluster (Appearance, Notifications, Account, Privacy) updated to use `stringResource()`; remaining screens (Home, Pairing, Daily Question, etc.) still hardcoded |
| 9 | Logging | `android.util.Log.e` used in `QuestionJsonParser` — confirm no verbose/debug logs in release builds | Low | **Fixed** — no sensitive data in any log; `Log.d`/`Log.v` stripped in release via `-assumenosideeffects` ProGuard rule |
| 10 | Pairing security | Direct Firestore fallback in `createInvite` bypassed server-side rules | High | **Fixed** — fallback removed; CF is the only path |
| 11 | Pairing security | No rate limiting on `acceptInviteCallable` — 6-char codes are enumerable | High | **Fixed** — 10 attempts/hour per user; `invite_attempts` TTL via Firestore field override |
| 12 | Pairing security | `recoveryPhrase` left in plaintext on invite doc post-accept | High | **Fixed** — wiped via `FieldValue.delete()` in accept batch |
| 13 | Pairing security | `encryptionVersion` hardcoded to 2 even when no E2EE fields present (iOS) | High | **Fixed** — derived from key presence: 2 if E2EE, 0 if plaintext |
| 14 | Notifications | No push sent when partner answers or sends a chat message | Medium | **Fixed**`onAnswerWritten` gated on prefs; `onMessageWritten` CF added |
| 15 | Notifications | Notification prefs were local-only; server CFs had no way to respect them | Medium | **Fixed** — prefs synced to Firestore user doc on toggle (Android + iOS) |
| 16 | Functions | `invite_attempts` subcollection had no cleanup — would grow forever | Medium | **Fixed**`expiresAt` TTL field added; `firestore.indexes.json` configures auto-delete |
| 17 | Deploy | `onMessageWritten` CF + `invite_attempts` TTL not yet live — run `firebase deploy --only functions && firebase deploy --only firestore:indexes` | Medium | **Pending** — code complete; deploy step requires Firebase CLI access from the project owner |
---
## Sign-off
| Tester | Date | Build | Result |
|--------|------|-------|--------|
| | | | |