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-XXXX — 4-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.
Q8 — Codex's legal questions¶
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)¶
functions/utils/family-code.js— core change:const CODE_LEN = 12;→const CODE_LEN = 8;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)}`;- Update header comment from "12 chars" → "8 chars"
-
Entropy note: ~40 bits at 8 chars (was ~60 at 12) — still safe given the 10/min/IP rate limit on lookup
-
Tests asserting format:
tests/e2e/auth.spec.ts— grep forKP-patternstests/e2e/soft-launch.spec.ts— owner-sees-family-code + lookup-positive flowtests/e2e/confirm-flow.spec.ts— if any code-input assertions-
scripts/smoke/specs/kp-staging-*.mjs,scripts/smoke/auth.mjs -
API input validators (may hardcode length):
functions/api/tenants/lookup-by-code.jsfunctions/api/tenants/join.js-
functions/api/tenants/me/family-code.js -
UI copy showing format examples:
src/signup.html— onboarding code input-
src/app.html— workspace settings showing owner's code -
Mobile RN:
-
mobile-rn/src/api.ts— any client-side validation regex -
Schema:
-
db/schema_v2.sql— verify no CHECK constraint hardcoding length 12; if present, drop or relax -
Docs:
kindredpics-site/CLAUDE.md— auth section mentions- 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-Q9deliverables/TECH-gap_remediation_handoff_v1.0_2026-05-23.md(in KP repo) — Codex's P0/P1 gaps that drove the rebuildactive/kindredpics.md— open_decisions section will mirror this doc's status