Manage your trips and save them privately on your pds

README.md

Triposphere #

Local-first, offline-first PWA for trip itineraries. Your trip data lives in private atproto Spaces should you choose to sync.

  • Trips can have multiple stops. Each stop can have different modes of transport, stay and interesting locations.
  • Map links open in google maps, PDFs can be uploaded for different stops.
  • One trip equals one atproto Space, to allow sharing different trips with different people.
  • Invite others to your trip via handle + QR code.
  • Pure SPA PWA for ease/speed of deployment.

Stack #

SvelteKit 2 (adapter-static) + Svelte 5 runes + TypeScript + Tailwind 4 + Vite PWA + Dexie (IndexedDB) + Phosphor Icons.

Dev #

bun install
bun run dev        # http://localhost:5173
bun run check      # svelte-check
bun run test       # vitest unit tests (11 passing)
bun run build      # static output in build/
bun run smoke      # build + preview + route/PWA assertions
bun run lexicons:validate
bun run spaces:probe [pdsUrl]
bun run icons:generate  # rasterize PWA PNGs from static/icons/icon.svg

CI (.github/workflows/ci.yml) runs check + test + lexicons + probe + smoke on every push/PR, then builds and pushes the Docker image to GHCR on main.

Publishing images #

No code changes needed — images can be published from anywhere:

echo "$CR_PAT" | docker login ghcr.io -u <github-username> --password-stdin
docker build -t ghcr.io/<owner>/triposphere:latest .
docker push ghcr.io/<owner>/triposphere:latest

For a one-off multi-arch publish from a laptop (so both Intel/AMD and ARM machines can pull it):

docker buildx build --platform linux/amd64,linux/arm64 \
  -t ghcr.io/<owner>/triposphere:latest --push .

For CI pushes, add repo secret CR_PAT (classic PAT with packages:write, or fine-grained token with packages write for the owner). The workflow uses it and falls back to GITHUB_TOKEN on github.com. Code does not need to live on GitHub — the same steps run from a laptop, Tangled knot, or any CI with the token stored as a secret.

Self-host (Docker) #

docker compose up --build -d   # http://localhost:4173
# or: docker build -t triposphere . && docker run -p 4173:80 triposphere

The container serves static files only via nginx (SPA fallback). It never sees trip data. Users bring their own Bluesky handle + spaces-capable PDS.

Custom domain (HTTPS) #

PWA install + atproto OAuth both need a public https:// origin. On any server with ports 80/443 open:

  1. Point DNS at the server: trips.example.com → A <server-ip> (and AAAA if available).
  2. Run: DOMAIN=trips.example.com docker compose -f docker-compose.prod.yml up -d --build

That's it — Caddy gets a Let's Encrypt cert automatically and proxies to the app. The app container picks up APP_HOST at startup and writes it into oauth-client-metadata.json, so atproto login works with no rebuild. Verify with: curl https://trips.example.com/oauth-client-metadata.json.

Local dev stays on docker compose up (plain HTTP on :4173); only the public domain needs the prod file.