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
- Agent plugins detected as drifted, with reason and a Repair button.
- Keep Mac awake during agent runs while sessions are active (Advanced settings).
- Release notes card in General settings, plus changelog in update descriptions.
Changed
- Charge-limit and fan controls wait until a matching helper build is installed.
- Fans and OS notifications show an Enable-helper option where needed.
- Code was reorganized into clearer folders, with no behavior change.
Fixed
- Keep Mac awake during agent runs: Advanced settings holds off idle sleep while any agent is working or waiting on you.
- Settings open reliably from the island gear menu.
- Privileged operations use one installer path with consistent signing; fan control and charge limiting work correctly again.
- Temperature sensor selection sources readings reliably.
- opencode sessions keep the model identity through Stop envelopes.
"
+ },
+ {
+ "version": "0.4.1",
+ "date": "2026-09-06",
+ "notesHtml": "Fixed
- Settings not opening from the island gear menu.
"
+ },
+ {
+ "version": "0.4.0",
+ "date": "2026-09-06",
+ "notesHtml": "Added
- First-run onboarding: interests, panel shaping, slot choices; replayable from Settings.
- Day modules: tap-to-edit events, calendar-color dots, access gates.
- Power flow and significant-energy sections, charge limit top-up mode.
- Ship DMG installer with appcast; drop the zip.
- Report a bug from the About pane; Sentry crash reporting.
Changed
- Agent approvals terminology; island "Collapsed" renamed to "Compact".
- Bridge endpoint baked into `~/.nook/endpoint` for plugins.
- Plug/charge state read from IOPS (registry keys lag ~30 s).
- License check timeout improved.
Fixed
- Call-detection scrim faked rings; nil panel ids crashed Settings unwraps; media-key tap teardown leaked run-loop sources; empty sessions read in-flight; interrupted pi turn sent no last assistant message.
"
+ },
+ {
+ "version": "0.3.0",
+ "date": "2026-09-05",
+ "notesHtml": "Added
- Call band: caller photo, live waveform, route pickers, banner suppression.
- Music interception service and battery thermal service.
- Power panel: wall-input wattage, charge limit policy, top-up mode.
- External brightness controls; keyboard system; global slot ordering.
- Report-a-bug About entry; Sentry crash and performance reporting.
Changed
- Island sequencing deepened behind IslandSurface; settings unified behind PaneShell.
- Media-key handling: peek stacks for call and media bands.
Fixed
- NowPlaying auto-pause for browser sources; battery module; sizing and expansion bugs.
"
+ },
+ {
+ "version": "0.2.0",
+ "date": "2026-08-26",
+ "notesHtml": "Added
- Agent session cards: status verbs, slot config sheet, approvals.
- Media, call, power, Day modules; Settings with per-pane cards.
Fixed
- Initial public reliability pass.
"
+ }
+];
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()) ? "−" : "+"}