/** * The release list for The Nook's in-app changelog (Settings > Changelog). * * The /changelog page already answers this question, but only as HTML, and it * assembles the list inside the route component. A native client must not * couple to that markup, and the appcast alone is not the full story: releases * older than the appcast's retention live only in the checked-in archive. So * this module owns that merge — live appcast items, their notes filled from the * archive wherever the feed lost them, then archived releases the appcast has * aged out — and normalizes it into data a client can render without parsing * HTML. `withArchivedNotes` is exported so /changelog runs the identical rule * rather than a second copy of it. * * Underscore-prefixed file — not a route. */ import { ARCHIVED_RELEASES } from "~/routes/nook/changelog-archive"; import { fetchAppcastReleases, type AppcastRelease } from "./_appcast"; export interface ReleaseSection { /** "Added" / "Changed" / "Fixed"; "Notes" when the notes carry no label. */ title: string; bullets: string[]; } export interface AppRelease { version: string; /** Build number, null for archived releases (they carry none). */ build: string | null; /** ISO calendar date, "2026-10-02"; null when no date was published. */ date: string | null; sections: ReleaseSection[]; } const ENTITY_REPLACEMENTS: [RegExp, string][] = [ [/</g, "<"], [/>/g, ">"], [/"/g, '"'], // Python's html.escape, which embed_release_notes.py writes the appcast // with, prefers the hex form for apostrophes (NOOK-565). [/'/g, "'"], [/'/g, "'"], [/'/g, "'"], [/ /g, " "], // Last, so "&lt;" cannot decode twice. [/&/g, "&"] ]; function decodeEntities(text: string): string { return ENTITY_REPLACEMENTS.reduce((acc, [re, to]) => acc.replace(re, to), text); } function stripTags(text: string): string { return text.replace(/<[^>]*>/g, ""); } /** Collapses the whitespace an HTML source wraps bullets with. */ function oneLine(text: string): string { return text.replace(/\s+/g, " ").trim(); } /** * Reads the notes shape `embed_release_notes.py` writes — * `

Added

` — into titled bullet groups. * * A `