For agents, on top of README.md, which they read first.

Notes for agents

Moving to newer Strudel is a rebase, not a merge. git fetch strudel main && git branch -f strudel-upstream strudel/main, rebase strudel-llm-rebased onto it, then rebase jevstrudel's own commits (git rebase --onto strudel-llm-rebased <old strudel-llm-rebased> jevstrudel). The fork's Header.jsx changes live in website/src/repl/components/panel/Panel.jsx since upstream deleted that file (2026-09-24). After it, rehash pnpm (nix/CLAUDE.md).

Never push to strudel or strudel-llm. Their push URLs are set to NO_PUSH on purpose.

Songs load samples from one relative URL, jev-samples/strudel.json. The site generates it (website/src/pages/jev-samples/strudel.json.js), live in the dev server. Never point a song at an absolute or localhost URL again — the static site has no sampler (the separate one on :5432 was removed 2026-09-24).

Everything jevstrudel adds lives in the jev panel, on the left (LeftPanel in website/src/repl/components/panel/Panel.jsx: songs, activity, listening, you, mcp, data; any display name opens that account's profile there, website/src/jev/NameLink.jsx), except Jev at work on the playing song, which is the pane under the editor (website/src/jev/JevPane.jsx: the booth and the mixer, skeletons until a song plays); the header and the right panel are Strudel's, except ⏭ next and autoplay beside play. The mcp tab and /ai/ render one guide, website/src/jev/ConnectAi.jsx; edit it there. Its data tab shows everything the site stores, secrets aside (website/src/jev/DataTab.jsx, worker/src/data.ts): a new store or table joins it. Its songs tab (the welcome tab) is the song browser. website/src/jev/songs.mjs indexes songs/*/*/ at build time; a new song appears in the dev server on reload, and on the public site after a deploy.

Claude plays through the dev server. .mcp.json runs nix run .#mcp, a stateless stdio proxy (tools/mcp/) to the MCP hub in the jevstrudel Worker, which buck2 run //:dev runs under wrangler dev (worker/). Tool edits in worker/src reach Claude Code by themselves (tools/list_changed). Starting the dev server is Claude's job: if a strudel tool says it is not running, run nix develop -c buck2 run //:dev as a background task and read its output through the task — not detached, not redirected to a file (the user, 2026-09-24). It stops with the session; the next session starts it again. Nothing else needs restarting. Tools take an optional session_id, needed only when several tabs are open.

Never git stash or checkout files in the main tree while the dev server runs. Vite can cache a module mid-swap and keep serving it after the file is restored: the page then fails to hydrate ("does not provide an export named …") on every load until the file is touched (2026-09-25). Compare against HEAD with git show HEAD:path instead.

.mcp.json is read only at startup. It has one server, strudel, the stdio proxy; keep it stdio, since an SSE or HTTP entry fails for the whole session if the server isn't up when Claude Code starts (2026-09-24). The proxy talks to the local dev server by default; its use_environment tool switches it to the public site's hosted MCP (prod) and back (dev), and the tool list changes to match. The first switch to prod in a session opens the site's passkey sign-in and consent in the user's Windows browser and waits for it; prod's tools reach only the user's own signed-in tabs on the public site, in the sandbox, and publish under their name. The sign-in lives in the proxy's memory only and is revoked when the session ends (tools/mcp/).

The site is on 4322 (website/astro.config.mjs), the Worker on 4323 (worker/wrangler.json). 8080, upstream's default, is Gerrit on this host. The dev server serves from /, not /strudel/, and its sample map points at /@fs/… paths; that is Vite serving the working tree, not a bug.

A missing sample is silent. A sound whose file 404s just doesn't play, with no error back through MCP. Check that curl localhost:4322/jev-samples/strudel.json lists local sounds, and that dirt-samples names exist (gh api repos/tidalcycles/Dirt-Samples/contents) — oh does not, 808oh does (2026-09-24).

Check songs in a silent headless tab, never the user's. Open http://localhost:4322/?session=probe headless (Playwright) and pass session_id: 'probe': ?session= pins a tab's id across reloads, and the user's tabs have random ids that play out loud. get_logs then shows the song's warnings; raising the tempo in setcps runs a whole song in seconds, since warnings depend on the patterns, not the speed.

play_code stops before it plays, so a song whose sections are absolute-cycle masks always starts at cycle 0, and it returns the evaluation error if the code fails.

Every deploy gets a release note first. Add a ## YYYY-MM-DD.N — title entry at the top of RELEASES.md, a line per change a listener would notice; the site shows it in its update prompt, and nix run .#deploy refuses when the newest note is already live (tools/deploy/release-check.mjs).

Listen on localhost before deploying. The user hears changes on the dev server with their own key and says when to nix run .#deploy; the public site has listeners.

Songs go in songs/<theme>/<song>/, spec first. See songs/CLAUDE.md. Don't repeat the theme in the song folder name (songs/jev/typesafe-jungle, not songs/jev/jev-typesafe-jungle).

Review a song with the song-review workflow (.claude/workflows/song-review.js) before the user listens: Workflow({ name: 'song-review', args: { song: 'jev/<song>', apply: false } }). Four adversarial lenses (concept and facts, music and mix, code and form, Jev and cost), each finding verified by a skeptic, then a ranked plan; apply: true makes the fixes spec-first on a worktree branch. Its checklists hold what each song so far got wrong: add a lesson there when a review pays for one.

Parallel song agents: one writer for shared state. Forks in worktrees make a song each and report their samples/README.md rows; the parent merges, records the rows, updates songs/jev/README.md, and is the only one that plays in the browser (one session, one song at a time). Give every fork a sample-name prefix so new sounds cannot collide. The worktree command guard refuses nix develop -c bash, runtime-built commands and the literal word "eval" — forks run speak/vocode from nix build .#speak .#vocode store paths via script files (2026-09-24).