From 53f141396633fdf2df7420b407409a15ed8c97b3 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Mon, 5 Oct 2026 09:51:08 -0400 Subject: [PATCH] nook: render the full release history on /changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The appcast retains only recent items, so releases before 0.6.0 had no surface. The five older sections (0.5.0 back to the first widely distributed 0.2.0) were recovered from the changelog file's git history (The-Nook 1ad8a69 / db165cd), converted to the same HTML the embed step produces, and checked in as an archive the page merges behind the appcast items — appcast stays authoritative, archive fills the gaps. 0.3.1 and 0.6.1 shipped without written notes and are absent by design. Also fixes the accordion snapping 0.6.3 back open when all sections were collapsed: after any click the state is a version or an explicit none sentinel, never the initial first-open default. Tests: tsc clean; dev render shows all eight releases with notes in the DOM. Issue: FRE-13 --- src/routes/nook/changelog-archive.ts | 38 ++++++++++++++++++++++++++++ src/routes/nook/changelog.tsx | 34 ++++++++++++++++++------- 2 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 src/routes/nook/changelog-archive.ts 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()) ? "−" : "+"}