Case Study · Product + Infrastructure ← Portfolio
Cycle Route Log

A cycle-trip planner for places you've never been

Plan a route somewhere new and most of it is just a claim until you check it: whether the ferry takes bikes, if that room is really free, whether the shop is still there. A confident answer isn't the same as a true one. So the tool keeps what's confirmed apart from what still needs checking, and the AI is built to say when it doesn't know instead of guessing. I make the calls.

Models sound just as certain when they're wrong as when they're right. So when I plan through places I've never been, I treat what the AI tells me as a lead to check, and keep the facts that matter — distances, ferries, what's actually open — coming from sources I trust.

The name is deliberate. A planned route is only a theory. The AI and I draft it together, but the deciding is mine: shaping it, prompting carefully, confirming the facts before I rely on them out there. A route only proves out once it's ridden and logged — what worked, what didn't. The plan is a guess; the ride is the evidence. That's why it's a log, not a planner.

Role
Sole product owner, architect & engineer
Stack
Next.js · TypeScript · MapLibre · Supabase · Anthropic API — PWA, local-first
Live
Open the planner →
Demo trip: Berlin → Copenhagen
Source
Private repository — see ADR on visibility below; full decision log available on request
01 — The problem

The map can't lie when you're 40km from anywhere

A real two-person cycle tour, Berlin to Copenhagen along EuroVelo 7, planned in a shared spreadsheet. The spreadsheet worked at the kitchen table and failed on the road: no offline access, no map, no way to tap a stop and navigate to it when the signal dropped somewhere in rural Mecklenburg. The wedge was the constraints of cycle touring — real distances, ferry timetables, accommodation that may or may not be open in shoulder season.

Those constraints are exactly where general-purpose travel AI fails. Travel-planning LLMs hallucinate hotels, invent ferry schedules, and fabricate distances. Building on a model as the primary source of physical-world facts would be irresponsible for a tool you actually rely on mid-trip. So the architecture draws a hard line — and the line is the interesting part.

Deterministic · ground truth
AI · language & reasoning
Routing, distances, elevation
Eliciting constraints from plain language
Accommodation & ferry schedules
Critiquing a plan, finding the gaps
Pricing, opening hours, seasonality
"What if we had an extra day?" reasoning
Where things physically are
Synthesis ("stock up before Møn")

This is the split at runtime: when the AI needs a fact, it calls a tool, and the tool returns ground truth. AI did more behind the scenes — drafting the structured trip data while building the app — but every generated record is validated against a strict schema before it's trusted.

02 — How the work was run

Each phase shippable, validated on a real trip

The project moves in phases, each independently useful even if work stopped at its end. Decisions are captured as Architecture Decision Records before anything hard-to-reverse is written; work is tracked on a GitHub Projects board with issues titled by their roadmap ID. Solo, but run like a team — and pointed at a hard external deadline.

  • 0FoundationRepo, docs, ADRs, CI, Vercel + Supabase plumbingDone
  • 1Static PWAHardcoded Berlin → Copenhagen, offline, mobile, used on the tripCurrent
  • 2Editable & collaborativeAuth, sync, two people editing one planPlanned
  • 3Trip criticOne-shot AI review of a plan against its constraintsPlanned
  • 4What-if assistantScoped chat, diff-based modifications to a tripPlanned
  • 5GeneralisationUseful beyond trip one — speculativeFuture

Phase 1 is "done" only when it is installed on both phones, works with no signal, and is the thing actually reached for during the trip — late August. Real-world use, not a green CI badge, is the acceptance test.

03 — Key decisions

Every decision audited against the road

Governing constraint · Offline-First
"It has to work with no signal, on a phone, in the rain."

One real-world constraint governs the project, and every decision is judged against it rather than against what's convenient to build. Local-first storage, a PWA that installs without an app store, pre-computed routes that don't need a live API — none of these are technical preferences. They're the consequences of taking the trip seriously as the acceptance test.

The decisions build on one another: the offline-first posture everything inherits, the split between the plan and the ridden run, the rule that the app surfaces facts to verify rather than inventing them, and one decision that isn't about code at all. A coherent log that evolves — not standalone documents.

AI Layer Design record LLM orchestration

Is this an AI app? At runtime, no — in the making, very much

The running app is deterministic: when the model needs a fact it calls a tool that returns ground truth rather than generating one, and the planned features (critic, what-if, generator) share one orchestration substrate, with API keys only in edge functions. Deterministic backbone, AI on top. But AI did real work building it — generating the day and contingency data the app runs on — and every generated record passes through a deterministic script that validates it against a strict schema before it's committed and trusted. Those generation scripts are kept as an audit trail.

Why it mattersThe discipline is restraint, not avoidance. AI earns the jobs it's reliable at — language, reasoning, drafting structured data — and hits a hard wall everywhere facts matter: distances, schedules and prices never come from a model in a tool you trust on the road, and nothing it generates is trusted until a deterministic check has passed. Right-sizing the intelligence — a real collaborator, on a short leash — is the same judgment across the portfolio.

ADR-0004 Accepted Trip Runs as the unit of lived experience

The plan is a guess; the run is what happened

A trip is the plan — route, days, stops. A run is one rider's attempt at it, with its own dates and a per-day log of what actually happened: real distance, where you slept, the weather, notes. One trip can have many runs. The plan stays clean; the run carries the reality.

Why it mattersThis is the "log, not a planner" idea made structural. The plan is what's intended; the run is what's lived, and they're modelled as separate things so neither corrupts the other. Day logs stay anchored to the plan's days even when the plan is edited later, so the record of what happened never quietly drifts.

ADR-0003 Accepted Contingencies as a first-class concept

Surface the options; let the rider decide

A contingency is anything that helps you abandon, shorten or rescue a day — the nearest bike shop, a train that takes bikes, a bed to bail to. They're a first-class part of the model, and the app's job is to surface them with enough detail to verify and act on — never to recommend, book, or predict. Each one carries notes on how to check it (say, how to confirm bike-carriage rules on a given train).

Why it mattersIt's the same discipline as the AI layer, in the data model: lay out real, checkable options and be honest about what still needs confirming, rather than pretending to know which one will save the day. The kind of contingency is free text, so a new sort can be added the moment a trip surfaces a gap — no schema migration, no guessing the full list up front.

Decision record Accepted Repository visibility

The decision that isn't about code

The repository stays private, with the case study and selected artefacts — ADRs, roadmap, project board — published separately as redacted copies. The driver wasn't technical: it was a deliberate choice to curate what goes public rather than expose a working repo wholesale, so the portfolio reads as a considered presentation, not a code dump.

Why it mattersTreating repository visibility as a decision to record — not a default to drift into — is a judgment made above the architecture. Publishing redacted artefacts on request keeps control over what represents the work while still giving reviewers the full reasoning. Choosing curation over convenience is the unglamorous, senior call.

Also in the log: ADR-0001 (PWA over native) — a web app, not a native build, so two people can install one offline tool without app-store friction; ADR-0002 (Supabase over Firebase) — predictable pricing, Postgres, magic-link auth; ADR-0005 (persistence) — the backend decision deliberately deferred from day one until the first feature that actually needed to write data, then settled on the lowest-cost option that covers every later phase. The map and routing stack (MapLibre, BRouter) are deliberate tooling picks rather than headline decisions. Full set available to reviewers on request.

04 — Working with AI, concretely

The collaborator has guardrails

AI was a genuine collaborator across this project — but a directed one. The discipline lives in a checked-in set of working rules, not in good intentions. The point of the portfolio is not that AI helped; it's how it was kept on a leash by someone who has built systems for a quarter-century.

  • Read the doc first. No proposing alternatives before reading the relevant ADR. Context precedes opinion.
  • ADR before anything hard to reverse. Architectural decisions get a written record with options considered and consequences — before the code, not after.
  • Phase awareness. When a Phase 1 task seems to need a Phase 2 capability, the answer is scope reduction, not scope expansion.
  • Right-size the intelligence. The model gets the bounded job it's reliable at; ground-truth facts never come from it — see the AI Layer record.
  • Match existing patterns. No second way to do something the codebase already does once.
05 — What's still open

Honest about the edges

The sync strategy is named but not chosen — by design, it waits for Phase 2. The two AI features are specified and not yet built; their real value won't be known until a plan is reviewed against a route that exists. And the project's largest open question is the one only the trip can answer: whether the app is genuinely the thing reached for on the road, or just a nicer spreadsheet. Knowing which questions are still open — and which are safe to leave open — is part of the work.