A jam is a shared-deadline creative challenge: one prompt, a roster of participants, a single due date. Submissions point at records that li
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 2 folders 7 files
README.md

@atjam/web#

TanStack Start app for atjam.

Setup#

pnpm install               # from the repo root, hydrates all workspaces
cp .env.example .env       # then edit
pnpm dev                   # starts on http://127.0.0.1:3000

Use 127.0.0.1, not localhost. The OAuth spec requires a loopback IP for local-dev redirect URIs, and the OAuth helpers in src/lib/oauth.ts only switch into dev mode when window.location.hostname === "127.0.0.1".

src/routeTree.gen.ts is generated by the TanStack Router Vite plugin on first dev/build. It's gitignored. Until you've run pnpm dev (or pnpm build) once, pnpm typecheck will fail with "cannot find module './routeTree.gen'" — this is expected.

Routes#

  • / — home feed of rounds across VITE_KNOWN_ORGANIZERS.
  • /jam/$did/$rkey — jam detail + its rounds.
  • /round/$did/$rkey — round detail: assignment, milestones, signups, submissions.
  • /oauth/callback — OAuth redirect target. Don't navigate here directly.

Authentication#

OAuth is wired via @atcute/oauth-browser-client (PKCE + DPoP, browser-only). The flow:

  1. Header <AuthBar /> renders an input where the user types their handle (alice.bsky.social).
  2. startLogin(handle) resolves the handle → DID → PDS → authorization server, then redirects.
  3. The PDS redirects back to /oauth/callback, which calls completeLogin() (consumes the code, persists the DID in localStorage), then sends the user home.
  4. On every page load, <AuthProvider /> calls resumeAgent() which rehydrates the OAuth session and exposes a typed Client (@atcute/client) for record writes.

Use useAuth() for the auth state, or useSignedInAgent() when a component only renders for signed-in users.

Client metadata#

  • Production: public/oauth-client-metadata.json is served at https://atjam.at/oauth-client-metadata.json and is the client_id the PDS sees.
  • Local dev: uses the loopback client_id pattern (http://localhost?redirect_uri=...&scope=...) — no metadata file needed, the params are inline. This is why the dev mode check is hardcoded to 127.0.0.1.

If you change the canonical hostname (e.g. add a staging deployment), update both public/oauth-client-metadata.json and the production branch of getOAuthMetadata() in src/lib/oauth.ts.

Scope#

The app requests atproto plus write access to all five atjam collections (at.atjam.{jam,round,signup,invitation,submission}). The scope string is derived from NSIDS in @atjam/lexicons so it stays in sync with the lexicons. The metadata file's scope field must be kept manually consistent with this.

Data flow#

Concern Mechanism
List rounds across the network com.atproto.repo.listRecords against each DID in KNOWN_ORGANIZERS (no global indexer in v1).
Get a specific record com.atproto.repo.getRecord against the owning PDS.
Backlinks (signups for a round, submissions for a round, rounds for a jam) Constellation /links (Microcosm).
DID → PDS endpoint plc.directory for did:plc:, .well-known/did.json for did:web:.

Every external read is wrapped in a TanStack Query queryOptions in src/lib/queries.ts, and route loaders call queryClient.ensureQueryData(...) so SSR is uniform.

Open assumptions (will need to verify or change)#

  1. Constellation endpoint shape. [GET /links?target=&collection=&path=&limit=&cursor=] is a guess — wrapped in src/lib/constellation.ts, single point to update.
  2. Write paths still needed. Signup (Step 4), submit (Step 5), and create-round (Step 6) UIs are not yet built — see ../PLAN.md. Auth (Step 3) is in.
  3. KNOWN_ORGANIZERS is a v1 shortcut. Replace with a real Jetstream-backed indexer when the network has more than a handful of organizers.