Skip to content

One-command local demo, plus the fixes found running it - #1

Merged
xuckless merged 6 commits into
mainfrom
demo-mode
Nov 7, 2026
Merged

xuckless merged 6 commits into
mainfrom
demo-mode

Conversation

@xuckless

@xuckless xuckless commented Nov 7, 2026

Copy link
Copy Markdown
Owner

Makes Tranzit runnable as a local demo with one command, and fixes the failures found while running it end to end on an Android emulator.

Run it

./demo.sh up            # Postgres, the Go services, OTP and live Calgary data (first run ~15 min)
./demo.sh app android   # or ios
./demo.sh drive android # play a GPS track into the emulator during a journey

No accounts or API keys: demo mode skips sign-in, place search falls back to Photon, and the map falls back to OpenFreeMap.

What changed

Demo stack (demo.sh, demo/)

  • One compose file: PostGIS, the four Go services in demo mode, OTP (the graph is built on first start), and rt-feed. rt-feed proxies Calgary's GTFS-RT feeds and replays a recording when they're unreachable.
  • A seeded demo rider with saved places and a week of planner events.
  • GPS playback for drive, and the stats dashboard via up --stats.

Services

  • DEMO_USER_ID demo mode.
  • AUTH_ENFORCE now defaults to true, with ownership checks on posts and friendships.
  • New report endpoints (route_reports) and an alerts endpoint that turns Calgary's HTML alerts into readable text.
  • Photon fallback for place search.
  • route-service corrects for OTP reading Calgary time with a different UTC offset; trips were being planned an hour in the past.
  • route-save keeps a trip for 15 minutes after the socket drops, so reconnect and restore work.
  • user_calendar upserts on its natural key.
  • A CI workflow at the repo root.

App

  • Planner:
    • week and month views
    • overlapping events side by side
    • multi-day events on every day they cover
    • repeating events
    • repeating device-calendar events imported correctly
    • offline deletes that stick
  • Routes and map:
    • stale routes cleared on a new destination
    • pre-search prompt, friendly errors with Retry, and request timeouts
    • correct "fastest" ranking
    • safe areas
    • alerts banner and sheet
    • "live data unavailable" indicator
  • Commute:
    • an arrival card when the trip completes
    • ticking countdowns
    • background location
    • real community reports
    • fixed duplicate Live Activities and lost boarding answers
  • Sign-out confirms first and clears local data and notifications.
  • SQLite "database is locked" errors are fixed.
  • Keyless map tiles.

Checked

  • go vet and go test pass in all four services and rt-feed; tsc, bun test (52 tests) and expo lint (0 errors) pass in the app.
  • On a Pixel 7 emulator against the local stack:
    • every tab
    • search, then routes, then starting a journey
    • kill and relaunch mid-trip (the trip is restored)
    • GPS playback to the destination, ending on the arrival card
    • planner Day, Week and Month views
    • the alerts sheet and Settings

Not checked

  • iOS (built on Linux): the iOS Simulator path, Live Activities and iOS background location.
  • A transit trip end to end. Testing ran at 1 AM Calgary time, when only walking routes were available.
  • The offline fallback ships without a recording. Run ./demo.sh record 30 once during the day to make one.

…less place search

- DEMO_USER_ID puts db-middleware, route-service and route-save in demo mode:
  every request is that user and Supabase is never contacted.
- AUTH_ENFORCE now defaults to true. Ownership checks cover user_posts,
  user_friendships (requester/addressee) and posts (via the event's owner).
- New route_reports table plus /api/v1/reports/summary and /reports/recent.
- /api/v1/alerts returns active GTFS-RT alerts in an app-friendly shape,
  turning Calgary's HTML descriptions and data-effect markers into text and
  headers. rt-service now keeps every informed route/stop, not just the first.
- route-service falls back to Photon (OpenStreetMap) for search, autocomplete,
  details and nearby when GMAPS_API_KEY is empty.
- route-service corrects for OTP reading Calgary wall-clock time with a
  different UTC offset (plan.date is compared with the intended instant).
- route-save keeps a session for 15 minutes after its socket drops, so the
  app can reconnect and RESUME instead of losing the trip.
- user_calendar upserts on its (user_id, event_id) natural key.
- rt-service feed URLs are overridable; APP_TOKEN is optional.
- Root CI workflow: go vet/test for each service, tsc, lint and bun test
  for the app.
- Day / Week / Month switch with a Today button; arrows step by the view.
- Overlapping events sit side by side; multi-day events show on every day
  they cover; the all-day strip scrolls; cards only show the lines that fit.
- Repeat picker in add-trip (daily, weekdays, weekly, monthly, optional end
  date) stored as an RRULE in recurrence_rule and expanded on the timeline.
- Device-calendar imports keep every occurrence of a repeating event and
  drop events that were removed from the device.
- Offline deletes of events and saved places are replayed instead of the
  row coming back on the next sync; permanent 4xx failures stop retrying.
- user_calendar is upserted in place instead of delete-then-insert.
- bun tests for lane layout, recurrence and day clipping.
- A new destination clears the previous routes; cached routes only come back
  for the destination they were planned for.
- 'Tap Find Routes' before a search, friendly errors with Retry, and a prompt
  when there is no origin (location denied or unavailable).
- 15 s timeouts on every request, with typed errors for offline / timeout /
  HTTP status; search shows 'Search unavailable' with Retry.
- 'Fastest' ranks by the true arrival time including the final walk.
- Header and tab bar use safe-area insets; the route map fits between the
  search card and the sheet; the active chip is readable.
- Stop and bus panels keep data while refreshing and show errors with Retry.
- The map shows 'Live data unavailable' when the vehicle stream is down and
  an alerts chip that lists active service alerts; Routes shows an alert
  banner for the lines in view.
- Accessibility labels and larger hit areas on icon buttons.
- Trips end with a 'You've arrived' card when Route Save reports COMPLETED
  or the rider comes within 40 m of the destination; teardown stops the Live
  Activity / Android notification, background location and the socket.
- Countdowns and 'Leave by' tick every 15 s; past the time it reads
  'Leave · Now'. 'Offline · retrying' with a retry action after 10 s
  without a socket.
- Background location (expo-task-manager) while a trip is active, with an
  explanation before asking and an Android foreground-service notification.
- iOS Live Activities are no longer duplicated on every tab return.
- 'I boarded / I missed it' answers are queued and replayed on reconnect;
  a reconnect race that opened two sockets is fixed.
- Community reports are stored in route_reports and the thank-you uses the
  real recent count; the hardcoded '1,240 commuters' and the dead 'Thank a
  Driver' button are gone. The Report tab lists recent reports.
- Service-alert banner for the trip's lines; 12-hour times; the sheet opens
  at half height; 'Plan a trip' button on the empty state.
- EXPO_PUBLIC_DEMO_MODE=true skips sign-in: a fixed demo rider, the token
  'demo' on every backend call, and service URLs defaulting to localhost.
- Map tiles fall back to the keyless OpenFreeMap styles when no MapTiler key
  is set.
- Sign-out asks first (warning guests that their data can't be recovered)
  and clears the local SQLite mirror, scheduled notifications and any trip
  tracking.
- SQLite transactions are queued on the main connection with a busy
  timeout, fixing 'database is locked' errors from concurrent writes.
- Settings' Notifications and Help rows open system settings and the issue
  tracker; the background-location task is registered at startup.
- DOCS.md updated for the planner, outboxes and commute behaviour.
- demo/compose.yml runs PostGIS (GTFS + user data), the four Go services in
  demo mode, OpenTripPlanner and rt-feed on this machine, with no Tailscale,
  mTLS or Supabase. Containers are prefixed tranzit-demo- so they never clash
  with a real deployment.
- demo/otp builds the Calgary graph on first start into a cached volume.
- demo/rt-feed proxies Calgary's GTFS-RT feeds, records them on request
  (./demo.sh record), and replays the recording time-shifted to now when the
  live feed is unreachable.
- demo/userdb seeds a demo rider with saved places and a week of planner
  events, refreshed relative to today on every ./demo.sh up.
- ./demo.sh app android|ios builds and launches the app in demo mode;
  ./demo.sh drive plays a GPS track (demo/gps, generated from OTP itineraries
  by make-track.py) into the emulator or simulator.
- ./demo.sh up --stats starts the ops dashboard against the demo stack.
- README: demo quickstart; the false 'rebuilt weekly in CI' claim is gone.
@xuckless
xuckless merged commit 12de53e into main Nov 7, 2026
10 checks passed
@xuckless
xuckless deleted the demo-mode branch November 7, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant