Skip to content

KP rebuild follow-up decisions (post Option B ship)

Context

Option B workspace rebuild shipped to main 2026-05-23 (commits e4b9edc + 3dec457 + 79d3e54). SPEC-rebuild_v2.0 left Q2-Q9 open. Devin answered most during the post-ship FounderOS reconcile session.

Decisions

Family-code format (was: KP-XXXX-XXXX-XXXX, 12 chars)

Change to: KP-XXXX-XXXX4-4 Crockford base32 (no I/L/O/U/0/1).

Why: - 8 chars Crockford = 1.1T combinations, safe against rate-limited bruteforce - 4-4 hyphenation is the industry sweet spot for human-shareable codes (Discord, most SaaS invite codes) - Crockford alphabet eliminates the most ambiguous OCR/typing pairs (I↔1, O↔0, etc.) - Migration is free — D1 wipe today means zero issued codes outstanding

Required code changes (KP repo, separate session): - functions/utils/family-code.js — change generator alphabet to Crockford base32; change length 12→8; change KP-${slice(0,4)}-${slice(4,8)}-${slice(8,12)}KP-${slice(0,4)}-${slice(4,8)} - Any Playwright tests asserting the 4-4-4 format - Any UI copy mentioning the code format

Q3 — multi-tenant Library view

Decision: Filter dropdown with surnames (e.g., [All ▾] [Davidson] [Smith]). Not chip filter, not interleaved feed.

Why: Surnames are the natural mental model for a multi-family user. Dropdown scales better than chips when a user has 4+ workspaces.

Q4 — tenant-switcher in upload UI

Decision: Yes. When a user has 2+ memberships, upload UI must show a tenant-switcher before staging photos.

Why: Without this, photos silently land in the "first" workspace and require manual move — exactly the friction KP is supposed to eliminate.

Decision: Defer all four to counsel. Don't block Phase 2 on them.

Specifically deferred: - Can users extend the 30-day workspace? - Can non-face labels (object/scene) be offered without biometric consent? - What retention presets are legally allowed? - Paid scans: owner/admin only or any uploader?

How to apply: Phase 2 (batch model) ships without these answered. When Phase 3 (paid scans + trust tiers) gets close, Devin schedules a counsel call. Until then, conservative defaults: no extensions, no non-face labels, single retention preset (30-day delete originals), owner/admin only for paid scans.

Q2 — password-reset From address

Decision (final, 2026-05-23 evening): Keep using KindredPics <[email protected]> (existing Resend sender). Brand bleed is accepted to avoid $20/mo Resend Pro upgrade.

Path explored and dropped: [email protected] from a Resend-verified kindredpics.com domain. Blocked by Resend free-tier 1-verified-domain limit (StampReady already holds that slot). Not worth $20/mo at this stage.

Adopted mitigations (free, no new vendor): - Change local-part from [email protected][email protected] so the address itself reads "kindredpics" (less weird than "noreply" + cross-domain). Env var swap only; we already own all of stampready.app. - Add Reply-To: [email protected] header so customer replies don't land in StampReady ops. - Display name "KindredPics" stays — most inboxes show that first; underlying address is small grey text.

Revisit triggers for Resend Pro ($20/mo): first paying KP customer; first deliverability complaint; first time someone in a meeting asks "why am I getting StampReady email from you?"

Code changes (KP repo): - functions/utils/email.js — extend sendEmail() to accept a replyTo param; default to env.EMAIL_REPLY_TO - functions/api/auth/forgot-password.js — pass replyTo (or rely on env default) - Cloudflare Pages env: set EMAIL_FROM = 'KindredPics <[email protected]>' and EMAIL_REPLY_TO = '[email protected]' - [email protected] itself is being set up via Cloudflare Email Routing → forwards to [email protected] (free; for Gmail Send-mail-as + receiving generally)

Q5 — smoke-signin fate

Decision (revised after code inspection): Already resolved by the post-rebuild code. No further work needed.

Why: Inspecting functions/api/auth/smoke-signin.js shows it already mints a kp_session cookie based on SMOKE_TOKEN validation alone — it does NOT send email or call any magic-link flow. The "Sign in to KindredPics" emails to smoke-*@example.invalid in the Resend log are pre-rebuild artifacts from when Playwright tests were hitting the old magic-link endpoint (now 410-gone via functions/api/auth/request-magic-link.js). Those will bounce out of Resend's retry queue within 48-72hr.

No code changes needed. Original plan (convert to password-based + short-circuit email) is moot — the current implementation is already the equivalent (SMOKE_TOKEN-gated session mint, no email).

Q9 — marketing-docs rewrite scope

Decision: Hybrid surgical edit (~30% of docs). Not a full rewrite.

Keep intact: kitchen-table hero promise, Ancestry competitor frame, three ICP triggers, founder story, anti-positioning bullets.

Surgical edits: - Drop "forever" / "your archive" / "kept forever" wherever it appears - Add "your family identifies them together, then you export and own them" framing - Reframe pricing-page retention claims (90-day originals + "kept forever" face index) → 30-day + export-and-take-it-with-you - New anti-positioning bullet: "not long-term storage — you export and take the photos" - Re-balance docs/icp.md "what they tried first" if any comparisons assume permanent storage

Files to touch (KP repo): - src/pricing.html — retention copy + "kept forever" + 90-day originals - src/index.html — any "your archive" / "forever" hero or value-prop copy - src/about.html — verify it doesn't promise permanence - kindredpics-site/docs/positioning.md — surgical edits per above - kindredpics-site/docs/icp.md — surgical edits per above - Onboarding copy (signup flow / first-run UI) — ensure 30-day deadline is visible from day one

Family-code migration spec (4-4-4 → 4-4)

Status: TARGET. Shipped is 4-4-4 (12-char); migration is free since D1 wipe = zero outstanding codes.

Surprising-good-news: the shipped alphabet is already Crockford-flavored (23456789ABCDEFGHJKMNPQRSTUVWXYZ — drops 0/1/I/L/O, keeps U). Alphabet doesn't change; only length does.

Files to touch (KP repo)

  1. functions/utils/family-code.js — core change:
  2. const CODE_LEN = 12;const CODE_LEN = 8;
  3. formatCode: return `KP-${code.slice(0, 4)}-${code.slice(4, 8)}-${code.slice(8, 12)}`;return `KP-${code.slice(0, 4)}-${code.slice(4, 8)}`;
  4. Update header comment from "12 chars" → "8 chars"
  5. Entropy note: ~40 bits at 8 chars (was ~60 at 12) — still safe given the 10/min/IP rate limit on lookup

  6. Tests asserting format:

  7. tests/e2e/auth.spec.ts — grep for KP- patterns
  8. tests/e2e/soft-launch.spec.ts — owner-sees-family-code + lookup-positive flow
  9. tests/e2e/confirm-flow.spec.ts — if any code-input assertions
  10. scripts/smoke/specs/kp-staging-*.mjs, scripts/smoke/auth.mjs

  11. API input validators (may hardcode length):

  12. functions/api/tenants/lookup-by-code.js
  13. functions/api/tenants/join.js
  14. functions/api/tenants/me/family-code.js

  15. UI copy showing format examples:

  16. src/signup.html — onboarding code input
  17. src/app.html — workspace settings showing owner's code

  18. Mobile RN:

  19. mobile-rn/src/api.ts — any client-side validation regex

  20. Schema:

  21. db/schema_v2.sql — verify no CHECK constraint hardcoding length 12; if present, drop or relax

  22. Docs:

  23. kindredpics-site/CLAUDE.md — auth section mentions
  24. Any SPEC/handoff deliverables referencing the format

Verification: after migration, grep -rn 'CODE_LEN.*12\|.\{12\}.*family\|KP-[A-Z0-9]\{4\}-[A-Z0-9]\{4\}-[A-Z0-9]\{4\}' should return zero non-archive hits.

Cross-references

  • deliverables/SPEC-rebuild_v2.0_2026-05-23.md (in KP repo) — original Q1-Q9
  • deliverables/TECH-gap_remediation_handoff_v1.0_2026-05-23.md (in KP repo) — Codex's P0/P1 gaps that drove the rebuild
  • active/kindredpics.md — open_decisions section will mirror this doc's status