Skip to content

Feature: Trip Planner & Live Tracker (v0)

ID: 0001
Status: In progress (M1–M9 shipped; M10–M11 remain)
Owner: @satya
Created: 2026-05-02
Updated: 2026-05-12
Related ADRs: 0001 (Flutter), 0002 (Supabase + NestJS), 0004 (monorepo),
0005 (SDD), 0006 (Docker/Coolify)

Milestone tracker — see progress.md for live milestone status. Travelers (members + invites) shipped early as an out-of-sequence M11 slice ahead of M10 because spec 0003 needed share-with-collaborators to land first.

This is the first feature spec for Treeper. It covers what a user can do without any AI, community, or marketplace involvement: plan a trip on their phone and use it on the road. Pillars P1 and P2 from PRODUCT.md.

Subsequent specs (0002+) layer on AI ingest, communities, and the marketplace.


People plan trips across notes apps, screenshots, group chats, and inboxes. The plan goes stale the moment the trip starts because it lives in a place that doesn’t work offline or doesn’t update easily on the road.

We can fix that with one tightly-scoped, mobile-first product surface: a trip you can build in under a minute, edit anywhere, view offline, and actually use during travel.

From PRODUCT.md §2:

  • Solo planner (primary). One place for an upcoming trip.
  • Group lead (primary). Builds the plan others will join later.
  • Active traveller (primary). Uses the plan offline, marks things done.

Out of scope for this spec:

  • Trip-mate seeker, inspiration hoarder, curated planner — served by later specs.
  • F1.1.a Create a trip (title, start date, end date, cover image optional).
  • F1.1.b View trip overview (cover, dates, day count, activity count, total estimated cost).
  • F1.1.c Edit trip metadata (title, dates, cover, currency, visibility).
  • F1.1.d Delete a trip (soft-delete with 30-day undo, then hard-delete).
  • F1.1.e Archive a trip (hidden from main list, still searchable).
  • F1.2.a Add one or more destinations to a trip (city / region / poi).
  • F1.2.b Per-destination arrival and departure date (must fit inside trip dates).
  • F1.2.c Reorder destinations.
  • F1.2.d Single-destination trips remain a first-class case (no required multi-stop UI).
  • F1.3.a Days auto-generated from trip start/end dates.
  • F1.3.b Empty days are valid (no required activity).
  • F1.3.c Dates that change resize the schedule, preserving existing activities (clipped to new range; clipped activities go to a “needs re-scheduling” tray, never deleted).
  • F1.4.a Activity kinds: transport, lodging, food, sight, freeform. Kind is mandatory.
  • F1.4.b Required fields: title, kind, day_id. Optional: time_start, time_end, location (lat/lng + label), notes (markdown), cost_amount, cost_currency.
  • F1.4.c Per-day ordering: activities sort by time_start ASC NULLS LAST, ties broken by user-set order index.
  • F1.5.a Reorder activities within a day.
  • F1.5.b Move an activity from one day to another via drag.
  • F1.5.c Reorder days within a trip (e.g. swap day 2 and 3).
  • F1.6.a Per-activity cost in any supported currency (ISO-4217).
  • F1.6.b Day total = sum of activity costs converted to trip currency at the trip’s chosen rate snapshot.
  • F1.6.c Trip total = sum of day totals.
  • F1.6.d Per-traveller split (n equal shares) shown in summary.
  • F1.7.a Per-day map: pins for activities that have a location, ordered path between them.
  • F1.7.b Full-trip map: all destinations + all activities.
  • F1.7.c Tap a pin → activity detail.
  • F1.8.a Up to N photos per activity (initial limit: 8). Stored in Supabase Storage.
  • F1.8.b One or more external URLs per activity, with a free-text label.
  • F1.8.c File attachments (PDF tickets) — stretch; behind feature flag for v0.
  • F1.9.a Duplicate an existing trip into a new draft (same days, same activities, no live-mode log).
  • F1.9.b “Save as template” stores a personal template under the user’s account; templates can seed new trips.
  • F1.10.a All reads on an opened trip work offline.
  • F1.10.b Edits made offline queue locally and replay to the API on next network event.
  • F1.10.c Conflict policy: last-write-wins per field at v0 (ADR follow-up for richer resolver later).
  • F1.10.d Sync state visible in UI (synced / queued / conflict).
  • F1.11.a Visibility: private (default), unlisted (link-only), public (discoverable later — placeholder UI for v0).
  • F1.11.b Changing visibility takes effect within 60 s, including for any cached share links.
  • F1.12.a Generating a link for an unlisted trip produces a stable URL that renders cover, days, activities — no editing.
  • F1.12.b Revoking the link invalidates it.
  • F1.12.c Public web rendering is out of scope at v0; the link opens the app via deep-link and falls back to a minimal HTML preview hosted by the backend.
  • F2.1.a Per trip toggle between plan mode and live mode.
  • F2.1.b Live mode default-shows “today” with the next-up activity at top.
  • F2.1.c Auto-suggests live mode when current device time enters the trip date range.
  • F2.2.a Tick an activity as done; record actual start / end time (defaults to now).
  • F2.2.b Optional actual cost overrides planned cost.
  • F2.3.a Photos attached to a done activity appear in an end-of-trip recap.
  • F2.3.b Free-text journal field per activity.
  • F2.3.c Voice memo — stretch, behind flag for v0.
  • F2.4.a Once a trip has been opened, all of its content is available offline until manually purged.
  • F2.4.b Opened photos are cached; the user can pin a trip for “always available offline” with size warning.
  • F2.5.a When an activity’s actual_start exceeds planned_start by > 15 min, show a “running late” indicator.
  • F2.5.b Offer a one-tap “shift remaining of today” that pushes all un-done activities back by the drift amount.
  • F2.6.a Auto-generated screen at trip end: cost vs planned, day-by-day photo strip, top-level metrics.
  • F2.6.b Shareable as an image (out of scope at v0 — flagged).
  • AI ingest from social → spec 0002.
  • Prompt-to-itinerary → spec 0003.
  • Communities, open trips, trip-mates → spec 0004.
  • Trusted contacts → spec 0005.
  • Verified marketplace, day adventures → spec 0006.
  • Deals / price-watch → spec 0007.
  • Real-time collaboration on the same trip with multiple editors — defer to a later spec; v0 is single-author.
  • Web client. Mobile only at v0.
  • As a solo planner, I can create a trip with a title, start date, and end date in under 30 seconds, so that my plan starts existing.
  • As a solo planner, I can add activities to a day with a kind, title, and optional time/cost, so that the plan reflects what I intend.
  • As a solo planner, I can drag activities to reorder them, so that the day matches reality.
  • As a group lead, I can mark a trip as unlisted and share a link, so that friends can see the plan without an account.
  • As an active traveller, I can view the entire trip offline once it has been opened, so that travel doesn’t break the app.
  • As an active traveller, I can flip a trip into live mode and tick activities done, so that I have a journal at the end.
  • As a solo planner, I can change a trip’s dates and have the schedule resize without losing activities, so that re-planning is safe.

Screen names (subject to change in design pass):

  • TripList — home; user’s trips, separated by upcoming / past / archived.
  • TripDetail — header + day tabs; floating ”+” for activity.
  • ActivityEditor — bottom sheet, kind selector first.
  • MapView — switchable per-day / per-trip.
  • LiveDay — today view; large “next up” card; quick-tick.
  • Recap — end-of-trip summary.

Interactions:

  • Drag-and-drop reorder (long-press to grab).
  • Pull-to-refresh in TripList triggers a sync.
  • Sync state lives in a small chip in the TripDetail header.

This spec does not prescribe visual design; that is the next deliverable under the design system specs.

Each criterion maps to one or more F-ids and must be testable.

AC-1 F1.1.a Given a signed-in user with no trips,
when they tap "New Trip" and fill title + dates,
then a trip exists in their account and appears in TripList.
AC-2 F1.1.d Given a trip exists,
when the user deletes it,
then it disappears from TripList immediately and is fully
gone after 30 days; an "Undo" toast is shown for 10 s.
AC-3 F1.3.c Given a trip with activities on day 3,
when the user shortens the trip to end before day 3,
then the day-3 activities move to a "needs re-scheduling"
tray and are not deleted.
AC-4 F1.4.a Given the activity editor is open,
when the user attempts to save without selecting a kind,
then save is blocked and a kind picker is shown.
AC-5 F1.5.b Given activities exist on day 1 and day 2,
when the user drags activity X from day 1 onto day 2,
then activity X belongs to day 2 and its order index is
appended at the end of day 2's activities.
AC-6 F1.6.b Given activities with mixed currencies,
when the user views a day total,
then the total is shown in trip currency using the trip's
stored rate snapshot, plus a footnote with the rate date.
AC-7 F1.7.a Given a day has at least two activities with locations,
when the user opens the day map,
then pins appear in chronological order with a visible
connecting path.
AC-8 F1.10.a Given a trip has been opened on the device,
when the device is offline,
then the trip's content (text, last-cached photos) is
viewable.
AC-9 F1.10.b Given the device is offline,
when the user edits an activity title and reconnects,
then the edit reaches Supabase and the trip on a second
device reflects it within 30 s.
AC-10 F1.10.c Given two devices edit the same activity field while
offline,
when both come online,
then the most recent edit by wall-clock time wins, and
both devices show the same value within 60 s.
AC-11 F1.11.b Given a trip is `unlisted` with a share link,
when the user changes visibility to `private`,
then the share link returns 404 within 60 s.
AC-12 F2.1.a Given a trip's start date <= today <= end date,
when the user opens the trip,
then live mode is suggested by default.
AC-13 F2.2.a Given live mode is on for a trip,
when the user ticks an activity done,
then `actual_start` and `actual_end` default to "now" and
are persisted.
AC-14 F2.5.a Given an activity's actual_start exceeds planned_start by
more than 15 minutes,
when live mode renders the day,
then a "running late" indicator appears on the activity
and a one-tap "shift remaining of today" CTA is offered.
AC-15 F2.6.a Given a trip whose end_date < today,
when the user opens it,
then the recap screen renders with cost-vs-planned,
photo strip, and a list of completed activities.

Real schema lives in Supabase migrations. This is the v0 sketch.

users (managed by Supabase auth)
trips id, owner_id, title, start_date, end_date, currency,
cover_url, visibility, archived_at, deleted_at,
created_at, updated_at
trip_destinations id, trip_id, name, geo (lng,lat), arrival_date,
departure_date, order_index
trip_days id, trip_id, date, day_index
activities id, day_id, kind, title, time_start, time_end,
location_label, location_geo, notes_md,
cost_amount, cost_currency, order_index,
planned_start, planned_end, actual_start, actual_end,
done, created_at, updated_at
activity_attachments id, activity_id, kind (image/file), url,
bytes, mime_type, order_index
activity_links id, activity_id, url, label
trip_share_links id, trip_id, slug, revoked_at, created_at
trip_templates id, owner_id, source_trip_id, name, payload (jsonb)
sync_journal (client-only; outbox of edits to replay)

Notes:

  • actual_* columns kept beside planned_* so live-mode timing isn’t destructive.
  • deleted_at enables the 30-day undo window from F1.1.d.
  • payload on trip_templates is the denormalised snapshot at save time.

NestJS REST endpoints (mobile is the only first-party client). All require Supabase JWT.

POST /v1/trips
GET /v1/trips
GET /v1/trips/:id
PATCH /v1/trips/:id
DELETE /v1/trips/:id
POST /v1/trips/:id/destinations
PATCH /v1/trips/:id/destinations/:destId
DELETE /v1/trips/:id/destinations/:destId
POST /v1/trips/:id/days/:dayId/activities
PATCH /v1/trips/:id/days/:dayId/activities/:actId
DELETE /v1/trips/:id/days/:dayId/activities/:actId
POST /v1/trips/:id/share-links (creates / rotates slug)
DELETE /v1/trips/:id/share-links/:slug
POST /v1/uploads/sign (signed URL for Supabase Storage)

Sync model:

POST /v1/sync/pull { since: cursor } -> { entities: [...], cursor }
POST /v1/sync/push { ops: [{ entity, op, payload, client_ts }] }

The mobile client treats /v1/sync/* as the only non-mutation read-source once a trip is loaded.

ConcernTarget
Cold start to TripList< 1.5 s on a 2022-era mid-range Android.
Activity save (online)< 300 ms p50 to ack from API.
Offline read100% of opened-trip content available offline indefinitely.
Sync replayAll queued ops drain within 30 s of regaining network.
ConflictLast-write-wins per field; never silently drop a queued op.
Crash-free sessions> 99.5% on mobile in beta.
AccessibilityAll flows reachable via screen reader and 200% font scale.
PrivacyA private trip is never fetchable by any other user, even by guess.
  • Q1. Currency rate source. Snapshot per-trip means stale rates on long-running trips. Decision: snapshot at trip create + manual refresh button. Re-evaluate before v1.
  • Q2. Map provider. Options: Mapbox, Google, Apple. Cost + offline caching matter. ADR before implementation.
  • Q3. State management library on Flutter. Riverpod is the working default; ADR if we deviate.
  • R1. Live-mode timing UX needs user testing before we ship. Beta cohort.
  • R2. Soft-delete with 30-day undo doubles row count on heavy churners; mitigate with periodic hard-delete job.
  • Branch. All work on main until first beta build is tagged.
  • Flags. F1.8.c (file attachments), F2.3.c (voice memo), F2.6.b (recap share image) ship behind feature flags off by default.
  • Beta. Internal TestFlight + Play Internal track once AC-1 to AC-9 pass green.
  • GA. When AC-1 to AC-15 pass and crash-free target is met for two consecutive beta builds.