Skip to content

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/, active/kindredpics-log.md (session history — load on demand only).

Canonical rule: CLAUDE.md is legacy project memory; active/kindredpics.md is 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 branch production. Note: CF Pages default subdomain stays nanny-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.app REMOVED 2026-05-28.
  • CF hardening applied 2026-05-28 (audit at deliverables/OPS-cloudflare_audit_v1.0_2026-05-28.md): DMARC p=nonep=quarantine on kindredpics.com; always_use_https ON; min_tls_version 1.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-picskindredpics (id 3735c88a-0b7a-49e5-a4b2-f24cd9621519). R2 nanny-pics-photoskindredpics-photos. Old nanny-pics D1 + R2 DELETED 2026-05-26 per Devin (rollback window closed 5/24).
  • Auth: OTP primary, password fallback. Signup is OTP-only (/signup two-step form). Signin is dual-path: OTP primary, password fallback via toggle. Endpoints /api/auth/request-otp + /api/auth/verify-otp (shipped 2026-05-25 ec935be, migration 027). /forgot-password + /reset-password remain 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 (commit dd45aad). Used as workspace-invite mechanism/api/tenants/join adds an authenticated user to an additional workspace via code.
  • Retention: 30 days from finalize (P0.2 done). Was 90 days at sign. Purge clock starts at upload completion, not reservation.
  • Auto-Rekognition: REMOVED from upload path (P0.3 partial). processPhoto() runs with skip_rekog=1 shim 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_status is pending | gps_removed | unsupported_format | failed. No more always-zero gps_stripped.
  • Tests: 225 passed / 0 failed / 6 skipped against prod (2026-06-11; chromium + mobile-safari + mobile-chrome). Correction: an earlier note here claimed "144 passed / 0 skipped" — that was wrong; the suite actually had ~63 pre-existing failures (stale smoke-email fixtures devindavidson7@/kp-smoke@ — neither user exists, use claude-test@stampready.app; auth/smoke specs asserted the retired password-primary /signin; a 12-char family-code test). All repaired 2026-06-11. Always check the N failed line, not just N passed. Dead gallery-features.spec removed (5e83f3d); trust-tier-approval.spec added.
  • 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_sent CHECK constraint missed 15d). 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_batches table yet; photos still belong directly to a tenant, not to a named batch.
  • P0.5 trust tiers + uploader-first approvalSHIPPED 2026-06-11 (commit 46ec24a, migration 035, deployed + live-verified). Tiers owner > admin > trusted > contributor on tenant_members.role (membercontributor). A manual tag auto-approves only if the actor uploaded the photo OR is owner/admin/trusted; a contributor tagging someone else's upload → pending until the uploader (owner/admin fallback) approves. Pending tags hidden everywhere (manifest, per-photo GET, people/tree) until approved. Endpoints: GET /api/face-tags/pending, POST /api/face-tags/:id/approve|reject, GET /api/tenants/me/members, POST /api/tenants/me/set-role. SPA PendingApprovals banner in LibraryView. Shared helpers functions/utils/trust-tiers.js + tag-approval.js; context via extended getTenantContext (now returns userId+role). New signups join as contributor.
  • Paid scan-request flow (Phase 3) — scan_requests table not built; cost-estimate modal not built; Paddle webhook → scan state not wired.
  • Export as handoff package (Phase 4) — TRUNCATION BUG FOUND + FIXED 2026-06-11 (commit acb928e). GET /api/users/export-zip streams R2 originals + manifest.json, scoped to caller's uploader_user_id. The 2026-05-29 "verified, 6 photos" claim was a FALSE POSITIVE — small exports finished synchronously, but the streaming writer ran as a detached promise with no waitUntil(), so CF reaped the isolate before finalize() wrote the central directory + EOCD. A 34-photo/116MB export truncated non-deterministically (35MB then 29MB across runs) and unzip rejected it as "not a zip file". Fix: waitUntil(writePromise) keeps the isolate alive until the full archive flushes. Re-verified live: 121,838,500-byte ZIP, 35 entries, unzip -t clean, valid EOCD. The old "bad CRC on manifest" caveat is ALSO already gone (ZipStream's crc32 does the final XOR; manifest validates clean). Note: ~6 min to download 116MB (~330KB/s, sequential R2 gets + JS CRC32) — functional but slow; very large exports (100-photo free tier ≈ 340MB) may need parallel R2 reads or chunking later.
  • Purge reminders (Phase 4) — VERIFIED 2026-05-29. Email pipeline shipped at POST /api/photos/purge-reminders, idempotency table purge_reminders_sent (migrations 030 + 032 in remote D1) + cron 15 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. See FounderOS/deliverables/OPS-kp_launch_readiness_v1.0_2026-05-29.md.
  • Marketing copy already aligned 2026-05-29 (stale flag corrected 2026-06-07). src/pricing.html + src/index.html describe 30-day workspace + biometric-privacy purge. No "kept forever" / 90-day language survives in user-facing copy. The 90-day option in photos.html is share-link TTL, not retention.

TARGET intent (Devin's 5 truths, 2026-05-23)

What KP is becoming (some shipped, some not — see LIVE / IN-PROGRESS):

  1. 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.html hero all reflect 30-day workspace reality).
  2. 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.
  3. Rekognition only behind consent + paywall. Same as #2; paywall enforcement waits on Phase 3.
  4. Uploader-first approval model. SHIPPED 2026-06-11 (P0.5, commit 46ec24a). Contributor tags on others' uploads go pending → uploader/owner approves. Live-verified full loop.
  5. Auth = OTP primary + password fallback + shareable family code KP-XXXX-XXXX workspace-invite. Shipped (OTP ec935be 2026-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.sh OR wrangler 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.html as 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 (RESIZEkp-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 via src/data/store.ts
  • Analytics: PostHog (displaced Humblytics 2026-05-22)
  • Cron: kp-cron Cloudflare Worker (cron-worker/) — */5 * * * * drain, Sun 23:00 UTC weekly digest

Files to read first

  • kindredpics-site/CLAUDE.mdlegacy project memory; conflates LIVE + PLANNED — disambiguate against this brief
  • kindredpics-site/deliverables/SPEC-rebuild_v2.0_2026-05-23.md — option B rebuild SPEC + open questions Q1-Q9
  • kindredpics-site/deliverables/TECH-gap_remediation_handoff_v1.0_2026-05-23.md — Codex's P0-P2 gap analysis; canonical for what's NOT yet built
  • kindredpics-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 headers
  • kindredpics-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 + normalizer
  • kindredpics-site/image-worker/src/index.js — sibling Worker for CF Images resize
  • kindredpics-site/db/schema_v2.sql — 35-table consolidated schema (current live)
  • active/kindredpics-log.md — session-by-session work history (load on demand)

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 wizard
  • reference_kindredpics_deploy.md — deploy gotcha
  • reference_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

Resolve-all batch — SHIPPED 2026-06-11 PM (commits 05a04a6 + 9be3e5c)

Devin gave Claude open-ended permission to "resolve all known KP issues." Committed (05a04a6 resolve-all + 9be3e5c chain-fix), pushed (main==origin/main), and deployed to prod (Pages 401 probes confirm live). Deploy-state re-verified 2026-06-11 PM.

Shipped (live D1): - 34 orphan photo rows (ids 38-71) + 4 stuck upload_batches (ids 6-9) DELETED from prod D1. Lindsey's 2026-06-07 failed-R2-PUT residue. Pre-checked FK references (face_tags=0, memories=0, scan_requests=0). Clean.

Shipped (live prod, deploy-verified): - /api/maintenance/sweep-stale-batches (NEW). Cron-token-auth endpoint closing upload_batches the SPA never closed. Three buckets: (a) stale >2h, 0 pending → close; (b) stale >24h with pending → close anyway (closed_with_pending); © stale >2h, 0 photos → DELETE. Returns {ok, swept:{closed_clean, closed_with_pending, deleted_empty}, errors, ms}. Live (401 without cron token). - Sweep chained into daily reap (functions/api/maintenance/reap.js imports runSweep). 9be3e5c replaced the originally-planned 6th cron trigger (50 8) — CF free plan caps cron triggers at 5, so the sweep rides the existing 45 8 UTC reap slot. No separate cron-worker redeploy needed; reap.js surfaces sweep errors via batch_sweep without failing reap. Manual trigger still available via cron-worker ?run=sweep-batches. - /api/auth/me returns free_scans: { quota, used, remaining }. Live. - src/app.html stashes me payload as window.KP_ME; gold "Find people automatically" CTA renders dynamic copy (· N free while remaining > 0, · $0.01 / photo once quota used). Safe fallback to static label pre-hydration.

Test coverage (tests/e2e/auth-me-free-scans.spec.ts): API shape + math; SPA hydration + CTA label. Smoke-token gated, prod-targeted.

Family-code sweep: verified clean live (1 tenant, family_code='PT82XRSV', len=8). No action needed.

Deferred from this batch (with reasons): - Phase 3 P0.5 trust tiers + uploader-first approval — multi-week product decision; not safe to autonomously ship. - Phase 3 follow-ups (per-photo "Scan this · $0.01" CTA, multi-select, no-faces badge) — depend on Lindsey's still-pending UX feedback. - UI tenant switcher — currently zero users with multi-membership; backend ?tenant=<slug> query-param already supports it (functions/utils/tenant.js:43). Build when first 2-membership user lands. - Batch picker (user-set name + visibility before upload) — auto-named "YYYY-MM-DD upload" is fine until Phase 4 export packages ship and named batches become user-visible. - FingerprintP watermark refresh — cosmetic, no problem stated. - Import Review UI batch-aware — no current import-review surface; scope unclear.

Latent issue — RESOLVED 2026-06-11 PM (commit 4eaeddc): - .github/workflows/deploy.yml referenced the DELETED kindredpics project, failing on every push to main since 2026-06-03. Fixed: trigger changed to workflow_dispatch-only (no auto-deploy on push — honors the "run deploy.sh explicitly" rule) + all 3 --project-name refs → kp. Workflow is now a correct one-click manual fallback; scripts/deploy.sh stays canonical.

Current priority

  1. 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. DONE 2026-06-03 PM — see kindredpics-log.md. 0a. Auto-close upload_batches (Bug C residual). SHIPPED 2026-06-07 (commit d5212e6, deploy 6a8e49d8). upload-queue.js now creates one batch per enqueue() via /api/batches/create and closes it via /api/batches/:id/close on worker drained. End-to-end verified live: create batch_id=11 → close → idempotent re-close all returned 200. Same commit also closes the auto-batch race that split Lindsey's 32-photo upload into 4 batches — client now passes one upload_batch_id to every sign call, so the 6-way parallel signs can't race the find-or-create window anymore. UI smoke test 2026-06-07 22:40 CT (batch_id=12): 20 in-page-built JPGs enqueued in one KP_UPLOAD_QUEUE.enqueue() call → 1× /batches/create, 20× /sign (all batch_id=12), 20× R2 PUT (all 200), 20× /finalize (all 201), 1× /batches/12/close (auto-fired on drain), wall-clock 5 sec, batch photo_count=20 status=upload_complete. Fixtures cleaned up: 20 photo rows + 20 R2 objects + 2 test batches (11, 12) deleted. 0b. UI E2E Playwright specs for the Lindsey class — SHIPPED 2026-06-11 (commit 5ce7141). tests/e2e/library-render.spec.ts (2 cases: manifest↔SPA↔DOM count parity; every photo has thumb/medium) + tests/e2e/tree-add.spec.ts (2 cases: live modal submit + tenant-scoped insert via /api/persons + soft-delete cleanup via DELETE /api/admin/persons/:id; empty-submit client guard intact). Pre-seeds localStorage.kp_consent='denied' to dismiss the mobile cookie banner that was overlaying MobileTabBar on ≤720px. Visibility-filtered selectors across desktop sidebar + mobile tab bar split. 12/12 pass across chromium + mobile-safari + mobile-chrome in 16s against prod. Smoke fixture: claude-test@stampready.app (user_id=1) on tenant_id=1 "Test Family". 6 test rows soft-deleted post-runs.
  2. Phase 2 backbone SHIPPED 2026-05-27 (commits 0150ae4 + afc53a2). upload_batches table + 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. 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.
  3. Phase 3 P0.5 trust tiers + uploader-first approval — SHIPPED 2026-06-11 (commit 46ec24a, migration 035, live-verified). Paid scan-request flow already shipped 2026-05-30 (test mode). Still ahead in Phase 3: per-photo "Scan this · $0.01" CTA, multi-select, no-faces badge (await Lindsey UX feedback). Possible polish: thumbnails in the PendingApprovals banner show via /p/:id/thumb; a dedicated approvals view/nav entry if the LibraryView banner proves too subtle.
  4. Family-code migration: generator + endpoint already 8-char; Davidson tenant_id=1 manually rotated to KP-B385-YKRS 2026-05-23 via wrangler d1 execute. If any new tenants land with 12-char codes (shouldn't), repeat. Better: SQL one-shot to sweep all LENGTH(family_code) != 8 rows.
  5. 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.

Do NOT

  • Workspace model is largely live now: retention + auth + auto-Rekog-removal + Phase 2 batches + Phase 3 paid scan-request + P0.5 trust tiers/uploader-first approval all shipped. Still ahead: batch-aware Import Review UI, batch picker, Phase 3 UX polish (per-photo scan CTA, multi-select, no-faces badge). Don't overstate the remaining UX polish as missing core.
  • Don't make a contributor's manual tag on someone else's upload auto-approve — it MUST go pending (P0.5). Only the photo's uploader or owner/admin/trusted bypass approval. Don't let pending/rejected tags leak into manifest, per-photo GET, people, or tree.
  • 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 push and assume deploy. Run bash scripts/deploy.sh explicitly.
  • Don't sign content-type on R2 presigned PUTs — Safari mangles. Sign only host.
  • Don't add a new public route without updating _middleware.js bypass 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-pics D1/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 <kindredpics@stampready.app> (existing Resend sender; brand-bleed accepted to avoid $20/mo Resend Pro). Add Reply-To: support@kindredpics.com. 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 - devin@kindredpics.com domain/email verification: blocking the password-reset From wire-up