- Before exploring the codebase, check if SESSION_NOTES.md exists and read it first. It contains the current state of the project.
- Prefer searching (grep/glob) over reading entire files. Only read a full file if it is small or you actually need all of it.
- When you need one function or class, read only that section of the file, not the whole file.
- Never read: build output, log files, lock files (package-lock.json, poetry.lock), .git contents, node_modules, pycache, or any file over 1000 lines unless explicitly asked.
- Do not re-read a file you already read this session unless it has changed.
- Keep answers short. No summaries of what you just did unless asked.
- End of session: the user runs /wrapup and you update SESSION_NOTES.md.
- Start of session: the user runs /warmup and you orient using SESSION_NOTES.md.
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
The public companion website for DZSL — a native GTK4 DayZ server browser / mod manager for Linux (the desktop app lives separately at /mnt/Storage1tb/coding/DZSL). This repo is the marketing site and the owner-facing backend: a React SPA plus Cloudflare Pages Functions, deployed to Cloudflare Pages at dayzlinux.com.
# Full stack locally (THE important one): builds, then runs Pages Functions +
# KV on http://localhost:8788 so /api/* returns live data like production.
./start-server.sh
# UI only, hot reload, http://localhost:5173 — but /api/* routes DO NOT run.
./start-server.sh --dev # == npm run dev
npm run build # tsc -b && vite build (app only — see note below)
./deploy.sh # build + wrangler pages deploy --project-name dayzlinuxThere is no test runner and no linter configured — don't go looking for one. Correctness is enforced by TypeScript strict (incl. noUnusedLocals/noUnusedParameters).
npm run build only type-checks/builds the app (src/). The Pages Functions in functions/ are compiled by Cloudflare at deploy time, so a type error there won't surface in npm run build. Always check them separately after editing:
npx tsc --noEmit -p functions/tsconfig.jsonThere are two runtimes: the React SPA (src/) and Cloudflare Pages Functions (functions/api/*). Under plain vite dev (port 5173) the /api/* routes don't exist — the SPA _redirects fallback serves index.html instead, so a fetch to /api/... returns HTML, not JSON. Every client data helper (src/lib/api.ts, auth.ts, owner.ts) defends against this by checking the response content-type and throwing a "run pages:dev" error. When you need live data or auth locally, use ./start-server.sh (port 8788), not --dev.
Server list data flow. functions/api/servers.ts proxies the large (~20 MB) community feed from dayzsalauncher.com, edge-cached (cf: { cacheTtl }). That feed is shaped { result: [{ endpoint: { ip, port }, name, players, mods, ... }] }; src/lib/api.ts#fetchServers unwraps and flattens it into the flat GameServer[] the UI uses. functions/api/stats.ts aggregates totals from the same feed for the header stats bar. The browser intentionally shows only populated servers, busiest-first.
Owner auth + server ownership (the substantial subsystem). Server owners sign in with Steam OpenID 2.0 and claim their servers:
functions/_lib/holds shared backend helpers. The_prefix means Pages does not route these as endpoints; route files underfunctions/api/**import from them. Every function declares its bindings viaEnvfrom_lib/env.ts.- Login:
_lib/steam.tsbuilds the OpenID redirect and, on callback, re-validates the assertion with Steam (check_authentication) before trusting the SteamID. Realm/return_to are derived from the live request origin, so it works on both localhost:8788 and production unchanged. - Sessions: opaque 256-bit random tokens (
_lib/session.ts) stored in KV, set as anHttpOnly; Secure; SameSite=Laxcookie. No passwords are ever handled; only public Steam data (SteamID, persona, avatar) is stored.SameSite=Laxis the CSRF defense — there are no separate CSRF tokens. - Ownership = name-token verification (
_lib/claims.ts,api/claims/*): claiming a server issues aDZSL-XXXXXXXXtoken; the owner pastes it into their live server name;claims/verifyre-reads the feed and confirms it appears, then flips the claim toverified. Only then can they edit/promote the listing (api/listings/[key].ts, owner-checked).
KV is the only datastore (binding DZSL_KV). Key patterns: session:<token>, claim:<ip:port>, listing:<ip:port>, owner:<steamId> (index of an owner's server keys). Listing keys carry a colon, so dynamic routes URL-encode/decode the ip:port.
Frontend wiring. main.tsx wraps the router in <AuthProvider> (src/lib/AuthContext.tsx); useAuth() exposes { user, loading, refresh }. Routing is react-router v7 with a single Layout route (shared header/nav/footer/stats bar) wrapping page routes in App.tsx. The campfire ambience toggle lives in the shared Layout header on purpose, so audio persists across page navigation (procedural + sampled audio in src/lib/campfire.ts).
- KV namespace:
npx wrangler kv namespace create DZSL_KV, then paste itsidintowrangler.toml. Localpages devsimulates KV on disk under.wrangler/regardless of the id. - Steam Web API key (optional — only adds persona name + avatar; login works without it):
npx wrangler pages secret put STEAM_API_KEYfor production, andSTEAM_API_KEY=...in.dev.vars(gitignored; see.dev.vars.example) for local.
Deploys go to the Cloudflare Pages project dayzlinux (custom domain dayzlinux.com, plus dayzlinux.pages.dev). Bindings (DZSL_KV) and compatibility_date come from wrangler.toml. Commit/push and deploy only when explicitly asked.