- Svelte 54%
- TypeScript 43%
- Docker <1%
- CSS <1%
- HTML <1%
- 1 more language <1%
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:
- Point DNS at the server:
trips.example.com → A <server-ip>(and AAAA if available). - 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.