NextCleanup and the strategy-fix chain are closed. APP-4 (donor list) and APP-18 (store resilience) are now built and deployed (kindred-ros-app v27, api v28, verified live). The beta long pole is APP-5: rebuild the CARE engine to Kevin's CARE Clarity brief (spec in JC inbox 2026-06-16 0016). Then APP-3 Stripe scaffold, gated on Kevin's entity decision (now a live ask, he is back). Housekeeping: move the a41126c web-push fix to master and tidy app-favicon-stars.
Waiting onKevin, entity decision (gates Stripe go-live). CARE is build-to-spec per Kevin's own CARE Clarity brief, no anchor sign-off pending (APP-5 rebuilds to it; the old 1/3/5 anchors are superseded).
Updated 2026-06-16
Summary
Core app (kindred-ros). The CARES strategy fix is now live end-to-end on production. The Neon enum migration ran first against a dev branch (ep-wild-shadow, 3 users / 0 contacts) earlier today. Found mid-session that the api's actual Fly secret pointed at the production branch (ep-raspy-union) which still had the old enum values, so a second migration was run against the right branch with a snapshot first; both ALTER TYPE … RENAME VALUE statements landed clean, all 679 existing rows auto-flipped via in-place rename, and a real PATCH-triggered assignment write/read confirmed the prod path. Email plumbing is live (RESEND_API_KEY, APP_URL at the app web URL, REPLY_TO=jean-claude@kindredros.com); Apple Developer license is purchased. APP-2 (profile/settings) shipped today, and a series of post-deploy bug fixes closed out a spinner-incident chain on the mobile web build (localhost API_BASE fallback, missing dashboard refresh on profile save, $$ render glitch). Both production data items are now resolved (2026-06-09): the donor rows were deduped (APP-20, snapshot first; the whole list had been double-imported May 1, so 679 rows became 352 unique contacts and the dashboard totals corrected, one-time giving from about $785k to about $485k), and the dev-vs-prod DATABASE_URL footgun is structurally blocked (APP-21, a drizzle target guard that refuses the prod host unless ALLOW_PROD is set, plus a labeled .env and a MIGRATIONS doc). The last strategy-fix check, one test email (APP-16), is now done. A reset email sent through the live api landed in the jean-claude@ inbox, so the APP-12→APP-16 deploy chain is fully closed. Phase 1 build queue: donor list view (APP-4), store resilience (APP-18), Stripe scaffold (APP-3, frozen on the entity decision). Full task list in app-product-tasks.md.
Open
APP-4 (donor list) and APP-18 (store resilience) are built and now deployed (kindred-ros-app v27, api v28), verified live. APP-4 display polish already landed (0cd623d).
CARES input redesign (APP-5) is a build to Kevin's CARE Clarity brief, the beta long pole. It is not a Kevin sign-off: his brief fully specifies the model and the old 1/3/5 anchors are superseded.
Stripe scaffold (APP-3) waits on Kevin's entity decision (now a live ask, he is back).
Updates
2026-06-09: Status sweep. Ben's June 9 working block is queued: cleanup first (APP-21 label dev vs prod DATABASE_URL, APP-20 dedupe the prod donor rows after a snapshot, APP-16 final test email), then build (APP-4 donor list view as the main item, APP-18 store resilience). Added APP-18 to the build queue in next. Kevin is out all week, so APP-3 Stripe go-live (entity decision) and APP-5 CARES input redesign (CARE anchor sign-off) stay frozen until he is back. No task states changed yet; these are queued, not done.
2026-06-09 (afternoon): Cleanup block done. APP-21, dev and prod turned out to be separate Neon projects both keyed plain DATABASE_URL; relabeled .env DEV-ONLY, added a guard in drizzle.config.ts that refuses the prod host without ALLOW_PROD=1, wrote MIGRATIONS.md (repo edits GIT COMMIT READY, not committed by this instance). APP-20, the "few dupes" was actually the whole donor list double-imported May 1 (325 groups / 679 rows); snapshotted, Ben confirmed, deleted 327 rows → 352 unique contacts, dashboard one-time total corrected $785k → $485k. Only APP-16's test email remains before the build queue (APP-4 donor list).
2026-06-10: Status sweep. Reconciled the summary with the tasks file and Ben's June 9 session wrap. APP-4 donor list view is built and pushed to master (aeb48aa, live-review polish 0cd623d), reviewed against the 352-row post-dedupe list with the strategy-led recommended order holding, and is now awaiting Ben's deploy and a browser pass rather than sitting in the build queue. Remaining before it ships is small display polish (Giving-column zeros, column separators, in-app row navigation) plus moving the web-push crash fix a41126c from app-favicon-stars onto master. Build queue is otherwise down to APP-18 store resilience. Stripe (APP-3) and the CARES input redesign (APP-5) stay frozen on Kevin (entity decision, CARE anchor sign-off); he is out all week.
2026-06-15: Kevin is back, and APP-18 is specced. The "frozen, he is out all week" framing is retired: the entity decision (the Stripe go-live gate, APP-3) is now a live ask, surfaced in the decision brief in Ben's inbox (2026-06-15 1118); no decision recorded yet, so APP-3 stays waiting-on-kevin. CARE stays build-to-spec per his own brief (APP-5), no anchor sign-off pending. New build movement: the APP-18 store-resilience build spec was delivered to Claude Code (2026-06-15 1118 JC inbox), so APP-18 moves from queued to specced-and-handed-off: wrap every loading-flag action in try/finally, surface failures instead of swallowing or throwing uncaught, route a 401 to the existing sign-out path, and revert the temp [auth] logging at 7d28dbd. Donor-table polish and the a41126c web-push fix move to master are still queued. Deploy stays Ben's gate.
2026-06-15 (evening):APP-18 store resilience built + pushed to master (84cf6e6); deploy is Ben's gate.fetchContacts is now try/catch/finally (loading flag always resets); apiFetch throws a typed ApiError with status; 401→sign-out, other failures→a contactsError "Try again" banner on the Today screen and donor table; sibling fetchers route 401→sign-out instead of rejecting uncaught; the temp [auth] 401 logging (7d28dbd) was reverted. Follow-on a4d12da cleaned up the pre-existing mobile type errors APP-4 had flagged (radius.xl, DashboardStats.contactCount, score-union casts, a dead matrix tone read, an invalid tab option) so mobile tsc --noEmit is now fully clean. Deploy stays Ben's gate.
2026-06-15 (infra note):Tigris object storage provisioned on the shared kindred-ros-ops app (Fly's S3-compatible store, bucket kindredros-studio-media) for the social Studio's uploaded media. Recorded here because it's shared infra future app work may reuse for blob storage (uploads/exports) instead of putting image bytes in Postgres. Private bucket, presigned-URL access; provisioned via fly storage create. Reusable gotcha: aws-sdk-v3's default checksums break presigned URLs on S3-compatible stores (Tigris/R2/MinIO) — set requestChecksumCalculation/responseChecksumValidation to WHEN_REQUIRED. Full social-Studio build is logged in social.md; this is the cross-cutting infra line only.
Recent
2026-06-16DNS + OAuth wiring session (Ben, Cowork). Added the two custom-domain CNAMEs in Squarespace (app→kindred-ros-app.fly.dev, ops→kindred-ros-ops.fly.dev; apex/www/email records untouched); both Fly certs Issued and both hosts serve HTTPS 200 (closes APP-22's DNS+cert work). Updated the kindred-ros-ops Google OAuth client: added JS origin https://ops.kindredros.com and redirect https://ops.kindredros.com/api/auth/callback/google (old kindred-ros-ops.fly.dev redirect kept), so ops dashboard Google sign-in works on the branded host. Nothing added for the product app: confirmed from code it uses email/password only (bcrypt + JWT via jose), no Google OAuth, and the ops client is the only OAuth client across all projects under jean-claude@kindredros.com. Logged as APP-23 — BETA-acceptable, social sign-in is the future upgrade. Still open on APP-22: set APP_URL=https://app.kindredros.com on kindred-ros-api. - 2026-06-15 (evening): APP-18 store resilience built + pushed to master (84cf6e6); deploy is Ben's gate. Closed the 2026-06-03 spinner class on the client: fetchContacts now try/catch/finally so the loading flag always resets; apiFetch throws a typed ApiError with the HTTP status; 401→logout() (existing sign-out), other failures→contactsError banner; fetchTodayContacts/fetchDashboard/fetchMe route 401→sign-out instead of rejecting uncaught; Today + donor table show a calm "Try again" state. Reverted the temp [auth] 401 logging (7d28dbd). Then fixed the pre-existing mobile type errors APP-4 had flagged as unrelated (a4d12da): added radius.xl (modal corners were rendering unrounded), added DashboardStats.contactCount (the /dashboard route already returns it), cast the 1–5 score unions in contacts/new, replaced a dead JourneyMatrixEntry.tone read with .aim, and dropped an invalid contentStyle tab option — mobile tsc --noEmit is now fully clean. (API runs via tsx, no tsc step; its tsc-only resolution warnings are pre-existing and unrelated — the one file touched, auth.ts, is clean.)
2026-06-15Decision (Ben), the web instance of Kindred rOS lives at app.kindredros.com (Fly app kindred-ros-app). Added APP-22: CNAME app to kindred-ros-app.fly.dev, Fly cert, and APP_URL=https://app.kindredros.com on kindred-ros-api (retires the leftover fly.dev APP_URL from APP-11). Email-safe subdomain CNAME; steps in Ben's DNS checklist. Web-vs-native blocker (blockers item 4) updated: the web instance is confirmed and domained; only the "also submit to the stores" half may remain for Kevin. Also today: CARE reconciled to Kevin's CARE Clarity model (build-to-spec, the old 1/3/5 anchor sign-off is superseded, APP-5 updated). - 2026-06-09 (evening, later): Web push crash guarded and APP-4 reviewed live. Web push: on web, Expo's push token call needs a VAPID key and Device.isDevice is true in a browser, so the path threw. Fixed with a Platform.OS === "web" guard in registerDeviceToken (push stays native-only); committed a41126c but on branch app-favicon-stars — recommend moving it to master (same worktree pattern as APP-21) so master has the fix, then reconcile/delete that branch. Real web push later is a feature, not a config tweak (VAPID keypair + app.json + server Web Push); backlog under APP-7, also gated on the frozen web-vs-native call. APP-4 live review (build on localhost): renders well, 352 rows (matches the dedupe). Polish for the builder, in JC-inbox note 2026-06-09 1830 APP-4 review: (1) Giving column shows zeros as "— /mo" / "— once" and needs column separators so Capacity/CARE/Giving stop scanning as one value (Capacity itself is a clean annualized number; my first read misattributed this); (2) an empty right region + row click opens a new window (row handler likely uses window.open/Linking instead of in-app router.push; a detail pane was not in the spec); (3) the unauthorized path throws uncaught and sticks a spinner = the APP-18 resilience gap. None block; all display/UX polish. Deploy + browser QA remain Ben's gate. - 2026-06-09 (evening): APP-4 donor list view built and pushed to master (aeb48aa). Enhanced (tabs)/contacts.tsx into a responsive screen: card list on narrow/native, dense sortable table on web, default order = the API's theSort (labeled "recommended order," one-tap reset), client-side column sort + name/strategy/follow-up-due filters over the in-memory list, no API change. Engine untouched; sort/scoring mechanics left to Kevin's rubric. Type-clean. Not yet deployed or browser-QA'd (Ben's gate). Spec in repo docs/spec-app-4-donor-list-view.md; PM spec in JC inbox 2026-06-09 1805. Build queue now down to APP-18 (store resilience). - 2026-06-09 (afternoon, later): Workflow recalibrated with Ben + cleanup closed out. Charter (CLAUDE.md) updated: commit AND push are free (commit straight to master, git push origin master; no repo auto-deploys, so push ≠ deploy), deployment is the only hard gate. APP-21 moved off the feature branch and pushed to master as 5fe38d4. APP-16 closed — test email sent via the live api's /auth/forgot-password for jean-claude@kindredros.com, and Ben confirmed it arrived; the APP-12→APP-16 strategy-fix deploy chain is fully done. APP-20 snapshot pre-donor-dedupe-prod-2026-06-09 deleted on Ben's call (0 dup groups on re-check). Next: build queue — APP-4 donor list view, then APP-18 store resilience. - 2026-06-09 (afternoon): APP-21 and APP-20 done in the working session. APP-21: found dev and prod are separate Neon projects (not branches), both keyed plain DATABASE_URL — relabeled .env as DEV-ONLY, added a target guard to drizzle.config.ts that refuses the prod host unless ALLOW_PROD=1, and wrote packages/db/MIGRATIONS.md. Guard verified three ways. Repo edits are GIT COMMIT READY (note in Ben's inbox); this instance does not commit. APP-20: the dedupe was far bigger than scoped — the whole donor list was double-imported May 1, 325 dup groups / 679 rows. Snapshotted (pre-donor-dedupe-prod-2026-06-09), Ben confirmed, deleted 327 rows → 352 unique contacts, 0 dups left. Dashboard one-time total corrected $785k → $485k. APP-16's last check (one test email) still open.
2026-06-05Reconcile sweep. Corrected APP-5: the CARE A/R/E 1/3/5 anchors are already drafted and sitting in Kevin's inbox for sign-off (2026-06-03 2022 CARE A-R-E 1-3-5 anchors for sign-off), so the open piece is Kevin's approval, not JC drafting. Confirmed this file is clean of the retired "Kevin-facing instance" phrasing. No task states changed; APP-16/18/20/21 remain as last recorded. - 2026-06-03 (late): Post-deploy bug-fix sweep on kindred-ros-app. Three shipped fixes worth recording for the audit trail: - 926fade — useStore.ts API_BASE fallback was http://localhost:3001 while every auth file's fallback was the fly URL. The Metro/Expo env substitution didn't reach useStore.ts at build time, so login worked (auth files) but every post-login apiFetch went to localhost and ERR_CONNECTION_REFUSED'd. Aligned the fallback so the build no longer depends on EXPO_PUBLIC_API_URL substituting. This was the root cause of the Today screen spinner. - 700f1fc — added apps/mobile/.env.production with EXPO_PUBLIC_API_URL=https://kindred-ros-api.fly.dev so future expo export --platform web runs pick the URL up automatically, no inline env var required. Belt-and-suspenders with 926fade. - 2a4e976 — profile save now calls fetchDashboard() after updateMe() succeeds. The dashboard slice (yearlyGoal, percentRaised, leftToRaise) was separate from user, so changing the yearly goal in profile left the Today and Dashboard screens showing stale numbers until a manual reload. Fix is in the store action, so every caller of updateMe gets the refresh.
2026-06-03APP-19 closed — production enum migrated. Snapshot pre-strategy-rename-prod-2026-06-03 taken off the prod branch, then ALTER TYPE … RENAME VALUE … ran clean against ep-raspy-union-an9mdayh. Enum is now Personal Meeting / Letter + Follow-up / Group Engagement; the 679 existing rows auto-flipped via in-place rename (227/227/225). APP-16 strategy-write verification ran against prod immediately after: PATCH /contacts/:id triggered an assignment that wrote and read back Personal Meeting cleanly. APP-16 still has one remaining check (one test email). After verify, the $$ fix shipped to mobile (commit 82b3a6c; bundle entry-eb1daa45…) — single $ on the one-time alert card now. APP-20 (dedupe) and APP-21 (DATABASE_URL labeling) still open per Ben's plan.
2026-06-03Wrong-database finding (load-bearing). APP-12's migration ran against a dev Neon branch (ep-wild-shadow, 3 users / 0 contacts) from the local .env, not the production branch (ep-raspy-union) the api's Fly secret points at. So the live strategy code writes new enum names against a prod DB that still has the old ones — prod strategy writes fail until fixed. Added APP-19 (re-run the migration against prod, snapshot first — URGENT, first task tomorrow), APP-20 (dedupe the double-imported donor rows, snapshot first), APP-21 (label dev vs prod DATABASE_URL to prevent recurrence). APP-16 re-scoped to verify against prod. Corrected the app-product summary, which had wrongly read "shipped end-to-end." Tonight: Claude Code ships the "$$ fix" (api + mobile); it doesn't change the enum hazard since the strategy code is already on prod.
Read more — full task list (app-product-tasks.md)
App / Product — Tasks
Read at session start. Update statuses here as work moves. Edit only this file. The Obsidian Git plugin syncs it; the Ops Dashboard renders it.
APP-1 Fix the CARES strategy-function bug (assignStrategy fixed thresholds vs assignStrategies dynamic thirds) — done · committed 4ab672f; deploy tracked in the deploy chain below (APP-12+)
APP-2 Spec + build the user profile / settings screen — done 2026-06-03. Built and shipped at 12e6710. API: routes/users.ts adds GET /users/me + PATCH /users/me (auth-gated, returns user minus passwordHash; PATCH accepts name, role, organization, yearlyGoal, edition); mounted at /users. Mobile: new app/profile.tsx screen — read-only header (email, plan, member-since) + editable fields + faith/secular chip switcher + sign-out (with confirm). Entry point: ProfileButton next to the bell on the Today tab ((tabs)/_layout.tsx); the redundant header logout icon was removed since sign-out now lives in the profile screen. Store: fetchMe + updateMe actions on the existing apiFetch helper. Schema unchanged — no migration. Deployed: kindred-ros-api (health 200, /users/me 401 without auth — gate working) and kindred-ros-app (root 200, /profile 200).
APP-3 Scaffold the Stripe integration against test keys (checkout, webhook, tier upgrade) — ready · owner: JC · go-live blocked on entity decision
APP-4 Donor list view (sortable table) — built, pushed (master aeb48aa); deploy and a browser pass are Ben's gate. Cowork PM spec delivered 2026-06-09 (01 - Inboxes [JC, BK, KE]/Jean-Claude/2026-06-09 1805 APP-4 donor list view — build spec). Decision: one responsive screen, the existing (tabs)/contacts.tsx keeps its card list on narrow/native and gains a dense sortable table on web. Default order = the API's theSort (strategy-led, lead with the sort not the score); column sort + name/strategy filter are client-side over the in-memory list; no API change for v1. Row tap to contacts/[id]; no inline quick-actions in v1. Scoring/sort mechanics deferred to the engine (Kevin's rubric), not hardened into the view. Built and pushed 2026-06-09 — committed to master aeb48aa. Enhanced (tabs)/contacts.tsx per spec: responsive card-list↔table switch at 760px (web table, card list on narrow/native); theSort default labeled "recommended order" with a one-tap reset; client-side sortable headers (name, strategy tier, capacity/yr, careScore, last contact, follow-up) and name search + strategy + follow-up-due filters over the in-memory list; follow-up-due row flag; TY indicator; reuses existing useStore/theme/CSV-XLSX import; engine untouched. Type-clean (tsc on the mobile app reports no errors in the file; the other screens' pre-existing radius.xl/DashboardStats type errors are unrelated). Ready for Cowork review + Ben's deploy call; spec copied to repo docs/spec-app-4-donor-list-view.md. Reviewed live + polished 2026-06-09 (commit 0cd623d, on master). Ben (PM) ran the web build: 352 rows (matches the post-dedupe count — data integrity confirmed), strategy-led recommended order, full columns/filters/search all present. One finding fixed: the Capacity and Giving columns read as one mashed value (Capacity was already annualized; the noise was Giving rendering zeros as "— /mo"/"— once" and bleeding into the adjacent number). Fix: Giving shows only non-zero figures (monthly/mo and/or one-time once, else a single —); Capacity annualizes from monthly when annual is absent (Kevin's "one annualized number"); subtle right-border column separators. Type-clean, web bundle recompiles. Deployed 2026-06-16 with kindred-ros-app v27 (rode the APP-18 mobile deploy); live and verified on app.kindredros.com. The 401-spinner Ben hit was the separate APP-18 store-resilience gap, now also shipped.
APP-5 CARES input redesign to Kevin's definitive CARE model — ready (build-to-spec, not a Kevin sign-off). Kevin's CARE Clarity brief (01 - Inboxes [JC, BK, KE]/Ben/CARE Clarity.md) is the spec: capacity as one annualized number, attributes and relationship as yes-counts, engagement as a level, and the sorted list (not the score) sets the strategy. This supersedes the older JC-drafted 1/3/5 anchors (2026-06-03 2022 ...); do not put those to Kevin. The current engine still runs the prior 1-5 + careScore model, so this is a real rebuild, not just helper text. Fully specified by Kevin's CARE Clarity doc; no open questions, no Kevin gate. Full build spec delivered 2026-06-16 (01 - Inboxes [JC, BK, KE]/Jean-Claude/2026-06-16 0016 APP-5 CARE engine rebuild): rewrite theSort to Kevin's five-key order (Monthly High Hope, Annual High Hope, Attributes, Relationship, Engagement, all descending), strategy by list-relative thirds (not cutoffs), score ranges A 0-5 / R 0-6 / E 1-6, the input UI, and a snapshot-first migration. The doc settles all of it: capacity is two fields (Monthly High Hope + Annual High Hope, both sort keys), the sort is capacity-first lexicographic with no lift, strategy is list-relative thirds, careScore is supplementary. No open questions. This is the beta long pole (see Ben note 2026-06-16 0016 App beta readiness).
APP-6 Four Footings / reflection flows QA — ready · owner: JC · days 31-60
APP-7 Push notifications made operational (not just wired) — ready · owner: JC · days 31-60
APP-8 App store listing prep (copy, screenshots text, privacy) — ready · owner: JC. Apple Developer license purchased (Kevin), so Apple submission is unblocked on the license front (setup deferred). Only relevant at all if the app goes native — the web-vs-native call is Friday's (see blockers). Google Play ($25) still needs the payment method.
APP-9RESEND_API_KEY on kindred-ros-api — done 2026-06-02. Restage showed the digest unchanged — the existing key was already the kindredros.com Resend key, not the feared seen.place one (harmless no-op). Domain is verified, so noreply@ sending works. Risk cleared.
APP-10REPLY_TO on kindred-ros-api — done 2026-06-02. Address: jean-claude@kindredros.com (Ben's pick — real, human-monitored, catchall; it's JC's own address). Code wiring was in 909b180; secret set in this deploy. Future swap to a support@/hello@ alias is a one-line secret change.
APP-11 Fix kindred-ros-web orphan + set APP_URL Fly secret on kindred-ros-api to the live app URL, then retire/rename kindred-ros-web — ready · owner: BK. Correction (JC, 2026-06-01): APP_URL should NOT be kindredros.com. The reset email builds ${APP_URL}/auth/reset-password?token=… (email.ts:27); that page lives in the app's Expo web build (apps/mobile/app/auth/reset-password.tsx), deployed as the Fly app renamed kindred-ros-web→kindred-ros-app. kindredros.com is the Squarespace marketing site and has no /auth/reset-password route, so a reset link there 404s. Set APP_URL to the app web URL (kindred-ros-app.fly.dev or its custom domain). The 909b180 code default (kindredros.com) is wrong for the reset path. Note: APP_URL is shared with the welcome email's "Open Kindred" button — pointing it at the app login is fine. Resolved 2026-06-02: APP_URL Fly secret set to https://kindred-ros-app.fly.dev, and the email.ts default corrected off kindredros.com in commit 69bbaf4 (so the fallback is no longer a 404 trap). Remaining piece is only the physical kindred-ros-web→kindred-ros-app retire/rename, which rides the fly.toml rename in APP-14(a) / the mobile deploy (APP-15).
Deploy chain — strategy fix to live (sequence; owner: Ben)
APP-12 Run migration 0001_rename_strategy_values.sql against Neon — done 2026-06-02 (Ben). Migration ran on Neon ahead of the api deploy, so the enum labels match the strategy-fix code (4ab672f). Gate cleared — APP-13 is safe to ship.
APP-13 Deploy kindred-ros-api with the strategy fix + email secrets — done 2026-06-02. Deployed against master at kindred-ros-api.fly.dev. Strategy fix (4ab672f) live alongside the email fixes: 69bbaf4 (APP_URL default → kindred-ros-app.fly.dev), 86459df (pnpm@9.15.4 pin via packageManager field, unblocked the Docker build after pnpm 10 strict-builds gate). Fly secrets set on kindred-ros-api: APP_URL=https://kindred-ros-app.fly.dev, FROM_EMAIL=Kindred rOS <noreply@kindredros.com>, REPLY_TO=jean-claude@kindredros.com. RESEND_API_KEY digest unchanged (was already the kindredros.com key — APP-9 cleared).
APP-14 Resolve the orphan dirty working tree in kindred-ros — done 2026-06-02. All commits landed, including the mobile fly.toml rename kindred-ros-web→kindred-ros-app. (Original triage retained below.) Owner: Ben. JC triaged it (2026-06-01): not junk, it's coherent app-product work, all dependencies resolve, recommend committing as 3 logical commits rather than stashing:
(a) Mobile features — contacts/[id].tsx (donor action modals: outreach / follow-up / gift / thank-you; modals defined in-file, uses getContactPlaybook which is exported from shared), (tabs)/planner.tsx (Four Footings / Reflection / Goals / Tracker / banks tabs; all six subscreens already tracked under app/planner/), (tabs)/_layout.tsx (logo header → needs untracked apps/mobile/assets/logo.png), package.json + pnpm-lock.yaml (xlsx dep), fly.toml (rename kindred-ros-web→kindred-ros-app + health checks). Self-contained, looks complete.
(b) API org-contact support — routes/contacts.ts (+contactType, +orgName on the input). Tiny, additive, safe. Note: API-only; no mobile UI consumes org fields yet (partial feature, not broken).
(c) Signer split — untracked apps/signer/ (complete: Dockerfile, package.json, server.js) + the apps/web/server.js/package.json deletions. Pair as one commit. Gap: apps/signer/ has no fly.toml, so it's not independently deployable yet.
Verified structurally (imports resolve, components/exports present), not via a full RN build. Blocks APP-15. (docs/spec-strategy-assignment-fix.md also shows dirty but that's JC's strategy-fix doc, not orphan.)
APP-15 Deploy mobile to kindred-ros-app — done 2026-06-02. Fresh npx expo export --platform web rebuilt dist/ (new bundle entry-e47cc236…), then fly deploy rolled both machines on kindred-ros-app. Production mobile now matches the renamed enum (badges/colors use canon names from 4ab672f). Verified: https://kindred-ros-app.fly.dev/ HTTP 200 and https://kindred-ros-app.fly.dev/auth/reset-password?token=test HTTP 200, confirming the APP_URL fix end-to-end.
APP-16 Post-deploy verification — done 2026-06-09. Health check done (/health 200 on api; / 200 + /auth/reset-password 200 on app). Strategy-assignment write against production verified 2026-06-03 alongside APP-19: PATCH /contacts/:id with engagementScore=5 returned strategy: "Personal Meeting", careScore 77. Final check (test email) done 2026-06-09: POST /auth/forgot-password against the live api for the one prod user jean-claude@kindredros.com returned {ok:true} 200; confirmed the code reached the send path (reset token set on the user, expiry ~1h) with no Resend errors in fly logs. Email plumbing (Resend + FROM + REPLY_TO + APP_URL) exercised end-to-end through the deployed api, and delivery confirmed — Ben saw the "Reset your Kindred password" message land in the jean-claude@ inbox. APP-16 fully closed; the whole strategy-fix deploy chain (APP-12→APP-16) is now done.
APP-17 Rotate the Resend API key — ready, optional · owner: Ben. Key value appeared in chat during the email work. Per the credential-handling calibration (private context, small access list), rotation is optional convenience — not urgent. Sending works on the current key. Do it when next in the Resend Console.
APP-18 Mobile store: make fetch loading flags resilient to failed/hung requests — done 2026-06-16 (built 84cf6e6 + type cleanup a4d12da; DEPLOYED to kindred-ros-app v27 — live + verified) · owner: JC. Today the useStore actions like fetchContacts do set({ isLoadingContacts: true }) before the await and set({ isLoadingContacts: false }) after — but the "after" line is unreachable if apiFetch throws (401, network error) or never resolves (cold-start hang). That leaves the UI permanently on <ActivityIndicator/>. Triggered by: 2026-06-03 spinner incident; the api had min_machines_running = 0 and the cold-start on autostop was long enough that the bundle's apiFetch hung, locking the Today screen on its spinner. Fixed the immediate cause by bumping the api to min_machines_running = 1 (commit 564e860), but the client-side fragility remains. Scope: wrap every action that toggles a loading flag in try/finally so the flag always resets; surface user-visible errors on top-level fetches (toast or banner) instead of swallowing. Also: revert the temp [auth] 401 logging in apps/api/src/middleware/auth.ts added at 7d28dbd once the resilience is in place. DONE 2026-06-15 (84cf6e6, master; deploy Ben's gate):fetchContacts now wraps its work in try/catch/finally so isLoadingContacts always resets (no stranded spinner on a throw or cold-start hang); apiFetch throws a typed ApiError carrying the HTTP status; a 401 routes to logout() (the existing sign-out) and non-401 failures set a new contactsError state; fetchTodayContacts/fetchDashboard/fetchMe route 401→sign-out instead of rejecting uncaught. The Today screen and the donor table render a calm "couldn't load / Try again" state from contactsError. The temp [auth] 401 logging was reverted as specced. Mobile tsc --noEmit is clean. DEPLOYED 2026-06-16: rebuilt the web export (npx expo export -p web, bundle entry-d60d4e7b… pinned to the prod api) and fly deploy'd kindred-ros-app → v27; verified live on both kindred-ros-app.fly.dev and app.kindredros.com (HTTP 200, new bundle served, /auth/reset-password 200). The client-side resilience fix is now live. API also deployed 2026-06-16 → kindred-ros-api v28 (so the [auth] logging revert is now live; /health 200, auth gate 401 confirmed). APP-18 fully shipped end to end — app v27 + api v28, both verified. ⚠️ Deploy footgun found: apps/api/fly.toml has dockerfile_context = "../..", which current flyctl ignores — fly deploy from apps/api/ fails with root paths "not found" (packages/db, pnpm-workspace.yaml). Deploy the api from the repo root instead: fly deploy . --config apps/api/fly.toml --dockerfile apps/api/Dockerfile -a kindred-ros-api. FIXED 2026-06-16 (a0523cd, master): dropped the ignored dockerfile_context (replaced with a comment documenting the root-context constraint) and added a root pnpm deploy:api script that runs the correct command — future api deploys just work via pnpm deploy:api from anywhere in the repo.
APP-19 Re-run the enum migration 0001_rename_strategy_values.sql against the PRODUCTION Neon branch (ep-raspy-union-an9mdayh) — done 2026-06-03. Snapshot pre-strategy-rename-prod-2026-06-03 (id br-soft-dust-an2i8tfz) created off production first. Migration ran clean (both ALTER TYPE … RENAME VALUE statements landed). Verified: enum is now Personal Meeting / Letter + Follow-up / Group Engagement, and the 679 existing rows auto-flipped via in-place rename (227 / 227 / 225 distribution). End-to-end check: logged in via api → GET /contacts returned Scott Stark with strategy Personal Meeting → PATCH /contacts/:id with engagementScore=5 returned strategy: "Personal Meeting", careScore 77. Live strategy writes against prod work now.
APP-20 Dedupe the production donor rows — done 2026-06-09. Far bigger than the task assumed. Not "a few rows (Scott Stark, Mike Walters, Ben)" — the entire donor list was double-imported on May 1 (all dup rows share created_at = May 01 2026 20:45:51). Found 325 duplicate groups out of 679 rows (324 exact pairs + Ryan White ×4). Snapshot pre-donor-dedupe-prod-2026-06-09 (br-small-truth-an90akf8, off production) taken first. Deleted 327 rows (the later/extra copy of each group; matched on user_id + first/last name + contact_type + one_time_gift + monthly_committed; kept earliest created_at, deterministic id tiebreaker). Result: 679 → 352 unique contacts, 0 duplicate groups remaining. Dashboard totals corrected: one_time_gift sum $785,167 → $485,410, monthly_committed$13,190 → $6,795 (27 rows were genuine singletons, so inflation was real but not a clean 2×). Run via the Neon serverless driver against the prod connection string pulled from neonctl (psql not installed); prod Fly secret left unchanged. Ben confirmed the half-table delete before it ran, then called to delete the snapshot — pre-donor-dedupe-prod-2026-06-09 was removed 2026-06-09 after the re-check showed 0 duplicate groups. (The two pre-strategy-rename-* snapshots from APP-19 remain.)
APP-21 Fix the DATABASE_URL footgun — done 2026-06-09 (committed and pushed to master as 5fe38d4; no deploy needed). Confirmed the footgun is deeper than "different endpoints": dev and prod are separate Neon projects, both keyed plain DATABASE_URL. dev = ep-wild-shadow-a4np441i (project Kindred-rOS dev, local .env); prod = ep-raspy-union-an9mdayh (project Kindred-rOS, branch production, the api's Fly secret — verified correct, unchanged). Fix landed in kindred-ros: (1) .env relabeled with a DEV-ONLY header naming both projects; (2) packages/db/drizzle.config.ts now prints the resolved target host + label (PRODUCTION/dev/UNKNOWN) before any push and throws on the prod host unless ALLOW_PROD=1 — so a plain pnpm db:push can only hit dev; (3) packages/db/MIGRATIONS.md documents the two targets and the snapshot-then-ALLOW_PROD=1 prod procedure. Guard verified in all three modes (dev proceeds, prod refused without flag, prod proceeds with flag). Root cause of the APP-19 wrong-branch situation, now structurally blocked.
APP-22 Point the product web app at app.kindredros.com — done 2026-06-16 · owner: Ben/Code (DNS + secret). Decision 2026-06-15: the web instance of Kindred rOS lives at app.kindredros.com (Fly app kindred-ros-app). Add a CNAME app to kindred-ros-app.fly.dev in Squarespace DNS, fly certs add app.kindredros.com -a kindred-ros-app, then fly secrets set APP_URL=https://app.kindredros.com -a kindred-ros-api so reset and welcome emails use the branded host (retires the leftover kindred-ros-app.fly.dev APP_URL from APP-11). Subdomain CNAME, so it touches neither the apex nor any email record. Steps in Ben inbox 2026-06-15 1145 DNS repoint checklist. Cert step done 2026-06-15: ran fly certs add app.kindredros.com -a kindred-ros-app (cert created, awaiting DNS). Remaining: add CNAME app→kindred-ros-app.fly.dev in Squarespace (or A 66.241.124.97 / AAAA 2a09:8280:1::10e:b3ac:0), then fly secrets set APP_URL=https://app.kindredros.com -a kindred-ros-api. Documented in bknewcastle/kindred-ros issue #1 (alongside the ops.kindredros.com cert / OPS-20). DNS added + cert Issued 2026-06-15 — app.kindredros.com is live (HTTPS 200, CNAME → kindred-ros-app.fly.dev). Remaining: set APP_URL=https://app.kindredros.com on kindred-ros-api so reset/welcome emails use the branded host (+ any app-side OAuth redirect URIs). DONE 2026-06-16:APP_URL=https://app.kindredros.com set on kindred-ros-api (digest → bffb32b3, api restaged healthy, /health 200; branded host serves /auth/reset-password?token=test → 200). No app-side OAuth to change (the product app is email/password JWT only — see APP-23, which closes that sub-item). app.kindredros.com is fully live: DNS + cert Issued + APP_URL. ✅
APP-23 Product app has no Google OAuth — BETA-acceptable, upgrade later — backlog · owner: JC. Definitive from the code: the product app (kindred-ros-app) authenticates with email/password only — bcrypt-hashed passwords + JWT via jose; /auth/register hashes a password, /auth/login compares it and signs a JWT. No OAuth client, no NextAuth, no google-services files. Confirmed 2026-06-16 while wiring the OAuth consoles: nothing needed to be (and nothing was) added to the kindred-ros-ops Google OAuth client for app.kindredros.com — that client only serves the ops dashboard's NextAuth Google sign-in, and it's the only OAuth client across all three projects visible under jean-claude@kindredros.com (Kindred rOS Ops, theacuitylab.com, My First Project). Accepted for beta. The future upgrade (social sign-in, e.g. Google/Apple) would create its own OAuth client + redirect URIs (e.g. https://app.kindredros.com/...), not reuse the ops client. This closes the "+ any app-side OAuth redirect URIs" open item left on APP-22.
Payment method now gates only Google Play (if the app goes native) + the Fly org transfer + Anthropic reimbursement. Apple Developer license is already purchased.
Updates
2026-06-02: PM re-confirmed APP_URL = https://kindred-ros-app.fly.dev, not kindredros.com. Verified from the repo: apps/mobile/fly.toml is app = 'kindred-ros-app', app.json has the web bundler, Dockerfile.web exists, and app/auth/reset-password.tsx is present — so the Expo web build serves /auth/reset-password there. kindredros.com (Squarespace) has no such route and would 404. Corrects an earlier PM runbook line that wrongly said kindredros.com. Also fix the email.ts default (currently kindredros.com) to the app URL so the fallback isn't a 404 trap. Agent is authorized to set the Fly secret to kindred-ros-app.fly.dev.
2026-06-02: Email secrets resolved; kindred-ros-api deploying (APP-13 in progress). Commit 69bbaf4 pushed to master (email.ts APP_URL default → kindred-ros-app.fly.dev, off the kindredros.com 404 trap). Fly secrets on kindred-ros-api: APP_URL=https://kindred-ros-app.fly.dev and FROM_EMAIL rotated; RESEND_API_KEY digest unchanged (already the kindredros.com key — APP-9 cleared); REPLY_TO=jean-claude@kindredros.com (Ben's pick — APP-10 cleared). APP-9 and APP-10 done; APP-11's APP_URL piece done, only the physical kindred-ros-web retire/rename remains (rides APP-14a/APP-15).
2026-06-02: APP-14 done — all commits landed incl. the mobile fly.toml rename to kindred-ros-app. APP-15 now unblocked and is your action: production mobile is pre-rename and mis-renders strategy badges until it ships. Added APP-17 (rotate the Resend key, value was exposed in chat).
2026-06-02: Apple Developer license purchased (Kevin); APP-8 updated — Apple submission unblocked on the license front (setup deferred), only matters if the app goes native. Payment method now gates Google Play (if native) + Fly transfer + Anthropic reimbursement only.
2026-06-02: APP-12 done — Neon migration ran before the api deploy, so the ordering hazard is clear and the strategy-fix code (4ab672f) ships against the renamed enum. Only APP-16 (post-deploy verification) remains on the strategy-fix path.
2026-06-01: Confirmed APP-15 (mobile) IS affected by the enum rename — mobile renders strategy values (badges in (tabs)/index.tsx + contacts.tsx, colors in theme.ts), updated to canon names in 4ab672f. So mobile is a real deploy once APP-14 (dirty tree) clears, not a skip.
2026-06-01: APP-14 triaged. Orphan tree is coherent app-product work (mobile action modals + planner tabs + branding; API org-contact fields; signer split out of apps/web), not stale and not broken — all imports/exports resolve. Recommend 3 logical commits over a stash (details on the APP-14 line). Two gaps flagged: signer has no fly.toml (not yet deployable standalone), org-contact is API-only (no mobile UI yet). Owner/commit is Ben + Claude Code per the git workflow; JC does not commit these (not JC's changes).
2026-06-01: Tied email commits to tasks. 909b180 ("Fix email defaults") does the code wiring for APP-10 (REPLY_TO into both sends) and APP-11 (APP_URL default → kindredros.com, off the dead kindred-ros-web); both tasks still need their Fly secrets set, so they stay open. 95f44da switched the app email sender to noreply@kindredros.com. These ride into the APP-13 API release alongside the strategy fix (4ab672f).
2026-06-01: APP-1 marked done (committed 4ab672f). Added deploy chain APP-12→APP-16 (migration → API+secrets → resolve dirty tree → mobile-if-needed → verify), all owner Ben. PM call: run the migration via psql from Ben's machine with a Neon snapshot first; never hand DATABASE_URL to an agent. Mobile deploy decoupled from the API critical path and gated on clearing the orphan working-tree changes.
2026-06-01: Strategy-assignment fix committed locally (4ab672f). Needs migration 0001_rename_strategy_values.sql before deploy. Ben's go pending.
2026-06-01: Ben pushed noreply@ email change + monorepo split commits to kindred-ros origin/master. kindredros-website repo created under jean-claude-debug org and pushed. email.ts updated: APP_URL default changed to kindredros.com, REPLY_TO wired to both sends. APP-9/10/11 added to track remaining email secrets.
2026-06-03: Reworded APP-5's why-line to reference existing canon (axis definitions in Core Product Spine; weighting already in the math) and frame the ask as Kevin approving JC-drafted 1/3/5 anchors, not a blank rubric request.
2026-06-03: Deploy chain shipped. APP-13 (api) and APP-15 (mobile) done; strategy fix 4ab672f is live end-to-end against the renamed enum on Neon. APP-16 partial (health verified on both surfaces, deeper assignment+email checks remain). APP-17 reframed as optional rotation, not a nag. Next up on JC: APP-2 (profile/settings), APP-4 (donor list), APP-3 (Stripe scaffold) in that order.
2026-06-03: APP-2 built and pushed (12e6710). New routes/users.ts (GET/PATCH /users/me), new app/profile.tsx mobile screen + ProfileButton entry, store gains fetchMe/updateMe. No schema change. Deploy of api + mobile is the only remaining step.
2026-06-03: APP-2 done — api + mobile deployed off 12e6710. Profile screen live behind the person icon on the Today tab; /users/me is the new GET/PATCH endpoint. Next JC build: APP-4 (donor list view).
2026-06-03: Added APP-18 — mobile store fetch flags should be resilient to failed/hung requests. Found via the spinner incident this afternoon: api had min_machines_running = 0, autostopped, cold-start hung Ben's bundle long enough that isLoadingContacts never reset and the Today screen locked on its spinner. Immediate root cause fixed in 564e860 (api now warm); APP-18 covers the client-side fragility so the next cold start or 401 doesn't lock the UI again.
2026-06-03:Wrong-database finding (load-bearing). APP-12's migration ran against a dev Neon branch (ep-wild-shadow, 3 users / 0 contacts) from the local .env, not the production branch (ep-raspy-union) the api's Fly secret points at. So the live strategy code writes new enum names against a prod DB that still has the old ones — prod strategy writes fail until fixed. Added APP-19 (re-run the migration against prod, snapshot first — URGENT, first task tomorrow), APP-20 (dedupe the double-imported donor rows, snapshot first), APP-21 (label dev vs prod DATABASE_URL to prevent recurrence). APP-16 re-scoped to verify against prod. Corrected the app-product summary, which had wrongly read "shipped end-to-end." Tonight: Claude Code ships the "$$ fix" (api + mobile); it doesn't change the enum hazard since the strategy code is already on prod.
2026-06-03: APP-19 closed — production enum migrated. Snapshot pre-strategy-rename-prod-2026-06-03 taken off the prod branch, then ALTER TYPE … RENAME VALUE … ran clean against ep-raspy-union-an9mdayh. Enum is now Personal Meeting / Letter + Follow-up / Group Engagement; the 679 existing rows auto-flipped via in-place rename (227/227/225). APP-16 strategy-write verification ran against prod immediately after: PATCH /contacts/:id triggered an assignment that wrote and read back Personal Meeting cleanly. APP-16 still has one remaining check (one test email). After verify, the $$ fix shipped to mobile (commit 82b3a6c; bundle entry-eb1daa45…) — single $ on the one-time alert card now. APP-20 (dedupe) and APP-21 (DATABASE_URL labeling) still open per Ben's plan.
2026-06-03 (late): Post-deploy bug-fix sweep on kindred-ros-app. Three shipped fixes worth recording for the audit trail:
926fade — useStore.ts API_BASE fallback was http://localhost:3001 while every auth file's fallback was the fly URL. The Metro/Expo env substitution didn't reach useStore.ts at build time, so login worked (auth files) but every post-login apiFetch went to localhost and ERR_CONNECTION_REFUSED'd. Aligned the fallback so the build no longer depends on EXPO_PUBLIC_API_URL substituting. This was the root cause of the Today screen spinner.
700f1fc — added apps/mobile/.env.production with EXPO_PUBLIC_API_URL=https://kindred-ros-api.fly.dev so future expo export --platform web runs pick the URL up automatically, no inline env var required. Belt-and-suspenders with 926fade.
2a4e976 — profile save now calls fetchDashboard() after updateMe() succeeds. The dashboard slice (yearlyGoal, percentRaised, leftToRaise) was separate from user, so changing the yearly goal in profile left the Today and Dashboard screens showing stale numbers until a manual reload. Fix is in the store action, so every caller of updateMe gets the refresh.
2026-06-05: Reconcile sweep. Corrected APP-5: the CARE A/R/E 1/3/5 anchors are already drafted and sitting in Kevin's inbox for sign-off (2026-06-03 2022 CARE A-R-E 1-3-5 anchors for sign-off), so the open piece is Kevin's approval, not JC drafting. Confirmed this file is clean of the retired "Kevin-facing instance" phrasing. No task states changed; APP-16/18/20/21 remain as last recorded.
2026-06-09 (afternoon): APP-21 and APP-20 done in the working session. APP-21: found dev and prod are separate Neon projects (not branches), both keyed plain DATABASE_URL — relabeled .env as DEV-ONLY, added a target guard to drizzle.config.ts that refuses the prod host unless ALLOW_PROD=1, and wrote packages/db/MIGRATIONS.md. Guard verified three ways. Repo edits are GIT COMMIT READY (note in Ben's inbox); this instance does not commit. APP-20: the dedupe was far bigger than scoped — the whole donor list was double-imported May 1, 325 dup groups / 679 rows. Snapshotted (pre-donor-dedupe-prod-2026-06-09), Ben confirmed, deleted 327 rows → 352 unique contacts, 0 dups left. Dashboard one-time total corrected $785k → $485k. APP-16's last check (one test email) still open.
2026-06-09 (afternoon, later): Workflow recalibrated with Ben + cleanup closed out. Charter (CLAUDE.md) updated: commit AND push are free (commit straight to master, git push origin master; no repo auto-deploys, so push ≠ deploy), deployment is the only hard gate. APP-21 moved off the feature branch and pushed to master as 5fe38d4. APP-16 closed — test email sent via the live api's /auth/forgot-password for jean-claude@kindredros.com, and Ben confirmed it arrived; the APP-12→APP-16 strategy-fix deploy chain is fully done. APP-20 snapshot pre-donor-dedupe-prod-2026-06-09 deleted on Ben's call (0 dup groups on re-check). Next: build queue — APP-4 donor list view, then APP-18 store resilience.
2026-06-09 (evening): APP-4 donor list view built and pushed to master (aeb48aa). Enhanced (tabs)/contacts.tsx into a responsive screen: card list on narrow/native, dense sortable table on web, default order = the API's theSort (labeled "recommended order," one-tap reset), client-side column sort + name/strategy/follow-up-due filters over the in-memory list, no API change. Engine untouched; sort/scoring mechanics left to Kevin's rubric. Type-clean. Not yet deployed or browser-QA'd (Ben's gate). Spec in repo docs/spec-app-4-donor-list-view.md; PM spec in JC inbox 2026-06-09 1805. Build queue now down to APP-18 (store resilience).
2026-06-09 (evening, later): Web push crash guarded and APP-4 reviewed live. Web push: on web, Expo's push token call needs a VAPID key and Device.isDevice is true in a browser, so the path threw. Fixed with a Platform.OS === "web" guard in registerDeviceToken (push stays native-only); committed a41126c but on branch app-favicon-stars — recommend moving it to master (same worktree pattern as APP-21) so master has the fix, then reconcile/delete that branch. Real web push later is a feature, not a config tweak (VAPID keypair + app.json + server Web Push); backlog under APP-7, also gated on the frozen web-vs-native call. APP-4 live review (build on localhost): renders well, 352 rows (matches the dedupe). Polish for the builder, in JC-inbox note 2026-06-09 1830 APP-4 review: (1) Giving column shows zeros as "— /mo" / "— once" and needs column separators so Capacity/CARE/Giving stop scanning as one value (Capacity itself is a clean annualized number; my first read misattributed this); (2) an empty right region + row click opens a new window (row handler likely uses window.open/Linking instead of in-app router.push; a detail pane was not in the spec); (3) the unauthorized path throws uncaught and sticks a spinner = the APP-18 resilience gap. None block; all display/UX polish. Deploy + browser QA remain Ben's gate.
2026-06-15: Decision (Ben), the web instance of Kindred rOS lives at app.kindredros.com (Fly app kindred-ros-app). Added APP-22: CNAME app to kindred-ros-app.fly.dev, Fly cert, and APP_URL=https://app.kindredros.com on kindred-ros-api (retires the leftover fly.dev APP_URL from APP-11). Email-safe subdomain CNAME; steps in Ben's DNS checklist. Web-vs-native blocker (blockers item 4) updated: the web instance is confirmed and domained; only the "also submit to the stores" half may remain for Kevin. Also today: CARE reconciled to Kevin's CARE Clarity model (build-to-spec, the old 1/3/5 anchor sign-off is superseded, APP-5 updated).
2026-06-15 (evening):APP-18 store resilience built + pushed to master (84cf6e6); deploy is Ben's gate. Closed the 2026-06-03 spinner class on the client: fetchContacts now try/catch/finally so the loading flag always resets; apiFetch throws a typed ApiError with the HTTP status; 401→logout() (existing sign-out), other failures→contactsError banner; fetchTodayContacts/fetchDashboard/fetchMe route 401→sign-out instead of rejecting uncaught; Today + donor table show a calm "Try again" state. Reverted the temp [auth] 401 logging (7d28dbd). Then fixed the pre-existing mobile type errors APP-4 had flagged as unrelated (a4d12da): added radius.xl (modal corners were rendering unrounded), added DashboardStats.contactCount (the /dashboard route already returns it), cast the 1–5 score unions in contacts/new, replaced a dead JourneyMatrixEntry.tone read with .aim, and dropped an invalid contentStyle tab option — mobile tsc --noEmit is now fully clean. (API runs via tsx, no tsc step; its tsc-only resolution warnings are pre-existing and unrelated — the one file touched, auth.ts, is clean.)
2026-06-16: DNS + OAuth wiring session (Ben, Cowork). Added the two custom-domain CNAMEs in Squarespace (app→kindred-ros-app.fly.dev, ops→kindred-ros-ops.fly.dev; apex/www/email records untouched); both Fly certs Issued and both hosts serve HTTPS 200 (closes APP-22's DNS+cert work). Updated the kindred-ros-ops Google OAuth client: added JS origin https://ops.kindredros.com and redirect https://ops.kindredros.com/api/auth/callback/google (old kindred-ros-ops.fly.dev redirect kept), so ops dashboard Google sign-in works on the branded host. Nothing added for the product app: confirmed from code it uses email/password only (bcrypt + JWT via jose), no Google OAuth, and the ops client is the only OAuth client across all projects under jean-claude@kindredros.com. Logged as APP-23 — BETA-acceptable, social sign-in is the future upgrade. Still open on APP-22: set APP_URL=https://app.kindredros.com on kindred-ros-api.