
Most trip-planning tools don't really have a "trip." They have a list of pins on a map, or a folder of notes, or a spreadsheet with a date column. The trip itself — the thing you're actually planning — never becomes a real object in the system. It's just the implicit sum of whatever you've typed into the other boxes.
Fernweh made a different choice early on: a trip is a record with its own identity. Not a filter over a pile of stops, not a view computed on the fly — an actual row that other things can point at, reference, and stay attached to. It sounds like a small decision. It isn't. Almost everything else follows from it.
What "identity" actually buys you
If a trip is just an implicit grouping — "all the stops with this label" — then anything you want to attach to the trip has nowhere to live. Want a calendar event per leg? You'd have to regenerate it from scratch every time, with no memory of what you created last time or whether it's still accurate. Want a running total for the trip's cost? You're recomputing from raw stop data on every screen that shows it, with no single place that owns "what this trip costs right now."
Give the trip identity, and all of that flips. Other records can hold a reference to the trip instead of trying to reconstruct it. A calendar event doesn't have to guess whether it already exists for a given leg — it's tied to that leg, on that trip, and Fernweh knows to update it in place instead of creating a duplicate every time you move a stop. A total isn't a page-load computation; it's a property of a thing that exists.
Per-leg timing that actually recomputes
The clearest example is timing. A road trip isn't one duration — it's a sequence of legs, each with its own distance, driving time, and (for an EV) charging math, and the legs depend on each other in order. Move the third stop earlier in the day, and the legs on either side of it change: the leg before it gets shorter, the leg after it starts from a different point.
That only works cleanly if there's a real trip object holding the ordered sequence of legs, each one aware of its position and its neighbors. Without that, "recompute the schedule after an edit" turns into a tangle of ad hoc recalculations scattered across whichever screen happened to trigger the edit. With it, the recomputation has a natural home: update the trip, and the legs that depend on the change recompute from there.
The trip travels with you, not just the data
The other place identity matters is sync. Fernweh trips live in iCloud via CloudKit, which means a trip you're planning on your phone shows up on your iPad, and a trip you're sharing with someone else stays current for both of you as either person edits it. That kind of collaborative, cross-device sync is a lot more tractable when the thing being synced is a well-defined record with a stable identity that both devices — and both people — can agree is "the same trip." Sync a loose pile of stops instead, and you're left guessing at which pin on your phone corresponds to which pin on your collaborator's, especially the moment either of you reorders anything.
The alternative, and why it doesn't scale
It's worth being specific about what the alternative actually looks like, because it's not a strawman — it's how most tools work by default, notes apps and spreadsheets included. A notes app stores your trip as a block of text. A spreadsheet stores it as rows. In both cases, "the trip" is a label you apply to a collection of other things, not a thing in its own right. That's fine right up until you want the collection to do something collectively — recompute a schedule, sync as a unit, or hold state that belongs to the whole trip rather than any one stop.
The failure mode isn't dramatic. It's death by a thousand small inconsistencies: a calendar event that didn't update when you moved a stop, a total that's one edit behind, a shared trip where your collaborator sees a slightly different version of "the plan" than you do. Each one is a minor annoyance in isolation. Stacked across a multi-day trip with a dozen stops and a couple of collaborators, they compound into the specific kind of low-grade distrust that makes people stop trusting the tool and fall back to a group chat instead.
What this costs at build time
Giving the trip identity isn't free. It means every feature that touches a trip has to think in terms of "what changed on the trip record, and what needs to react to that" rather than "what do I compute fresh right now." That's a real constraint, and it shows up early — in how the schema is shaped, in how edits propagate, in how much a single stop move can ripple outward.
The payoff shows up in testing, which is where a lot of this discipline actually gets enforced. A trip with real identity is a thing you can construct in a known state, mutate in a specific way, and assert against — move stop three, then check that legs two and four recomputed and legs one and five didn't. That's a tractable, repeatable test. The looser "recompute everything from a pile of stops" version doesn't offer the same seams; there's no clean boundary between "before the edit" and "after the edit" to assert across, because nothing was holding state in between.
Why this is worth writing about
None of this is a flashy feature. Nobody opens Fernweh because the trip has a stable database identity. But it's the reason the features that do show up — the calendar events that stay accurate, the totals that update instead of drift, the trip that looks the same on every device — actually work instead of almost working. The trip being a first-class object isn't the feature. It's the thing that makes the features possible.
It's also, honestly, the kind of decision that's easy to skip early on and expensive to retrofit later. Treating the trip as an afterthought — a computed view instead of a real record — would have made every one of these things harder to build and easier to get subtly wrong. Building it in from the start meant paying that cost once, at the point where the codebase was smallest and the mistake was cheapest to fix.