docs(future): reconcile the QA backlog with what actually shipped

Three claims had gone stale and one was still true; verified each against the
tree rather than assuming.

Corrected:
- sexual_preferences non-binary configs are 110/150, not 48 — the pack was
  rewritten to the 150-cap since the item was filed.
- The androidTest item still described a single first-cut suite; there are now
  three (FirstRunRenderSmoke, HomeContentRenderSmoke, AssetDatabaseVerify).
- daily_fun_mc sex='neutral' re-verified as still true (all 511) on the rebuilt
  DB, so the item stays open on purpose.

Added a Content pipeline & app size section recording what shipped this cycle
and what is still open — none of it was tracked anywhere: build_db.py could not
produce a loadable database, the shipped bank was 2,292 questions out of sync
(wildcards and quality_time were entirely absent from the app), scale labels
were blank because the DB and the parser disagreed on case, and artwork went to
WebP (104M -> 28M; APK 141.9 -> 128.8 MB) after two tracked .bak databases were
found shipping inside assets/.

Still open, newly recorded: pack art is nodpi at 1024x512 (~3x the pixels the
~360dp card needs, and nodpi means no density split, so every device downloads
all 11.5 MB); and nothing in CI validates the seed JSON — every gate we have
runs only when a human remembers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-14 21:50:04 -05:00
parent 7700c84e4d
commit eb254d9a9c
1 changed files with 42 additions and 4 deletions

View File

@ -83,6 +83,36 @@ tokens R16, add-FAB `Color(0xFFB98AF4)`→`primary` R18.)_
## QA
### Content pipeline & app size (2026-07-14)
**✅ DONE — the seed→app path works and is now guarded.**
- **`build_db.py` produced an unloadable database** (2 tables vs `AppDatabase`'s 4 entities, no `room_master_table`);
with `createFromAsset` + no migrations/fallback that is a first-launch crash for every user. Hence the folk rule
*"never run build_db.py"* — a broken tool guarded by memory. Rebuilt to derive schema + identity hash from Room's own
exported schema, so it follows future schema changes instead of drifting again. Content problems are now **hard
failures** (parse error, 0 questions, missing category, dup id/text, non-integer depth, choice without options) —
previously it silently skipped a whole pack or filed it under a bogus `unknown` category. Guarded by
`AssetDatabaseVerifyTest`.
- **The shipped bank was 2,292 questions out of sync with the JSON** (6,103 → 3,811). Rebuilt: **+11 wildcards** (the
`DailyModeResolver` feature was built but had *zero* content, so wildcard days were dark), **+150 quality_time** (a
category the app could not show at all), `pack_id` populated on all 3,811 (the column was plumbed to the domain model
but always NULL — it's the hook a purchased/imported pack needs).
- **Scale labels were blank in the shipped app** — the DB stored `min_label`, the only parser
(`QuestionMapper.parseAnswerConfig`) reads `minLabel`, so `optString("minLabel","")` returned `""` and the UI rendered
bare numbers. The rebuild emits the shape the parser reads.
- **Artwork → WebP** (124 files, 104M → 28M on disk; APK 141.9 → 128.8 MB; pack art 26.4 → 11.5 MB). Each file encoded
lossless *and* q95, smaller kept, lossy accepted only at PSNR ≥ 40 dB; dimensions/alpha verified per file. Also
stopped shipping ~4MB of dead weight: `app.db.bak_q4`/`bak_q5` were **tracked inside `assets/`**, and everything under
`assets/` is packaged.
**Still open:**
- **Pack art is oversized for its slot.** 23×2 illustrations at **1024×512** in `nodpi``nodpi` means **no density
split**, so every device downloads all 11.5 MB regardless of screen. The cards render at ~360dp wide, so this is
roughly 3× the pixels needed. Ship per-density variants (or one 2× asset) and the number drops again.
- **Nothing in CI validates the seed JSON.** `scripts/nav-scan.sh`, `theme-scan.sh` and the variety gate inside
`build_db.py` all only run when a human remembers. The catalog gate is the natural first CI addition — it is fast,
hermetic, and would have caught the duplicate text that blocked the importer earlier this cycle.
### From R31 (2026-07-09) — QA-environment observations (not app defects)
- **Add the RevenueCat `test_` SDK key to `local.properties` on the QA emulators.** The paywall currently can't load real offerings there (`RC_API_KEY` absent → `InvalidCredentialsError` → graceful "Couldn't load plans / Try again"). Now that RevenueCat is configured (Test Store + `closer_premium` entitlement, corrected this cycle), seeding the `test_…` key would let QA drive the *live* paywall (plan pills, purchase flow via the Test Store) instead of only the gate + the error state. The gate itself (free → Paywall) is verified; this unlocks the money-adjacent surface short of a real device.
- Minor: reinstalling the debug APK rotates the FCM token; the app re-registers a fresh one on foreground but stale tokens linger for ~33h until `pruneTokens` clears them on a real send. `qa/qa_push.js` now selects the newest token (was `docs[0]`), so the entrypoint smoke no longer false-FAILs after a reinstall.
@ -108,7 +138,7 @@ Still open:
- **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 analytics** — first-answer / reveal-viewed / waiting-abandoned / notif-tap events: the one piece of the R29 retention list not built.
- **Content retag**`daily_fun_mc` rows still carry `sex='neutral'` in the asset DB; after C3's HowWell-side exclusion this is purely Desire Sync pool hygiene (benign today via the binary filter). 48/150 sexual_preferences items are non-binary configs (Wheel-only) — intended?
- **Content retag**`daily_fun_mc` rows still carry `sex='neutral'` in the asset DB (**re-verified 2026-07-14 on the rebuilt DB: all 511**); after C3's HowWell-side exclusion this is purely Desire Sync pool hygiene (benign today via the binary filter). _(Count corrected 2026-07-14: sexual_preferences non-binary configs are now **110 of 150**, not 48 — the pack was rewritten to the 150-cap since this was filed. Still the same question: intended?)_
- **npm audit** — 9 moderate remain after the 2026-07-08 firebase-functions v7 bump (retry-request/teeny-request via @google-cloud/storage, i.e. **firebase-admin** transitive — untouched by the functions bump); revisit on the next firebase-admin major. _(Re-verified 2026-07-11: still 9 moderate, unchanged set; deliberately not running `npm audit fix` — no lockfile churn on the production backend for documented moderates.)_
- **`scheduledOutcomesReminder` scaling cap** — the daily cron scans `couples` with a flat `.limit(200)` and no pagination (`functions/src/couples/scheduledOutcomesReminder.ts:35`), so couples beyond the first 200 silently never get 30/60/90-day outcome nudges. Fine pre-launch; **before the userbase approaches ~200 couples**, paginate with the same `orderBy(__name__)+startAfter` page loop `assignDailyQuestion` already uses (see `assignDailyQuestion.ts` PAGE_SIZE pattern).
- **Compose stability: adopt `ImmutableList` for `*UiState` list/set fields (codebase-wide).** Strong-skipping (K2 default) is on, and the Home refactor added `@Immutable` to the Home card models — but `List<>`/`Set<>` params still only reference-skip. Add the `kotlinx.collections.immutable` dependency (JetBrains, first-party) and switch `*UiState` list/set fields to `ImmutableList`/`ImmutableSet` (with `.toImmutableList()` at each VM construction site) so repeated card/list params become structurally skippable. **Do it consistently across all ~20 screens in one pass — not per-screen — to avoid a snowflake.** `ui/home/HomeModels.kt` is already partly there (`@Immutable` on `HomeAction`/`PendingActionCard`/`HomeCategorySummary`), so Home is the natural reference implementation.
@ -153,10 +183,18 @@ Improvement & feature ideas surfaced while QA-testing as a consumer (each works
`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
**Grown since (2026-07-14) — now 3 real suites, same "prove it fails when the bug returns" bar:**
`ui/home/HomeContentRenderSmokeTest.kt` (R31 — renders the VM-free `PairedHomePreviewScreen` light+dark, the net for
the Home decomposition) and `data/local/AssetDatabaseVerifyTest.kt` (4 tests — forces a real Room open of the bundled
`app.db` via `openHelper.readableDatabase`, which is what triggers the copy + schema/identity check, then asserts
content, no orphan `category_id`s, integer depth, and the camelCase `answer_config` contract). That last one exists
because `build_db.py` had drifted into producing a database Room could not load at all — a first-launch crash for
every user that nothing would have caught.
**Still to grow:** all three are Hilt-/Firebase-free (leaf composables + a raw DB open) — 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).
`connectedDebugAndroidTest` into the per-round gate / CI alongside `qa/entrypoint_smoke.sh`. **Note the running
theme:** every one of these suites was written *after* the class of bug it guards had already shipped. *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