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

+20 -51
View File
@@ -178,8 +178,8 @@ func docURL(docPath string) string {
// BuildNavigation creates the category → subcategory navigation from the
// docs filesystem. The section and group containing currentPath render
// expanded; everything else starts collapsed (with Getting Started as the
// fallback when nothing matches).
// expanded; everything else starts collapsed. When nothing matches (docs
// home, 404), the sidebar renders fully closed — headers only.
func (h *DocsHandler) BuildNavigation(currentPath string) *templates.Navigation {
docs, err := h.ListDocuments()
if err != nil {
@@ -198,16 +198,17 @@ func (h *DocsHandler) BuildNavigation(currentPath string) *templates.Navigation
}
type groupBuild struct {
items []templates.NavItem
urls []string
overview bool // has a pinned index page
items []templates.NavItem
urls []string
// overview: the group leads with its "#"Overview landing doc (pinned
// subdirectory index), so it sorts first.
overview bool
}
type sectionBuild struct {
items []templates.NavItem
urls []string
pinned map[string]bool // landing pages lead their section
groups map[string]*groupBuild
overview bool
items []templates.NavItem
urls []string
pinned map[string]bool // landing pages lead their section
groups map[string]*groupBuild
}
builds := make(map[string]*sectionBuild, len(sectionOrder))
@@ -231,10 +232,6 @@ func (h *DocsHandler) BuildNavigation(currentPath string) *templates.Navigation
if place.pin < 0 {
build.pinned[item.URL] = true
}
if place.pin == -100 {
// Only the root Overview forces its section open.
build.overview = true
}
} else {
group, ok := build.groups[place.group]
if !ok {
@@ -301,52 +298,24 @@ func (h *DocsHandler) BuildNavigation(currentPath string) *templates.Navigation
}
}
}
if build.overview {
section.Collapsed = false
}
nav.Sections = append(nav.Sections, section)
}
// Nothing matches the current page (404, search landing): open the
// first section so the sidebar doesn't render fully closed.
anyOpen := false
for _, s := range nav.Sections {
if !s.Collapsed {
anyOpen = true
break
// No default expansion when nothing matches the current page (404,
// search landing): the sidebar renders fully closed. Same for the docs
// HOME: although the root overview doc lives there (its contains-URL
// rule would open "Getting Started"), the spec is "home = all closed,
// headers only" — the standard docs-site behavior.
if strings.TrimSuffix(currentPath, "/") == "/docs" {
for si := range nav.Sections {
nav.Sections[si].Collapsed = true
}
}
if !anyOpen && len(nav.Sections) > 0 {
nav.Sections[0].Collapsed = false
return nav
}
return nav
}
// ApplyNavOverrides merges the browser-saved collapse state (docs_nav
// cookie) into the nav so the sidebar first-paints in the visitor's chosen
// state — no post-paint JS correction, no jumping. Keys mirror the
// data-key attributes DocsSidebar renders: section titles, and
// "Section / Group" for groups. Entries for unknown keys (renamed or
// removed sections) are ignored; keys absent from the map keep the
// path-derived default.
func ApplyNavOverrides(nav *templates.Navigation, overrides map[string]bool) {
if len(overrides) == 0 {
return
}
for si := range nav.Sections {
s := &nav.Sections[si]
if v, ok := overrides[s.Title]; ok {
s.Collapsed = v
}
for gi := range s.Groups {
if v, ok := overrides[s.Title+" / "+s.Groups[gi].Title]; ok {
s.Groups[gi].Collapsed = v
}
}
}
}
// containsURL reports whether currentPath is one of the given doc URLs.
func containsURL(currentPath string, urls []string) bool {
for _, u := range urls {