A local-first, AI-powered personal assistant for health and daily routines, built as an installable iPhone PWA. Logging a meal, a glass of water, or a habit takes one or two taps, and Graham turns that history into briefings, reviews, and suggestions without keeping any personal data on a server.
- Fast logging for meals, water, habits, supplements, weight, and caffeine, plus Siri Shortcuts URLs (
/log?water=1) for hands-free logging. - Meal photo logging: snap a photo, get an editable calorie and macro estimate from a vision model, and save it as a reusable "usual."
- AI features: a morning briefing written from your real data, an evening wind-down recap, a Sunday weekly review, a brain dump that sorts free text into tagged chips, and a chat with an adjustable personality.
- Pulse surface: a breathing orb with three ranked suggestions for what to log or open next, learned from your own rhythm.
- Integrations: read-only Google Calendar, weather, UV and sun times, and a tailorable news feed.
- Your data stays yours: everything lives on the device, with one-tap JSON export and import.
flowchart LR
subgraph Device["iPhone (PWA)"]
UI[React UI] --> IDB[(IndexedDB logs)]
UI --> LS[(localStorage config)]
SW[Service worker]
end
subgraph Vercel["Vercel serverless (stateless)"]
AI["/api/ai"]
CAL["/api/calendar"]
PUSH["/api/push + push-cron"]
end
UI -- "task + data snapshot" --> AI --> Anthropic[Claude API]
UI -- "OAuth code / refresh" --> CAL --> Google[Google OAuth]
UI -- "subscription + times" --> PUSH --> Redis[(Upstash Redis)]
Cron[External scheduler] --> PUSH -- "generic ping" --> SW
Local-first storage. Logs live in IndexedDB (via idb) in a single store with a common { id, type, refId, timestamp, data } shape, indexed by type and time, so a new tracker is just a new type value with no migration. Config and preferences live in localStorage.
Stateless AI proxy. The Anthropic API key exists only as a server environment variable. The client sends a task type and a snapshot of the relevant local data; the server picks a fixed, task-specific prompt and token cap, calls the model, and returns structured JSON. Nothing is stored between requests, and the endpoint is rate-limited per IP.
Privacy-preserving push notifications. Web Push with VAPID keys. The server stores only the push subscription and the user's chosen notification times. Every payload is a generic wake-up ping ("Your briefing is ready"), and the real content is composed on the device when the app opens. An external scheduler hits a secret-protected cron endpoint every ~10 minutes to send due pings.
Calendar OAuth without server storage. The serverless function handles only the two steps that need the client secret (code exchange and token refresh). Tokens are handed to the device and kept there. The tradeoff, documented in docs/DEVELOPMENT.md, is acceptable for a read-only scope.
On-device suggestion engine. Every loggable action is a candidate, collapsed to one per domain so the three suggestions are always distinct. Time-of-day curves handle cold start. Once a domain has 5+ logs, a recency-weighted model (30-day half-life) of when you actually log it is blended in by confidence, capped at 0.8 so defaults never fully disappear. All of it is computed locally from existing log history.
Network-first app shell. The service worker serves navigations network-first, so an installed PWA never boots a stale cached shell that references asset hashes a new deploy has already removed.
React 18, TypeScript, Vite, vite-plugin-pwa (Workbox, injectManifest), IndexedDB (idb), @dnd-kit for drag-to-reorder, Vercel serverless functions, Anthropic API (Claude), Google Calendar API, Web Push (web-push, VAPID), Upstash Redis.
npm install
cp .env.example .env.local # fill in your own keys
npm run devThe AI, calendar, and push features each need their own credentials (see .env.example). Full setup, including Google Cloud OAuth and installing the PWA on an iPhone over HTTPS, is in docs/DEVELOPMENT.md.
Built by Sameer Khan, with Claude as a pair programmer.