A single Cloudflare Worker that bundles:
- Hono API at
/api/* - React 18 + Vite SPA served from
dist/client/via the Workers Assets binding - D1 (SQLite) — 5 tables:
families,caregivers,babies,events,shifts - KV for magic-link tokens, invite codes, sessions
- Anthropic Claude Sonnet 4.6 for the AI "Today's 3 Things" summary (graceful template-only fallback when the key is missing)
- Resend for magic-link email delivery (dev mode logs codes to the Worker console instead)
Production deploy walkthrough: DEPLOY.md.
Relay is a family operations layer for new parents — less tracking, more peace of mind. The hero feature is multi-caregiver shift handoff: when your partner or nanny hands the baby off to you, you instantly know what mattered in the last few hours, what's coming next, and what to do — without asking.
This repo is the MVP (web-only, single Worker, ~25KB of TypeScript) demonstrating the core loop end-to-end. iOS and Android apps are out of scope for this repo.
# 1. Install dependencies
pnpm install
# 2. First time only — set up the local D1 database
pnpm db:migrate:local
# 3. (Optional) Add secrets for the AI / email paths to work locally
cp .env.example .dev.vars
# edit .dev.vars: set ANTHROPIC_API_KEY and RESEND_API_KEY
# 4. Run Vite + Worker on http://localhost:5173 with HMR
pnpm devStop the server with Ctrl-C. Magic-link codes are printed to the dev-server console as
[relay:dev-mailer] magic code for <email>: 123456 when RESEND_API_KEY is unset.
| PRD module | MVP status |
|---|---|
| §5.1 M1 Multi-Caregiver Accounts (3 roles) | ✅ |
| §5.2 M2 Shift / Handoff (auto + manual takeover) | ✅ |
| §5.3 M3 Quick Logging (feed / sleep / diaper / mood) | ✅ |
| §5.4 M4 Today's 3 Things (real Claude + fallback) | ✅ |
| §5.5 M5 Week View | ❌ deferred |
| §5.6 M6 Baby Profile / Settings (basic only) | ✅ |
| §6.4 End-to-end encryption | ❌ deferred |
| §10 COPPA / GDPR full | ❌ stub policy pages |
| Apple IAP / Google Billing | ❌ Free tier only |
| Real-time WebSocket sync | ❌ 10s poll instead |
| Offline queue / >24h offline mode | ❌ online-only |
.
├── DEPLOY.md ← production deploy walkthrough
├── README.md ← this file
├── package.json
├── wrangler.toml ← Worker + asset + D1 + KV bindings
├── vite.config.ts ← @cloudflare/vite-plugin integrates everything
├── tsconfig.json
├── tsconfig.node.json
├── drizzle.config.ts
├── migrations/
│ └── 0001_init.sql ← 5 tables + indexes
├── src/ ← Worker code (server-side)
│ ├── worker.ts ← Hono entry, route mounting
│ ├── env.ts ← Bindings type definitions
│ ├── shared.ts ← Shared TS domain types (CaregiverRole, RelayEvent, …)
│ ├── api/
│ │ ├── auth.ts POST /auth/request-link, /verify, /logout · GET /auth/me
│ │ ├── families.ts POST /families, /:id/invites, /join · GET /me, /:id/caregivers
│ │ ├── caregivers.ts PATCH /:id (role) · DELETE /:id
│ │ ├── events.ts POST / · GET / (auto-shift trigger)
│ │ ├── shifts.ts POST /takeover · GET /current
│ │ └── today.ts GET / (composed snapshot + AI items)
│ ├── db/
│ │ ├── schema.ts Drizzle table defs
│ │ ├── client.ts drizzle(env.DB) factory
│ │ └── repos/ familyRepo / caregiverRepo / babyRepo / eventRepo / shiftRepo
│ ├── lib/
│ │ ├── id.ts UUIDs + 6-digit codes + constant-time compare
│ │ ├── session.ts Cookie + KV session, requireAuth middleware
│ │ ├── permissions.ts canInvite / canChangeRoles / canRemoveOthers
│ │ ├── mailer.ts Resend adapter + dev console fallback
│ │ ├── claude.ts Anthropic Messages fetch (8s timeout)
│ │ ├── prompts.ts buildTodayPrompt + parseTodayResponse + fallbackThings
│ │ └── validate.ts Zod-validator wrapper that emits uniform error envelope
│ └── validation/
│ └── schemas.ts All request-body Zod schemas
├── web/ ← Vite SPA (client-side React)
│ ├── index.html
│ └── src/
│ ├── main.tsx
│ ├── App.tsx react-router routes, auth guards
│ ├── styles.css
│ ├── api/
│ │ ├── client.ts typed fetch wrapper with ApiError
│ │ └── auth.tsx AuthProvider + useAuth context
│ ├── pages/
│ │ ├── Login.tsx email → magic code
│ │ ├── VerifyCode.tsx paste code → session
│ │ ├── Onboarding.tsx create family + baby
│ │ ├── JoinFamily.tsx paste 6-digit invite code
│ │ ├── Today.tsx active caregiver + AI + quick log + events
│ │ ├── Family.tsx members, role changes, invites
│ │ └── More.tsx profile / privacy / subscription / help
│ └── components/
│ ├── ActiveCaregiverCard.tsx
│ ├── QuickLog.tsx
│ ├── EventList.tsx
│ └── TodaysThings.tsx
└── tests/
└── unit/ 23 passing unit tests
├── id.test.ts ← UUID + 6-digit code + safeEqual
├── permissions.test.ts
└── prompts.test.ts ← buildTodayPrompt / parser / caps gate
| Script | What it does |
|---|---|
pnpm dev |
Vite + Worker via @cloudflare/vite-plugin on http://localhost:5173 |
pnpm build |
TypeScript + Vite production build → dist/client/ and dist/ssr/ |
pnpm typecheck |
tsc --noEmit on both project tsconfigs |
pnpm test |
Vitest unit + integration |
pnpm db:migrate:local |
Apply migrations/*.sql to the local Miniflare D1 |
pnpm db:migrate:remote |
Apply migrations to the production D1 |
pnpm deploy |
pnpm build && wrangler deploy |
pnpm dev- Open
http://localhost:5173in two browsers (or one normal + one Incognito). - Browser A: sign in as
parent@example.com, paste the 6-digit code from the terminal, create familySmith Family+ babyEmma. - Browser A: tap Family → Generate invite code. Copy the 6-digit code.
- Browser B: tap Join a family, paste the code, sign in as
caregiver@example.com. - Browser B: tap Feed → 4 oz → Save.
- Browser A: within 10 s, the active-caregiver card flips to the caregiver, and the feed appears in Recent.
- Browser A: tap Take over → card flips back.
- Log 3-4 more events. After ~1 minute, the Today's things card refreshes via Claude (or shows a fallback note if no key).
- Browser A (Primary): change Browser B's role to Co-Parent — the dropdown updates immediately.