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:
-
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-stopand--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). -
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 AccountMenuthe signed-in rider (avatar, handle, sign out); a ghost sign-in link when signed out AppHeaderthe top bar: brand, a middle slot, actionsButtonvariantprimary / secondary / ghost / link;sizesm / md;icon,iconEnd,iconOnly+label; a link whenhrefis set;pressedCarda ruled box: aselement,paddingnone / sm / md (none lets children own the inner cells and rules);header/footersnippetsBadgevariantneutral / accent / live / outline;colordotAvatarsizesm / md / lg; initials fallbackStatlabel, value, unit (inside a <dl>)SegmentedControlone-of options with optional colour swatches; scrolls sideways Dialognative <dialog>;variantsheet / fullscreenFieldlabel + hint/error wrapper; for,required,hideLabel; thechildrensnippet getsid,describedBy,invalidTextFieldsingle-line input; typetext / email / url / password / search;prefixcell;aftersnippet under the cell (gets the inputid, used byHandleInputfor its list);hint,error,required,disabled,autofocus,autocomplete,spellcheck,inputmode; bindsvalueTextAreamulti-line input; rows(4),maxlengthwith a tabular counter; bindsvalue;hint,error,required,disabledHandleInputATProto handle typeahead with the @prefix cell; bindsvalue;onselect(actor); silent when offlineNoticevariantinfo / error;title;actionssnippetIconnamed stroke icons, filledfor solid; add new ones inIcon.svelte -
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.tsholds 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/:RideMapand 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.jsextensions (SvelteKit 3 subpath imports;$libis gone). HandleInputcalls 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.