Too lazy to plan? No idea where to go? Refuse to pay full price? We help you decide what to do just by doomscrolling our app — Navi works out what you'd love, catches the discount while it's live, books everything in one tap, and then handles every day of the trip for you.
Built in a ~9-hour sprint at the Y Combinator hackathon, by two Claude agents working in parallel lanes of one repo (see the build story below).
The reason your next trip isn't booked isn't a shortage of travel sites. It's that planning is real work — thirty open tabs, prices that move while you hesitate, and a decision most people don't feel qualified to make. Nearly everyone who fails to plan falls into at least one of these three camps (usually all three at once): can't be bothered to plan, won't book without a genuine deal, and doesn't actually know what kind of trip they want — just a budget and a feeling they'd recognize on sight.
Navi is built for exactly those people. It deletes every step between "I wish" and "I'm there":
PULL — you're scrolling reels; a trip that fits you surfaces inline ┐
├→ APPROVAL → BOOK → LIVE IT
PUSH — your taste is ready + a discount appears; a notification lands ┘
1 · You never have to figure out what you want. You set a budget and scroll a feed of short travel videos — the thing you already do at 11pm anyway. Liking and skipping is the entire interface. From your thumb alone the app works out your travel style — beach or city, hostel or boutique, packed days or slow ones — and tells you, in plain words, what it's learned about you.
2 · You never have to hunt for the deal. The moment your taste is clear and a real discount goes live on a trip that fits it, the trip comes to you: a notification with the reasons it's yours ("because of the surf lesson at Carcavelos you watched twice…"), the full day-by-day plan, the price next to the usual price, and a countdown — because discounts don't wait for you to finish deliberating. One tap books all of it: flights from your city, hotel, activities, even the train tickets.
3 · You never have to run the trip. Day-of, the app is the one paying attention. It pings you when it's actually time to leave for the 08:45 train. Your passport and boarding passes appear on their own as you walk into the airport. Every ticket lives in one place, working offline. And when plans change — "extend Lisbon by two days" — the trip rewrites itself, changed days flashing amber with the exact price difference.
You bring the budget and the thumb. It does the rest.
- Full-screen reels feed — video edge to edge, gradient scrim, glass chips; Instagram grammar, not a card carousel
- Tap ladder — double-tap = ♥ like (+1.0), triple-tap = ★ love (+1.5); same signals as the labeled Love / Like / Skip rail, one reaction per reel
- Taste engine — swipes update a vector over 8 settings (coastal, food, nightlife…) and 7 style axes (pace, authenticity, physical intensity…); a floating taste chip narrates what the app currently believes about you
- Survey-seeded onboarding — interest chips, travel frequency, an optional dream destination — a gentle prior the swipes then sharpen
- Evidence-backed matching — deals are ranked against the centered taste vector, and the winning trip carries receipts: the actual clips you reacted to, named in the offer copy
- One shared offer screen for both entry paths — reached warm from the feed or cold from a lock-screen notification with no feed in memory; only the copy differs
- Expiring price — a live countdown that survives backgrounding and force-quit; the red is reserved for exactly this
- Room market check — cheapest rooms for your dates, cheapest-first, deals flagged as ≥15% under the result-set median (no API exposes "discounted" — below-median is the honest computation); Amadeus live path ready behind a key, realistic fixture otherwise
- Full itinerary before you pay — every day expanded, place photos, per-block prices; the payment sheet shows where you're going, not a spinner
- One-tap booking — simulated pay (labeled as such), booking reference, the itinerary fills in with a haptic beat
- Shortest-path optimization — free activities between anchors (meals, trains, flights stay pinned) are reordered by an exact per-segment search before booking; "Route-optimized · saves 47 min of walking"
- The day timeline — every block with photos and prices, a "now" marker driven by the demo clock, wallet passes for the day's tickets
- Departure-aware pings — leave-now notifications name the actual scheduled train: "Catch the 08:45 train from Rossio — 12 min walk, runs every 20 min", fired at
departure − walk − buffer, never twice, never after it's too late to matter - Delay injection — the disruption beat: inject a 15-min delay and the alert reads "Your 08:45 now departs 09:00 — leave by 08:48 instead"; the ping moves with the train
- Documents on arrival — geofenced, and honest about what a phone can actually hold: walk into SFO and your outbound boarding pass surfaces on its own, drawn as a real pass (carrier bar, route, gate/seat/group, barcode) — not the return pass, which belongs to Lisbon. The hotel confirmation appears at the door, tickets at the venue, never anywhere else. No digital passport: it's a physical book a human checks, and faking one makes everything around it look fake too.
- Taste-ranked free time — when the plan leaves a real gap, one suggestion appears: "Free 16:00–17:30 · you'd like MAAT, 8 min walk" — ranked against your vector, so an authenticity-lover gets the neighborhood market and a crowds-and-cocktails profile gets Time Out Market, never the same list
- Spoken replans, with the truth attached — hours are free ("stay 3 more hours" just moves the day around, no cost, flight untouched). Days are not: your ticket is fixed, so "extend Lisbon by two days" rebooks the return leg and says so in plain English — old date, new date, and the airline's change fee — before you can confirm. Ask to stay past your own departure and it refuses outright, naming the flight and the airport cutoff instead of quietly producing an impossible plan.
- Offline-first — wallet, documents, and the timeline all work with wifi off; every live call (LLM itinerary generation, hotel rates, transit) validates its response and degrades to committed fixtures
The mark is a navigation arrow with a green destination dot sitting on the exact line its tip points along — the whole product in two shapes. Ink #0E0E10, live green #2FBF71, Plus Jakarta Sans; the same tokens the app is built from.
All brand art is generated from vector source, not hand-exported: scripts/make-logo.ts renders the icon, splash art, favicon and social card from one SVG definition (npm run logo), so the mark can never drift between sizes. On launch the static splash hands off to an animated one (components/BrandSplash.tsx) — the arrow flies its diagonal, the dot lands, the wordmark fades up — playing over the already-mounted app, so the beat costs no startup time.
Every frame below is generated by the project's own eval harness (npm run eval:shots): Playwright drives the real app through the real state machine, jumping a simulated clock that also moves a simulated GPS position derived from the itinerary — as if we were physically there at that moment. The full contact sheet with each frame's simulated time, coordinates and pass conditions is in evals/shots/index.md.
The harness also captures a force-reload frame (
04-booked-after-reload) that is pixel-identical to the booked frame above — that identity is the assertion, so it lives inevals/shots/rather than in this gallery.
- Expo SDK 54 · React Native · TypeScript, demoed on a physical iPhone via Expo Go; zustand + AsyncStorage (debounced, namespaced) for state, reanimated for the touches that matter
- One trip state machine —
browsing → offered → booked → active → complete; every mutation goes throughtransition(), every illegal move is refused, and all five phases round-trip persistence identically - One injectable clock — demo time is
Date.now() + offset; scrubbing time moves position, pings, and documents together, because position is derived from the itinerary track, not faked separately - Money is integer cents everywhere — totals are asserted equal to the sum of parts; no float ever reaches a screen
- LLM itinerary generation (claude-opus-5, JSON-schema-constrained) with a strict validator between the model and the UI — chronology, non-overlap, contiguous hotel nights, exact totals; anything invalid serves the hand-written fallback instead of throwing
- Fixture-first live data — hotels (Amadeus), POIs (OpenTripMap), transit (Google) all ship working against realistic committed fixtures; dropping a key into
.env.localswitches that provider live with zero code changes. Keys never enter git. - Honest simulation — payments, wallet passes, remote push and GPS are simulated in Expo Go and labeled as such in-app; transit "delays" are injected, never scraped, because live data for a trip dated October 2026 cannot exist
Two suites, because the two halves fail differently:
- Taste engine (
npm run eval:taste) — persona simulations asserting the vector converges to the right style - Execution (
npm run eval:exec) — E1–E12 headless assertions: itinerary validity, malformed-input survival, replan diff correctness, exact price deltas, ping timing (once, on time, never late), document triggering, state-machine legality, rehydration, optimizer optimality + deadline safety, transit schedule-grid conformance, hotel median math, POI persona ordering - Contract (
npx tsx evals/seam.ts) — the seam between the two build lanes: the feed's output is provably consumable by the offer screen - Visual sim (
npm run eval:shots) — the 11-beat screenshot walk above
npm install
npm start # or: npm run tunnel (public wifi)Scan the QR with the iPhone Camera app → opens in Expo Go (SDK 54).
Optional live-data keys go in .env.local (gitignored — never in app.json):
ANTHROPIC_API_KEY=... # live LLM itinerary generation
AMADEUS_CLIENT_ID=... # live hotel rates (free test tier)
AMADEUS_CLIENT_SECRET=...
OPENTRIPMAP_API_KEY=... # live POIs (free)
GOOGLE_MAPS_API_KEY=... # live transit (billing required)Everything works with zero keys — that's the point of fixture-first.
The Settings tab in-app is a demo harness: every eval scenario (airport docs, the Sintra ping, the delayed train, the amber extension…) is one tap, seeded through the real state transitions.
Two Claude agents built this in parallel, in one shared working tree, both on main — Lane A owned discovery (feed, taste engine, matching), Lane B owned planning, purchase and execution (offer, booking, trip runtime, evals). The seam between them is a frozen additive-only type contract plus a single navigation call, enforced by a contract test; cross-lane requests went through a HANDOFF.md ledger instead of touching each other's files. The result of ~9 hours at the YC hackathon: the funnel above, green evals, and a commit history where the merge conflicts fit on one hand.
Navi is a hackathon prototype. Bookings, payments and wallet passes are simulated; no card is charged and no train is harmed.













