Case Study · Mobile + AI

Notebound

An iOS app that turns a trip's photos, notes, and voice into a written story and a mapped, day-by-day itinerary — powered by a multi-model Claude pipeline, built offline-first.

A calm, voice-first capture experience.

Travel is messy and hands-busy, so capture had to be effortless: hold to speak, or jot a line, tag it, and move on. The aesthetic is warm and quiet — the photos carry the color, the UI gets out of the way.

Voice-first capture — hold to record, or type; auto-tagged by category.
Voice-first capture — hold to record, or type; auto-tagged by category.
Trip feed — notes with categories, locations, and a map toggle.
Trip feed — notes with categories, locations, and a map toggle.
The generated story — narrative written from your notes and photos.
The generated story — narrative written from your notes and photos.
Itinerary view — a day-by-day plan with a category-pinned map.
Itinerary view — a day-by-day plan with a category-pinned map.

From a spoken note to a published story.

Voice → intent capture

Hold to record. A transcript is classified by a Claude Haiku function into the right kind of note and auto-tagged — food, stay, activity — with location attached.

Multimodal photo curation

Claude Opus actually looks at the photos (downsized to base64 for vision), judges the strongest few per note, and picks a cover — never one-per-note padding.

Auto-itinerary with map

In the same generation call, the model emits a structured day-by-day itinerary with coordinates, rendered as morning/afternoon cards over a category-pinned map.

Offline-first capture & sync

Notes and photos capture with no signal and drain through upload/offline queues when connectivity returns. Generation status streams back over Supabase realtime.

EXIF & trip-aware location

Dates come from photo EXIF; a GPS fix is validated against the trip's anchors, inferring the nearest known city when a reading is implausible.

Readiness gates

A free local gate blocks thin trips instantly; a server-side LLM gate returns an honest “not enough notes” state rather than fabricating a story.

One generation call, the right model for each job.

A React Native client talks to Supabase. Three Deno edge functions front Claude: cheap, fast Haiku for classification and tagging; Opus for the multimodal heavy lift. Reliability is defended server-side with a pg_cron sweep.

React Native · ExpoVoice / Text captureOffline + upload queuesRealtime statusSupabase · Edge Functions (Deno)detect-intenttag-notegenerate-blogClaudeHaiku 4.5 · intentHaiku 4.5 · tagsOpus 4.8 · multimodalPostgres (notes · trips · blog_posts·itinerary jsonb) · pg_cron stale-sweep

Model selection

Haiku for classification/tagging keeps per-note cost near zero; Opus is reserved for the one creative, multimodal call that actually warrants it.

Vision without blowing the budget

Photos are downsized to ~1536px and base64-encoded just for the model's eyes; the blog always embeds the original full-res URLs.

Supplementary, never fatal

A malformed itinerary stores null but the narrative still saves — one feature failing never blocks the core output.

Reliability backstop

A pg_cron job marks any run stuck “generating” past 5 minutes as errored, so the client never spins forever.

See it run.

Shipped with discipline.

Built spec → plan → task-by-task with review at each step. 289 tests, TypeScript clean, migrations verified live against Supabase before merge. Edge functions versioned and redeployed per change.

Built with the help of Claude Code — the same AI-augmented workflow I use to move fast without giving up rigor.