KindredPics — active brief¶
Read first. Source of truth for "what is KP right now." Reference, don't duplicate. Cross-refs:
projects/kindredpics.yaml(config),C:\Users\devin\kindredpics-site\CLAUDE.md(legacy project memory — describes a mix of LIVE and PLANNED state without distinguishing them; treat with caution), memory pointers below,decisions/.Canonical rule:
CLAUDE.mdis legacy project memory;active/kindredpics.mdis current operating truth where explicitly marked. This brief separates LIVE / IN-PROGRESS / TARGET so you don't conflate shipped behavior with intent.
LIVE state (shipped to prod 2026-05-23)¶
Option B workspace rebuild merged to main. Commit e4b9edc (rebuild) + 3dec457 (merge) + 79d3e54 (Playwright refresh). Branch fix/rebuild is merged.
- Live URL: https://www.kindredpics.com — Cloudflare Pages project
kindredpics, production branchproduction. Note: CF Pages default subdomain staysnanny-pics.pages.dev(CF locks it to creation-name; can't rename). Custom domains:nanny-pics.pages.dev(locked default),www.kindredpics.com(production).nanny.stampready.appREMOVED 2026-05-28. - CF hardening applied 2026-05-28 (audit at
deliverables/OPS-cloudflare_audit_v1.0_2026-05-28.md): DMARCp=none→p=quarantineon kindredpics.com;always_use_httpsON; min_tls_version1.2; Bot Fight Mode + Block AI bots + Crawler Protection enabled. - Davidson tenant + all prior data: wiped. Fresh empty schema applied to new D1. Davidson re-signs up.
- Infra renamed: D1
nanny-pics→kindredpics(id3735c88a-0b7a-49e5-a4b2-f24cd9621519). R2nanny-pics-photos→kindredpics-photos. Oldnanny-picsD1 + R2 DELETED 2026-05-26 per Devin (rollback window closed 5/24). - Auth: OTP primary, password fallback. Signup is OTP-only (
/signuptwo-step form). Signin is dual-path: OTP primary, password fallback via toggle. Endpoints/api/auth/request-otp+/api/auth/verify-otp(shipped 2026-05-25ec935be, migration 027)./forgot-password+/reset-passwordremain for password-path users. Magic-link is DEAD — endpoints return 410. Mobile-rn LoginScreen aligned. - Family code: SHIPPED as
KP-XXXX-XXXX(8-char, 4-4 hyphenation, Crockford-flavored alphabet — no 0/1/I/L/O). Migrated from 12-char today (commitdd45aad). Used as workspace-invite mechanism —/api/tenants/joinadds an authenticated user to an additional workspace via code. - Retention: 30 days from
finalize(P0.2 done). Was 90 days atsign. Purge clock starts at upload completion, not reservation. - Auto-Rekognition: REMOVED from upload path (P0.3 partial).
processPhoto()runs withskip_rekog=1shim to preserve derivative generation. Face indexing only fires when manually triggered from admin queue. Paid scan-request UI is Phase 3, not yet built. - Metadata sanitize: truthful state (P0.1 done).
photos.metadata_sanitize_statusispending | gps_removed | unsupported_format | failed. No more always-zerogps_stripped. - Tests: 108/114 Playwright passing against prod (6 skipped,
KP_SMOKE_TOKEN-gated). - Launch-readiness verified 2026-05-29 — autonomous Playwright + cron-trigger harness drove signup→upload→reminder-emails (15d/7d/3d/1d)→purge→export end-to-end. All four trust-chain touchpoints work in prod. Davidson tenant untouched. Two prod-only bugs found + fixed mid-test (stale Resend key on KP Pages;
purge_reminders_sentCHECK constraint missed15d). Same-day cosmetic follow-up shipped: CRC-32 spec compliance in ZipStream, purged-photo stub in export, path-comment fixes, daily reap cron for OTP + login_attempts (45 8 * * *), compat_date bump. Deliverable:FounderOS/deliverables/OPS-kp_launch_readiness_v1.0_2026-05-29.md.
IN-PROGRESS / NOT YET SHIPPED (Codex's gap doc, Phases 2-5)¶
Per deliverables/SPEC-rebuild_v2.0_2026-05-23.md + deliverables/TECH-gap_remediation_handoff_v1.0_2026-05-23.md:
- P0.4 batch/workspace model — no
upload_batchestable yet; photos still belong directly to a tenant, not to a named batch. - P0.5 trust tiers + uploader-first approval — current roles still owner/admin/member. Manual tags still auto-insert as
approved. Uploader-first approval model is Devin's intent but NOT in code. - Paid scan-request flow (Phase 3) —
scan_requeststable not built; cost-estimate modal not built; Paddle webhook → scan state not wired. - Export as handoff package (Phase 4) — VERIFIED 2026-05-29. Photo-bytes ZIP shipped at
GET /api/users/export-zip(streams R2 originals + manifest.json; live test: 7839 b ZIP, 6 photos + manifest). Note: comment in source says/api/users/me/export.zipbut Pages routing strips the/mesegment; actual path is/api/users/export-zip. Cosmetic gap:manifest.jsonhas a bad CRC on streaming ZIPs (data extracts fine, strict ZIP validators may complain). - Purge reminders (Phase 4) — VERIFIED 2026-05-29. Email pipeline shipped at
POST /api/photos/purge-reminders, idempotency tablepurge_reminders_sent(migrations 030 + 032 in remote D1) + cron15 8 * * *. Marks coded + tested live: 15d / 7d / 3d / 1d (4 marks). End-to-end Playwright + cron-trigger harness landed all 4 emails in Devin's inbox + verified idempotency. SeeFounderOS/deliverables/OPS-kp_launch_readiness_v1.0_2026-05-29.md. - Marketing copy still pre-rebuild.
docs/positioning.mdanddocs/icp.md(both dated 2026-05-18) describe the permanent "kitchen-table archive" framing. Pricing copy on/pricingstill says "kept forever" + 90-day originals. These must be rewritten to match the 30-day workspace reality before any growth push.
TARGET intent (Devin's 5 truths, 2026-05-23)¶
What KP is becoming (some shipped, some not — see LIVE / IN-PROGRESS):
- Temporary 30-day collaborative tagging/export workspace. Shipped via 30-day retention; positioning copy aligned 2026-05-29 (
docs/positioning.md,docs/icp.md,src/pricing.html,src/index.htmlhero all reflect 30-day workspace reality). - No unpaid face processing. Auto-Rekog removed (shipped); paid scan-request UI is Phase 3 — until then, face indexing happens only via admin manual trigger.
- Rekognition only behind consent + paywall. Same as #2; paywall enforcement waits on Phase 3.
- Uploader-first approval model. Not yet in code (Phase 3 / P0.5).
- Auth = OTP primary + password fallback + shareable family code
KP-XXXX-XXXXworkspace-invite. Shipped (OTPec935be2026-05-25; family code 8-char Crockford-flavored, 4-4).
Driver for the 30-day timeline: BIPA-class biometric-privacy compliance. Not a product preference. Don't propose extending retention without counsel sign-off.
Repo¶
- Root:
C:\Users\devin\kindredpics-site - Origin:
https://github.com/StampReady/kindredpics.git - Branches:
main(working — option-B rebuild now lives here),production(Cloudflare Pages deploy target) - Deploy is NOT push-to-deploy. After
git push:bash scripts/deploy.shORwrangler pages deploy src --project-name=kindredpics --branch=production --commit-dirty=true - Last commit on main:
79d3e54 [TEST] Refresh Playwright suites for email+password auth + workspace family-code
Stack (current)¶
- Frontend: vanilla HTML/CSS/JS on Cloudflare Pages, with
/app.htmlas a single-file React 18 + Babel-Standalone SPA (not pure vanilla in app shell) - Backend: Cloudflare Workers + R2 + D1
- Images: Cloudflare Images via sibling Worker service binding (
RESIZE→kp-image-resize) - Mobile: Expo SDK 54 / RN 0.81 / React 19 at
mobile-rn/; bottom-tab nav, native-stack for Photo/Person/Upload; shares SPA data-shape viasrc/data/store.ts - Analytics: PostHog (displaced Humblytics 2026-05-22)
- Cron:
kp-cronCloudflare Worker (cron-worker/) —*/5 * * * *drain, Sun 23:00 UTC weekly digest
Files to read first¶
kindredpics-site/CLAUDE.md— legacy project memory; conflates LIVE + PLANNED — disambiguate against this briefkindredpics-site/deliverables/SPEC-rebuild_v2.0_2026-05-23.md— option B rebuild SPEC + open questions Q1-Q9kindredpics-site/deliverables/TECH-gap_remediation_handoff_v1.0_2026-05-23.md— Codex's P0-P2 gap analysis; canonical for what's NOT yet builtkindredpics-site/docs/positioning.md+docs/icp.md— marketing positioning (PRE-rebuild; needs rewrite)kindredpics-site/functions/_middleware.js— public-route allowlist + magic-link 410 shims + security headerskindredpics-site/functions/api/photos/sign.js+finalize.js— upload path (30-day retention live here)kindredpics-site/functions/utils/family-code.js— KP-XXXX-XXXX generator + normalizerkindredpics-site/image-worker/src/index.js— sibling Worker for CF Images resizekindredpics-site/db/schema_v2.sql— 35-table consolidated schema (current live)
Memory pointers (load on demand)¶
project_kp_product_truth_2026-05-23.md— 5 truths (TARGET, partial-live)project_session_2026-05-18_kp_marketing_launch.md— positioning + marketing surface launch (PRE-rebuild)project_session_2026-05-16_to_17.md— face-tag overhaul, /name-people.html wizardreference_kindredpics_deploy.md— deploy gotchareference_safari_r2_signed_headers.md,reference_csp_blocks_presigned_uploads.md,reference_cf_images_binding_api.md,reference_pages_cant_bind_images.md— upload-path gotchas
Current priority¶
- Nanny-pics → kp-app migration (Devin-action). Pages project rebuild SOP emailed 2026-06-03. ~30s downtime when swapping custom domain. Blocks the "all nanny-pics references removed from CF" goal.
0a. Auto-close upload_batches (Bug C residual). SPA-side
/api/batches/:id/closecall from upload-queue.js on batch drain, OR idle-cron sweep that closes batches with no pending photos + last upload >10 min ago. Currently cosmetic ("in progress" caption sticks) but future users will hit the same confusion. 0b. UI E2E Playwright specs for the Lindsey class of bugs.tests/e2e/library-render.spec.ts+tests/e2e/tree-add.spec.ts. Would've caught both Lindsey bugs; API-only probes (v1.4 audit) missed them. - Phase 2 backbone SHIPPED 2026-05-27 (commits
0150ae4+afc53a2).upload_batchestable +photos.upload_batch_id+POST /api/batches/create+POST /api/batches/:id/close+ sign.js auto-batch (find-or-create on 30-min idle window per uploader). Migration 029 applied to remote D1. Verified end-to-end via UI: 3 photos via "Add photos" modal → all in sameupload_batch_id, status='upload_complete' after explicit close,purge_due_at=upload_completed_at + 30 days. Still ahead in Phase 2: batch-aware Import Review UI; user-facing "name your batch / choose visibility" picker (current copy auto-names "YYYY-MM-DD upload"); idle-cron sweep for never-closed batches. - Phase 3: trust tiers + uploader-first approval (P0.5) then paid scan-request flow (P0.3 full).
- Family-code migration: generator + endpoint already 8-char; Davidson tenant_id=1 manually rotated to
KP-B385-YKRS2026-05-23 viawrangler d1 execute. If any new tenants land with 12-char codes (shouldn't), repeat. Better: SQL one-shot to sweep allLENGTH(family_code) != 8rows. - AI / Rekognition watermark in app.html
FingerprintP(line ~2280) still renders the new KP script as a 260px decorative bg. Consider whether the script-as-watermark works visually or if it should be replaced with a different motif.
Done this session (2026-06-07 — Lindsey OTP email fix)¶
- Root cause: 2026-06-03 PM migration was supposed to push 19 env vars to new
kpPages project. Only 12 landed.EMAIL_FROMandEMAIL_REPLY_TOwere missing.functions/utils/email.js:11silently returns{ok:true,dev:true}whenEMAIL_FROMis absent — UI shows "code sent," no actual Resend call. Lindsey clicked "Send code" 3× today (D1email_otp_codesrows 3-5 at 14:30-14:33 UTC), no email arrived. - Fix shipped: PATCH to
/accounts/{aid}/pages/projects/kpadded[email protected]+[email protected]assecret_text. Env var count 12 → 14. Fresh deploy viabash scripts/deploy.shlanded athttps://bc161122.kp-ce8.pages.dev(production).environment=Noneverify warning is the known CF API false-positive. - Wrangler auth fix:
wrangler pages deployhit auth 10000 when shell env not exported.set -a; source ~/.claude/.env; set +abeforebash scripts/deploy.shresolved it (CLOUDFLARE_API_TOKEN now visible to wrangler subprocess). - Remaining 5 env vars audited + pushed same session. Cross-referenced every
env.Xreference infunctions/against prod. 5 actually-broken vars pushed (AWS_ACCESS_KEY_ID,AWS_REGION,R2_ACCESS_KEY_ID,R2_ACCOUNT_ID,STRIPE_KP_PRICE_ID=price_1Tct6H0MzwWYk80n2nLhalWJ). Env var count 14 → 19, matching the original 19 the 2026-06-03 migration promised. Redeploy7b6994f3landed. Dormant impacts unblocked: Rekognition signing (cron-drain face-detect), R2 presigned URLs, paid-scan Stripe Checkout (was returning 503 "payment not configured"). Skip list confirmed false-alarms:KP_BASE_URL(cron-worker only),STRIPE_KP_WEBHOOK_SECRET(live mode unused),CSAM_*/PHOTODNA_*/TAKEDOWN_EMAIL(CSAM_DRIVERdefaults tonoop),PADDLE_*(dead code, Stripe replaced). - Email pipeline VERIFIED working — Lindsey OTP row 6 (id 6, created 14:44:32 UTC, consumed 14:44:51) shows a real 19-second receive→enter cycle. Session minted.
- NEW BUG SURFACED IN SAME SESSION — R2 cred dead. Lindsey attempted 32-photo upload at 14:53:08 UTC. sign() succeeded for all 32 (D1 has rows 38-71 across batches 6/⅞/9 — race-condition 4-batch split is a separate auto-batch bug). Every R2 PUT 401'd Unauthorized. Verified with direct presigned PUT test using local
~/.claude/.envR2 creds → R2 returns<Code>Unauthorized</Code>. Old uploads (ids 1-37) intact in R2; CORS rules fine; bucket fine. R2 token scoped to oldnanny-pics-photosbucket name; bucket rename 2026-05-23 invalidated write access. Lindsey's "checkmark → /signin redirect" was a side-effect: SPA app shell re-checked session after the 32 PUT errors and middleware bounced. - Devin action emailed (gmail msg
19ea32e66ceb2b44): mint new R2 token via dashboard, scoped tokindredpics-photos, paste back. Then push to Pages + redeploy. Canonical stays PENDING-R2-CRED until done. - R2 cred rotation COMPLETE. Devin minted new R2 token
kp-pages-write-2026-06-07(KeyID6448d6e4...) via dashboard, scoped tokindredpics-photosObject Read & Write. Saved to~/.claude/.envvia Edit (NOT sed -i). Pushed to kp Pages prod via PATCH. Discovered SMOKE_TOKEN on prod didn't match local KP_SMOKE_TOKEN — synced. Redeployed (a4b1097bthenc3ab1b36). - End-to-end SPA smoke test PASSED 2026-06-07 15:08 UTC: smoke-signin 200 → sign 200 (photo_id 72, batch 10) → R2 PUT 200 (639 bytes) → finalize 201 (row flipped processed/visible). Fixture cleaned up. Devin notified via gmail
19ea358dbd567ed0(reply in thread19ea32e66ceb2b44). - Orphan cleanup (deferred): 32 photo rows (ids 38-71) + 4 upload_batches (ids 6-9) stuck in 'upload'/'uploading' state. Hold until Lindsey retries successfully, then sweep both stale batches + any new failure in one shot.
Done this session (2026-06-03 PM — nanny-pics CF project rebuild COMPLETE)¶
- New CF Pages project
kp(id8cc6d3a8-2f6b-44e5-9c98-bdc42619d489) created via API. CF auto-suffixed default subdomain tokp-ce8.pages.devbecausekp.pages.devwas already taken in the global namespace. Acceptable since*.pages.devis never user-facing — users seewww.kindredpics.com. - All 19 production env vars pushed via single PATCH call. 11 reused from local
~/.claude/.env; 4 freshly generated (SIGNING_KEY, INTERNAL_CRON_TOKEN, DIGEST_CRON_TOKEN, PASSWORD_SALT); 2 re-typed plain strings ([email protected], [email protected]); 2 random placeholders (PASSWORD_HASH, ADMIN_PASSWORD_HASH — Devin to overwrite with real values via CF dashboard when needed; legacy /photos.html family-pw gate now broken, admin password access broken until set). - D1 + R2 + RESIZE service binding all migrated to new project, production env only. Preview env intentionally left unbound to prevent any future "nanny-pics-photos / bef2b393..." style stale references.
- Custom domain cutover: added
www.kindredpics.comto newkpproject → CF auto-removed it from oldkindredpicsproject → DNS CNAME explicitly updated via DNS-write token fromnanny-pics.pages.dev→kp-ce8.pages.dev(CF didn't auto-update the CNAME, so manual PATCH was required). ~3 min real downtime between custom-domain swap and CNAME update; longer than the 30s I'd estimated. - Old
kindredpicsproject DELETED via API after cutover verified. Onlykpremains. Zero nanny-pics references in CF account. - Live smoke verified:
https://www.kindredpics.com/appserves the new project (200 OK), SPA loads 34 photos, 3 people, smoke-signin works with fresh SIGNING_KEY. All existing kp_session cookies invalidated by the SIGNING_KEY rotation — Devin + Lindsey need to OTP-sign-in again. - Lindsey emailed (gmail-personal msg
19e8f055b98f45db) with re-sign-in heads-up. Sent on Devin's behalf — flagged for him. - scripts/deploy.sh updated:
--project-name=kindredpics→--project-name=kp(line 9 + line 24 verify_deploy invocation). Uncommitted — will land in commit. - Audit of CF API tokens completed earlier same day: 5 dead tokens deleted by Devin (kp-direct-upload, R2 Account Token, nanny-pics-sync, old-butterfly-846a, stampready-backups-write). 2 kept:
kindredpics-gh-deploy(now serves asCLOUDFLARE_API_TOKEN, the Pages/D1/R2/Workers token) andClaude DNS(the DNS-write token used for the CNAME swap, stored asCLOUDFLARE_API_TOKEN_DNS). Thekindredpics-gh-deploytoken name still says "kindredpics" — separate cleanup if Devin wants to rename it.
Done this session (2026-06-03 AM — Lindsey-reported bugs fixed in prod + UI smoke green)¶
- Lindsey's 2026-06-02 19:07 CT email surfaced 3 real bugs, all now FIXED in prod (commit
f7e6f00, deploysa81dbd8d+d869440c). - Bug A (invisible uploads): 19 photos uploaded 2026-05-30 had
staging=1in D1 (origin: stale SW or cached client; couldn't pin to a current code producer).manifest.jsfiltered them out by default → invisible to LibraryView. Fixed: D1 recovery (UPDATE photos SET staging=0for her 19 rows),sign.jsforcesstaging=0regardless of client input,manifest.jsdrops thestaging=0filter entirely,sw.jsbumped v9 → v10 to bust iOS Safari caches. - Bug B (tree add silent 422): Modal opened, submit returned 422 with "apply failed: NOT NULL constraint failed". Two missing tenant_id values:
persons.tenant_idANDchange_log.tenant_id(4 INSERT sites). Fixed:tree-suggest.jsresolves tenant viagetTenantContextand passes toapplyTreeEdit.tree-edit.jsacceptstenant_id, injects into allpersons+relationships+change_logINSERTs, scopes all lookups by tenant (closes a latent cross-tenant person-merge leak).admin/queue.jsinjectssuggestion.tenant_idwhen approving queued tree edits. - Bug C (today shows "in progress"): All 5 of her upload_batches were stuck
status='uploading'— SPA never calls/api/batches/:id/closeafter a batch drains. The "Recent batches" rail caption stays "in progress" forever. Recovery only: D1 batch update closed her 5 batches. Still open as cosmetic: SPA-side auto-close on drain + idle-cron sweep are both unimplemented. Photos still render in the main grid; caption is the only artifact. - Smoke testing: live chrome-devtools-mcp drove
www.kindredpics.comas[email protected](Davidson tenant). Verified across 3 viewports (375 mobile / 768 tablet / 1280 desktop) — no horizontal overflow, all h1s render, 34 photos load in LibraryView, all 15 visible nav buttons clickable. End-to-end tree add-relative: clicked Tree → "Add a relative" → filled modal → submit → modal closed → KP_DATA refreshed → new person rendered with id=14, tenant_id=1, status='approved'. Cleanup applied (deleted 3 test rows + 3 change_log entries). Console: 1 cosmetic ReactfetchPriorityprop-casing warning, zero functional errors. - Playwright spec NOT added this session. chrome-devtools-mcp covered the same surface area for the bug verification. Recommendation: add
tests/e2e/library-render.spec.ts(upload → reload → assert N imgs in grid) +tests/e2e/tree-add.spec.ts(open modal → submit → assert person count increment) as permanent regression coverage — both bugs would've been caught and the missing UI E2E coverage is what let Lindsey hit them. ~30 min to add. - Nanny-pics migration prep done, awaiting Devin's hands: CF API rejects
nanny-pics.pages.devremoval with error 8000021 — it's the project's permanent locked default subdomain, not a removable custom-domain attachment. Only fix = full Pages project rebuild + DNS re-point + secrets re-migration. Full SOP emailed 2026-06-03 (gmail msg19e8dca324f67a33). Snapshot of current CF config atC:\tmp\kp-mig\cf-project.json(19 env vars, D1 'kindredpics', R2 'kindredpics-photos', service binding RESIZE → kp-image-resize, custom domain www.kindredpics.com). Suggested new project name:kp-app(CF holds the freed name for 24h). - Stale canonical note corrected: "all 3 saved CLOUDFLARE_API_TOKEN values 9109-invalid" is no longer true — the .env token deployed cleanly twice today.
Done previous session (2026-05-31 evening — pricing decision shipped: Option A min-batch gate ≥50)¶
- Pricing structural decision RESOLVED. Devin picked Option A (minimum-batch UI gate ≥50 paid photos) over pack pricing or prepaid credits. Driver: simplest mental model + preserves penny-per-photo unit pricing.
- Server gate shipped in
functions/api/photos/scan-request.js— newMIN_PAID_PHOTOS = 50constant. Gate fires after promo lookup, before Stripe Checkout creation: ifpaidNow > 0 && paidNow < 50 && !preAppliedCouponId, DELETE the orphan scan_requests row and return 400{error: "min_batch_under_50", paid_count, free_tier_count, free_remaining, hint}. KPFAMILY/preapplied-coupon path bypasses the gate (zeroes total). Gate is on the paid portion only — free-tier users with quota remaining are never blocked. - SPA updates in
src/app.html: BIPA notice now reads "After that, $0.01 per photo — paid scans run in batches of 50+ photos at a time."kpStartScanFlowhandles the new 400 with an actionable alert pointing to KPFAMILY DM fallback. - Pricing.html intentionally NOT updated — the public page hides all paid pricing ("free during early access") on purpose. The 50-photo line lives at the in-app friction point only. Revisit when paid plans get an announced public surface.
- Cosmetic gap (not blocking): the gold CTA still reads "Find people automatically · 100 free" even for users past their free quota. Plumbing free_remaining into the React component is invasive vs the actual friction value; tracking as Phase 3 polish. Currently zero real users are past free quota so this is moot.
- Not yet deployed. Code-complete + syntax-checked;
bash scripts/deploy.shnot run yet. Devin's call on when to push (Lindsey's smoke test is still mid-flight).
Done this session (2026-05-31 PM — Stripe gateway E2E + friends/family coupon)¶
- Stripe gateway tested end-to-end in prod (test mode). Full path verified: scan-request → Stripe Checkout → user pays →
checkout.session.completedwebhook → atomic D1 batch flips scan_requestpending → scanning+ photovisible → scanning+rekog_paid_atstamped +rekog_statusqueued. Cron-drain takes it from there (downstream, not gateway-test scope). - Real defect surfaced: Stripe enforces a $0.50 USD minimum at Checkout Session creation, BEFORE any user-typed promo code applies. KP's penny-per-photo pricing means every batch <50 photos was 502'ing in prod with
amount_too_small(6 logged entries before fix). The Phase 3 paid flow was structurally broken without an unhit code path; the v1.4 audit's "free path 200" probe never exercised this. - Short-term workaround shipped (commit
90896f1, deploye67a1f80):/api/photos/scan-requestnow accepts optionalpromo_codein request body. If present, server resolves it via Stripe Promotion Codes API + pinnedStripe-Version: 2023-10-16(account-default response shape was incompatible), pre-applies the underlying coupon viadiscounts[0][coupon]=.... A 100%-off coupon zeroes total — Stripe accepts $0 sessions — and unblocks small batches. - Friends/Family coupon live in Stripe test mode: coupon
KP-FAMILY-100(100% off, once, max 50 redemptions), promo codeKPFAMILY(max 50 redemptions, 1 redeemed via E2E test). Devin can DMKPFAMILYto friends for early sharing. - Pricing structural decision STILL OPEN (not launch-blocking with workaround in place): the \(0.50 minimum collides with penny pricing. Three options to pick before first paying public user: minimum-batch UI gate (≥50 photos before paid CTA enables), pack pricing (\)0.50 for 50-photo bundle), or prepaid credits.
- All Stripe test fixtures cleaned up: user 8 (Stripe Test), photo 22, 9 scan_requests rows, 7 error_log entries (all
amount_too_small), 1 tenant_member row. D1 back to clean baseline (2 users, 1 tenant, 19 photos, 0 face_tags, 0 scan_requests, 0 error_log). Stripe-side product/price/coupon/webhook all kept. - Wife smoke-test watcher stopped at Devin's request 2026-05-31 ~14:47 CT. She uploaded 19 photos at 2026-05-30 17:55 CT and never clicked the gold "Find people automatically · 100 free" CTA across 21 hours. Awaiting her verbal feedback. Background poller pid 1279 killed; state.json frozen at 14:47:12.
- CF API token rotated mid-session. Old
cfut_Shc...was CF code 1000 (revoked). Devin generated freshcfut_Shc...token (same prefix is coincidence; new id2d60b9fd...). Five deploys landed clean this session:03970e32,f7f8b13f,6b169c90,c07bad53,1a68bc3c,e67a1f80.
Done earlier this session (2026-05-31 AM — multi-user smoke v1.4 audit + same-day fix-throughs)¶
- v1.4 probe suite (
FounderOS/deliverables/OPS-kp_multi_user_smoke_v1.4_2026-05-31.md). 62/62 probes pass. Covered all 6 items deferred from v1.3 + Phase 3 scan-request + Stripe webhook surface. Zero NEW cross-tenant leaks. - Re-seeded multi-tenant fixtures from scratch after wife's 2026-05-30 wipe (
db/smoke-v14-setup.sql): Family B + 5 fixture users + face_tag+memory+album+branch. Cleaned up viadb/smoke-v14-cleanup.sql. Lindsey's 19 photos untouched. - All 3 findings shipped same-day:
- Scoreboard cross-tenant
added_byexposure (Finding #1) — RETIRED, not patched. Initial intermediate fix shipped opt-out via migration 034 +users.scoreboard_opt_out(commitd6d74a0). Devin then pivoted: replaced the entire global-leaderboard concept with per-workspace on-demand contributor reports (commit0162671, deployf7f8b13f). Deleted/api/scoreboard,/scoreboard.html,/scoreboard.js, nav links from share+tree,PATCH /api/users/me, andscoreboard_opt_outfrom/api/auth/me. New endpointGET /api/tenants/me/contributor-report?format=csv|md(owner/admin gated, scoped to ctx.tenantId, aggregates face_tags + memories + suggestions, Content-Disposition attachment). Settings → Privacy card swapped for Contributor Report card with CSV+MD buttons.users.scoreboard_opt_outcolumn kept vestigial. - Bridge default-tenant resolution (Finding #2) — FIXED (commit
d6d74a0).getTenantContextnowORDER BY tm.joined_at ASC, t.slug ASC(was justt.slug). First-joined wins; slug only as tiebreaker. Robust under tenant slug renames. - smoke-signin hygiene (Finding #3) — FIXED (commit
d6d74a0). AddedWHERE status = 'active'filter to matchverify-otp.js. - CF API token rotation completed mid-session. Old
cfut_Shc...returned CF code 1000 (revoked). Devin generated fresh token; both v1.4-followup deploys (03970e32,f7f8b13f) landed cleanly. - What remains uncovered (only Item 6 from v1.3 list): UI tenant switcher in
/app.html— UI work, not a probe target. - Tooling: probe runner at
C:/Users/devin/AppData/Local/Temp/kp-launch-test/smoke_v14_runner.py.
Done previous session (2026-05-30 — Phase 3 paid scan-request flow shipped, test mode)¶
- Driver: wife's overnight test surfaced that paid Rekog vs free self-tagging was unclear, and there was no payment path. Phase 3 promoted from scheduled → activation-blocking.
- Decisions locked (open Qs from TECH-scan_request_architecture spec): Q1 free cap = first 100 photos auto-scan free per user (lifetime trial). Q2 no-face refund = none, user paid for the check. Q3 bulk discount = not yet. Q4 multi-uploader = uploader-pays only. Self-tag stays always-free, equal-prominence CTA.
- Stripe IDs (test mode): Product
prod_Uc7KaWPry0khzs, Priceprice_1Tct6H0MzwWYk80n2nLhalWJ($0.01 per_unit), Webhookwe_1Tct6H0MzwWYk80nsRBLUkYJ→/api/billing/stripe-webhook(5 events incl.checkout.session.completed). Live keys deferred — test mode lets wife pay with4242 4242 4242 4242test card without real charges. - Migration 033 applied to remote D1:
scan_requeststable +photos.processing_state(upload|visible|scanning|scanned|failed) +photos.rekog_paid_at+photos.rekog_free_tier+users.free_scans_used/free_scans_quota(default 100). Backfill: 2 photosscanned(had approved face_tags), 7visible. - Endpoints (commit
ae48a47):POST /api/photos/scan-request(uploader-pays gate + free-tier branch + Stripe Checkout for paid +{all_visible:true}SPA shorthand).POST /api/billing/stripe-webhook(HMAC-SHA256 sig verify against test+live secrets, idempotent state flip oncheckout.session.completed). Middleware bypass added for webhook path. - Upload path rewired:
processPhoto(skip_rekog=1)→ newgenerateDerivativesOnly()inupload.js+finalize.js. Both setprocessing_state='visible'in the photos UPDATE.processPhotosimplified: droppedtenants.face_detection_enabledgate,perPhotoSkipbranch, and auto-trash-no-face.cron-drainsetsprocessing_state='scanned'on success /'failed'on error, and marksscan_requestscomplete viajson_each-driven query when all its photos leave 'scanning'. - SPA (LibraryView): new gold "Find people automatically · 100 free" pill button alongside "Tag faces (free)" + "Add photos".
window.kpStartScanFlowglobal handler: BIPA notice viawindow.confirm→ POST{all_visible:true}→ free path (toast) or Stripe Checkout redirect. URL-param handler shows toast on?scan_paid=.../?scan_cancelled=...then strips them. - 3 CF Pages prod secrets pushed live (test mode) via direct
PATCH /accounts/{aid}/pages/projects/kindredpicsusing wrangler OAuth bearer token extracted from~/.wrangler/config/default.toml— bypassed the brokenwrangler pages secret put/memberships path (still 9106). All 3 STRIPE_* vars present in prod env_vars (count 16 → 19). Redeployed (deploy2e41d6ba) so functions read them. Webhook smoke verified:POST /api/billing/stripe-webhookwith fake sig returns "invalid signature" (= secret loaded, HMAC verify ran). All three savedCLOUDFLARE_API_TOKENvalues in.claude/.envreturn 9109 invalid — generate a fresh one before next CF Pages secret rotation or scripted ops. - Deferred this session: Playwright
scan-flow.spec.ts. Per-photo "Scan this · $0.01" CTA in PhotoView. Multi-select grid UI. SPA "no faces detected" badge with keep-or-dismiss.
Done previous session (2026-05-30 evening → 2026-05-31 morning — full reset + wife smoke test live)¶
- Full signup wipe at Devin's request: 177 rows across 22 tables deleted via
db/reset-signups-2026-05-30.sql. Schema + Stripe config + smoke fixtures preserved. Autoincrement reset. R2 photo orphans skipped (~27 objects, harmless — saved R2 creds don't have kindredpics-photos scope; next uploads overwrite by key on collision). - Test tenant seeded:
user_id=1[email protected] (placeholder owner),tenant_id=1"Test Family" slugtest-family, family_code PT82XRSV (user-visibleKP-PT82-XRSV). - Email sent to wife via
gmail-personalMCP (message ID19e7af0b2f97aacc) → [email protected]. Join linkhttps://www.kindredpics.com/signup?code=KP-PT82-XRSV, BIPA-light explainer, test-card callout (4242 4242 4242 4242), and ask to break the discoverability + free/paid clarity. - Wife smoke-test telemetry (via background D1 poller
C:/tmp/kp-watch/poll.shwriting deltas every 15s toC:/tmp/kp-watch/deltas.log): - 17:45 OTP requested for [email protected]
- 17:46 OTP consumed →
user_id=2"Lindsey Davidson" created, joined tenant_id=1 as member - 17:53-17:55 uploaded 19 photos (
photo_id 1-19, allprocessing_state=visible,uploader_user_id=2) - 17:55 → 2026-05-31 morning idle for 14+ hours, never clicked the gold "Find people automatically" button, zero
scan_requestsrows created - UX signal (unconfirmed, awaiting wife's verbal feedback): the gold CTA in LibraryView header may not be discoverable enough for a non-technical user, OR the "100 free" framing didn't trigger action, OR she just stepped away. Devin asking her for feedback at session-end. No code changes yet — wait for actual feedback signal before iterating.
- Watcher stopped. Background poller killed cleanly. Restart for next session:
bash C:/tmp/kp-watch/poll.sh &(script writes deltas + maintains state.json + prev.json in same dir). Deltas log preserved atC:/tmp/kp-watch/deltas.logfor reference.
Done previous session (2026-05-28 — second-pass gap closure: code-lookup defenses, retention onboarding, batches UI, real account delete, polish)¶
- TIER 1 (
d644943). Hardened/api/tenants/lookup-by-codeagainst enumeration: tightened per-IP from 30 to 20/min, added per-code cap of 5/min via existing rate-limit util. Audited/welcome+/onboardfor the same banned tokens as the prior marketing sweep — zero matches; tap-to-identify language already in place. ShippedGET /api/health/email-config(INTERNAL_CRON_TOKEN-gated) returning presence booleans forEMAIL_FROM/RESEND_API_KEY/EMAIL_REPLY_TO. Verified the live Pages env has all three viawrangler pages secret list— no missing-var email needed. - TIER 2 (
956f7e5, migration 031)./welcomestep 4 now carries a one-paragraph 30-day-retention callout so new users see the timer + reminder email schedule before they upload. NewGET /api/batches?limit=5&me=1endpoint withphoto_countsubquery +days_until_purge. NewRecentBatchesrail in the dashboard sidebar — quiet when empty, color-escalates at ≤7d / ≤3d. Real account-deletion shipped:users.deleted_at/deleted_reason+ newaudit_logtable;POST /api/auth/delete-accountwith typed-email anti-typo guard + sole-owner-of-active-tenant block; settings.html swap from mailto-only to a real form (mailto kept as accessibility fallback). Verified E2E via D1: created test user 3, signed in via smoke-signin, hit endpoint, confirmeddeleted_atpopulated, memberships=0, audit_log row inserted, kp_session cookie cleared. - TIER 3 (
57643ee). Three distinct per-guide OG images (1200×630 PIL-generated, gold/sage/sienna palettes) atsrc/assets/og/; live byte counts confirm uniqueness. Sitemap<lastmod>on all 6 URLs. Cookie consent banner cloned to/demo/index.html(same DOM-API safe-build pattern as /app.html).Leave tenantnow requires typing the workspace slug to confirm — symmetric with Delete Entire Tenant. - Test data cleaned up post-verification (user_id=3 + their membership row removed). Smoke fixture
[email protected]unchanged.
Done previous session (2026-05-27 — auth-page brand fix + smoke OTP infra + Phase 2 batch backbone + R2 CORS recovery)¶
- Auth-page logo conflict resolved (
478a49a). Dropped redundant<h1 class="brand-name">KindredPics</h1>from signin/signup/forgot-password/reset-password (it stacked a serif "K" under the cursive monogram K). Promoted.card-titleh2 → h1 for proper heading hierarchy. Monogram scaled 64 → 88px..brand-nameCSS rule retained forwelcome.html(still uses it for the "Welcome" h1). Verified zero brand conflicts via chrome-devtools-mcp on production: signup + signin both report{h1s: ["<page title>"], brandNameExists: false, monogramHeight: 88}. - Smoke OTP peek infra shipped (
478a49a, migration 028 applied). Hardcoded whitelist email[email protected]infunctions/utils/otp.js(SMOKE_OTP_WHITELIST_EMAIL). Whenrequest-otp.jssees this email it ALSO writes the plaintext 6-digit code tosmoke_otp_codes. New endpointPOST /api/auth/smoke-otp-peek(gated byenv.SMOKE_TOKEN— same secret assmoke-signin) returns + marks consumed the latest plaintext for the whitelist email. Real users' codes remain SHA-256 hashed inemail_otp_codes. Verified E2E: signup → peek → verify-otp → land at/app; sign out → signin OTP → peek → verify → land at/app(same user_id=2, tenant_id=2, family_codeCMM7C6NVformatted asKP-CMM7-C6NV). - Phase 2 P0.4 backbone shipped (
0150ae4+afc53a2). Migration 029 (upload_batchestable +photos.upload_batch_idindex, applied to remote D1). EndpointsPOST /api/batches/create(name + visibility, returns batch_id) andPOST /api/batches/:id/close(idempotent, rejects if pending photos remain, setsupload_completed_at+purge_due_at = +30 days).sign.jsaccepts optionalupload_batch_id; if omitted, find-or-create on a 30-min idle window for the same uploader (auto-batch fallback so existing clients aren't broken). Initial auto-close-on-pending=0 logic infinalize.jswas racy (small batches finalize photo #1 before photo #2 signs, prematurely closing) — replaced with explicit/closeendpoint. Verified E2E via the "Add photos" modal: 3 UI uploads (ui-a.jpg / ui-b.jpg / ui-c.jpg) all landed inupload_batch_id=4, status='upload_complete' after explicit close, photo retention + batch purge_due_at both at 2026-06-26. - R2 CORS recovered on
kindredpics-photos(9a829dc). Browser-direct PUTs were failing withnet::ERR_FAILED(preflight 403, "CORS not configured for this bucket"). The 2026-05-23 bucket rename fromnanny-pics-photosdropped the prior CORS rules. Re-applied viawrangler r2 bucket cors set kindredpics-photos --file infra/r2-cors-kindredpics-photos.json— verified preflight returns 204 +Access-Control-Allow-Origin: https://www.kindredpics.com. Config + recovery steps now committed toinfra/.
Done previous session (2026-05-25 morning — UI polish + role rename)¶
- Step-4 illustration replaced (
72a016e) — scene-matched watercolor (album + glasses + candle + rosemary). Resolves the cream-bg stopgap. - WebP conversion (
df50929) — all 4 onboarding PNGs → WebP via PIL q=85 method=6. 10.2 MB → 655 KB (94% reduction). PNGs deleted; welcome.html refs swapped. - Landing OG swapped to 4-panel (
d43c8c6) — 1200×630 JPG (129 KB) + WebP companion (65 KB), full og:image:width/height/type/alt + twitter:card summary_large_image. App/admin routes still use wordmark OG. - Role label "owner" → "host" (
4d0c4fa) — 8 user-facing files updated. Driver: "owner" implied possessive hierarchy over family memories; "host" fits the kitchen-table metaphor. DB role value'owner'unchanged; settings.html badge has display-mapping (role==='owner' ? 'host' : role)./api/tenants/transfer-ownershipendpoint + JSON fieldnew_owner_user_idunchanged. "tenant owner" in privacy/terms also swapped to "workspace host" for consistency. Legal terms kept: DMCA "rights owner," IP "ownership of every photo." - Inviter name on /signup lookup (
8f7df45) — anti-phishing trust signal. Lookup-by-code endpoint adds JOIN to tenant_members+users; returnshost_name. Preview text shows "Invited by {host} to {family} ({n} members)". Falls back to old "Found:" copy if host_name is null. No new personal data exposed (host display_name already public to members).
Friction-point sequencing (in-flight 2026-05-25)¶
After scenario walk-through, Devin approved fixes for friction points 1, 2, 5 (out of 5 identified for grandma's invite path):
- ✅ #5 inviter name — shipped
8f7df45 - ✅ #1 email OTP — shipped
ec935be. Migration 027 (newemail_otp_codestable; users.password_hash/salt/set_at relaxed to NULLABLE). Endpoints/api/auth/request-otp+/api/auth/verify-otp. Signup is OTP-only (two-step form). Signin is dual-path (OTP primary, password fallback via toggle). Sender stays[email protected]— stampready.org would need Resend Pro $20/mo, deferred per Q2 ADR. D1 wiped clean before migration (2 users + 2 tenants + 2 members removed per Devin authorization). New signups become user_id=1. Devin signup-tested end-to-end 2026-05-25 — workflow worked. - Brand tile asset (
5ee3c3c) —src/assets/marketing/kp_brand_tile_1024.png. Framed KP monogram with four profile-face corner ornaments. For IG/Pinterest/press tile contexts only. Not a replacement for the in-product logo. Gemini ✧ scrubbed before commit.
Pending for next session (queued 2026-05-25 evening — interrupted mid-build)¶
- Logo conflict fix on auth pages (signin, signup, forgot-password, reset-password) — Devin flagged "conflicting and overlapping logos" on
/signupafter OTP signup-test. Issue: the lockup shows thekp_logo_letters_512.pngKP monogram image AND a redundant<h1 class="brand-name">KindredPics</h1>text H1 directly below. The H1 "K" visually echoes the cursive K in the monogram → double-K stacking. Plan: drop the<h1 class="brand-name">text, promote the page-specific.card-title(h2) to h1 for proper heading hierarchy, scale monogram 64px → 88px for presence. Apply uniformly to all 4 auth pages. Tagline stays. - Smoke-test infra for visual OTP flow — Devin wants screenshot-able end-to-end test runs. Use existing
KP_SMOKE_TOKEN+ chrome-devtools-mcp +[email protected]as whitelisted test inbox. Plan: inrequest-otp.js, when email matches smoke whitelist (compare against envKP_SMOKE_TEST_EMAIL), also write the plaintext code to a separatesmoke_otp_codestable (or KV with TTL). New endpoint/api/auth/smoke-otp-peek(gated byKP_SMOKE_TOKEN) returns the latest unconsumed plaintext for the whitelisted email. Real users' codes stay hashed and unrecoverable. Smoke script then drives: navigate/signup→ fill form with whitelisted email → POST request-otp → poll smoke-otp-peek → fill code → verify → screenshot at every step. Output PNG sequence intoC:/Users/devin/AppData/Local/Temp/kp-smoke/. - Friction point #2 — Apple/Google OAuth — still deferred per recommendation until 50+ workspaces.
- ⏸️ #2 OAuth (Apple/Google) — deferred until 50+ active workspaces; OTP closes ~80% of the gap
- ⏸️ #3 server-side email invite — Devin chose NOT to ship; Web Share + inviter-name carry the load for now
- ⏸️ #4 i18n on /signup + /welcome — Devin chose NOT to ship; revisit when ICP language data demands it
Done previous session (2026-05-24 evening — /welcome watercolor illustrations)¶
- Generated 4 onboarding wizard illustrations via Gemini 2.5 Flash Image (nano-banana). Style: hand-painted watercolor + faded graphite linework, kitchen-table memoir aesthetic. Cream/sienna/sage/dusty-rose palette, soft upper-left light, hand-drawn imperfection.
- Step 1 (Welcome): photos + chipped mug + dried flower on weathered table — transparent PNG
- Step 2 (Invite): two hands of different ages passing a small photograph — transparent PNG
- Step 3 (Upload): open shoebox with photos floating up, knitted shawl draped on table — transparent PNG (minor text-leak on box label, acceptable)
- Step 4 (Done): open family album + reading glasses + lit candle + rosemary sprig — REPLACED 2026-05-25 with scene-matched watercolor (commit
72a016e). All 4 illustrations use the same painted-checkerboard "transparency" border convention (note: they're opaque RGBA with painted art mimicking a transparent edge, not literal alpha=0). - Wired into
src/welcome.html: new.step-illustrationclass (360px max, 4:3, drop-shadow), step 1 eager-loaded, steps 2-4 lazy-loaded. Width/height attrs prevent CLS. Alt text descriptive for screen-readers. - Bonus marketing assets staged at
src/assets/marketing/: vertical 4-panel (story asset) + horizontal panorama (OG image candidate). - Deployed via
bash scripts/deploy.sh— wrangler uploaded 5184 files, all 4 PNGs serving HTTP 200 at/assets/onboarding/.verify_deploy.shflaggedenvironment=None(known false-positive between CF API and the verify script), but the deploy landed. - Smoke-tested in Chrome via devtools MCP: authed via
/api/auth/smoke-signinwithKP_SMOKE_TOKEN, walked all 4 steps. Zero console errors, stepper dots advance correctly, illustrations composite cleanly against the white card (transparency works as expected — initial fullPage screenshot artifact showed checkerboard but real viewport renders fine). - Pushed to origin/main as
7b375f2 [BRAND] /welcome: wire watercolor step illustrations (1-4).
Open from this session:
- Regenerate step 4 DONE 2026-05-25 commit 72a016e — scene-matched watercolor (album + glasses + candle + rosemary) drops cleanly into the painted-checkerboard set.
- Convert PNGs to WebP DONE 2026-05-25 commit df50929 — PIL q=85 method=6 delivered 94% reduction (10.2 MB → 655 KB). PNGs deleted; welcome.html refs swapped to .webp. WebP support is universal (Safari 14+, 2020), no <picture> fallback wrapped.
- Style drift acknowledged on steps 1-3 (polished Ghibli-leaning) vs. the softer original watercolor anchor — Devin accepted as cohesive set; flag for revisit if brand polish ever escalates.
- REJECTED 2026-05-25 — confusing as a single image. Used welcome-panorama.png as og:imagewelcome-4-panel.png instead (commit d43c8c6): center-cropped 1200×896 → 1200×630, exported as JPG (129 KB) + WebP (65 KB), wired into src/index.html with full og:image:width/height/type/alt + twitter:card summary_large_image. App/admin routes still use the wordmark OG.
Done this session (2026-05-23)¶
- Landing page redesigned: 4-section flow (Question→Relatable→Problem→Solution), KP script wordmark, centered narrow column (kills wasted right-side space). Commits
1e38107,02f2882, etc. - KP script wordmark persisted across all surfaces: favicon (
kp_logo_square_512.png), apple-touch (kp_logo_apple_180.png), OG image (kp_logo_og_1200x630.png1200×630 cream), topbar/footer, hero, auth pages (4), app.html sidebar/mobile-topbar via rewrittenWordmark+ nulledCrest. Oldlogo-*PNGs deleted. - iOS Safari upload fix
ee13c2dconfirmed holding — SentryKINDREDPICS-WEB-2resolved. - CF Pages env var
[email protected]set. /photosmodal trap fix1f297ef—#album-modal/#share-modalhiddenattribute now respected via!important(inlinedisplay:flexwas overriding user-agent[hidden] { display: none })./welcome4-step onboarding wizard106324c/d2c297a: Welcome → Invite (code + copy + Web Share) → Upload (camera roll) → Done. Auto-shown on first signin/signup, opt-out vialocalStorage.kp_onboarded. Verified end-to-end on prod.- Orphan
SMOKE_TOKENrotated. New value stored asKP_SMOKE_TOKENinkindredpics-site/.env; documented inFounderOS/deliverables/SEC-secrets_inventory_v1.0_2026-05-23.md. - Davidson tenant_id=1 family_code migrated from 12-char to 8-char (
KP-B385-YKRS). Soft-launch suite now 27/27 + 6/6 family-code pass.
Do NOT¶
- Don't claim KP is "live" with the workspace model end-to-end — Phases 2-4 are still ahead. Only retention + auth + auto-Rekog-removal shipped.
- Don't add a Rekognition call from any upload path — auto-Rekog is explicitly removed (Codex P0.3). Face indexing only from admin manual trigger until Phase 3 paid-scan UI ships.
- Don't soften the 30-day retention without counsel sign-off — it's BIPA-class compliance, not a UX choice.
- Don't
git pushand assume deploy. Runbash scripts/deploy.shexplicitly. - Don't sign
content-typeon R2 presigned PUTs — Safari mangles. Sign onlyhost. - Don't add a new public route without updating
_middleware.jsbypass list. - Don't reinstate magic-link in any form — it's been replaced + 410'd.
- Don't rewrite the kitchen-table hero promise without asking — founder's exact words are locked.
- Old
nanny-picsD1/R2 deletion is DONE (2026-05-26) — no longer a guard rail.
Last handoff¶
deliverables/OPS-handoff_v1.0_2026-05-23.md exists in the KP repo (pre-FounderOS). After next real KP session: pwsh -File C:\Users\devin\FounderOS\scripts\new-handoff.ps1 -Project kindredpics.
Open decisions¶
See decisions/2026-05-23-kp-rebuild-followups.md for the full SPEC Q2-Q9 disposition. Status:
Resolved (decided 2026-05-23):
- Q3 multi-tenant Library view: filter dropdown with surnames ([All ▾] [Davidson] [Smith])
- Q4 tenant-switcher in upload UI: yes — required when user has 2+ memberships
- Q6 old-resource grace: 24hr — already shipped in rebuild commit, delete old nanny-pics D1/R2 after 2026-05-24
- Q7 auto-rekog removal phase: Phase 1 — already shipped via Codex P0.3 partial
- Q8 legal/policy questions: defer all four to counsel; Phase 2 ships without them. Conservative defaults: no extensions, no non-face labels, single 30-day retention preset, owner/admin only for paid scans
- Family-code format: TARGET = 4-4 Crockford KP-XXXX-XXXX (shipped is 4-4-4 12-char; migration free)
Decided 2026-05-23 evening (full disposition in ADR):
- Q2 password-reset From: KindredPics <[email protected]> (existing Resend sender; brand-bleed accepted to avoid $20/mo Resend Pro). Add Reply-To: [email protected]. Revisit on first paying customer / deliverability complaint.
- Q5 smoke-signin fate: already resolved by post-rebuild code (SMOKE_TOKEN-gated session mint, no email). Magic-link removal closed the gap implicitly.
- Q9 marketing-docs rewrite: hybrid surgical edit (~30% of docs). Keep kitchen-table promise + Ancestry frame + ICP triggers + founder story; edit retention/storage language.
Other open:
- Storage tier model (docs/icp.md flagged): likely moot under 30-day framing
- Cloudflare Queues for parallel Rekognition: lower priority now that Rekog is admin-manual-only
- Client-side EXIF strip: P0.1 server-side shipped; client-side JPEG strip before R2 PUT still on list
- KP CLAUDE.md rewrite: drop magic-link references; reframe "Auth model (post-2026-05-22)" section
- [email protected] domain/email verification: blocking the password-reset From wire-up