jevstrudel.git / website / src / jev / README.md
README.mdpreviewREADME.mdsource400 lines · 26.8 KB · raw
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.