04
Product
16
Backend
09
Auth
12
iOS
07
Infra
02
Real-Time

Model trips as destinations with effect-based modifications

ADR-0033 ACCEPTED · 2025-09-03
Trip model as destinations with effect-based modifications

Context

Traditional trip planning follows a direct manipulation model: start with an empty itinerary, add each element through forms, the trip IS the data you entered. This doesn't match how people naturally think about trips.

We want users to start with minimal input like "Vienna birthday weekend" and iteratively refine through natural dialogue. The trip should be a generated artifact from accumulated intents, not a form to fill out.

Decision

Trips as destinations

Trips are sequences of destinations — continuous periods in one location. Each destination has a location, duration or date range, locked status, and notes. This handles everything from single-city weekends to multi-country journeys.

Modifications as source of truth

Trips are derived state, generated from an accumulated history of modifications. Every change (including initial creation) is stored as a Modification containing the user's input and timestamp. The current trip state is always regenerable from this history. This is event sourcing.

Effects are events, not commands. Despite imperative naming (AddDestination, SetDuration), effects are fully-specified facts about transformations. By the time an effect is stored, all locations are resolved strings, all dates are concrete, all references are resolved. Events can always be replayed deterministically; commands might produce different results depending on external state.

Effects as transformation language

A constrained set of effects represents all possible trip transformations — structural changes (add/remove destinations), temporal changes (set dates and durations), constraints (lock a destination in place), and metadata (purpose, notes).

Each effect application is a pure function: apply_effect(Trip, Effect) → Trip. No side effects, no external dependencies during replay.

Effects can come from any source — GPT-5 from natural language (ADR-0032), drag-and-drop generating ReorderDestination, form controls, or API calls. All produce the same effects, applied uniformly.

Determinism guarantees

  • Deterministic UUIDs: Destinations get stable IDs via UUID v5 from trip_id and effect index
  • No external dependencies: Effect application reads only from payload and current state
  • Complete payloads: Effects contain all resolved data — the LLM/UI layer does command→event translation
  • Ordered application: Modification table position column ensures consistent replay

Effect versioning

Following Greg Young's guidance: additive changes use #[serde(default)], semantic changes create new effect types (AddDestinationV2), both coexist in the enum permanently. The key insight: "A new version must be convertible from the old version. If not, it's a new event, not a new version."

Consequences

Users interact through conversation, not forms. Complete modification history enables undo and experimentation. Creation and mutation use identical patterns. Effect application is deterministic, pure, and testable.

The cost is API dependency for natural language processing, grammar maintenance as effect types evolve, and regeneration cost (mitigated by caching).

References