diff --git a/README.md b/README.md
index 438b8cc..afd1d3f 100644
--- a/README.md
+++ b/README.md
@@ -9,8 +9,15 @@ and every call is a door.

-Drop a zip of any JavaScript or TypeScript repository on the page, or pick a
-folder. Nothing is uploaded: the code is parsed in your browser tab.
+Paste the URL of any public JavaScript or TypeScript repository on GitHub, or
+walk a local folder. The code is parsed in your browser tab, and a folder is
+never uploaded. Once a repository is open, the address bar is its share link,
+for example
+[`?repo=valuecodes/code-backrooms`](https://code-backrooms.valuecodes.fi/?repo=valuecodes/code-backrooms).
+
+A GitHub repository comes through this site's Cloudflare Worker as one zip
+download from GitHub, after a Cloudflare Turnstile check that keeps bots from
+using it as a proxy.
## What you are walking through
@@ -67,17 +74,17 @@ Then open http://localhost:3000.
## How it works
-Everything runs in the browser tab. Code decides which rooms and doors exist.
+Parsing and building run in the browser tab. Code decides which rooms and doors exist.
A seed taken from a hash of the source decides only sizes and placement, so
the same code always gives the same world.
```mermaid
flowchart TD
subgraph load["Load: apps/web"]
- A["Zip, folder, or bundled example"] --> B["Source filter JS/TS files and manifests, size and count caps"]
+ A["GitHub repository, folder, or bundled example"] --> B["Source filter JS/TS files and manifests, size and count caps"]
end
- subgraph analyse["Analyse: Web Worker for a zip or folder"]
+ subgraph analyse["Analyse: Web Worker for a repository or folder"]
B --> C["@repo/parser Babel AST per file"]
C --> D["CodeGraph modules, functions, calls, control-flow trees, imports"]
end
diff --git a/apps/web/AGENTS.md b/apps/web/AGENTS.md
index d49a1e8..0e91fe9 100644
--- a/apps/web/AGENTS.md
+++ b/apps/web/AGENTS.md
@@ -153,8 +153,8 @@ not be laid out`. The HUD reads `2/7 modules · last 14 ms` on the way;
whole world, on `?view=map` too; wider than 600 m the map hides rooms and
area labels and draws zone names larger, each within its band.
- Milestone 20: repository loading (Phase 7). A bare URL opens a
- landing page: drop or pick a `.zip` (GitHub's "Download ZIP" works as
- is), pick a folder, or follow Demo (`?code=self`, this repository's own
+ landing page: drop or pick a `.zip` (removed in Milestone 23), pick a
+ folder, or follow Demo (`?code=self`, this repository's own
sources) or a bundled example. Files are read in the tab and never leave
it, and a reload returns to the landing page. `source-filter.ts` keeps
sources (`.ts .tsx .mts .cts .js .jsx .mjs .cjs`) and manifests
@@ -175,10 +175,10 @@ not be laid out`. The HUD reads `2/7 modules · last 14 ms` on the way;
toward its first doorway and back while its gaze sways
(`landing-backdrop.tsx`, `driftPose` in `backdrop.ts`). It is skipped
without WebGL2 (it runs under `prefers-reduced-motion` too), swallowed by an error
- boundary if the canvas fails, and dropped as soon as a read starts. The
+ boundary if the canvas fails, and darkened while a read runs. The
world view (`world-app.tsx`) and the backdrop are lazy chunks, so the
landing page paints before three.js loads.
-- Milestone 22 (current): a public GitHub repository by URL. The landing
+- Milestone 22: a public GitHub repository by URL. The landing
page takes `github.com/owner/repo`, `owner/repo` or a
`/tree//` URL (`github-url.ts`; refs with `/` are not
supported). One load is one request to GitHub: the page calls this site's
@@ -191,16 +191,22 @@ not be laid out`. The HUD reads `2/7 modules · last 14 ms` on the way;
only once a URL is typed). Each IP is rate-limited to 5 loads a minute.
Answers are edge-cached for 10 minutes, and zips over 64 MB are refused or
cut off. A loaded repository writes `?repo=` so a reload or a shared link
- fills the field in again. The visitor still presses open, because each
- Turnstile token is single-use. Without a site key at build time the field
+ fills the field in again. Without a site key at build time the field
is hidden, so production never falls back to the test key.
+- Milestone 23 (current): GitHub first. The landing page drops the zip
+ drop zone and picker: its card is the GitHub field, with a quiet "walk a
+ local folder" link below (the card's only button when the build has no
+ Turnstile key). A `?repo=` link opens by itself as soon as its check
+ passes, once, unless the field is edited first; the token is spent
+ through a ref so an auto-open and a click cannot send it twice. While a
+ read runs the backdrop stays and darkens over `FADE_MS`, and no read hands
+ over sooner, so the page fades into the loading screen instead of
+ cutting to black.
---
## Workspace Commands
-| Task | Command |
-| ---------- | ------------------------------ |
| Task | Command |
| ---------- | ------------------------------ |
| Dev | `pnpm --filter web dev` |
@@ -224,14 +230,20 @@ Or run repo-wide via root `pnpm typecheck` / `pnpm test` / `pnpm build`.
### Deploying (Cloudflare Workers Builds, repository connected in the dashboard)
-- **Build settings:** set the root directory to the repository root. The build
- command is `pnpm --filter web build`, and the deploy command is
- `pnpm --filter web exec wrangler deploy`.
-- **Build variables:** `PNPM_VERSION=12.4.2` and `NODE_VERSION=24.21.0`, since the
- build image's pnpm default is older. Also set `VITE_TURNSTILE_SITE_KEY` to the
- widget's site key. Vite inlines it at build time, so it is a _build_ variable.
-- **Runtime secret:** `TURNSTILE_SECRET` under the Worker's Variables and Secrets.
- `wrangler.jsonc` declares no value for it, so deploys keep it.
+- **Worker:** `code-backrooms`, the `name` in `wrangler.jsonc`. Workers Builds
+ expects the connected Worker's name to match, and local `wrangler secret` and
+ `wrangler tail` target it without `--name`.
+- **Build settings:** root directory `apps/web`, build command `pnpm run build`,
+ deploy command `npx wrangler deploy`.
+- **Build variables:** `VITE_TURNSTILE_SITE_KEY`, the widget's site key. Vite
+ inlines it at build time, so it is a _build_ variable. Also set
+ `NODE_VERSION=24.21.0` and `PNPM_VERSION=12.4.2`: the version files sit
+ outside the `apps/web` root, and the build image's defaults are older.
+- **Runtime secret:** `TURNSTILE_SECRET` under the Worker's Settings → Variables
+ and Secrets, type Secret, or `pnpm --filter web exec wrangler secret put
+TURNSTILE_SECRET`. Never a build variable: those exist only during the build,
+ and the Worker then answers every GitHub load with 503 "not set up on this
+ server". `wrangler.jsonc` declares no value for it, so deploys keep it.
- **Turnstile widget:** create it in the Cloudflare dashboard. Its hostnames must
include the production domain, plus the `workers.dev` domain if preview builds
are on. A preview without its hostname refuses GitHub loads and everything else
@@ -252,7 +264,7 @@ src/
app.tsx landing page, loading screen, or the world as a lazy chunk
world-app.tsx the world: full-screen