diff --git a/src/routes/nook/changelog-archive.ts b/src/routes/nook/changelog-archive.ts new file mode 100644 index 0000000..38a2f42 --- /dev/null +++ b/src/routes/nook/changelog-archive.ts @@ -0,0 +1,38 @@ +// Releases older than the appcast's retention, recovered from the +// changelog file's git history (The-Nook 1ad8a69 / db165cd) and checked +// in so /changelog renders the full story. 0.3.1 and 0.6.1 shipped +// without written notes and are absent by design. Generated with the +// same section->HTML rules as embed_release_notes.py. +export interface ArchivedRelease { + version: string; + date: string; + notesHtml: string; +} + +export const ARCHIVED_RELEASES: ArchivedRelease[] = [ + { + "version": "0.5.0", + "date": "2026-09-07", + "notesHtml": "

Added

Changed

Fixed

" + }, + { + "version": "0.4.1", + "date": "2026-09-06", + "notesHtml": "

Fixed

" + }, + { + "version": "0.4.0", + "date": "2026-09-06", + "notesHtml": "

Added

Changed

Fixed

" + }, + { + "version": "0.3.0", + "date": "2026-09-05", + "notesHtml": "

Added

Changed

Fixed

" + }, + { + "version": "0.2.0", + "date": "2026-08-26", + "notesHtml": "

Added

Fixed

" + } +]; diff --git a/src/routes/nook/changelog.tsx b/src/routes/nook/changelog.tsx index 0455352..4edb324 100644 --- a/src/routes/nook/changelog.tsx +++ b/src/routes/nook/changelog.tsx @@ -17,19 +17,39 @@ 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"; - return fetchAppcastReleases(); + // 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()); - // Newest release starts open; every other row starts collapsed. + // 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) => - openVersion() === null ? index === 0 : openVersion() === version; + 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 ( <> @@ -65,11 +85,7 @@ export default function NookChangelogPage() { type="button" class="hover:bg-base flex w-full items-baseline gap-x-3 gap-y-1 p-5 text-left transition-colors" aria-expanded={isOpen(release.version, i())} - onClick={() => - setOpenVersion( - isOpen(release.version, i()) ? null : release.version - ) - } + onClick={() => toggle(release.version, i())} > {isOpen(release.version, i()) ? "−" : "+"}