/** * `/changelog` on the Nook subdomain — rendered from the Sparkle appcast. * * The appcast already ships HTML release notes to the updater on every * launch, so this page renders exactly what Sparkle publishes: one source * of truth, no second place to write release notes. Doubles as the raw * material for the release-announcement job (marketing/README.md). * * Layout: the version list lives in a fixed-height vertical scroller, so * expanding a release never shifts the page — history is browsed inside * the panel. Rows collapse to their header; notes stay in the DOM * (visibility toggled, not unmounted) so crawlers still see every entry. */ import { For, Show, createSignal } from "solid-js"; import { query, createAsync } from "@solidjs/router"; import { PageHead } from "~/components/PageHead"; import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders"; import SubdomainHeader from "~/components/SubdomainHeader"; import { fetchAppcastReleases } from "~/routes/api/the-nook/_appcast"; import { ARCHIVED_RELEASES } from "~/routes/nook/changelog-archive"; const getReleases = query(async () => { "use server"; // Appcast items are authoritative (they carry build numbers); releases // older than the appcast's retention come from the checked-in archive, // recovered from the changelog file's git history. Both normalize to // AppcastRelease so the page renders one shape. const live = await fetchAppcastReleases(); const liveVersions = new Set(live.map((r) => r.version)); const archived = ARCHIVED_RELEASES.filter( (r) => !liveVersions.has(r.version) ).map((r) => ({ ...r, build: null })); return [...live, ...archived]; }, "nook-changelog-releases"); export default function NookChangelogPage() { const releases = createAsync(() => getReleases(), { deferStream: true }); // Newest release starts open; every other row starts collapsed. `null` // means "the user hasn't touched the accordion yet"; after any click the // signal holds a version or the NONE sentinel — collapsing the open row // sets NONE, so all sections stay closed instead of snapping back open. const NONE = "__none__"; const [openVersion, setOpenVersion] = createSignal(null); const isOpen = (version: string, index: number) => { const open = openVersion(); if (open === null) return index === 0; return open !== NONE && open === version; }; const toggle = (version: string, index: number) => setOpenVersion(isOpen(version, index) ? NONE : version); return ( <>

Changelog

What changed in each release.

0} fallback={

No releases published yet — check back soon.

} >
{(release, i) => (
{/* Notes are the publisher's own HTML, written to the same S3 appcast the Sparkle updater consumes. Kept mounted and toggled via `hidden` so the content stays crawlable. Releases that shipped before notes were embedded in the appcast carry no description — say so instead of rendering a blank expanse. */}
No notes recorded for this release.

} >
)}
); }