Adventure motorcycle live tracking and microblogging for the Atmosphere
rider apps web
4 folders · 8 files

.impeccable
scripts
src
static
.gitignore
DESIGN.md
package.json
README.md
tsconfig.json
vite.config.ts
worker-configuration.d.ts
wrangler.jsonc
README.md

web: the SvelteKit app (ride.rtw.run) #

The web Worker from PRODUCT.md §8 App layout. So far it has only the public ride page skeleton, on mock data, at /@rtw.run/kerala2026 (the DID form /did:plc:mockrider/kerala2026 serves the same page).

devenv shell
pnpm install            # at the repo root (pnpm workspace)
pnpm run map-assets     # fonts and sprites from spikes/map (run its scripts/fetch-assets.sh first)
pnpm run dev            # port 5173; tiles.rtw.run allows it in CORS
pnpm run check

How the UI is put together #

Restyle the app in these places, from broadest to narrowest:

  1. src/lib/styles/tokens.css: every colour, type size, space, rule, and layout size. Components use only these custom properties. The map reads --color-route, --color-route-today, --color-stop and --color-live. The look is a rally roadbook: roll-chart paper, heavy black rules, square corners, one rally red for "now"; Saira Extra Condensed for places and numbers, Courier Prime for the rider's words (both self-hosted via @fontsource).

  2. src/lib/ui/: the shared kit, used by every page (ride page now, rider app and dashboard later). Import from #lib/ui/index.js.

    Component Variants and props
    AccountMenu the signed-in rider (avatar, handle, sign out); a ghost sign-in link when signed out
    AppHeader the top bar: brand, a middle slot, actions
    Button variant primary / secondary / ghost / link; size sm / md; icon, iconEnd, iconOnly + label; a link when href is set; pressed
    Card a ruled box: as element, padding none / sm / md (none lets children own the inner cells and rules); header / footer snippets
    Badge variant neutral / accent / live / outline; color dot
    Avatar size sm / md / lg; initials fallback
    Stat label, value, unit (inside a <dl>)
    SegmentedControl one-of options with optional colour swatches; scrolls sideways
    Dialog native <dialog>; variant sheet / fullscreen
    Field label + hint/error wrapper; for, required, hideLabel; the children snippet gets id, describedBy, invalid
    TextField single-line input; type text / email / url / password / search; prefix cell; after snippet under the cell (gets the input id, used by HandleInput for its list); hint, error, required, disabled, autofocus, autocomplete, spellcheck, inputmode; binds value
    TextArea multi-line input; rows (4), maxlength with a tabular counter; binds value; hint, error, required, disabled
    HandleInput ATProto handle typeahead with the @ prefix cell; binds value; onselect(actor); silent when offline
    Notice variant info / error; title; actions snippet
    Icon named stroke icons, filled for solid; add new ones in Icon.svelte
  3. Feature components are built only from the kit and tokens:

    • src/lib/ride/: Timeline (the journal; tracks the stop being read), DayHeader, PostCard (a numbered stop), PhotoViewer, ReplyList, RideSummary; journal.ts holds the order and the stop numbers the map shares; RideView (view.svelte.ts) shares the selected stop, its day and the expanded map between the map and the journal;
    • src/lib/map/: RideMap and the basemap style.

Conventions:

  • Variants are props that set data-* attributes, styled in the component.
  • Components don't set their own outer margins; layouts space them with gap.
  • Imports use #lib/… with .js extensions (SvelteKit 3 subpath imports; $lib is gone).
  • HandleInput calls the ATProto typeahead client through #lib/atproto/typeahead.js (src/lib/atproto/typeahead.ts): no auth or custom headers, results cached per query, and failures are silent.

Sign-in #

/signin is a plain form posting to /oauth/login, so it works with JavaScript off; HandleInput only adds the typeahead. Core (apps/core) redirects to the rider's PDS and back through /oauth/callback, which sets the sid cookie. src/hooks.server.ts turns that cookie into locals.user through src/lib/server/session.ts, and src/routes/+layout.server.ts shares it with every page. AccountMenu puts the rider in the header, posts sign-out to /oauth/signout, and falls back to the DID when Slingshot can't resolve a handle. D1 stores only the DID; handles are resolved when shown. In dev, just dev proxies /oauth/* to core.

Mock data #

src/lib/mock/ride.ts cuts spike 10's route (Kochi → Munnar → Thekkady → Alappuzha) into four days of a live ride (day 3 is a rest day), with 12 posts.

Photos are real Kerala pictures from Wikimedia Commons, hotlinked, with credits shown in the viewer. node scripts/mock-photos.mjs refreshes src/lib/mock/photos.json. The Kumarakom stop uses three synthetic photos generated for the design comp (src/lib/mock/photos/, labelled in the viewer; each has its generation prompt in a .json sidecar).

Tiles come from one dev pack (tiles.rtw.run/dev/south-india.pmtiles) until the India cells exist.