1// Release notes: RELEASES.md at the repo root, newest first, one `## ` 2// heading per deploy: 3// 4// ## 2026-09-24.2 — Jev arranges the whole song 5// - a line a listener would care about 6// 7// The heading's first word is the release's version. The site embeds its 8// own version (ReplPage.astro, meta jev-release) and publishes every note 9// (/release-notes.json). The release notes modal lists every release up to 10// the one it is about, each a heading that expands: when a newer deploy is 11// waiting, the ones newer than the page's own are expanded; after a return 12// visit, the ones not seen yet; opened from the header's version, the newest. The deploy refuses to publish 13// a site whose newest note is already live (tools/deploy/). 14export function parseReleases(text) { 15 const releases = []; 16 for (const block of text.split(/^## /m).slice(1)) { 17 const [heading, ...lines] = block.split('\n'); 18 const [, version, title] = heading.match(/^(\S+)\s*(?:[—-]\s*(.*))?$/) ?? []; 19 if (!version) continue; 20 const notes = lines 21 .filter((l) => /^\s*- /.test(l)) 22 .map((l) => l.replace(/^\s*- /, '').trim()) 23 .filter(Boolean); 24 releases.push({ version, title: title?.trim() ?? '', notes }); 25 } 26 return releases; 27} 28 29// The releases a page on `version` has not seen, newest first; all but the 30// newest are dropped when the page's version is unknown. 31export function newerThan(releases, version) { 32 const i = releases.findIndex((r) => r.version === version); 33 return i === -1 ? releases.slice(0, 1) : releases.slice(0, i); 34} 35 36// What the release notes modal lists: every release up to and including 37// `upTo` (all of them when it is unknown), newest first, each with `open` 38// when it is one of `open`'s (or, if none of those is listed, the newest). 39export function releaseList(releases, upTo, open) { 40 const from = releases.findIndex((r) => r.version === upTo); 41 const listed = from === -1 ? releases : releases.slice(from); 42 const opened = new Set(open.map((r) => r.version)); 43 if (!listed.some((r) => opened.has(r.version)) && listed[0]) opened.add(listed[0].version); 44 return listed.map((r) => ({ ...r, open: opened.has(r.version) })); 45}