feat(docs): standalone docs site chrome + category/subcategory navigation

Docs pages no longer embed the app Header (whose hx-post login form silently
degraded to a native GET, leaking credentials into the URL — this page never
loaded htmx). New docs-owned topbar: search, theme picker, Open App; anonymous
visitors get a plain /login link. Optional-auth middleware (hand-rolled —
echojwt's ErrorHandler return ends the chain, producing empty 200s) populates
the user for signed-in visitors.

Navigation rebuilt from the docs directory tree: 6 sections, subcategory
groups, server-rendered expansion and active states. Search results are a
templ partial (/docs/search/partial); code highlighting is server-side chroma
(previously dead hljs CSS); prose is theme-aware via typography DEFAULT vars
(prose-invert painted unreadable text under light themes). /docs renders the
index directly; standalone 404 without the tailwind CDN.

Tests: navigation routing/expansion (internal/docs), render pins asserting no
login form/password field/app sidebar ever return on docs pages.
lint-rendering: exempt *.test.ts fixtures (jsdom DOM building); docs.ts
baseline 5 -> 1.
This commit is contained in:
John O'Keefe committed 2026-10-24 14:31:36 -04:00
1 parent 9d0ae2bcc4
commit 84cd6328f4
19 files changed
+2030 -1097

No files matched your search

+30 -1
View File
@@ -6,9 +6,9 @@ import (
"bookhoard/internal/utils"
"encoding/json"
"fmt"
"strconv"
"net/url"
"sort"
"strconv"
"strings"
"time"
@@ -33,6 +33,35 @@ func navItemClass(current, target string) string {
return base + "text-content-muted hover:text-content hover:bg-surface-hover"
}
// docsNavClass returns classes for docs sidebar links (server-rendered active
// state — there is no client-side highlighter). indent sets the left padding,
// deeper for subcategory group items.
func docsNavClass(current, target, indent string) string {
base := "nav-item flex items-center " + indent + " pr-4 mr-2 py-1.5 rounded-lg text-sm transition-colors "
if strings.TrimRight(current, "/") == strings.TrimRight(target, "/") {
return base + "bg-brand/15 text-brand font-medium"
}
return base + "text-content-muted hover:text-content hover:bg-surface-hover"
}
// collapsibleState renders the data-collapsed attribute value for a docs nav
// items container; web/src/docs.ts toggles it on click.
func collapsibleState(collapsed bool) string {
if collapsed {
return "true"
}
return "false"
}
// chevronStyle pre-rotates a docs nav section/group chevron when collapsed
// (quote-free value: templ double-escapes quotes in style attributes).
func chevronStyle(collapsed bool) string {
if collapsed {
return "transform: rotate(-90deg);"
}
return "transform: rotate(0deg);"
}
// themeCheckClass returns the class suffix for a theme option's checkmark
// wrapper: empty for the active theme (visible), " hidden" otherwise. The
// check element is always rendered so updateThemeIndicators() can move it