Commit Graph

763 Commits

Author SHA1 Message Date
null d324efe182 refactor(home): extract status strip -> components/HomeStatusStrip.kt
Step 6/9. Verbatim move (strip public, chip private). compileDebugKotlin green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:32:54 -05:00
null 48197ffd1e refactor(home): extract action feed -> components/HomeActionFeed.kt
Step 5/9. Verbatim move (section public, card private). compileDebugKotlin green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:31:42 -05:00
null 88460922c3 refactor(home): extract pending cards -> components/HomePendingCards.kt
Step 4/9. Verbatim move (section public, card-view private). compileDebugKotlin green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:30:25 -05:00
null 07c810c9a1 refactor(home): extract category preview -> components/HomeCategoryPreview.kt
Step 3/9. Verbatim move (grid public, mini-card private). compileDebugKotlin green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:29:06 -05:00
null 18ad31506e refactor(home): extract loading/error/empty -> components/HomeStateCards.kt
Step 2/9. Verbatim move, public entries. compileDebugKotlin green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:27:52 -05:00
null 5f39194c00 refactor(home): extract shared card kit -> components/HomeActionStyle.kt
Step 1/9 of the HomeScreen decomposition. Moves the 6 shared style decls
(homeActionGlyph, HomeGlyphIcon, homePrimaryArt, HomeActionColors,
HomeActionTone.actionColors, HomePill) verbatim into ui/home/components/, made
public per the components/ visibility idiom. No behavior change. compileDebugKotlin green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:26:08 -05:00
null 047185134e review 2026-07-11 19:17:51 -05:00
null eb70177178 revoew 2026-07-11 19:15:33 -05:00
null a7f252e656 review 2026-07-11 18:49:40 -05:00
null a63bf350b1 fix(crash): LocalDate.ofInstant crashes on API<34 + add Lint/R8 CI gates
Adding Android Lint to CI immediately caught two real crash bugs invisible to
all emulator QA (the fixture emulators run API 34+):

- SettingsViewModel + YourProgressViewModel called LocalDate.ofInstant, which
  was only added in API 34 — but minSdk is 26. On every device running Android
  8-13 (the bulk of the install base) these throw NoSuchMethodError and crash
  the Settings and Your Progress screens. Fixed with the API-26-safe equivalent
  Instant.atZone(zone).toLocalDate() (same result).

The other two Lint errors were false positives (ProduceStateDoesNotAssignValue
on two EncryptedChatImage composables that DO assign value inside the producer —
the check misfires on a suspend/?.let RHS) — explicitly @Suppress'd with a note,
so Lint reaches 0 errors legitimately rather than via a blanket baseline.

CI (android-ci.yml) gains two jobs:
- android-lint: ./gradlew :app:lintDebug (fails on error-severity; 113 existing
  warnings are non-fatal and left for a separate burndown).
- release-build: first-ever R8 gate — builds :app:bundleRelease with a throwaway
  keystore + dummy RC_API_KEY (satisfies the release guards; AAB not distributed),
  so the minify/shrink/sign toolchain can never silently rot. Verified locally:
  bundleRelease SUCCEEDS today (95MB AAB). A green build proves the toolchain,
  not runtime survival of reflectively-loaded classes (Tink) — that stays a
  release-APK QA item.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 18:41:04 -05:00
null 53d18beb03 feat(seed): sex_and_desire light-depth batch 6 of 6 (6 written, 338->344) - hits 40.1% light 2026-07-11 18:32:47 -05:00
null 5af109e119 feat(seed): sex_and_desire light-depth batch 5 of 6 (8 single + 4 multi + 4 tot + 4 scale, 318->338) 2026-07-11 18:32:15 -05:00
null a362742494 feat(seed): sex_and_desire light-depth batch 4 of 6 (12 written + 4 single + 4 tot, 298->318) 2026-07-11 18:31:30 -05:00
null 57b0f02763 feat(seed): sex_and_desire light-depth batch 3 of 6 (12 written + 4 single_choice, 282->298) 2026-07-11 18:30:51 -05:00
null b3165645e7 chore(repo): track the repaired pre-push hook + installer; backlog entries (Batch 2)
- scripts/git-hooks/pre-push: the fixed hook (dirty-tree guards kept; the
  self-defeating 'unpushed commits' blocker replaced with a behind-remote
  fail-fast + an informational listing of what a push will publish). Hooks
  don't sync on clone, so scripts/install-git-hooks.sh installs them; verified
  idempotent against the live .git/hooks copy.
- Future.md: scheduledOutcomesReminder .limit(200) no-pagination scaling cap
  recorded with the fix pattern (assignDailyQuestion's page loop); npm-audit
  entry re-verified 2026-07-11 (still 9 moderate, transitive; no lockfile churn).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 18:30:27 -05:00
null 25c463ee2a feat(seed): sex_and_desire light-depth batch 2 of 6 (16 written, 266->282) 2026-07-11 18:30:21 -05:00
null 2dc07de350 feat(seed): sex_and_desire light-depth batch 1 of 6 (16 written, 250->266) 2026-07-11 18:29:50 -05:00
null 08e9be626e docs: reconcile stale docs with project reality (Batch 1 doc hygiene)
Every point-in-time doc now carries an explicit status header so no one works
from a dead plan or stale claim:

- CopyMigration.md: CLOSED — migration stopped by owner decision after Slice 1;
  Slices 2-4 cancelled (several target strings since rewritten by the Closer
  Couples prose rename anyway).
- store-assets.md + NameChange.md: the support-URL 'couplesconnect.app' claim
  was stale — ExternalLinks.kt already uses closer.app/support; remaining action
  is Play-console listing fields only.
- internal-testing-checklist.md: SUPERSEDED by store-assets.md + release_guide;
  also corrected its applicationId error (app.closer -> closer.app, the same
  namespace swap behind the 4e807cb0 deep-link bug).
- private-mvp-checklist.md: SUPERSEDED by the ClaudeQAPlan system.
- UI_UPGRADE / standardization-review / ui-review / date-planning-roadmap /
  IOS_E2EE_STATUS / iphone ARCHITECTURE_AUDIT: stamped historical/paused with
  pointers to the live sources (Future.md, ClaudeBrandingReview, ERM).

NOT touched: DEVELOPMENT_LOG.md (verified alive — newest entry 2026-07-08,
prepend-ordered; earlier 'log is dead' read was wrong) and the Engineering
Reference Manual (another agent's Phase-4 review owns its drift).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 18:29:08 -05:00
null 654be1feff fix(seed): rewrite sex_and_desire_074 to drop 'Discuss' (banned phrase per QUESTION_QUALITY_CHECKLIST.md) 2026-07-11 18:26:46 -05:00
null 4e807cb088 fix(settings): Manage-subscription deep link used namespace, not applicationId
ExternalLinks.MANAGE_SUBSCRIPTION pointed Play's subscription manager at
package=app.closer (the code namespace); the Play package is the applicationId
closer.app. In production the Manage button would miss the app's subscription
entry. Invisible to all QA so far because no real subscription has ever existed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 18:22:03 -05:00
null 35b0eac2df review 2026-07-11 18:11:31 -05:00
null 0c588d0a7d chore: gitignore release_guide.md (local ship-readiness operator doc)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 18:10:42 -05:00
null 9401762bd4 review 2026-07-11 17:41:53 -05:00
null 523c7120b8 review 2026-07-11 17:09:05 -05:00
null 6668a3c4b7 refactor(brand): in-app prose -> 'Closer Couples' (launcher + wordmark stay 'Closer')
Extends the store/marketing rename to in-app user-facing PROSE, per owner decision.
Every string where 'Closer' names the app in a sentence now reads 'Closer Couples'
(Android + iOS): settings, pairing, invite share, widget headlines, paywall
subhead/thank-you, age gate, privacy body.

Deliberately KEPT as 'Closer' (judgment calls, documented in docs/NameChange.md):
- launcher label / iOS CFBundleDisplayName (14 chars truncates under the icon)
- wordmark logos (onboarding, auth alt-text, widget header, share-card drawText,
  iOS onboarding) + their asserting tests
- 'Grow Closer' onboarding pun (intimacy, not the app name)
- entity/length-capped lines: privacy rotator 'so Closer cannot read them'
  (hard 64-char cap), iOS 'not even Closer's servers'
- iOS 'Settings > Closer' help path (must match the kept display name)
- 'Closer Premium' tier label (matches the RevenueCat entitlement display name)
- all technical identifiers (app.closer / closer.app / closer_premium / etc.)

TodayWidgetTest exact-match assertions updated. compileDebugKotlin + unit tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 17:07:31 -05:00
null b17271c89f review 2026-07-11 17:05:54 -05:00
null d3763fd37b docs(daily): reconcile wildcard counts across guide + schema; pool 12->11
Self-review of the wildcard authoring spec caught two real issues:

1. Count contradiction: the new Wildcard section said '+12 free' while the pack's
   authoritative counts (QUESTION_SCHEMA.md daily table + metadata, and the guide's
   Required Counts + Final Production Gate 'count validation') still said 500/75. A
   content agent running count validation would fail, or worse delete weekday
   questions to hit 500. Reconciled all four locations to the weekday pack (frozen
   500) + wildcard add-on -> 511 total / 86 free / 425 premium, and folded the
   wildcard set into the Final Production Gate review sampling.

2. Rotation math: the picker indexes epochDay % poolSize and wildcard days fall ~10
   apart, so a pool size sharing a factor with 10 (12 is even) only surfaces a
   fraction of the questions per year. Pool size must be coprime with 10 -> 11, not
   12. Documented as a hard constraint so it isn't 'rounded to 10' later.

Also hardened the section: net-new (not a patch), answer_config option mirror,
unique snake_case option ids, and a QUESTION_SCHEMA.md cross-reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:48:18 -05:00
null f74f0e96f5 feat(daily): wildcard-day picker branch + authoring spec (Option A)
The client's DailyModeResolver promotes ~10% of days (dayOfYear % 10 == 3) to a
'Wildcard' theme, but there were zero wildcard questions and the server never
assigned one — so those days showed the Wildcard banner over a normal weekday
question. This adds the server half:

- pickDailyQuestionId now detects wildcard days (new pure, tested dayOfYearUtc /
  isWildcardDay helpers mirroring the client cadence) and prefers the mode_wildcard
  pool. Until that content is seeded it falls back to the day's WEEKDAY mode (not
  the whole pool), so it's a safe no-op pre-content. +3 unit tests (fn 80 -> 83).
- Authoring spec for the missing content added to
  seed/questions/DAILY_SINGLE_CHOICE_WEEKDAY_SYSTEM.md (## Wildcard Mode): 12 free
  single_choice, day-agnostic voice, REQUIRED mode_wildcard tag, id scheme, schema,
  and the post-authoring rollout (asset-db data-only insert + Firestore pointer seed
  + deploy).

Content authoring handed to another agent per the guide. Takes effect after the
wildcard rows land in app.db + Firestore and assignDailyQuestion is deployed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:39:59 -05:00
null f336c91731 docs(manual): batch 6 - fix broken iOS-Android sealed-answer bridge anchor (→ slug mismatch) 2026-07-11 16:25:36 -05:00
null 690b567f82 docs(plan): phase 4 batch 5 no-op - qa_push.js fix is internal to the QA helper 2026-07-11 16:24:20 -05:00
null fcee89912f docs(plan): phase 4 batch 4 no-op - copy catalog + string cleanup don't touch the manual 2026-07-11 16:24:04 -05:00
null dc624ebb21 docs(manual): batch 3 - flip per-module bullet for billing webhook to exported 2026-07-11 16:23:38 -05:00
null 0b92632973 docs(manual): batch 2 - RevenueCat 10.13.0 + HMAC-SHA256 webhook in tree + gitignore entry 2026-07-11 16:23:16 -05:00
null c60fa958a5 fix(qa): qa_push.js picks newest FCM token, not docs[0]
After an APK reinstall a device registers a fresh FCM token alongside stale
ones (~33h). The smoke harness grabbed docs[0] (unordered) and could hit a
stale token -> FCM 'Requested entity was not found' -> false smoke FAIL.
Now sort fcmTokens by updatedAt and try newest-first across all tokens,
skipping dead ones, mirroring the app's sendToUser + pruneTokens behavior.

Also file two R31 QA-env observations to Future.md (add the RevenueCat test_
key to local.properties so QA can drive the live paywall; FCM token churn note).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:06:07 -05:00
null 89d06eeb7d fix(billing): correct RevenueCat webhook auth to HMAC-SHA256 + enable export
The webhook verified an Ed25519 signature in an X-Signature header, but RevenueCat
offers no public-key signing — it sends HMAC-SHA256 in X-RevenueCat-Webhook-Signature
(t=<ts>,v1=<hex>) computed over "<ts>.<rawBody>". As written, every real event would
have 401'd and premium would never sync for the partner.

- Rewrite verification to HMAC-SHA256 with a +/-5-min timestamp replay guard and a
  constant-time compare; extract a pure verifyWebhookSignature() for unit testing.
- Rename secret REVENUECAT_SIGNING_KEY -> REVENUECAT_WEBHOOK_SECRET (it is an HMAC
  secret, not an Ed25519 key). Never deployed, so no migration.
- Uncomment the export in index.ts (deploy still gated on seeding the secret).
- Add revenueCatWebhook.test.ts: valid / tampered / wrong-secret / missing / stale.
- Reconcile Future.md + Engineering_Reference_Manual.md to the real scheme.

Verified against the live account: the entitlement identifier is now closer_premium,
so events match entitlementLogic. Build + 80 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 03:43:59 -05:00
null 48217dd716 chore(security): gitignore revenucat_secret_api.md (holds a RevenueCat secret key)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 03:43:43 -05:00
null 8cb23ea244 build(billing): bump RevenueCat SDK 10.12.0 → 10.13.0
Routine SDK patch bump. Resolves + compileDebugKotlin clean; no API changes needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 01:45:36 -05:00
null 2c3c2df8c4 fix(copy): reconcile divergent premium benefit lists + drop inaccurate claim
The paywall and subscription screens showed two different premium benefit lists (centralizing
into CloserCopy surfaced the drift). Unified to a single CloserCopy.premiumBenefits used by both.

Also an accuracy fix: dropped "Exportable memories" — it directly contradicts the app's own
privacy copy ("Closer does not currently offer a data export", strings.xml privacy_no_export_body),
i.e. a benefit the app explicitly does not provide. Remaining items are all verified real features
(QuestionComposer, Connection Challenges, Desire Sync, Memory Lane, date planning, answer history).

compileDebugKotlin clean; single source of truth for both surfaces going forward.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:50:29 -05:00
null 42fba641f1 chore(cleanup): remove the unused server/ Express backend (Tier 3)
server/ was a standalone Express webhook/health service (couples-connect-server, 17 files)
that duplicated the Firebase Cloud Functions — entitlement logic, FCM sends, an answer
listener, a webhook route — and was documented as "not client-facing… most teams will not
need it." It hadn't been touched since 2026-06-20 (pre-dating the whole v2 Functions
migration), had no deploy/CI wiring, and was a standing source of "which backend is real?"
confusion. Cloud Functions are the sole backend.

Removed the directory + all doc references so nothing dangles: README + Engineering Reference
Manual directory trees and the "Optional Express server" section; the iOS ARCHITECTURE_AUDIT
intro, its Section 8 (now a "removed" note), and the file-count table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:48:02 -05:00
null 23cd213953 refactor(copy): Slice 1 — dedup age-gate copy into CloserCopy.AgeGate
The 18+ min-age error was triplicated verbatim (SignUpViewModel, CreateProfileViewModel ×2);
consolidated to CloserCopy.AgeGate.ageError(minAge). Also lifted the two disclaimer variants
(kept distinct: brief on sign-up, with-reason on the DOB step — not unified) and the duplicated
"Please enter your date of birth." prompt. 6 call sites now source from the catalog; copy is
byte-identical (no behavior change), compile clean.

Adds docs/CopyMigration.md — the migration plan, scoped (after a gap review) to the ~15-25
brand-voice/duplicated lines, NOT all ~281 UI literals: the bulk stays inline until the single
i18n → strings.xml pass, since double-migrating to a Kotlin catalog first is wasted work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:39:04 -05:00
null 0195564d25 docs(readme): refresh ready-state home screenshots 2026-07-09 00:30:27 -05:00
null 4103c43393 docs(manual): drop dead references to files removed by the 21392ec2 dead-file sweep
The 2026-07-09 Tier 1 dead-file sweep (commit 21392ec2) removed
8 files the manual still cited. Each was verified unreferenced by
symbol-level git-grep + a clean :app:compileDebugKotlin + unit-test
compile. This patch aligns the manual with the post-sweep source.

Five stale references fixed:

1. Android Repository layout (line 102): dropped NotificationHelper +
   NotificationPermissionHelper from the core/notifications/ file
   list. Both removed (dead duplicates of NotificationChannelSetup).

2. Android Repository layout (line 108): dropped the data/questions/
   package from the layout. The directory is gone (QuestionJsonParser
   was the only file; the path is now empty + dead).

3. Android Repository layout (line 118): dropped PartnerNotificationScheduler
   from the top-level notifications/ package list. Removed as
   superseded by PartnerNotificationManager.

4. 'Note on the manual's older description' paragraph (line 147):
   rewritten to record the swept files (NotificationHelper,
   NotificationPermissionHelper, PartnerNotificationScheduler, the
   domain/model/ Entitlment.kt / InviteStatus.kt / QuestionSessionStatus.kt
   trio, and the data/questions/ package) with one-line reasons
   pulled from the 21392ec2 commit body, and to point QuestionDao at
   data/local/QuestionDao.kt as the now-canonical question-content
   source.

5. Logging section (line 1220): replaced the dead QuestionJsonParser
   BuildConfig.DEBUG example pointer with the live
   analytics/CompositeRetentionAnalytics.kt (one of the few current
   Android files that still guards android.util.Log with
   BuildConfig.DEBUG). The historical QJP pointer is preserved as a
   one-line breadcrumb so the next reader knows why it changed.

6. R23-DQ-001 landmine (line 1355): the parenthetical said
   'QuestionDetailViewModel (pack questions) is local-only and not
   affected' - QuestionDetailViewModel is gone, pack questions are
   now read directly by QuestionPackLibraryViewModel. Updated the
   parenthetical + added a one-line breadcrumb pointing at the
   21392ec2 retirement.

Anchor check: 30/30 internal anchors still resolve (Python GFM slug
checker).

Deliberately NOT in this commit:
- No mention of the CloserCopy catalog added - that's a documentation
  addition, not a stale-reference fix, and belongs in its own batch
  (the brand/ package is currently undocumented in the manual; a
  future pass should add CloserBrandCopy + CloserCopy to the
  Android Repository layout).
- No mention of the strings.xml purge (59e03369) - the manual
  doesn't reference any specific string resources, so the purge is
  invisible to the manual.
2026-07-09 00:29:26 -05:00
null 51efff3f78 refactor(copy): start CloserCopy catalog — migrate paywall + subscription voice copy
Establishes a typed Kotlin copy catalog (ui/brand/CloserCopy.kt), sibling to the existing
CloserBrandCopy (privacy rotator). Rationale over strings.xml: the app is English-only and
pre-launch, so a typed catalog gives one-place brand-voice review + compile-time safety
without Compose stringResource() friction or orphaned-string drift. strings.xml's real value
is the localization pipeline, which we migrate to in one pass when i18n is on the roadmap
(gate recorded in Future.md).

First slice (highest-value, monetization surface):
- PaywallScreen + SubscriptionScreen voice copy (benefit lists, headlines, value props,
  "Thank you for supporting Closer", couple-shared taglines) now source from CloserCopy.
- Generic chrome ("Continue", "Restore", "Manage subscription", error-retry) intentionally
  stays inline — it isn't brand voice.
- Surfaced real copy drift: the paywall and subscription benefit lists differ (swap two
  items + reorder) — co-located and flagged in CloserCopy.kt for a copy decision, left
  verbatim (not silently unified).

compileDebugKotlin clean; no behavior change (copy identical, just relocated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:26:15 -05:00
null 59e033692b chore(cleanup): purge 55 unused string resources
strings.xml had 127 entries but the app renders copy from hardcoded Compose literals
(only ~4 files use stringResource) — so ~half of strings.xml was a resource catalog that
was pre-created for the pairing/home/partner-home/settings-nav screens and never wired up.

Removed the 55 entries with zero R.string./@string references anywhere (kt/xml/manifest),
including whole dead sections (Settings nav labels, all Pairing subsections, Home screen,
Partner home). Kept everything actually referenced: app_name, today_widget_description,
common actions in use, and the Appearance/Notifications/Account/Privacy sections.
Resources compile clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:20:32 -05:00
null 21392ec2f3 chore(cleanup): remove verified dead files (Tier 1 dead-file sweep)
Removes files confirmed unreferenced by symbol-level git-grep and proven safe by a
clean :app:compileDebugKotlin + compileDebugUnitTestKotlin (no main/test references).

Dead code (every top-level type had 0 external references):
- core/notifications/NotificationHelper.kt — dead duplicate of the live
  NotificationChannelSetup (which is what CloserApp/AppMessagingService actually call)
- core/notifications/NotificationPermissionHelper.kt — unused permission helper
- notifications/PartnerNotificationScheduler.kt — superseded by PartnerNotificationManager
- data/questions/QuestionJsonParser.kt — superseded by the Room/asset-DB path
- data/repository/FakeQuestionRepository.kt — orphaned fake, no test/DI consumer
- ui/questions/QuestionDetailViewModel.kt — superseded VM, no composable binds it
- domain/model/{Entitlement,InviteStatus,QuestionSessionStatus}.kt — unused models/enums
  (entitlement state is read as Firestore booleans, not this model)

Stray artifacts:
- gitleaks-current.json / gitleaks-history.json — sanitized scan output (history = []),
  referenced by no CI/config; regenerable
- 19 stale .gitkeep placeholders in directories that now hold real files

Deliberately KEPT (flagged but not dead): WheelHistoryScreen/ViewModel.kt (named
"WheelHistory" but declare the live, nav-wired GameHistoryScreen/GameHistoryViewModel).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:10:40 -05:00
null a8819650d2 docs(store): refine banner card icon balance 2026-07-09 00:04:16 -05:00
null 2aa1157fe1 docs(store): rebalance README banner card icons 2026-07-08 23:57:51 -05:00
null 19b2ce8918 docs(brand): retire unreferenced generated-art feature graphics; single source of truth
Removed the two internal closer_app_explainer_feature.{png,svg} and
closer_e2ee_privacy_feature.{png,svg} files from docs/brand/generated-art/.
They were unreferenced banner-like art referenced by no store/README
doc, and the new docs/store/feature-graphic-1024x500.png (the
white-keyhole logo heart, 122aef7b) is the single source of truth for
README/store banner work.

docs/NameChange.md §5 updated to reflect the retirement (was 'OPTIONAL,
not store-facing'; now 'Retired ... so there is a single source of
truth') and points to the new feature-graphic as the canonical banner.
2026-07-08 23:51:10 -05:00
null 122aef7b88 docs(store): use white-keyhole logo heart in banner 2026-07-08 23:48:39 -05:00
null cbb42a6e5c docs(plan): Phase 3 complete - 13 batches, 11 issues found and fixed
Completed the full evidence-first re-review of
docs/Engineering_Reference_Manual.md started in the previous commit.
13 batches, 11 issues found and fixed (Batches 11 and 13 were clean),
all committed individually per the standing one-commit-one-batch rule.

Final verification: 30/30 internal anchors resolve cleanly with a
GFM-compliant slug checker; 0 broken TOC links.

Batches 1-13 commits on dev:
  ef5a2331  batch 1  iOS platform row E2EE not-yet-implemented claim
  1b36eba6  batch 2  recovery phrase wordlist size 256 -> 248
  0af6f24a  batch 3  Argon2id memory 46080 -> 47104 KiB
  5e292e66  batch 4  DOW mode table was missing Saturday
  5f7c785f  batch 5  3 missing rule helpers + isPublicKey regex +
                     releaseKeys sender-read fix
  aa356148  batch 6  Cloud Functions handler types table was imprecise
  ceb98f4a  batch 7  iOS DefaultEntitlementChecker actor doesn't
                     exist; gated-features list was wrong
  29cc39aa  batch 8  How-to-gate section header + game-push flag
                     claim (notificationsSent map -> 4 Timestamps)
  9330c0c4  batch 9  Tink template name SHA128 -> SHA256
  22c816ad  batch 10 RevenueCat version 8.20.0 -> 10.12.0 + Glance
  b3725463  batch 11 engineering conventions verified clean (no edits)
  80751f82  batch 12 A-201 landmine heading restored
  c5ead4cb  batch 13 where-to-look-first verified clean (no edits)

The full table of issues + commit SHAs is appended to this plan file
for the next session's reference.
2026-07-08 23:33:59 -05:00