25
Backend
04
Security
08
iOS
07
Infra

Model trip versions as named refs into a modification tree

ADR-0046 ACCEPTED · 2026-03-26
Model trip versions as named refs into a modification tree

Decision

Give every modification a parent pointer, so a trip's modifications form a tree, and make versions named pointers into that tree, the way git branches point at commits. The trip row holds the head modification and the active version. Switching versions moves those two pointers and copies nothing. A modification written against an older version starts a new branch by taking that version's tip as its parent. Replay collects the ancestors of a tip and applies them in log order (ADR-0051).

A version is a save slot that moves forward on its own. Every modification is stored immediately and advances the version it was written to. There's no draft, no commit step and no unsaved state. The first modification on a trip creates a version named "Original", and creating another version forks from the current head. All versions are peers, with no main branch. Most people never make another version, so the app keeps versions out of sight until someone does.

Why

Replay of the log (ADR-0033) already let a trip go back in time, but only along one line. Planning is exploratory. "What if we drove the coast instead of flying to Florence?" is a differently shaped trip, and people want to work it through, compare it with the Florence plan and keep both. On a linear history, rewinding and editing overwrites the Florence plan. A tree keeps both, the branches share their history up to the fork, and a new version costs one row.

Rejected alternatives

  • Copying the modifications on every branch. It duplicates the shared history, and keeping the copies identical up to the fork becomes something to maintain.
  • Marking replaced modifications as forked. A forked-at timestamp meant marking existing rows inactive before every insert. Those state changes were fragile, and a parent pointer gives the same structure for free.
  • A separate what-if mode. Edits piled up in a temporary space until the user decided what to do with them. It caused the anxiety the feature was meant to remove. People didn't know when to leave the mode, and feared losing their changes when the app went into the background. What-if exploration now exists only in how the app presents versions, and the server just stores modifications and advances versions.

Consequences

Modifications are only appended, so a branch stays intact while a version points at it. Branching is an insert and switching is an update. Deleting a version leaves its modifications behind as an unreachable branch. Rebuilding a trip walks its whole ancestry, so the cost grows with history. There's no merge, because versions are alternative trips with no parallel work to reconcile (ADR-0048). Switching versions writes no modification, so it only works online (ADR-0073).