# Kin — scope & architecture *Written 2026-08-09, at project start.* ## What this is A personal "relational wealth" app: the single place that answers **who haven't I talked to in too long?** and makes fixing that a two-tap action. Inspired by Monica CRM, but deliberately thin — one user, one server, no accounts, no social features. ## Product principles 1. **The due list is the product.** Contacts, notes, and history exist to feed one screen: who to reach out to today. Everything else is secondary. 2. **Logging must be cheaper than not logging.** Two taps ("text, today, done") or the log stays empty and the due list lies. The old web CRM had 44 people and 0 interactions — that's the failure mode to design against. 3. **Cadence over guilt.** Each person gets a cadence (weekly → 6-monthly) or none at all. No global "you're behind on 30 people" — only people you *chose* to track surface in reminders. Snooze is honest: it defers, it doesn't fake contact. 4. **Thin client, durable data.** All state lives in the `personal` Postgres database on the VPS. The phone caches for instant open and offline reading; writes go straight to the API. ## Architecture ``` Pixel (Kin app, Expo RN) ──Bearer token──▶ Caddy ──▶ crm container (Bun+Hono) ──▶ personal-db (Postgres) Browser (web UI) ──basic_auth───▶ ↑ same API, same data ``` - **Server**: the existing `crm` container, extended. Hono + porsager/postgres on Bun. Now has boot-time SQL migrations (`schema_migrations`), a `GET /api/due` endpoint, cadence/snooze/archive columns, relationship create/delete, and Bearer-token auth for non-browser clients. - **Due math** (server): `urgency = days_since_last_contact / cadence_days`, anchored on `created_at` for never-contacted people so new adds surface immediately. `overdue` ≥ 1.0, `due_soon` ≥ 0.75. Sorted by urgency, so a weekly friend 3 days late outranks a yearly contact 3 days late. - **App**: Expo SDK 57 + expo-router, structured like the GTD app (read-cache store on `useSyncExternalStore` + AsyncStorage, refresh on foreground/focus). Dark warm theme, Pixel 9 XL first. - **Reminders**: local notifications only, no server push. On every sync the app reschedules a daily digest for the next 7 days ("N people are due for a catch-up") at the configured hour. If the app isn't opened for a week, the notifications run out — which is itself the right nudge. Server-side push via the existing ntfy container is the v2 escape hatch if this proves too passive. ## Screens | Screen | Job | |---|---| | **Today** (home) | Due list in sections: Reach out / Coming up / On track / Snoozed. Quick-log button on every row. Empty state doubles as onboarding. | | **People** | Whole network, client-side search + tag filter (44 people — no server round-trips). | | **Person** | Contact actions (Message/Call/WhatsApp/Email deep links), cadence picker, log + snooze, notes, relationships, history (long-press to delete). | | **Log** (modal) | Type chips + today/yesterday + optional note. Two taps minimum. | | **Add/Edit** (modal) | Name, phone, email, location, tags, notes, cadence. | | **Settings** (modal) | Server URL, token, reminder hour. Probes the API before saving. | ## Deliberately out of v1 - **Offline write queue** — GTD's op-queue store is proven but heavy; a relationships app tolerates "log it when you're back online". Revisit if it annoys in practice. - **Birthday reminders** — column exists (`people.birthday`), UI doesn't. - **Relationship editing in-app** — API supports create/delete now; app only displays. Add when graph curation actually matters. - **Web UI parity** — the vanilla-JS web UI still works for bulk edits at crm.rehbock.xyz; it doesn't know about cadence yet. - **Contact import from the phone** — seed data came from the newsletter subscriber list; `expo-contacts` import is a v2 candidate. - **Multi-user / open-source hardening** — single-user by design. The repo is structured to be open-sourceable (no secrets in git, migrations from scratch, auth documented), but no login system until someone else needs it. ## Infrastructure facts - VPS dir: `~/personal/crm` (container `crm`, network `caddy_net` + `personal-db_default`). DB: `personal` in the shared `personal-db` Postgres. - Caddy: `crm.rehbock.xyz`; Bearer requests bypass basic_auth (app validates), everything else challenges. - CI: Gitea Actions on the desktop runner (label `desktop`), signed APK per push to main, released as `kin-v1..apk` for Obtainium. - Keystore: `~/.android-keys/kin-release.jks`, alias `kin` — distinct from GTD's so Android treats the apps independently.