1# website/src/jev 2 3jevstrudel's additions to the website. 4 5- `ReplPage.astro` — the REPL page and its link preview. `pages/index.astro` 6 renders it for the site, and `pages/songs/[theme]/[song].astro` once per 7 song at `/songs/<theme>/<song>/`: that page opens on its song, and a link 8 to it previews with the song's own card. 9- `songs.mjs` — the build-time song index. `ReplPage.astro` calls 10 `loadSongs()` in its frontmatter: Vite globs `songs/*/*/SPEC.md` and 11 `song.js`, Astro compiles each spec to HTML, and the page embeds the list as 12 JSON (`<script id="jev-songs">`). The jev panel 13 (`repl/components/panel/WelcomeTab.jsx`) reads it to list the songs (by theme, most art 14 first), show a spec, and load and play a song. Each song's card shows its title, its 15 duration (read from `song.js`), its README's first paragraph and its 16 `cover.webp`. 17- `client.mjs` — the browser side: reads that JSON back and picks the song 18 the page opens on (the song page's own, or the featured 19 `featuredSongId`, Lightning in a Bottle, unless the URL carries code). It 20 is selected on the jev panel (a song page opens on the song itself), 21 loaded, and plays on the first click (browsers keep audio off until 22 then): a song page whenever it is opened, the home page's featured song 23 once per tab, and neither on a reload. Picking a song 24 puts its address and title in the address bar and tab. 25 26- `jevCore.mjs` — `jev({ state, questions })`: TypeSafe's API as Strudel 27 patterns, in the shape of its JavaScript SDK. Questions are built with 28 the SDK's `choice`, `score` and `noul`; `djJev.every(cycles, { fallback })` 29 asks them all in one request every `cycles` cycles and returns the answers 30 as patterns (`move.choice`, `energy.score`, …) to compose in Strudel; and 31 `walk(answer, { max, sections })` is a song's form as rules in code over 32 one choice. A section's `times` is the most it plays in one performance: 33 a loop is a cycle of sections, so only a total bounds it, and a section 34 that has played its `times` (or leads to an ending only through one that 35 has) is no longer offered. `walk` refuses a section, or a way on, that 36 can never play within the cap (a detour the song would claim and Jev 37 never hear of). The form's history rows also carry what each 38 jev() asked after it settled for that section (`arrangement`: its levels, 39 transitions and energy, less what it `forget`s), so the form's Jev hears 40 what the arrangement built and released. Its header shows the whole 41 shape. `.every` also takes 42 `after: { section }` (asked once another jev on the cadence has 43 answered, told its answer: questions in one request cannot see each 44 other; its requests then also carry where the song is in the other's 45 form, and each history row names the section it was), `sample` (draw a 46 choice from Jev's probabilities at that temperature), `minConfidence` 47 (fall back below it; not on a sampled question, since sampling is what a 48 close call is for), `forget` (questions whose past answers are left out 49 of the history Jev is sent: songs forget `into`, since Jev copied its 50 own "cut") and `allow` (narrow a choice per request from what is 51 decided, e.g. which transitions suit the section being entered; the 52 fallback must stay allowed). History rows that played a fallback say 53 "(fallback)". An answer that lands after its section has begun keeps the 54 section and plays the rest of its answers from the next cycle. Before a 55 song starts, `askOpening` asks each jev() about its opening, so the first 56 section is Jev's call too. `setWrittenParts` records which part (orbit) plays in 57 which cycles as written, from the song queried ahead: a level question 58 named for a part is not asked about a section where that part does not 59 play (it keeps its fallback, marked absent, and the mixer greys it). A question a song reads ahead (`.early(1)`) that sounded 60 its fallback before the answer landed keeps it for that section. Each 61 request also carries `listenersSoFar`, every listener's 🔥/😴 for the 62 sections that matter (the last few played and those that may come next, 63 only names the song's form has; `reactions.mjs`). `setReplay` makes the 64 jev()s evaluated after it play a recorded performance instead of asking 65 Jev: no request at all, and a recorded answer the song's rules no longer 66 allow plays the fallback. Decisions 67 can also come from outside: `followDecisions()` puts the starting song's 68 jev()s in follow mode, where they ask nothing and play what they are 69 handed (`receive(segment, decision)`, one receiver per jev() in 70 declaration order), each value checked against the song like an answer 71 (a section, against the sections its form allows there); 72 a segment with nothing received when it begins plays its fallbacks, and 73 a decision that arrives after that is a late answer. `watchDecisions()` 74 is the other end, every decision the song settles as `shareDecision()` 75 shapes it, and `setReactions()` hands the jev()s reaction totals counted 76 elsewhere (`follow.test.mjs`). `jev.mjs` wires it to the 77 relay and is what the REPL's scope sees (`jev`, `choice`, `score`, `noul`, 78 `walk`). Calls go through this site's relay (`worker/`), which adds the 79 site's own TypeSafe key, because TypeSafe's API refuses calls from web 80 pages; visitors need no key. `jevCore.test.mjs` covers it with a stubbed 81 Jev, and `allSongs.test.mjs` evaluates every song the way the REPL does 82 and plays it to its end against a stand-in Jev, checking each request 83 against the relay's rules (`nix flake check` runs both). 84- `JevPane.jsx` — the pane under the editor: the booth above the mixer, 85 both showing skeletons (8 empty sections, an idle desk) until a song 86 with a `jev()` plays, and beside them Jev's request log. 87- `jevLog.mjs`, `JevLog.jsx` — Jev's request log, in place of popups: 88 every section request (a line when asked, filled in with Jev's answer 89 and its time when it lands) and every ⏭/📻 pick, newest on top, the 90 page's last 200. 91- `ConnectAi.jsx` — how to connect your own AI to the hosted MCP: the 92 jev panel's mcp tab and `/ai/` (`pages/ai.astro`) both render it. 93- `DataTab.jsx` — the jev panel's data tab: everything the site stores, 94 for anyone to read (`worker/src/data.ts`, "What is public" in 95 `worker/README.md`): every account and its page, listeners' songs whole 96 (held ones as text), covers, comments, pitches, Jev's queue, 97 performances with Jev's decision history, reactions, votes, what is 98 live, and the caches, each section saying what it holds and what it 99 withholds (secrets, counted and dated only). Other people's writing is 100 text here: nothing in it plays or shows an image. 101- `profile.mjs`, `Profile.jsx`, `NameLink.jsx` — profiles. Every display 102 name (a song's author, a comment's or pitch's, a tab in "listening now", 103 an account on the data tab) is a `NameLink`, which opens that account's 104 profile in the jev panel, with a back link: its name and when it joined, 105 its public songs (played as any listener song, in the sandbox), its 106 public comments and pitches (and how many wait on Jev or were held), what 107 it played lately, and what its tabs play now, from the lobby. It reads 108 the data tab's account page (`/jev/data/accounts/<id>`). 109- `activity.mjs`, `ActivityTab.jsx` — the jev panel's activity tab: what 110 happened, newest first (`/jev/data/activity`, `worker/src/activity.ts`): 111 songs published and revised with Jev's verdict and score, comments, 112 pitches, and every take with who played it and "▶ replay", thirty at a 113 time with "older". 114 A listener's song plays from it in the sandbox; a site song as its card. 115- `parties.mjs`, `LiveParties.jsx` — the listening tab's live parties 116 (`/jev/parties`): each one's song, people and how long it has run, and 117 "join" for one whose host is listed, which asks the room for its name 118 (`/jev/parties/<id>`) and joins it as a guest (`joinParty`). A party's 119 host is listed when its tab is (the lobby's "show me here", 120 `setListedCheck`), and unticking or ticking it mid-party takes the party 121 off the list or puts it back at once (`listedChanged`); worker/README.md, 122 Listening parties, has why the id and not the name. 123- `History.jsx` — what a listener played (`PlayedList`): the you tab's 124 "recently played" for the signed-in listener and a profile's "played 125 lately", from the account's page. Its own takes (every performance it 126 played signed in), each with its time, its path and "▶ replay", merged 127 newest first with the radio's plays that have no take (`playedLately` in 128 `profile.mjs`); every song plays again as its card plays it. 129- `budget.mjs`, `JevBudget.jsx` — today's Jev budget where calls are made: 130 in the booth's title row, and on each ⏭ pick in the request log 131 (`JevPicks.jsx`). Signed out, calls share the address's allowance. 132- `social.test.mjs` — the plain logic of the five above. 133- `JevBooth.jsx` — Jev made visible while a song plays, at the top of the 134 pane under the editor: a cell per section (each answer, and whether Jev answered, 135 is being asked, or could not be reached; click one for every answer's 136 probabilities and what Jev was told) and 🔥/😴 buttons Jev hears; and 137 (`JevBoothEffects`) each request into the request log and the playing 138 choices lit up in the code. 139 `boothHighlight.mjs` also colours Jev's API in the editor. All of it 140 reads `boothStore.mjs`, which `jev()` writes and each successful 141 evaluation swaps. 142- `reactions.mjs` — every listener's 🔥/😴: each press in the booth is 143 also posted to the site's `/jev/reactions` (`worker/src/listening.ts`), 144 counted per song, section and reaction. The playing song's tally is 145 fetched as each song loads; the song's view on the jev panel shows it per 146 section, and Jev hears it as `listenersSoFar`. 147- `performance.mjs`, `JevPerformance.jsx` — recorded performances. When a 148 song as written ends, or is stopped or replaced after its opening 149 section, the recorder posts what every jev() played, segment by segment 150 (`/jev/performances`), with the SHA-256 of its code, and the booth 151 offers "share this performance": `/songs/<theme>/<song>/?performance=<id>`. 152 Opening that link fetches the performance before the song is evaluated 153 and replays it (`jevCore`'s `setReplay`): the same sections, levels and 154 choices, no Jev calls, and the booth says "replaying a performance" (and 155 that the song has changed since, when its code hash differs). Picking 156 another song ends the replay. The Worker adds who played it (signed in) 157 and when. A listener's song is recorded too, from the sandbox's booth 158 (each jev() apart, rebuilt by `sandboxProtocol.mjs`), and its link is 159 `/?listener=<id>&performance=<id>`: the page fetches the performance and 160 hands it to the frame with the play (`armSandboxReplay`), so the replay 161 runs in the sandbox like the song, never in the page. The stored rows 162 are also Jev's decision history: `nix run .#jev-history -- <song>` 163 (`tools/jev-history/`). 164- `takes.mjs`, `JevTakes.jsx` — Jev's takes on a song's view (a site 165 song's, beside its 🔥/😴 tally, and a listener song's, in 166 `Listeners.jsx`): how Jev moves through the song over its recorded 167 performances (a form map in written order: a line for the song as 168 written, an arc for every other way Jev went, thicker the more often, 169 and per section how often it played, Jev's answer times, how often Jev 170 could not be reached, and the sections it picked next with their shares), 171 and every take, newest first, with who played it and when, its path 172 through the form and "▶ replay", the take's replay link 173 (`performance.mjs`). The written order 174 is the booth's form while the song plays here, else read from the code's 175 `walk()` (`writtenOrder`), else unknown (ALL GREEN builds its form in 176 code), when the sections are placed where Jev plays them and nothing is 177 called "as written". Read from `worker/src/listening.ts`'s 178 `/jev/performances?song=` and `/jev/performances/moves?song=`; shows 179 nothing where there is no Worker or no take. 180- `JevMixer.jsx` — the same answers as a mixing desk, under the booth: a 181 fader per part level (a score, or a choice among out/back/full), a 182 selector knob per choice, a knob per noul, the form's section on an LCD, 183 and live level meters per part and for the master. Controls glide to 184 each section's settings as it begins; a dashed ghost shows the next 185 section's once Jev has answered. 186- `meter.mjs` — the song's loudness as it plays, read off what superdough 187 sends the speakers (after its master limiter, `output.speakers`) and off 188 each orbit's output: songs put every part Jev levels on an orbit 189 named for its question (`.orbit('riff')`), so each part is heard alone. 190 Each request tells Jev how the finished sections measured, in total and 191 per part (`measuredSound`), and the strip's detail view tables it. 192- `measured.mjs` — a song's measured mix as written, recorded in its 193 SPEC.md as `measured:` by `tools/measure/` (one JSON line, with the hash 194 of the `song.js` it measured), whole and per section on the song's 195 `.every(N)` cadence (`sectionCycles`); pages get each section's total only. The art critic, recorded or asked live on 196 unedited code, reads it as `measuredMix` while the hash still matches. 197- `preload.mjs` — a song's samples, loaded before its first bar: the song 198 is queried as written (`asWritten` in `jevCore.mjs`, which asks Jev 199 nothing), and the scheduler's start waits for the samples its first 200 cycles play while the rest load behind them. superdough otherwise drops 201 a note whose sample is still loading. 202- `ask.mjs` — one Jev call through the site's relay; everything below uses it. 203 The relay answers a request it has already answered, byte for byte, 204 within the hour from its cache, and says which in each answer's 205 `Jev-Cache` header (`hit` or `miss`; `CACHE_HEADER`). 206 Each request also tells the relay, in headers it logs and never forwards, 207 how many seconds before its section begins it went out (from the 208 scheduler, via `jev.mjs`) and which attempt it is (worker/README.md, Logs). 209 For a signed-in listener, each answer the relay charged carries 210 `Jev-Budget: used/limit`, and once the day's budget is spent a 429 with 211 `Jev-Budget: spent` and `Retry-After` until 00:00 UTC: calls back off 212 until then (songs play their fallbacks) and say the budget is spent. 213- `account.mjs`, `AccountControl.jsx` — accounts, signed in with a passkey 214 (worker/README.md, Accounts). The header's "🔑 sign in" opens a panel to 215 sign in with a passkey or make an account (a display name, and a passkey 216 on this device); signed in, it shows the name, today's Jev calls against 217 the daily budget, "add a passkey" (another device) and "sign out". 218 `account.mjs` asks the Worker who is signed in (`/jev/auth/me`) and runs 219 the ceremonies with `@simplewebauthn/browser`; the session is an HttpOnly 220 cookie the page never reads. The panel also lists the AI apps connected 221 through the hosted MCP (`listApps`, `/jev/me/apps`), each with a 222 "disconnect". Nothing else needs an account. 223- `myTabs.mjs` — the hosted MCP's end in the page (worker/README.md, The 224 hosted MCP; `/ai/` explains it to visitors): while someone is signed in, 225 the tab joins that account's own hub over `/jev/me/tabs`, under the same 226 session id as the dev hub's, and answers what the listener's own AI asks: 227 play, stop, the editor's code, the sandbox's logs since the last play, and 228 status. What the AI plays is foreign code (`{ ai: app }`) and plays only 229 in the sandbox; `playForeign` answers once that play has started or 230 failed. The jev panel's mcp tab says so, with the tab's id, while it is joined. 231- `tabTools.mjs`, `tabCommands.mjs` — the tools both MCPs share 232 (worker/README.md, The tools both MCPs share), answered by the tab: the 233 sounds tab's sounds, Jev's pick of a sound (`soundPick.mjs`) and of a song 234 (the mood picker), the settings tab's allowed settings 235 (`worker/src/mcp-settings.mjs`), the console tab's lines, a song played by 236 id as its card plays it, and a 🔥/😴 (`press.mjs`, which the booth's 237 buttons press too). `tabTools.mjs` takes the page's modules as `deps`, so 238 it runs in vitest; `tabCommands.mjs` supplies the real ones, loaded on the 239 first such command by `myTabs.mjs` and by the dev hub's tab 240 (`repl/useWebSocketMCP.jsx`). 241- `sounds.mjs` — the sounds tab's sounds as data: which sub-tab each is 242 under (`inCategory`, which the sounds tab filters with), its variants, and 243 how a song plays it (a drum machine's `<machine>_<sound>` as a bank). 244- `soundPick.mjs` — Jev choosing a sound for a description: one Choice 245 among up to 255 names; past that, banks of at most 255 (several to a 246 request), then one question among each bank's three likeliest, as Krug's 247 JevLM does. Asked through `ask.mjs`, so charged as the page's own calls. 248- `reference.mjs` — the reference tab's functions out of jsdoc's 249 `doc.json` (`referenceFunctions`, which the tab renders), and as text for 250 the MCP's `api_reference` (`referenceData`, `pages/jev/reference.json.js`). 251 jev's own entries (`jev`, `choice`, `score`, `noul`, `walk`, tagged 252 `jev`) are the jsdoc in `jevCore.mjs`, which `package.json`'s 253 `jsdoc-json` reads beside `packages/`; `reference.test.mjs` checks every 254 name in the REPL scope has one and plays each example, and the hosted 255 MCP's primer song, against a stand-in Jev (`playThrough.mjs`, shared with 256 `allSongs.test.mjs`). 257- `radioHistory.mjs` — a signed-in listener's radio history, kept by the 258 Worker (`/jev/me/radio`): the radio records each song it starts, and 259 `picker.mjs`'s `notRecent` keeps the most recently played (on any device) 260 out of Jev's choices, leaving at least three. Signed out, the radio 261 remembers only the tab, as before. 262- `SongListening.jsx` — a song's page, its listening: 👥 listen together, who 263 has the song open now and how far through (`WhoIsListening` narrowed to 264 the song), and the live parties on it to join (`LiveParties` narrowed 265 likewise). The page puts comments first, then this, then the song. 266- `party.mjs`, `ListenTogether.jsx` — listening parties. "👥 listen 267 together" on a song's view in the jev panel starts one and gives a link, 268 `/songs/<theme>/<song>/?party=<room>`; whoever opens it hears the same 269 song with the same Jev decisions, in time with the host, joining 270 mid-song at the cycle the host is on. The host's page is the only one 271 that asks Jev: it sends each decision its jev()s settle 272 (`watchDecisions`) through the party's room, a Durable Object in the 273 Worker (`worker/README.md`, Listening parties, has the protocol), and a 274 guest's jev()s follow them (`followDecisions`). Each page estimates the 275 room's clock from a few pings; the host sends when its scheduler started, 276 and a guest's scheduler starts at the host's cycle now (the cyclist's 277 `lastEnd`). 🔥/😴 from anyone go to the room, which tells everyone the 278 totals, so the host's Jev hears every listener; the booth shows how many 279 are in. A guest follows the host from song to song (the radio's picks 280 included; a guest's own radio stays off) and stops when the host does. 281 The host's tab keeps the party's key in `sessionStorage`, so a reload 282 rejoins as host. 283- `lobby.mjs`, `Lobby.jsx` — who is listening now (worker/README.md, The 284 lobby). Every page joins the site's lobby and says what its tab plays: 285 a site song (and whether edited), a listener's song, or its own code; 286 playing or not, the playhead and tempo, the song's end once known (or 287 the most it can last while Jev walks its form) and the section. The jev 288 tab's "👥 listening now" lists everyone else's tabs grouped by who (an 289 account's display name, or "a listener" when not signed in; the Worker 290 says which, never the page), with each song's progress moving at its 291 tempo, "🎧 listen along" for a site song that is playing, "play it" for 292 one that is not, and "open it" for a listener's song. The header's 👥 is 293 how many pages are open and opens the list. A tab is listed by default 294 (the user asked for a more social site, 2026-09-27); "show me here" 295 hides it, kept per browser. Listening along is a listening party: the 296 tab asked starts one on its song (or passes on the one it is in) and 297 its room reaches the asker only, whose page joins as a guest 298 (`joinParty` in `party.mjs`) and plays in sync with the host's Jev. 299- `picker.mjs`, `JevPicks.jsx`, `nextTrack.mjs` — Jev beyond the booth: 300 the jev panel's mood picker ("how do you feel?"); beside play, "⏭ next" 301 (Jev picks another song now) and "autoplay" (when a song ends, Jev picks 302 the next; the jev panel's 📻 toggle is the same setting, kept per browser); 303 and "🎨 is it art?" in the booth's title row, which asks the art critic about 304 whatever is in the editor. Next and autoplay share one path 305 (`nextTrack.mjs`): Jev hears what the listener played (signed in, the 306 account's radio history, whose most recent songs it is not offered, and 307 each pick is added to it; signed out, this tab's), and when the editor 308 holds no site song as written it picks from that history alone. A 309 party's guest has neither: the host picks. 310- `agree.mjs`, `AgreeWithJev.jsx` — the jev panel's "do you agree with 311 Jev?": pick the more-art of two songs, then see the critic's scores and 312 how often you agree with it. Your tally stays in the browser 313 (localStorage), counted against the critic's current scores; each vote 314 is also posted, fire and forget, to the site's `/jev/votes` 315 (`worker/src/votes.ts`), which keeps only a count per pair and pick. 316 "Everyone's votes" (`VoteResults.jsx`, read from `/jev/votes/summary` 317 once opened) ranks the songs as listeners do, pair by pair (a 318 Bradley–Terry fit, `bradleyTerry` in `agree.mjs`, each song starting 319 from one win and one loss against an average song so a few votes move 320 it little), beside Jev's rank by art, with the overall agreement and the 321 pairs where listeners outvote Jev; every figure carries its count, and a 322 song with fewer than 5 votes is faded. The agreement report is 323 `agreementReport`, which `nix run .#agreement` (`tools/agreement/`) 324 prints too. 325- `critic.mjs` — the art critic's question, and how it reads a song's spec 326 and code: one source for every "N% art", recorded (`tools/critic/`, the 327 mean of several runs, written into SPEC.md by its `setRevisionArt` and 328 `setCritic`) or asked live (one run). The rubric itself lives in 329 `worker/src/art-rubric.mjs`, re-exported here, because the Worker scores 330 listeners' songs with the same questions. 331- `listeners.mjs`, `Listeners.jsx` — listeners' content 332 (`worker/src/content.ts`; worker/README.md, Listeners' content). The jev 333 tab's "listeners" section lists public listener songs as cards, most art 334 first; a card fetches the song whole and plays it, and its view (jev › 335 listeners › title) shows its cover, author, art, revisions and spec as 336 plain text. Signed in: "publish the editor's code as a song", and on your 337 own song's view a new revision from the editor or a cover. Every song's 338 view (the site's and listeners') has comments, and the section has 339 pitches for Jev songs, most voted first or newest: signed in, ▲ votes 340 for one (once per account, pressed again to take it back; who voted is 341 public, on the data tab), and a pitch that became a song links to it. 342 The song's maker says so, never the pitch's author: a site song with 343 `pitch:` in its SPEC.md, a listener song from "answers a pitch" on its 344 own view (`pitches.mjs`). "What you wrote" lists your items as public, held 345 (Jev's verdict, only you see it) or pending (why, and when Jev is asked 346 again), asking again every 20 s while anything waits. A listener song's 347 address is `/?listener=<id>` (it has no page of its own; `client.mjs`'s 348 `showSong` writes it and `useReplContext` loads it, without playing). A 349 listener's song is untrusted code and plays only in the sandbox below, 350 never in this page. 351- `sandbox.mjs`, `sandbox/player.mjs`, `sandboxProtocol.mjs`, 352 `sandboxPolicy.mjs`, `SandboxNotice.jsx` — the isolation boundary for 353 untrusted code (worker/README.md, "A listener's song runs in a sandbox"). 354 A listener's song, and code arriving in a link, play in an iframe with an 355 opaque origin (`sandbox="allow-scripts"`, no `allow-same-origin`) under a 356 strict CSP, so their code has no cookies, no storage of the site's, no way 357 into this page, and its Jev calls reach the relay without the visitor's 358 session. `sandbox.mjs` is the page's end: it tracks each code's 359 **provenance** (foreign vs the page's own; `guardEditor` makes the editor 360 play foreign code only in the frame, and the page's own scheduler refuse 361 to start while it holds foreign code), owns the frame, and forwards the 362 frame's Jev calls to the relay: with `credentials: 'omit'` for a 363 listener's song or a link's code, and with the listener's own session for 364 code their own AI played (charged to their budget; the frame never holds 365 the session). Plays are numbered (`run`) so the page knows which play a 366 state is about. 367 `sandbox/player.mjs` is a Strudel player with nothing of the page's, run 368 inside the frame. `sandboxProtocol.mjs` rebuilds every `postMessage` from 369 typed, bounded fields — nothing from the frame is evaluated or rendered as 370 HTML. `sandboxPolicy.mjs` is the one source of the CSP and CORS, for the 371 meta tag, the deploy's `_headers`, and the dev server. `SandboxNotice.jsx` 372 is the line above the editor while its code is someone else's. The site's 373 own songs are first-party and keep running in the page (visuals, parties, 374 performance recording); only untrusted code is sandboxed. 375- `releases.mjs`, `UpdateModal.jsx` — release notes from the repo's 376 `RELEASES.md`, every release a heading that expands: when a new deploy 377 is waiting, a modal shows what changed and an "update now" button reloads 378 into it (`src/pwa.ts`); after a return visit, what is new since; and the 379 page's version at the jev panel's foot (`ReleaseButton`) opens them all, 380 the newest expanded. 381- `site.mjs` — the site's name, tagline, URL, accent colour and 382 description: the tab title, the app manifest, and link previews. 383- `og.mjs` — the link-preview images, 1200×630, drawn at build time: the 384 site's (`/og.png`, nine covers beside the name) and each song's 385 (`/og/<theme>/<song>.png`, its cover beside its title, "N% art" and 386 length). 387 388`pages/jev/songs.json.js` builds `/jev/songs.json`, the site's song ids, 389which the Worker checks each vote against. `pages/jev/catalog.json.js` and 390`pages/jev/catalog/[theme]/[song].json.js` build the site's songs as the 391hosted MCP lists them and reads each whole (spec and code; 392`worker/src/catalog.ts`), and `pages/jev/reference.json.js` the reference 393tab's functions for `api_reference` (`worker/src/reference.ts`). 394`pages/ai.astro` is `/ai/`, "Make songs with your AI": how to connect an 395MCP client, and the tools, read from `worker/src/hosted-tools.ts`. 396 397`pages/jev-samples/strudel.json.js` beside it builds the sample map songs load 398(`samples('jev-samples/strudel.json')`) from the repo's `samples/` folder, 399with `sampleMap.mjs`, the builder `tools/worktree-samples/` also uses to 400give headless tools a worktree's own samples.