Go to file
null c3a3c38e0e fix(ui): sync Activity uiMode to in-app theme at startup (C-DARKART-002 architectural fix) 2026-06-28 16:35:01 -05:00
.vscode feat(db): add 2 new v2 question categories (difficult_conversations, home_life) — 20 total, 5,500 questions 2026-06-15 21:38:22 -05:00
app fix(ui): sync Activity uiMode to in-app theme at startup (C-DARKART-002 architectural fix) 2026-06-28 16:35:01 -05:00
docs brand: refresh dark-variant illustrations (couple_paywall, partner_activation, together_empty) and dark contact sheet 2026-06-28 16:34:51 -05:00
firestore-tests fix: prevent invite code enumeration via Cloud Function (batch v0.2.18) 2026-06-19 21:46:12 -05:00
functions feat: quiet hours notifications, settings UI, game session updates, docs 2026-06-28 10:00:25 -05:00
gradle/wrapper feat: Firebase + RevenueCat stack, PLAN.md integration, 11 implementation batches (v0.1.0) 2026-06-15 18:43:43 -05:00
iphone brand: finalize app icon, brand docs, onboarding visuals, feature graphic 2026-06-25 14:52:21 -05:00
qa chore: working tree changes — QA docs, app tweaks, Cloud Functions updates 2026-06-27 13:31:09 -05:00
scratchpad security(scratchpad): add R17 Firestore at-rest and negative-access probe scripts 2026-06-28 15:45:54 -05:00
scripts tools+test: extend theme-scan.sh and update notification + brand copy tests 2026-06-28 12:45:37 -05:00
seed docs(seed): update content guide to v3 — product standard, readability test, final approval 2026-06-25 18:56:48 -05:00
server feat: add onMessageWritten cloud function, notification settings screen, user repo cleanup 2026-06-20 18:25:05 -05:00
.eslintrc.js feat: Expo project bootstrap (v1.0.1) + untrack private docs 2026-05-13 04:21:43 -05:00
.firebaserc feat: code push -- notifications, cloud functions, iOS updates 2026-06-22 08:53:23 -05:00
.gitignore brand: add generated glyph assets + illustration exports, allow generated-art in git 2026-06-28 11:30:12 -05:00
.prettierrc feat: Expo project bootstrap (v1.0.1) + untrack private docs 2026-05-13 04:21:43 -05:00
ClaudeBrandingReview.md feat: wire theme-scan.sh into QA docs, fix script Tier 2, file 9 C-THEME P2 defects 2026-06-28 10:34:55 -05:00
ClaudeQACoverage.md docs(qa): update ClaudeQAPlan + ClaudeQACoverage with living-doc guardrails, wiring scanner, and device-matrix honesty 2026-06-28 11:30:29 -05:00
ClaudeQAPlan.md docs: consolidate Future backlog, update ClaudeQAPlan/ClaudeReport, note FUTURE.md removal in Engineering Manual 2026-06-28 12:45:54 -05:00
ClaudeReport.md Revert "fix(ui): route QuestionPackLibrary card art through BrandIllustration for in-app-dark theme (C-DARKART-002)" 2026-06-28 15:45:20 -05:00
ClaudeiOSPlan.md docs(qa): senior-QA review additions — Pass F, env/matrix, migration, iOS-native dims 2026-06-24 21:44:02 -05:00
Engineering_Reference_Manual_Plan.md docs(plan): Mark Engineering Reference Manual plan complete 2026-06-28 11:15:30 -05:00
Future.md docs(future): file P0 onboarding slide-3 crash (blocks first-run users from reaching login) 2026-06-28 16:22:18 -05:00
README.md docs(readme): modernize for 2026 — badges, TL;DR, architecture diagram, pillar grid, expanded security section 2026-06-28 15:53:41 -05:00
build.gradle.kts feat: Firebase + RevenueCat stack, PLAN.md integration, 11 implementation batches (v0.1.0) 2026-06-15 18:43:43 -05:00
firebase.json feat: strict E2EE — encryption migration, Firestore rules enforcement, version 2 protocol (batch v0.2.11) 2026-06-19 20:53:52 -05:00
firestore.indexes.json feat(app-check): stable debug token via BuildConfig; feat(firestore): indexes for questions + bucket_list 2026-06-23 12:17:17 -05:00
firestore.rules feat: quiet hours notifications, settings UI, game session updates, docs 2026-06-28 10:00:25 -05:00
gitleaks-current.json feat: update PrivacyScreen, add Firestore test scripts, gitleaks audit artifacts 2026-06-19 03:45:53 -05:00
gitleaks-history.json feat: update PrivacyScreen, add Firestore test scripts, gitleaks audit artifacts 2026-06-19 03:45:53 -05:00
gradle.properties feat: Firebase + RevenueCat stack, PLAN.md integration, 11 implementation batches (v0.1.0) 2026-06-15 18:43:43 -05:00
gradlew refactor: package rename from com.couplesconnect.app to app.closer, update build config and firebase setup 2026-06-16 20:03:58 -05:00
gradlew.bat refactor: package rename from com.couplesconnect.app to app.closer, update build config and firebase setup 2026-06-16 20:03:58 -05:00
local.properties.example feat: Firebase + RevenueCat stack, PLAN.md integration, 11 implementation batches (v0.1.0) 2026-06-15 18:43:43 -05:00
seed_generator.py refactor: package rename from com.couplesconnect.app to app.closer, update build config and firebase setup 2026-06-16 20:03:58 -05:00
settings.gradle.kts security: kimi-k2.7 review fixes — Ed25519 crypto API, Firestore rules try/catch removal, atomic idempotency, RevenueCat 8.20.0, rate limiter fix, remove plaintext fallback, tighten push wording 2026-06-16 22:42:53 -05:00
storage.rules fix: rate limiter bump (20/day, 100/week), firestore rules for image messages, storage rules for chat_media, gitignore ClaudeReport 2026-06-24 15:20:44 -05:00

README.md

Closer

🔐 Private daily questions for couples — end-to-end encrypted, never read, never sold. You and your paired partner hold the only key.

A native couples-relationship app that turns check-ins into small, intentional rituals: one daily question, curated conversation packs, private answers, mutual reveal, gentle reminders, and shared date planning — with real E2EE and calmer UX.

Not a social network. Not therapy. Not a productivity tracker. No public feeds, no likes, no followers, no infinite scroll.

The core loop is simple: answer honestly → choose what to reveal → keep a record of the conversations that mattered.


Android iOS Backend Min Android Min iOS Kotlin Swift License


TL;DR

What Why it matters
🔐 Real E2EE Answer content is encrypted on-device. Server only sees ciphertext. Couple-owned keys via Tink (Android).
💑 One subscription per couple No double-billing partners. Premium unlocks for both — server-verified.
🚫 No engagement traps No infinite scroll. No likes. No follower counts. One daily question is the loop.
🌗 Decoupled theme + art In-app light/dark controls art; system theme isn't required to match.
📱 Native on both platforms Kotlin/Compose on Android, SwiftUI on iOS — same Firebase backend, same data model.
🧪 QA you can run scripts/theme-scan.sh (Pass C) and scripts/wiring-scan.sh (Pass N) catch the silent-dead-feature and theme-hardcoding classes before merge.

Screenshots

Onboarding Home Daily question Question packs Answer history

Play hub Date planning Settings Login Bucket list (new Pixel 8 emulator)

📸 The bucket-list screenshot at docs/screenshots/10-bucket-list.png is a placeholder for a fresh capture from a Pixel 8 / API 35 emulator. This Linux environment has no Android SDK, so the image must be captured on a machine with Android Studio and copied into docs/screenshots/.


Why Closer exists

Subscription apps for couples have a trust problem — confusing trial wording, hard-to-cancel flows, partners getting double-billed. Couples products have a different trust problem: partners are asked to be vulnerable in the same space where everything else (social, productivity, dating) wants their engagement, their data, and their attention.

Closer treats both the same way: clear, straightforward, and built on honesty.

  • 🪞 Private first, reveal second. Each partner answers independently. Both decide what to share.
  • 🧠 Curated, not generated. 6,000+ hand-written prompts across 22 categories — no AI confabulation in the core loop.
  • 💸 One sub, not two. Subscription unlocks for both partners. Server-verified. No silent trial conversions.
  • 🔒 Encryption that earns the word. Tink AEAD with couple-owned keys. Recovery phrase. Server never sees plaintext.
  • 🌙 Quiet hours, server-side. Partner pushes respect the recipient's in-app window — not just foreground detection.

What Closer does

Feature Free Premium
Daily question (text / scale / multi / this-or-that)
6,000+ prompts · 22 question packs (incl. premium-only packs)
Private answers + mutual-reveal flow
Spin the wheel — category-randomized questions
Recent answer history (last 30 days)
Full answer history (search, filter, export)
Saved spin-wheel sessions
Memory Lane (locked time capsules)
Desire Sync (preferences alignment exercise)
Select Connection Challenges (multi-day programs) (free) + 🎟️ (premium tiers)
Push reminders with quiet-hour support
Account deletion + data export

One subscription unlocks premium for both partners — couples/{coupleId}/entitlements is per-couple, not per-user.


Platform status

Platform Stack Status Notes
Android Kotlin · Jetpack Compose · Material 3 · Hilt · Room · DataStore 🟢 Reference implementation Feature-complete MVP, light/dark theme polished
iOS SwiftUI · MVVM · async/await · Firebase iOS SDK 🟡 Scaffold landed on dev Full screen parity; pairing from iOS is blocked until E2EE keys are wired (Android-only today)
Backend Firebase Auth · Firestore · Cloud Functions · FCM · App Check 🟢 Shared source of truth 17 callable/trigger/scheduled/webhook functions
Billing RevenueCat · Google Play Billing · StoreKit 🟢 Server-verified Webhook → Firestore entitlements → CouplePremiumChecker

📐 iOS scaffold has all 49 screens mapped to SwiftUI views, Firebase + RevenueCat integration, and full screen parity on the dev branch. CryptoKit-based E2EE parity (interop with Android's Tink key material) is the only blocker for end-to-end iOS pairing.


Architecture at a glance

 ┌──────────────────────────┐         ┌──────────────────────────┐
 │  Android (Kotlin/Compose)│         │   iOS (SwiftUI)          │
 │  • Hilt DI · Room · DSK  │         │  • MVVM · AppState · SPM │
 │  • Tink AEAD + Argon2id  │         │  • CryptoKit (follow-up) │
 └────────────┬─────────────┘         └────────────┬─────────────┘
              │                                    │
              └──────────────┬─────────────────────┘
                             │
                  ┌──────────▼──────────┐
                  │   Firebase          │
                  │   • Auth (email /   │
                  │     Google via      │
                  │     Credential Mgr) │
                  │   • Firestore       │
                  │     (couple-scoped) │
                  │   • Cloud Functions │
                  │   • FCM             │
                  │   • App Check       │
                  │     (Play Integrity)│
                  └──────────┬──────────┘
                             │
                  ┌──────────▼──────────┐
                  │  RevenueCat         │
                  │  → webhook          │
                  │  → Firestore        │
                  │    entitlements     │
                  └─────────────────────┘
  • Couple-scoped data. Firestore rules deny cross-couple reads/writes. Users only see their own + their partner's surface.
  • Server-mediated pairing. 6-character invite codes are enumerable; invite reads/writes are server-side only.
  • Server-verified billing. RevenueCat → Cloud Function webhook → Firestore users/{uid}/entitlements/premiumCouplePremiumChecker observes both partners' premium state.
  • Local-first questions. Prompts ship in the app so daily questions load instantly; only assignment and sync hit the network.

Tech stack

Android

Layer Stack
Language Kotlin 2.x
UI Jetpack Compose · Material 3 · Navigation Compose
Architecture Clean architecture — core/ · data/ · domain/ · ui/
State ViewModel · Kotlin Coroutines · Kotlin Flow
DI Hilt
Local data Room · DataStore Preferences · bundled SQLite seed
Crypto Google Tink AEAD + Argon2id (Bouncy Castle KDF)
Auth Firebase Auth · Credential Manager for Google Sign-In
Build min 26 · target 35 · compile 35 · Java 17 · KSP

iOS

Layer Stack
Language Swift 6.0
UI SwiftUI · NavigationStack · TabView
Architecture MVVM · AppState ObservableObject · EnvironmentObject
Concurrency async/await · Swift 6 strict concurrency
Dependency management Swift Package Manager · XcodeGen (project.yml)
Auth Firebase Auth · Google Sign-In SDK
Crypto Apple CryptoKit (E2EE parity — follow-up)
SDK iOS 17.0+

Backend (shared)

Layer Stack
Auth Firebase Authentication — email/password · Google (Android uses Credential Manager)
Database Cloud Firestore (couple-scoped rules)
Server logic Firebase Cloud Functions (TypeScript)
Push Firebase Cloud Messaging (FCM)
Security Firebase App Check · Play Integrity (Android) · DeviceCheck (iOS, planned)
Billing RevenueCat (server-verified entitlements)
Analytics Firebase Analytics · Crashlytics

🚫 No anonymous auth. There is no anonymous sign-in or account-linking flow in either platform. Accounts are email/password or Google.


Repository layout

.
├── app/                       # Native Android app (Kotlin)
│   └── src/main/java/app/closer
│       ├── core/              # Firebase, analytics, billing, nav, notifications, security
│       ├── data/              # Room, Firestore data sources, repositories, seed parsing
│       ├── domain/            # Models + repository contracts
│       └── ui/                # Compose screens + feature ViewModels
├── iphone/                    # Native iOS app (SwiftUI)
│   ├── ARCHITECTURE_AUDIT.md  # iOS port blueprint (49 screens, schema, models)
│   ├── project.yml            # XcodeGen project spec
│   ├── Package.swift          # SPM dependency manifest
│   └── Closer/
│       ├── Models/            # Firestore + domain codable types
│       ├── Core/              # Auth · Billing · Notifications
│       ├── Services/          # FirestoreService (callable wrappers)
│       ├── Theme/             # CloserTheme (colors, typography, spacing)
│       ├── Components/        # Shared SwiftUI components
│       ├── Navigation/        # Root ContentView + TabView routing
│       ├── Onboarding/        # Onboarding · login · signup · profile creation
│       ├── Pairing/           # Invite code · partner confirm · recovery
│       ├── Home/              # Home dashboard · streak · partner mirror
│       ├── Questions/         # Daily Q · answer reveal · history · packs
│       ├── Play/              # Play hub + games (ToT, HowWell, DesireSync, …)
│       ├── Wheel/             # Spin wheel
│       ├── Dates/             # Date swipe · matches · builder · bucket list
│       └── Settings/          # Settings · paywall · help · data export
├── functions/                 # Firebase Cloud Functions (TypeScript)
│   └── src/
│       ├── auth/              # Auth + invite lifecycle
│       ├── billing/           # RevenueCat webhook + entitlement sync
│       ├── couples/           # Pairing, leave, daily-question triggers
│       ├── questions/         # onAnswerWritten · onMessageWritten · threads
│       ├── games/             # onGameSessionUpdate · onGamePartFinished
│       ├── notifications/     # quiet-hours helper · reminders
│       └── server/            # Internal Express webhook service
├── scripts/                   # Automated QA / lint scanners
│   ├── theme-scan.sh          # Pass C: light/dark theme-hardcoding scanner
│   └── wiring-scan.sh         # Pass N: dead-feature / orphan-wiring scanner
├── server/                    # Optional Express webhook/health service
├── seed/                      # Question-pack JSON + local DB generation
├── docs/                      # QA notes · release prep · roadmap · screenshots
└── firestore.rules            # Firestore security rules (single source of truth)

🧪 scripts/theme-scan.sh and scripts/wiring-scan.sh are run before every QA pass. They statically catch the two costliest QA classes: hardcoded theme colors and silent dead features.


Getting started

Prerequisites

  • Android: Android Studio · Android SDK · JDK 17
  • iOS: Xcode 16 · macOS · XcodeGen (brew install xcodegen)
  • Firebase: Project with Auth · Firestore · Cloud Messaging · Crashlytics · Analytics · App Check
  • Android config: app/google-services.json
  • iOS config: iphone/Closer/GoogleService-Info.plist
  • Billing: RevenueCat project with Android + iOS API keys
  • Node 20 for Firebase Functions tooling

Local config

# Android
cp local.properties.example local.properties
# iOS
cp iphone/Closer/GoogleService-Info.plist.example iphone/Closer/GoogleService-Info.plist
sdk.dir=/path/to/Android/Sdk
RC_API_KEY_ANDROID=your_revenuecat_android_key
RC_API_KEY_IOS=your_revenuecat_ios_key

Android

./gradlew :app:assembleDebug
./gradlew :app:installDebug
./gradlew :app:compileDebugKotlin   # fast verification
./gradlew :app:testDebugUnitTest    # 205 unit tests

iOS

cd iphone
xcodegen generate
xed Closer.xcodeproj
xcodebuild -project iphone/Closer.xcodeproj \
           -scheme Closer \
           -destination 'platform=iOS Simulator,name=iPhone 15' \
           build

Firebase Functions

cd functions
npm install
npm run build
npm run serve
npm test    # 24 functions tests

Optional server

cd server
npm install
npm run dev

Security & privacy

  • 🔐 E2EE answer content. Tink AEAD with couple-owned keys. Server never sees plaintext answers or capsule content.
  • 🧂 Key wrapping. Argon2id KDF over the recovery phrase; keys wrapped client-side.
  • 🪪 Recovery phrase. Server-blind; wiped from the inviter on acceptance.
  • 🚧 Firestore rules. Couple-scoped; deny-by-default; field allowlists on users/{uid} updates; shape-restricted couple create.
  • 🛡️ App Check. Play Integrity (Android), DeviceCheck (iOS, planned) — blocks abusive backend access.
  • 🌙 Quiet hours, server-side. Suppression is enforced where the push is sent, not where it might be foregrounded. Client cannot bypass by being backgrounded.
  • 💸 Server-verified billing. Cloud Function writes users/{uid}/entitlements/premium from the RevenueCat webhook. Client cannot self-grant.

Full architecture reference: docs/Engineering_Reference_Manual.md — the canonical source of truth for the security model, data model, and Cloud Functions wiring.


Roadmap

In progress:

  • 🔐 iOS E2EE parity (CryptoKit interop with Android's Tink key material) — unblocks pairing from iOS.
  • 🧪 On-device / instrumented test coverage (Compose UI / Espresso smoke) — currently app/src/test only.
  • 🎨 Activity uiMode sync to in-app theme (C-DARKART-002) — the dark-variant -night PNGs only render in the right combination today; architectural fix in MainActivity.
  • 🛒 Real release config — version, legal/support URLs, RevenueCat offerings verified end-to-end on internal testing.

Out of scope (for now):

  • AI-assisted question suggestions
  • Native group/relationship types beyond dyadic couples
  • Wearable (Wear OS / watchOS) companions
  • Live video or voice sessions

See Future.md for the full backlog.


Project history & docs

Doc Purpose
docs/Engineering_Reference_Manual.md Architecture, security model, data model, known landmines
docs/release/ Release prep + store assets
docs/qa/ QA playbook + private-MVP checklist
Future.md Backlog + roadmap
HISTORY.md Changelog + release notes
PROJECT.md Scope, feature matrix, architectural decisions

License

Private project. All rights reserved.