docs(seed): replace question guides with v2 — content guide, rewrite plan, new quality checklist
This commit is contained in:
parent
f47c8e2b64
commit
4686a2c200
|
|
@ -1,6 +1,7 @@
|
|||
# Claude QA Coverage Matrix
|
||||
|
||||
> Resume anchor. Status: `todo | pass | fail(→id) | n/a`. See `ClaudeReport.md` run-state header for current position.
|
||||
> **Round 6 (branding + Future.md regression) COMPLETE 2026-06-25, client `f47c8e2`:** new surfaces from `95cad84` (white-keyhole icons, animated chip+fill loader, splash, pairing hero) + `f47c8e2` (inclusive gender, turn copy, push-budget split, results-suppression `ActiveGameSessionMonitor`, paywall retry/offline/hide-Continue, auth rotator). **0 new issues; still 0 open P0–P3.** Live: loader (both themes), splash→handoff, launcher icon, ToT+How Well open (no crash → #4 VM injection sound), paywall purchase screen (friendly error + Try again + Continue hidden, online→generic msg), onboarding illustration. Unit tests green. Gender step / rotator / turn-copy / results-timing / weekly-cap = code+unit-verified (live deferred: fragile multi-text-field & 2-device timing; low risk over proven patterns). Baseline restored (QA re-signed-in via admin token; couple intact).
|
||||
> **Round 5 (functions deploy + expanded re-QA) COMPLETE 2026-06-25, client `765916a` + functions DEPLOYED:** E-OBS FIXED+DEPLOYED (12 senders set channelId; chat push → `partner_activity` live) + E-003 results-ready FIXED+DEPLOYED (finished-game → per-session results). **0 open P0–P3.** New Pass G (account creation + fake-account) clean. Varied gameplay (Standard/Deep, 0-match) + nav fuzzing — no new bugs. Baseline restored (couple intact, throwaway deleted, Sam re-paired).
|
||||
> _Round 4: E-003 + B-004 (P2) + A-OBS (P3) FIXED + verified live._
|
||||
|
||||
|
|
@ -62,6 +63,19 @@ _Deferred (nav-drift; standard list/detail, lower-risk): Question Packs detail,
|
|||
Answer Reveal (sealed), Date Builder/Plan Date, fresh-account auth/onboarding/pairing._
|
||||
|
||||
## Pass D — Security & Encryption (D1–D6)
|
||||
**R7 DEEP DIVE (multi-angle, 2026-06-25):** **D1 at-rest — CLEAN (admin ground-truth read):** messages `text` +
|
||||
`lastMessagePreview`, all 4 game-answer collections (`this_or_that`/`how_well`/`desire_sync`/`wheel`, both users),
|
||||
capsule title+content, `date_swipes.actions` = `enc:v1:`; `wrappedCoupleKey` = ciphertext (recovery-phrase-wrapped,
|
||||
**argon2id**); `encryptedRecoveryPhrase` server-blind + **wiped on acceptance** (confirmed absent on accepted invite);
|
||||
plaintext `inviteCode` **not exploitable** (no code-encrypted secret persists; `/invites/{code}` readable only by
|
||||
inviter). **D3 raw-API negative (LIVE, executed — no longer deferred):** non-member ID token (Identity Toolkit
|
||||
`signInWithCustomToken`) → Firestore REST on couple doc/conversation/messages/answers/session/capsules/partner-profile
|
||||
= **all `403 PERMISSION_DENIED`**; non-member writes (couple doc, partner entitlement, **real path
|
||||
`users/{uid}/entitlements/premium`**) = **all `403` → no self-grant**. Member token reads `200` (characterizes layer:
|
||||
**App Check not enforced on Firestore — rules are the sole gate, and they hold**). Only writable = cosmetic own-doc
|
||||
fields (`plan`) that **no gate reads**. **No P0/P1 security findings.** Two hardening notes → `Future.md`.
|
||||
|
||||
|
||||
**R3:** D2 deployed rules re-audited ✅ (B-001 sessions + D-001 capsules/challenges fixes present; hasPremium +
|
||||
entitlements server-only; ciphertext enforced; no catch-all). D1 at-rest ✅ (chat text + lastMessagePreview =
|
||||
`enc:v1:`; how_well answers + capsules = `enc:v1:`). D4/D5/D6 unchanged since R1 (code identical) → hold.
|
||||
|
|
@ -69,6 +83,22 @@ entitlements server-only; ciphertext enforced; no catch-all). D1 at-rest ✅ (ch
|
|||
statically member-scoped). No P0/P1 security findings.
|
||||
|
||||
## Pass E — Notifications (17 types × {foreground, background, killed} + tap-to-open)
|
||||
**R6 live (games + messages, 2026-06-25, build `f47c8e2`):** full live two-device run.
|
||||
- **chat_message** ✅ end-to-end: Sam→QA (QA bg) posts on **channel=partner_activity**, title "Sam sent a message"
|
||||
(partner name, not private), body "Tap to read and reply." — **message text NOT in payload** (privacy holds);
|
||||
small icon = white monochrome mark; tap→**main conversation with content** (verified via the exact intent —
|
||||
shade-tap is flaky in the adb harness, lands on launcher, but the contentIntent routing is sound).
|
||||
- **partner_started_game** ✅: QA started This or That → Sam (bg) posts on **channel=game_activity**, "QA is playing /
|
||||
QA has started a game. Tap to join!" (content-free); tap→**joins the active session** (same 1/5 prompt).
|
||||
- **partner_finished_game / results** ✅: both finished → **results push DELIVERED to backgrounded QA** (Round 5
|
||||
couldn't confirm this live) on **channel=game_activity**, "Sam finished the game / Sam finished — tap to see your
|
||||
results!" (content-free); tap→**per-session This or That results** (5/5), per E-003.
|
||||
- **#4 results-suppression** ✅: Sam stayed foreground on the session throughout → received **0** notifications
|
||||
(the partner_completed_part + partner_finished_game pushes to Sam were suppressed by ActiveGameSessionMonitor),
|
||||
while backgrounded QA got the results push. Clean confirmation of both delivery + suppression.
|
||||
- No FATAL either device; baseline tidy (0 active sessions, couple intact). **No issues found.**
|
||||
|
||||
|
||||
**R3 live:** FCM tokens valid for both. **chat_message ✅ full chain** (bg deliver + content-free + tap→exact
|
||||
conversation w/ content). **partner_started_game**: bg deliver + content-free ✅; tap→Play hub (not the game) =
|
||||
**E-003 (P2)**. **E-OBS (P3)**: bg pushes use fcm_fallback channel. date_match live-verified R2-B2. E-001/E-002 fixes
|
||||
|
|
|
|||
|
|
@ -81,6 +81,30 @@ confirms + enumerates this; the fix phase applies couple-shared everywhere.
|
|||
- **Test-data hygiene:** keep known test accounts; clean up artifacts (stray messages/reactions/sessions) between
|
||||
rounds so they don't masquerade as bugs.
|
||||
|
||||
## 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
|
||||
field, gate a premium feature, deliver/route a notification, start/finish a game, pair/unpair, create an account)
|
||||
from as many **independent angles** as apply — not just the in-app happy path:
|
||||
- **Real UI** (play-as-user) — the baseline angle.
|
||||
- **Crafted intent / deep-link** — fire the exact intent a notification/link carries (bypasses UI nav) to test routing
|
||||
in isolation; also send **malformed/missing extras** → must route gracefully or no-op, never crash.
|
||||
- **Raw API against the DEPLOYED backend** — hit Firestore/Storage/Functions REST **directly** with a real token,
|
||||
as a **member AND a non-member**, to exercise rules + App Check from OUTSIDE the app. A non-member (or no-App-Check)
|
||||
request must be **DENIED** — App Check `403` or rules `PERMISSION_DENIED`. The member request characterizes which
|
||||
layer enforces. **Any unauthorized `200` returning couple data = P0.**
|
||||
- **Admin inspection (ground truth)** — read the RAW stored docs/objects (admin bypasses rules) to assert what is
|
||||
actually persisted: ciphertext only, no plaintext, no raw keys/invite-seeds, no private content in pushes.
|
||||
- **Concurrency / race** — two partners (or two rapid taps) hit the same thing at once.
|
||||
- **Killed / cold state** — force-stop, then deliver + tap a notification; cold-start straight onto a deep link.
|
||||
- **Malformed / abusive input** — oversized, empty, rapid-fire, injection-ish, forged FCM payloads, replayed/expired
|
||||
tokens & invite codes.
|
||||
- **Offline / flaky** — drop network mid-action → graceful failure, recover on reconnect.
|
||||
|
||||
Record **which angles** were tried per area in `ClaudeQACoverage.md`. For security- or data-sensitive capabilities,
|
||||
"UI happy path only" is **not** a `pass`. **D3/Pass G negative access MUST be executed live via the raw-API angle each
|
||||
round — never deferred to "only 2 emulators."** (Mint a token for a non-member UID via admin → exchange for an ID
|
||||
token via the Identity Toolkit REST `signInWithCustomToken` → use it as Bearer against the Firestore REST API.)
|
||||
|
||||
## Continuity & resumability (this effort WILL span many context windows — don't lose state)
|
||||
State lives in **files**, not memory:
|
||||
- **`ClaudeReport.md`** = the issue log (committed). Each issue row is **self-contained in text** (repro + expected
|
||||
|
|
@ -230,8 +254,12 @@ Account); Paywall; Your Progress/Activity; Recovery.
|
|||
field, immutability, **no premium self-grant**, entitlements write:false; re-audit conversations/typing/reactions
|
||||
+ entitlement partner-read; **no catch-all** `match /{document=**}`; list/query not enumerable; `get()`-rules don't
|
||||
over-expose; **no legacy plaintext/downgrade path** (`coupleEncryptionEnabled` holds; no disabled-encryption branch).
|
||||
- **D3 Negative access tests:** a **non-member** account is *denied* reading messages/answers/dates/entitlements,
|
||||
writing plaintext to encrypted fields, self-granting premium, cross-couple access (live rules or rules-emulator).
|
||||
- **D3 Negative access tests (EXECUTE LIVE via raw API — do not defer):** a **non-member** account is *denied* reading
|
||||
messages/answers/dates/entitlements/sessions/capsules, writing plaintext to encrypted fields, self-granting premium,
|
||||
and any cross-couple access. Run it the **raw-API angle**: mint a non-member ID token (admin custom token →
|
||||
Identity Toolkit `signInWithCustomToken` REST) and issue Firestore REST GET/PATCH against the couple's docs — expect
|
||||
App Check `403` or rules `PERMISSION_DENIED` on every attempt. Also issue the **same** reads with a **member** token to
|
||||
characterize the enforcement layer (App Check vs rules). Any unauthorized `200` with couple data = **P0**.
|
||||
- **D4 Key exchange / management / recovery (E2EE crux):** couple key client-generated, only leaves device **wrapped**
|
||||
(KDF from invite seed; server holds only `wrappedCoupleKey`+`kdfSalt`/`kdfParams`+`encryptedRecoveryPhrase`); **KDF
|
||||
strength**; Tink AEAD = AES-GCM/256 with **AAD=coupleId**, no weak/custom crypto/nonce reuse; keybox/sealed/commitment
|
||||
|
|
|
|||
|
|
@ -1,5 +1,24 @@
|
|||
# Claude QA Report — Full-App QA (living report)
|
||||
|
||||
> **RUN-STATE: Round 7 (multi-angle DEEP DIVE) — 2026-06-25, client HEAD `f47c8e2`, functions deployed. Plan updated with a "Multi-angle attack mandate" + live raw-API D3.** Attacked security/data/concurrency from multiple angles (admin ground-truth read, raw Firestore REST as member+non-member, killed/cold state, malformed intents, simultaneous-start race). **Security cornerstone = FULLY CLEAN (deep):** D1 at-rest — messages/previews + all 4 game-answer collections (ToT/HowWell/DesireSync/Wheel, both users) + capsules + date-swipe actions all `enc:v1:`; couple key phrase-wrapped (argon2id), recovery phrase server-blind + `encryptedRecoveryPhrase` wiped on acceptance, plaintext `inviteCode` not exploitable (invite readable only by inviter; no code-encrypted secret persisted). D3 raw-API — **non-member denied ALL reads/writes (403)**; real premium path `users/{uid}/entitlements/premium` write **denied (403, server-only) → no self-grant**; cross-couple denied. Robustness — malformed/abusive deep-link intents (unknown type, missing extras, injection/path-traversal values) → 0 crash; killed-state cold-start chat deep-link → conversation loads. **NEW FINDING: F-RACE-001 (P1)** — simultaneous game start creates **two divergent active sessions** (TOCTOU). **SEVERITY BOARD: P1 = 1 open (F-RACE-001), P0/P2/P3 = 0 open.** Baseline restored (duplicate sessions ended, 0 active, couple intact). Two hardening notes → Future.md (App Check not enforced on Firestore; user-doc update rule allows arbitrary non-`hasPremium` fields).**
|
||||
>
|
||||
> **F-RACE-001 (P1, NEW — concurrency):** When BOTH partners start the *same* game within ~the same second, the couple
|
||||
> ends up with **2 active sessions with different question sets** (proven live: QA "Which should end the date?" vs Sam
|
||||
> "Which feels more romantic?", two session docs `bw8Q3X45…` + `yNOzMTOCsGlZPbnv2yBN`). Root cause: `GameSessionManager.
|
||||
> startGameWithCouple` (usecase/GameSessionManager.kt:84–106) does a **non-transactional check-then-create** —
|
||||
> `getActiveSessionForCouple` then `saveSession` (auto-id); two concurrent calls both read null → both create. The
|
||||
> existing `partner_active_session` guard only covers the non-simultaneous case. Impact: the two partners play separate
|
||||
> games and never get a shared reveal (core loop silently defeated); two active sessions can also lock/confuse the
|
||||
> "one active game" rule. No crash, no data loss; recoverable via "End their game"/admin. Repro: stage both at This or
|
||||
> That mood-select, fire "create" on both in parallel. **Suggested fix:** make session creation atomic — a Firestore
|
||||
> **transaction** with a per-couple active-session **sentinel** (e.g. `couples/{cid}` field `activeSessionId` or a
|
||||
> `sessions/_active` doc): read sentinel → if an active session exists, take the join/`partner_active_session` path;
|
||||
> else create the session doc (client-generated id) + set the sentinel in the same transaction. Clear the sentinel on
|
||||
> finish. Needs a `firestore.rules` update (member-only sentinel write) + a rules deploy + re-verify all 7 games. (This
|
||||
> is an architectural change to the core game flow — flagged for a focused fix-phase implementation.)
|
||||
|
||||
> **RUN-STATE: Round 6 (branding + Future.md regression QA) COMPLETE — 2026-06-25. Client HEAD `f47c8e2` on both emulators (build == HEAD, reinstalled).** Scope: regression-verify the new surfaces from the branding drop (`95cad84`: white-keyhole launcher/notification icons, animated app-icon-chip loader + fill, cold-launch splash, pairing hero) and the Future.md backlog clear (`f47c8e2`: inclusive gender options, turn-aware Home copy, push rate-limit budget split, results-push suppression via new ActiveGameSessionMonitor, paywall retry/offline/hide-Continue, auth privacy rotator). **0 new issues — SEVERITY BOARD STILL 0 open P0–P3.** LIVE-VERIFIED: animated loader (chip+fill, both themes), splash→handoff (white-keyhole icon, no white flash), launcher icon (round mask), This or That + How Well open with no crash (confirms #4's new VM injection is sound), paywall purchase screen shows friendly "Couldn't load plans" + Try again with Continue hidden (no dead button) + online→generic message (#5), onboarding carousel illustration. Unit tests green (NotificationRateLimiter rewritten + PartnerNotificationManagerTest repaired). No FATAL on either device all session. CODE/UNIT-VERIFIED (live deferred, low-risk over proven patterns + fragile multi-text-field/2-device paths): #1 gender step (EditProfile + onboarding sex step — same option list as the shipping Female/Male), #8 rotator on SignUp/Forgot (reuses the Login-proven BrandMessageRotator), #2 "Your turn to play." (static string in the proven GAME_WAITING path), #3 weekly-cap exemption (unit-tested; only triggers at ≥100/wk), #4 results-suppression timing (mechanism + VM wiring verified; simultaneous-finish timing is non-deterministic to drive). Baseline restored: 5554 signed out during the sign-up pass, re-signed-in QA (`Y05AKO2IlTPMa0JQW1BiNIM0uzK2`) via admin custom token; couple `Xal3Kw3gjSdn0niERYKJ` intact, Sam paired.**
|
||||
|
||||
> **RUN-STATE: Round 5 (functions deploy + expanded re-QA) COMPLETE — 2026-06-25. Client HEAD `765916a` on both emulators; Cloud Functions DEPLOYED (`firebase deploy --only functions` → "Deploy complete", all 30+ fns updated). Fixed + verified LIVE: E-OBS (all 12 FCM senders now set `android.notification.channelId` → backgrounded chat push lands on `partner_activity`, NOT `fcm_fallback`), E-003 results-ready (server sends `game_session_id`; finished-game deep-link → per-session "This or That Results" screen, not hub/setup). Expanded coverage per user request: VARIED GAMEPLAY (Standard/Deep + 0-match "Total opposites" result path), exhaustive NAV FUZZING (rapid triple-tap opens setup once via launchSingleTop; back-stack clean; no dead-ends/double-back), and NEW PASS G account-creation/fake-account — ALL SECURE: sign-up+validation (weak-pw → friendly error), fresh-account isolation (zero couple data), duplicate-email → `auth/email-already-exists`, invite single-use+24h-expiry + bogus code → "Invite not found", recovery phrase client-generated. **SEVERITY BOARD: 0 open at ALL levels (P0–P3).** Baseline restored: couple intact, both free, 0 active sessions, throwaway test account deleted, Sam re-paired.**
|
||||
> _Round 4 (carried): E-003 game-push + B-004 WaitingForPartner "Join the game" + A-OBS paywall copy all FIXED + verified live._
|
||||
> _Round 2 result (carried): FIX PHASE COMPLETE — P1×2 (B-001 session rules, C-NAV-001 back-stack), P2×4 (A-001, B-002, C-CC-001, C-DS-001), P3×4 (A-003, B-003, E-002, F-OBS) all FIXED; D-001 (P1 rules) + E-001 (P2 routing) fixed earlier. All verified LIVE except E-002/F-OBS (code+build; live-trigger deferred). Rules deployed._
|
||||
|
|
|
|||
11
Future.md
11
Future.md
|
|
@ -14,6 +14,17 @@ Improvement & feature ideas surfaced while QA-testing as a consumer (each works
|
|||
quiet-hours moon) used consistently would strengthen identity. Generate the G-set, drop the assets in,
|
||||
then wire them in. *Prompted by:* Pass H branding review.
|
||||
|
||||
### Security hardening (defense-in-depth — not vulnerabilities; rules already hold)
|
||||
|
||||
- **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.
|
||||
- **Tighten the `users/{uid}` update rule to a field allowlist.** The rule only blocks changing `hasPremium`; a user
|
||||
can write arbitrary *other* fields to their own doc (e.g. a cosmetic `plan`/junk). No gate reads those (premium gates
|
||||
on the server-only `users/{uid}/entitlements/premium` subcollection + `category.access`), so it grants nothing — but
|
||||
restricting updates to a known field set is cleaner. *Prompted by:* R7 D3 (`plan` field writable, unused by gating).
|
||||
|
||||
> Artwork to generate (ChatGPT prompts, house-style-matched) lives in `ClaudeBrandingReview.md`, not here.
|
||||
|
||||
<!--
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,230 @@
|
|||
# Closer Question Quality Checklist v2
|
||||
|
||||
**See also:** [QUESTION_CONTENT_GUIDE.md](QUESTION_CONTENT_GUIDE.md) — writing philosophy, voice & tone | [QUESTION_SCHEMA.md](QUESTION_SCHEMA.md) — JSON schema & validation rules | [QUESTION_REWRITE_PLAN.md](QUESTION_REWRITE_PLAN.md) — rewrite checklist & category order
|
||||
|
||||
## Purpose
|
||||
|
||||
This checklist prevents technically valid but boring questions from reaching the app.
|
||||
|
||||
Passing JSON validation is not enough.
|
||||
|
||||
Every question must also feel human, useful, and worth answering.
|
||||
|
||||
## Automatic Rejects
|
||||
|
||||
Reject any question that contains or strongly resembles:
|
||||
|
||||
* Describe...
|
||||
* Reflect on...
|
||||
* Discuss...
|
||||
* Evaluate...
|
||||
* In what ways...
|
||||
* How satisfied are you...
|
||||
* What boundary around...
|
||||
* Explore your feelings...
|
||||
* Identify the ways...
|
||||
* Rate the effectiveness...
|
||||
* Communication style
|
||||
* Emotional processing
|
||||
* Conflict framework
|
||||
* Relationship dynamic
|
||||
|
||||
These are therapy worksheet patterns.
|
||||
|
||||
Burn them politely.
|
||||
|
||||
## Duplicate Pattern Rejects
|
||||
|
||||
Reject the question if it repeats too much of a previous question:
|
||||
|
||||
* same opening phrase
|
||||
* same option list
|
||||
* same emotional goal
|
||||
* same situation
|
||||
* same answer pattern
|
||||
* same wording with one noun swapped
|
||||
|
||||
A file can be valid and still feel like a cursed spreadsheet.
|
||||
|
||||
## Opening Variety
|
||||
|
||||
No category should overuse any opening.
|
||||
|
||||
Watch for overuse of:
|
||||
|
||||
* What should we...
|
||||
* What do you...
|
||||
* Which...
|
||||
* How much...
|
||||
* What makes...
|
||||
|
||||
These are allowed, but not as the backbone of the pack.
|
||||
|
||||
## Consumer Test
|
||||
|
||||
Ask:
|
||||
|
||||
Would a real couple answer this voluntarily on a Friday night?
|
||||
|
||||
Reject if the honest answer is no.
|
||||
|
||||
## Conversation Test
|
||||
|
||||
A question should create at least one of these:
|
||||
|
||||
* a laugh
|
||||
* a story
|
||||
* a decision
|
||||
* a date idea
|
||||
* a compliment
|
||||
* a surprise
|
||||
* a memory
|
||||
* a useful truth
|
||||
* a follow up question
|
||||
|
||||
Reject questions that only collect data.
|
||||
|
||||
## Premium Test
|
||||
|
||||
Reject the question if it does not feel worth paying for.
|
||||
|
||||
A premium question should feel:
|
||||
|
||||
* specific
|
||||
* warm
|
||||
* memorable
|
||||
* useful
|
||||
* fun
|
||||
* relationship focused
|
||||
|
||||
## Option Quality
|
||||
|
||||
For multi_choice and single_choice:
|
||||
|
||||
* use 4 to 6 options
|
||||
* avoid obvious correct answers
|
||||
* avoid overlapping options
|
||||
* avoid vague options like "other"
|
||||
* avoid all negative options
|
||||
* avoid options that shame either partner
|
||||
* keep options short
|
||||
* make options feel realistic
|
||||
|
||||
## Written Question Gate
|
||||
|
||||
A written question is allowed only when typing creates more value than choices.
|
||||
|
||||
Reject written questions that ask for:
|
||||
|
||||
* basic preference
|
||||
* yes or no
|
||||
* generic reflection
|
||||
* long emotional labor
|
||||
* forced vulnerability
|
||||
|
||||
Written questions should create stories, memories, or meaningful appreciation.
|
||||
|
||||
## Scale Question Gate
|
||||
|
||||
Use scale only when intensity matters.
|
||||
|
||||
Good scale uses:
|
||||
|
||||
* How close does this feel?
|
||||
* How comfortable are you?
|
||||
* How often does this happen?
|
||||
* How important is this?
|
||||
* How ready are we?
|
||||
|
||||
Bad scale uses:
|
||||
|
||||
* random preferences
|
||||
* jokes
|
||||
* simple choices
|
||||
* topics better served by options
|
||||
|
||||
## Emotional Safety
|
||||
|
||||
Reject questions that pressure users to:
|
||||
|
||||
* confess
|
||||
* forgive
|
||||
* reveal trauma
|
||||
* admit guilt
|
||||
* justify boundaries
|
||||
* defend needs
|
||||
* disclose sexual details before consent context
|
||||
* compare partners cruelly
|
||||
|
||||
The app should open doors, not corner people.
|
||||
|
||||
## Fun Requirement
|
||||
|
||||
Every category must include playful moments.
|
||||
|
||||
Even serious categories need:
|
||||
|
||||
* small humor
|
||||
* soft wording
|
||||
* hopeful prompts
|
||||
* low pressure options
|
||||
* practical repair ideas
|
||||
|
||||
No category should feel like 250 tiny court summons.
|
||||
|
||||
## Category Fit
|
||||
|
||||
Every question must clearly belong to its category.
|
||||
|
||||
Reject questions that could fit anywhere.
|
||||
|
||||
Example:
|
||||
|
||||
Bad for Gratitude:
|
||||
|
||||
* What do you like most about us?
|
||||
|
||||
Better:
|
||||
|
||||
* What's one small thing I do that deserves more credit?
|
||||
|
||||
## Relationship Anchor
|
||||
|
||||
Every question should connect to the couple.
|
||||
|
||||
Prefer:
|
||||
|
||||
* us
|
||||
* we
|
||||
* you and me
|
||||
* our life
|
||||
* our future
|
||||
* our memories
|
||||
|
||||
Avoid generic survey voice.
|
||||
|
||||
## Final Human Read
|
||||
|
||||
Before committing a pack, read 30 random questions aloud.
|
||||
|
||||
Reject the pack if it sounds:
|
||||
|
||||
* robotic
|
||||
* repetitive
|
||||
* clinical
|
||||
* boring
|
||||
* too similar
|
||||
* too heavy
|
||||
* too generic
|
||||
|
||||
## Final Approval
|
||||
|
||||
A pack is ready only when:
|
||||
|
||||
* schema passes
|
||||
* counts pass
|
||||
* duplicate checks pass
|
||||
* tone review passes
|
||||
* category fit passes
|
||||
* consumer test passes
|
||||
* no obvious AI patterns remain
|
||||
|
|
@ -1,126 +1,97 @@
|
|||
# Closer Question Rewrite Plan
|
||||
# Closer Question Rewrite Plan v2
|
||||
|
||||
## Repo Alignment
|
||||
|
||||
This plan reflects the current repository schema.
|
||||
|
||||
Use these type names exactly:
|
||||
|
||||
- written
|
||||
- single_choice
|
||||
- multi_choice
|
||||
- scale
|
||||
- this_or_that
|
||||
|
||||
Do not rename them. Do not introduce `choice` or `either_or` unless the application code is updated first.
|
||||
**See also:** [QUESTION_CONTENT_GUIDE.md](QUESTION_CONTENT_GUIDE.md) — writing philosophy, voice & tone | [QUESTION_SCHEMA.md](QUESTION_SCHEMA.md) — JSON schema & validation rules | [QUESTION_QUALITY_CHECKLIST.md](QUESTION_QUALITY_CHECKLIST.md) — quality gate before commit
|
||||
|
||||
## Product Goal
|
||||
|
||||
Closer should feel like a couples game, not a survey or therapy worksheet.
|
||||
Closer is a relationship app, not a survey.
|
||||
|
||||
Questions should make couples:
|
||||
|
||||
- smile
|
||||
- laugh
|
||||
- learn something new
|
||||
- remember something
|
||||
- plan something together
|
||||
- have meaningful conversations
|
||||
The experience should feel like a couples game that naturally creates conversations, laughter, memories, flirting, and meaningful moments.
|
||||
|
||||
If a question feels like homework, rewrite it.
|
||||
|
||||
## Question Mix
|
||||
## Repo Schema
|
||||
|
||||
Every 250 question pack must contain:
|
||||
Use these type names exactly:
|
||||
|
||||
* written
|
||||
* single_choice
|
||||
* multi_choice
|
||||
* scale
|
||||
* this_or_that
|
||||
|
||||
Do not rename them unless the app code is updated first.
|
||||
|
||||
## Question Mix per 250
|
||||
|
||||
| Type | Count |
|
||||
|---|---|
|
||||
|---|---:|
|
||||
| multi_choice | 140 |
|
||||
| single_choice | 50 |
|
||||
| scale | 35 |
|
||||
| this_or_that | 15 |
|
||||
| written | 10 |
|
||||
|
||||
This means 190 of 250 questions, or 76%, are choice based.
|
||||
76 percent of questions must be choice based.
|
||||
|
||||
Written questions should stay rare.
|
||||
|
||||
## Free and Premium Split
|
||||
|
||||
Per 250 question pack:
|
||||
|
||||
* 75 free
|
||||
* 175 premium
|
||||
|
||||
Suggested free split:
|
||||
|
||||
* multi_choice: 42
|
||||
* single_choice: 15
|
||||
* scale: 11
|
||||
* this_or_that: 5
|
||||
* written: 2
|
||||
|
||||
Suggested premium split:
|
||||
|
||||
* multi_choice: 98
|
||||
* single_choice: 35
|
||||
* scale: 24
|
||||
* this_or_that: 10
|
||||
* written: 8
|
||||
|
||||
Free should feel useful, fun, and complete.
|
||||
|
||||
Premium should feel deeper, richer, and more varied.
|
||||
|
||||
## Emotional Mix
|
||||
|
||||
Aim for:
|
||||
Every category should roughly include:
|
||||
|
||||
- 35% fun and playful
|
||||
- 25% everyday relationship
|
||||
- 20% meaningful conversation
|
||||
- 10% future dreams and planning
|
||||
- 10% deeper vulnerability
|
||||
* 35 percent playful
|
||||
* 25 percent everyday relationship
|
||||
* 20 percent meaningful conversation
|
||||
* 10 percent future dreams and planning
|
||||
* 10 percent deeper vulnerability
|
||||
|
||||
Never group heavy questions together.
|
||||
|
||||
Every category must contain lighter moments.
|
||||
|
||||
## Consumer First Rules
|
||||
|
||||
People should naturally answer dozens of questions in one sitting.
|
||||
|
||||
Every category should create moments like:
|
||||
|
||||
- "I didn't know that."
|
||||
- "Really?"
|
||||
- "That's adorable."
|
||||
- "We should do that."
|
||||
|
||||
The product sells shared moments, not data collection.
|
||||
|
||||
## Type Definitions
|
||||
|
||||
### multi_choice
|
||||
|
||||
Select every option that applies.
|
||||
|
||||
Default to this type whenever possible.
|
||||
|
||||
### single_choice
|
||||
|
||||
Select one best answer.
|
||||
|
||||
### scale
|
||||
|
||||
Rate agreement, comfort, confidence, importance, or frequency.
|
||||
|
||||
### this_or_that
|
||||
|
||||
Very fast playful questions.
|
||||
|
||||
Should take under three seconds.
|
||||
|
||||
### written
|
||||
|
||||
Reserve for questions where typing creates significantly more value.
|
||||
|
||||
Never use written for simple preferences.
|
||||
Every category must include lighter moments.
|
||||
|
||||
## Rewrite Rules
|
||||
|
||||
For every category:
|
||||
|
||||
- Rewrite every question from scratch.
|
||||
- Follow the content guide.
|
||||
- Keep category ids.
|
||||
- Keep conversational wording.
|
||||
- Avoid therapy language.
|
||||
- Avoid repetitive templates.
|
||||
- Use 4 to 6 options for choice questions.
|
||||
- Keep written questions rare.
|
||||
- Add fun throughout every category.
|
||||
|
||||
## Fun Injection
|
||||
|
||||
Every category should include at least:
|
||||
|
||||
- 10 playful questions
|
||||
- 5 questions that create laughter
|
||||
- 5 questions that inspire a future date, memory, or shared activity
|
||||
|
||||
Even serious categories should include enjoyable moments.
|
||||
* Rewrite every question from scratch.
|
||||
* Keep category ids.
|
||||
* Use conversational language.
|
||||
* Avoid therapy wording.
|
||||
* Avoid survey wording.
|
||||
* Avoid repetitive templates.
|
||||
* Use 4 to 6 options for choice questions.
|
||||
* Keep written questions rare.
|
||||
* Add fun to every category.
|
||||
* Make every category feel different.
|
||||
* Reject questions that feel generated.
|
||||
|
||||
## Rewrite Order
|
||||
|
||||
|
|
@ -146,27 +117,44 @@ Even serious categories should include enjoyable moments.
|
|||
20. sex_and_desire
|
||||
21. sexual_preferences
|
||||
|
||||
## Validation
|
||||
## Serious Category Rule
|
||||
|
||||
Serious categories still need warmth, humor, and relief.
|
||||
|
||||
Do not make conflict, trust, boundaries, or intimacy feel like couples counseling homework.
|
||||
|
||||
Use soft, safe, specific wording.
|
||||
|
||||
## Validation Rules
|
||||
|
||||
Every rewritten pack must pass:
|
||||
|
||||
- Valid JSON
|
||||
- Exactly 250 questions
|
||||
- 75 free
|
||||
- 175 premium
|
||||
- 140 multi_choice
|
||||
- 50 single_choice
|
||||
- 35 scale
|
||||
- 15 this_or_that
|
||||
- 10 written
|
||||
- Valid depth values only: light, medium, deep
|
||||
- No numeric depth values
|
||||
- No malformed keys
|
||||
- No duplicate ids
|
||||
- No duplicate questions
|
||||
- Every choice question has options
|
||||
- Every scale question has scale configuration
|
||||
- Every written question has answer configuration
|
||||
- No therapy worksheet wording
|
||||
- No placeholder text
|
||||
- Every category feels fun, conversational, and rewarding
|
||||
* valid JSON
|
||||
* exactly 250 questions
|
||||
* exactly 75 free
|
||||
* exactly 175 premium
|
||||
* exactly 140 multi_choice
|
||||
* exactly 50 single_choice
|
||||
* exactly 35 scale
|
||||
* exactly 15 this_or_that
|
||||
* exactly 10 written
|
||||
* at least 76 percent choice based
|
||||
* valid depth values only: light, medium, deep
|
||||
* no numeric depth values
|
||||
* no malformed keys
|
||||
* no duplicate ids
|
||||
* no duplicate question text
|
||||
* every choice question has options
|
||||
* every scale question has scale configuration
|
||||
* every written question has answer configuration
|
||||
* no placeholder text
|
||||
* no therapy worksheet tone
|
||||
* no obvious AI repetition
|
||||
|
||||
## Final Rule
|
||||
|
||||
The schema only proves the file can load.
|
||||
|
||||
It does not prove the questions are good.
|
||||
|
||||
Quality review is mandatory before commit.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# Closer Question Schema
|
||||
|
||||
**See also:** [QUESTION_CONTENT_GUIDE.md](QUESTION_CONTENT_GUIDE.md) — writing philosophy, voice & tone | [QUESTION_REWRITE_PLAN.md](QUESTION_REWRITE_PLAN.md) — rewrite checklist & category order | [QUESTION_QUALITY_CHECKLIST.md](QUESTION_QUALITY_CHECKLIST.md) — quality gate before commit
|
||||
|
||||
## Purpose
|
||||
|
||||
This document defines the JSON schema, question types, validation rules, and required counts for Closer question packs.
|
||||
|
|
|
|||
Loading…
Reference in New Issue