docs(ui): drop sidebar saved-state — server defaults already ARE the standard docs behavior

Simplification per spec: the menu tracks state through the URL alone.
Land on /docs: fully closed, headers only (an explicit home rule now
overrides the contains-URL rule, since the root overview doc lives in
Getting Started and would otherwise auto-open it). Land on any doc page:
its containing section + group render open at first paint, current link
bold — no JS, no jump, arrows consistent. Navigate elsewhere: old
closes, new opens.

Deleted: docs_nav cookie const/parse, ApplyNavOverrides, cookie write +
collect/persist helpers in web/src/docs.ts, data-key attributes in the
sidebar templ, and the first-section-open fallback (docs home used to
force-open the first section — spec'd as closed).

toggleCollapsible remains for in-page peeking; purely ephemeral.
The [data-collapsed=true] CSS from the previous round stays — it is what
makes the server-rendered state actually hide sections at paint (the
original bug was that this CSS never existed).

Tests: TestBuildNavigationNothingCurrent now asserts zero sections open
on unmatched paths; TestBuildNavigation asserts Getting Started closed
on a device page (no site-wide pin). make test green; 146 vitests; live
verified: home 0 expanded, doc pages exactly 2 (section+group).
This commit is contained in:
John O'Keefe committed 2026-10-24 21:17:55 -04:00
1 parent a8f8c4def9
commit 73e8cdef4b
6 files changed
+155 -272

No files matched your search

+5 -33
View File
@@ -15,39 +15,13 @@ function toggleSidebar(): void {
overlay.classList.toggle("hidden");
}
// Docs sidebar collapse persistence. Rendering ownership: the server
// renders what is expanded (navigation defaults + docs_nav cookie
// overrides merged in internal/docs), so first paint is already correct —
// no post-paint correction, no jumping. This module only records intent:
// every toggle rewrites the cookie with the full current state.
const DOCS_NAV_COOKIE = "docs_nav";
type DocsNavState = Record<string, boolean>;
// collectDocsNavState reads the current collapsed flag of every
// collapsible in the sidebar straight from the DOM.
function collectDocsNavState(): DocsNavState {
const state: DocsNavState = {};
document.querySelectorAll<HTMLElement>("[data-key][data-collapsed]").forEach((el) => {
const key = el.getAttribute("data-key");
if (key) state[key] = el.getAttribute("data-collapsed") === "true";
});
return state;
}
function persistDocsNavCookie(state: DocsNavState): void {
try {
// Strictly-necessary UI state: first-party, no identifiers, one year.
document.cookie = `${DOCS_NAV_COOKIE}=${encodeURIComponent(JSON.stringify(state))}; path=/; max-age=31536000; SameSite=Lax`;
} catch {
// Storage blocked: the sidebar still works, state just doesn't persist.
}
}
// Docs sidebar sections toggle open/closed in-page (purely client-local,
// ephemeral — the SERVER decides what is expanded on each page load from
// navigation defaults, so there is no saved state and nothing to jump).
// The data-collapsed attribute is the single source of truth: the
// stylesheet hides on it, and the arrow rotates relative to it.
function applyCollapsed(items: HTMLElement, collapsed: boolean): void {
// The data-collapsed attribute is the single source of truth — the
// stylesheet hides on it, and the arrow is rotated relative to it.
items.setAttribute("data-collapsed", collapsed ? "true" : "false");
const arrow = items.previousElementSibling?.querySelector<HTMLElement>(".section-arrow");
if (arrow) {
@@ -60,9 +34,7 @@ function toggleCollapsible(header: HTMLElement): void {
if (!items) return;
const isCollapsed = items.getAttribute("data-collapsed") === "true";
applyCollapsed(items, !isCollapsed);
persistDocsNavCookie(collectDocsNavState());
}
let docsSearchTimeout: ReturnType<typeof setTimeout> | null = null;