website/src/jev
jevstrudel's additions to the website.
-
ReplPage.astro— the REPL page and its link preview.pages/index.astrorenders it for the site, andpages/songs/[theme]/[song].astroonce per song at/songs/<theme>/<song>/: that page opens on its song, and a link to it previews with the song's own card. -
songs.mjs— the build-time song index.ReplPage.astrocallsloadSongs()in its frontmatter: Vite globssongs/*/*/SPEC.mdandsong.js, Astro compiles each spec to HTML, and the page embeds the list as JSON (<script id="jev-songs">). The jev panel (repl/components/panel/WelcomeTab.jsx) reads it to list the songs (by theme, most art first), show a spec, and load and play a song. Each song's card shows its title, its duration (read fromsong.js), its README's first paragraph and itscover.webp. -
client.mjs— the browser side: reads that JSON back and picks the song the page opens on (the song page's own, or the featuredfeaturedSongId, Lightning in a Bottle, unless the URL carries code). It is selected on the jev panel (a song page opens on the song itself), loaded, and plays on the first click (browsers keep audio off until then): a song page whenever it is opened, the home page's featured song once per tab, and neither on a reload. Picking a song puts its address and title in the address bar and tab. -
jevCore.mjs—jev({ state, questions }): TypeSafe's API as Strudel patterns, in the shape of its JavaScript SDK. Questions are built with the SDK'schoice,scoreandnoul;djJev.every(cycles, { fallback })asks them all in one request everycyclescycles and returns the answers as patterns (move.choice,energy.score, …) to compose in Strudel; andwalk(answer, { max, sections })is a song's form as rules in code over one choice. A section'stimesis the most it plays in one performance: a loop is a cycle of sections, so only a total bounds it, and a section that has played itstimes(or leads to an ending only through one that has) is no longer offered.walkrefuses a section, or a way on, that can never play within the cap (a detour the song would claim and Jev never hear of). The form's history rows also carry what each jev() asked after it settled for that section (arrangement: its levels, transitions and energy, less what itforgets), so the form's Jev hears what the arrangement built and released. Its header shows the whole shape..everyalso takesafter: { section }(asked once another jev on the cadence has answered, told its answer: questions in one request cannot see each other; its requests then also carry where the song is in the other's form, and each history row names the section it was),sample(draw a choice from Jev's probabilities at that temperature),minConfidence(fall back below it; not on a sampled question, since sampling is what a close call is for),forget(questions whose past answers are left out of the history Jev is sent: songs forgetinto, since Jev copied its own "cut") andallow(narrow a choice per request from what is decided, e.g. which transitions suit the section being entered; the fallback must stay allowed). History rows that played a fallback say "(fallback)". An answer that lands after its section has begun keeps the section and plays the rest of its answers from the next cycle. Before a song starts,askOpeningasks each jev() about its opening, so the first section is Jev's call too.setWrittenPartsrecords which part (orbit) plays in which cycles as written, from the song queried ahead: a level question named for a part is not asked about a section where that part does not play (it keeps its fallback, marked absent, and the mixer greys it). A question a song reads ahead (.early(1)) that sounded its fallback before the answer landed keeps it for that section. Each request also carrieslistenersSoFar, every listener's 🔥/😴 for the sections that matter (the last few played and those that may come next, only names the song's form has;reactions.mjs).setReplaymakes the jev()s evaluated after it play a recorded performance instead of asking Jev: no request at all, and a recorded answer the song's rules no longer allow plays the fallback. Decisions can also come from outside:followDecisions()puts the starting song's jev()s in follow mode, where they ask nothing and play what they are handed (receive(segment, decision), one receiver per jev() in declaration order), each value checked against the song like an answer (a section, against the sections its form allows there); a segment with nothing received when it begins plays its fallbacks, and a decision that arrives after that is a late answer.watchDecisions()is the other end, every decision the song settles asshareDecision()shapes it, andsetReactions()hands the jev()s reaction totals counted elsewhere (follow.test.mjs).jev.mjswires it to the relay and is what the REPL's scope sees (jev,choice,score,noul,walk). Calls go through this site's relay (worker/), which adds the site's own TypeSafe key, because TypeSafe's API refuses calls from web pages; visitors need no key.jevCore.test.mjscovers it with a stubbed Jev, andallSongs.test.mjsevaluates every song the way the REPL does and plays it to its end against a stand-in Jev, checking each request against the relay's rules (nix flake checkruns both). -
JevPane.jsx— the pane under the editor: the booth above the mixer, both showing skeletons (8 empty sections, an idle desk) until a song with ajev()plays, and beside them Jev's request log. -
jevLog.mjs,JevLog.jsx— Jev's request log, in place of popups: every section request (a line when asked, filled in with Jev's answer and its time when it lands) and every ⏭/📻 pick, newest on top, the page's last 200. -
ConnectAi.jsx— how to connect your own AI to the hosted MCP: the jev panel's mcp tab and/ai/(pages/ai.astro) both render it. -
DataTab.jsx— the jev panel's data tab: everything the site stores, for anyone to read (worker/src/data.ts, "What is public" inworker/README.md): every account and its page, listeners' songs whole (held ones as text), covers, comments, pitches, Jev's queue, performances with Jev's decision history, reactions, votes, what is live, and the caches, each section saying what it holds and what it withholds (secrets, counted and dated only). Other people's writing is text here: nothing in it plays or shows an image. -
profile.mjs,Profile.jsx,NameLink.jsx— profiles. Every display name (a song's author, a comment's or pitch's, a tab in "listening now", an account on the data tab) is aNameLink, which opens that account's profile in the jev panel, with a back link: its name and when it joined, its public songs (played as any listener song, in the sandbox), its public comments and pitches (and how many wait on Jev or were held), what it played lately, and what its tabs play now, from the lobby. It reads the data tab's account page (/jev/data/accounts/<id>). -
activity.mjs,ActivityTab.jsx— the jev panel's activity tab: what happened, newest first (/jev/data/activity,worker/src/activity.ts): songs published and revised with Jev's verdict and score, comments, pitches, and every take with who played it and "▶ replay", thirty at a time with "older". A listener's song plays from it in the sandbox; a site song as its card. -
parties.mjs,LiveParties.jsx— the listening tab's live parties (/jev/parties): each one's song, people and how long it has run, and "join" for one whose host is listed, which asks the room for its name (/jev/parties/<id>) and joins it as a guest (joinParty). A party's host is listed when its tab is (the lobby's "show me here",setListedCheck), and unticking or ticking it mid-party takes the party off the list or puts it back at once (listedChanged); worker/README.md, Listening parties, has why the id and not the name. -
History.jsx— what a listener played (PlayedList): the you tab's "recently played" for the signed-in listener and a profile's "played lately", from the account's page. Its own takes (every performance it played signed in), each with its time, its path and "▶ replay", merged newest first with the radio's plays that have no take (playedLatelyinprofile.mjs); every song plays again as its card plays it. -
budget.mjs,JevBudget.jsx— today's Jev budget where calls are made: in the booth's title row, and on each ⏭ pick in the request log (JevPicks.jsx). Signed out, calls share the address's allowance. -
social.test.mjs— the plain logic of the five above. -
JevBooth.jsx— Jev made visible while a song plays, at the top of the pane under the editor: a cell per section (each answer, and whether Jev answered, is being asked, or could not be reached; click one for every answer's probabilities and what Jev was told) and 🔥/😴 buttons Jev hears; and (JevBoothEffects) each request into the request log and the playing choices lit up in the code.boothHighlight.mjsalso colours Jev's API in the editor. All of it readsboothStore.mjs, whichjev()writes and each successful evaluation swaps. -
reactions.mjs— every listener's 🔥/😴: each press in the booth is also posted to the site's/jev/reactions(worker/src/listening.ts), counted per song, section and reaction. The playing song's tally is fetched as each song loads; the song's view on the jev panel shows it per section, and Jev hears it aslistenersSoFar. -
performance.mjs,JevPerformance.jsx— recorded performances. When a song as written ends, or is stopped or replaced after its opening section, the recorder posts what every jev() played, segment by segment (/jev/performances), with the SHA-256 of its code, and the booth offers "share this performance":/songs/<theme>/<song>/?performance=<id>. Opening that link fetches the performance before the song is evaluated and replays it (jevCore'ssetReplay): the same sections, levels and choices, no Jev calls, and the booth says "replaying a performance" (and that the song has changed since, when its code hash differs). Picking another song ends the replay. The Worker adds who played it (signed in) and when. A listener's song is recorded too, from the sandbox's booth (each jev() apart, rebuilt bysandboxProtocol.mjs), and its link is/?listener=<id>&performance=<id>: the page fetches the performance and hands it to the frame with the play (armSandboxReplay), so the replay runs in the sandbox like the song, never in the page. The stored rows are also Jev's decision history:nix run .#jev-history -- <song>(tools/jev-history/). -
takes.mjs,JevTakes.jsx— Jev's takes on a song's view (a site song's, beside its 🔥/😴 tally, and a listener song's, inListeners.jsx): how Jev moves through the song over its recorded performances (a form map in written order: a line for the song as written, an arc for every other way Jev went, thicker the more often, and per section how often it played, Jev's answer times, how often Jev could not be reached, and the sections it picked next with their shares), and every take, newest first, with who played it and when, its path through the form and "▶ replay", the take's replay link (performance.mjs). The written order is the booth's form while the song plays here, else read from the code'swalk()(writtenOrder), else unknown (ALL GREEN builds its form in code), when the sections are placed where Jev plays them and nothing is called "as written". Read fromworker/src/listening.ts's/jev/performances?song=and/jev/performances/moves?song=; shows nothing where there is no Worker or no take. -
JevMixer.jsx— the same answers as a mixing desk, under the booth: a fader per part level (a score, or a choice among out/back/full), a selector knob per choice, a knob per noul, the form's section on an LCD, and live level meters per part and for the master. Controls glide to each section's settings as it begins; a dashed ghost shows the next section's once Jev has answered. -
meter.mjs— the song's loudness as it plays, read off what superdough sends the speakers (after its master limiter,output.speakers) and off each orbit's output: songs put every part Jev levels on an orbit named for its question (.orbit('riff')), so each part is heard alone. Each request tells Jev how the finished sections measured, in total and per part (measuredSound), and the strip's detail view tables it. -
measured.mjs— a song's measured mix as written, recorded in its SPEC.md asmeasured:bytools/measure/(one JSON line, with the hash of thesong.jsit measured), whole and per section on the song's.every(N)cadence (sectionCycles); pages get each section's total only. The art critic, recorded or asked live on unedited code, reads it asmeasuredMixwhile the hash still matches. -
preload.mjs— a song's samples, loaded before its first bar: the song is queried as written (asWritteninjevCore.mjs, which asks Jev nothing), and the scheduler's start waits for the samples its first cycles play while the rest load behind them. superdough otherwise drops a note whose sample is still loading. -
ask.mjs— one Jev call through the site's relay; everything below uses it. The relay answers a request it has already answered, byte for byte, within the hour from its cache, and says which in each answer'sJev-Cacheheader (hitormiss;CACHE_HEADER). Each request also tells the relay, in headers it logs and never forwards, how many seconds before its section begins it went out (from the scheduler, viajev.mjs) and which attempt it is (worker/README.md, Logs). For a signed-in listener, each answer the relay charged carriesJev-Budget: used/limit, and once the day's budget is spent a 429 withJev-Budget: spentandRetry-Afteruntil 00:00 UTC: calls back off until then (songs play their fallbacks) and say the budget is spent. -
account.mjs,AccountControl.jsx— accounts, signed in with a passkey (worker/README.md, Accounts). The header's "🔑 sign in" opens a panel to sign in with a passkey or make an account (a display name, and a passkey on this device); signed in, it shows the name, today's Jev calls against the daily budget, "add a passkey" (another device) and "sign out".account.mjsasks the Worker who is signed in (/jev/auth/me) and runs the ceremonies with@simplewebauthn/browser; the session is an HttpOnly cookie the page never reads. The panel also lists the AI apps connected through the hosted MCP (listApps,/jev/me/apps), each with a "disconnect". Nothing else needs an account. -
myTabs.mjs— the hosted MCP's end in the page (worker/README.md, The hosted MCP;/ai/explains it to visitors): while someone is signed in, the tab joins that account's own hub over/jev/me/tabs, under the same session id as the dev hub's, and answers what the listener's own AI asks: play, stop, the editor's code, the sandbox's logs since the last play, and status. What the AI plays is foreign code ({ ai: app }) and plays only in the sandbox;playForeignanswers once that play has started or failed. The jev panel's mcp tab says so, with the tab's id, while it is joined. -
tabTools.mjs,tabCommands.mjs— the tools both MCPs share (worker/README.md, The tools both MCPs share), answered by the tab: the sounds tab's sounds, Jev's pick of a sound (soundPick.mjs) and of a song (the mood picker), the settings tab's allowed settings (worker/src/mcp-settings.mjs), the console tab's lines, a song played by id as its card plays it, and a 🔥/😴 (press.mjs, which the booth's buttons press too).tabTools.mjstakes the page's modules asdeps, so it runs in vitest;tabCommands.mjssupplies the real ones, loaded on the first such command bymyTabs.mjsand by the dev hub's tab (repl/useWebSocketMCP.jsx). -
sounds.mjs— the sounds tab's sounds as data: which sub-tab each is under (inCategory, which the sounds tab filters with), its variants, and how a song plays it (a drum machine's<machine>_<sound>as a bank). -
soundPick.mjs— Jev choosing a sound for a description: one Choice among up to 255 names; past that, banks of at most 255 (several to a request), then one question among each bank's three likeliest, as Krug's JevLM does. Asked throughask.mjs, so charged as the page's own calls. -
reference.mjs— the reference tab's functions out of jsdoc'sdoc.json(referenceFunctions, which the tab renders), and as text for the MCP'sapi_reference(referenceData,pages/jev/reference.json.js). jev's own entries (jev,choice,score,noul,walk, taggedjev) are the jsdoc injevCore.mjs, whichpackage.json'sjsdoc-jsonreads besidepackages/;reference.test.mjschecks every name in the REPL scope has one and plays each example, and the hosted MCP's primer song, against a stand-in Jev (playThrough.mjs, shared withallSongs.test.mjs). -
radioHistory.mjs— a signed-in listener's radio history, kept by the Worker (/jev/me/radio): the radio records each song it starts, andpicker.mjs'snotRecentkeeps the most recently played (on any device) out of Jev's choices, leaving at least three. Signed out, the radio remembers only the tab, as before. -
SongListening.jsx— a song's page, its listening: 👥 listen together, who has the song open now and how far through (WhoIsListeningnarrowed to the song), and the live parties on it to join (LivePartiesnarrowed likewise). The page puts comments first, then this, then the song. -
party.mjs,ListenTogether.jsx— listening parties. "👥 listen together" on a song's view in the jev panel starts one and gives a link,/songs/<theme>/<song>/?party=<room>; whoever opens it hears the same song with the same Jev decisions, in time with the host, joining mid-song at the cycle the host is on. The host's page is the only one that asks Jev: it sends each decision its jev()s settle (watchDecisions) through the party's room, a Durable Object in the Worker (worker/README.md, Listening parties, has the protocol), and a guest's jev()s follow them (followDecisions). Each page estimates the room's clock from a few pings; the host sends when its scheduler started, and a guest's scheduler starts at the host's cycle now (the cyclist'slastEnd). 🔥/😴 from anyone go to the room, which tells everyone the totals, so the host's Jev hears every listener; the booth shows how many are in. A guest follows the host from song to song (the radio's picks included; a guest's own radio stays off) and stops when the host does. The host's tab keeps the party's key insessionStorage, so a reload rejoins as host. -
lobby.mjs,Lobby.jsx— who is listening now (worker/README.md, The lobby). Every page joins the site's lobby and says what its tab plays: a site song (and whether edited), a listener's song, or its own code; playing or not, the playhead and tempo, the song's end once known (or the most it can last while Jev walks its form) and the section. The jev tab's "👥 listening now" lists everyone else's tabs grouped by who (an account's display name, or "a listener" when not signed in; the Worker says which, never the page), with each song's progress moving at its tempo, "🎧 listen along" for a site song that is playing, "play it" for one that is not, and "open it" for a listener's song. The header's 👥 is how many pages are open and opens the list. A tab is listed by default (the user asked for a more social site, 2026-09-27); "show me here" hides it, kept per browser. Listening along is a listening party: the tab asked starts one on its song (or passes on the one it is in) and its room reaches the asker only, whose page joins as a guest (joinPartyinparty.mjs) and plays in sync with the host's Jev. -
picker.mjs,JevPicks.jsx,nextTrack.mjs— Jev beyond the booth: the jev panel's mood picker ("how do you feel?"); beside play, "⏭ next" (Jev picks another song now) and "autoplay" (when a song ends, Jev picks the next; the jev panel's 📻 toggle is the same setting, kept per browser); and "🎨 is it art?" in the booth's title row, which asks the art critic about whatever is in the editor. Next and autoplay share one path (nextTrack.mjs): Jev hears what the listener played (signed in, the account's radio history, whose most recent songs it is not offered, and each pick is added to it; signed out, this tab's), and when the editor holds no site song as written it picks from that history alone. A party's guest has neither: the host picks. -
agree.mjs,AgreeWithJev.jsx— the jev panel's "do you agree with Jev?": pick the more-art of two songs, then see the critic's scores and how often you agree with it. Your tally stays in the browser (localStorage), counted against the critic's current scores; each vote is also posted, fire and forget, to the site's/jev/votes(worker/src/votes.ts), which keeps only a count per pair and pick. "Everyone's votes" (VoteResults.jsx, read from/jev/votes/summaryonce opened) ranks the songs as listeners do, pair by pair (a Bradley–Terry fit,bradleyTerryinagree.mjs, each song starting from one win and one loss against an average song so a few votes move it little), beside Jev's rank by art, with the overall agreement and the pairs where listeners outvote Jev; every figure carries its count, and a song with fewer than 5 votes is faded. The agreement report isagreementReport, whichnix run .#agreement(tools/agreement/) prints too. -
critic.mjs— the art critic's question, and how it reads a song's spec and code: one source for every "N% art", recorded (tools/critic/, the mean of several runs, written into SPEC.md by itssetRevisionArtandsetCritic) or asked live (one run). The rubric itself lives inworker/src/art-rubric.mjs, re-exported here, because the Worker scores listeners' songs with the same questions. -
listeners.mjs,Listeners.jsx— listeners' content (worker/src/content.ts; worker/README.md, Listeners' content). The jev tab's "listeners" section lists public listener songs as cards, most art first; a card fetches the song whole and plays it, and its view (jev › listeners › title) shows its cover, author, art, revisions and spec as plain text. Signed in: "publish the editor's code as a song", and on your own song's view a new revision from the editor or a cover. Every song's view (the site's and listeners') has comments, and the section has pitches for Jev songs, most voted first or newest: signed in, ▲ votes for one (once per account, pressed again to take it back; who voted is public, on the data tab), and a pitch that became a song links to it. The song's maker says so, never the pitch's author: a site song withpitch:in its SPEC.md, a listener song from "answers a pitch" on its own view (pitches.mjs). "What you wrote" lists your items as public, held (Jev's verdict, only you see it) or pending (why, and when Jev is asked again), asking again every 20 s while anything waits. A listener song's address is/?listener=<id>(it has no page of its own;client.mjs'sshowSongwrites it anduseReplContextloads it, without playing). A listener's song is untrusted code and plays only in the sandbox below, never in this page. -
sandbox.mjs,sandbox/player.mjs,sandboxProtocol.mjs,sandboxPolicy.mjs,SandboxNotice.jsx— the isolation boundary for untrusted code (worker/README.md, "A listener's song runs in a sandbox"). A listener's song, and code arriving in a link, play in an iframe with an opaque origin (sandbox="allow-scripts", noallow-same-origin) under a strict CSP, so their code has no cookies, no storage of the site's, no way into this page, and its Jev calls reach the relay without the visitor's session.sandbox.mjsis the page's end: it tracks each code's provenance (foreign vs the page's own;guardEditormakes the editor play foreign code only in the frame, and the page's own scheduler refuse to start while it holds foreign code), owns the frame, and forwards the frame's Jev calls to the relay: withcredentials: 'omit'for a listener's song or a link's code, and with the listener's own session for code their own AI played (charged to their budget; the frame never holds the session). Plays are numbered (run) so the page knows which play a state is about.sandbox/player.mjsis a Strudel player with nothing of the page's, run inside the frame.sandboxProtocol.mjsrebuilds everypostMessagefrom typed, bounded fields — nothing from the frame is evaluated or rendered as HTML.sandboxPolicy.mjsis the one source of the CSP and CORS, for the meta tag, the deploy's_headers, and the dev server.SandboxNotice.jsxis the line above the editor while its code is someone else's. The site's own songs are first-party and keep running in the page (visuals, parties, performance recording); only untrusted code is sandboxed. -
releases.mjs,UpdateModal.jsx— release notes from the repo'sRELEASES.md, every release a heading that expands: when a new deploy is waiting, a modal shows what changed and an "update now" button reloads into it (src/pwa.ts); after a return visit, what is new since; and the page's version at the jev panel's foot (ReleaseButton) opens them all, the newest expanded. -
site.mjs— the site's name, tagline, URL, accent colour and description: the tab title, the app manifest, and link previews. -
og.mjs— the link-preview images, 1200×630, drawn at build time: the site's (/og.png, nine covers beside the name) and each song's (/og/<theme>/<song>.png, its cover beside its title, "N% art" and length).
pages/jev/songs.json.js builds /jev/songs.json, the site's song ids,
which the Worker checks each vote against. pages/jev/catalog.json.js and
pages/jev/catalog/[theme]/[song].json.js build the site's songs as the
hosted MCP lists them and reads each whole (spec and code;
worker/src/catalog.ts), and pages/jev/reference.json.js the reference
tab's functions for api_reference (worker/src/reference.ts).
pages/ai.astro is /ai/, "Make songs with your AI": how to connect an
MCP client, and the tools, read from worker/src/hosted-tools.ts.
pages/jev-samples/strudel.json.js beside it builds the sample map songs load
(samples('jev-samples/strudel.json')) from the repo's samples/ folder,
with sampleMap.mjs, the builder tools/worktree-samples/ also uses to
give headless tools a worktree's own samples.