feat(docs): "?" help affordance on every filter mount + docs loader fixes
The filter manual (1ed1602e) was reachable only by knowing /docs
already existed — nothing in the UI pointed at it (the parked manual
plan's open decision). Every filter-builder mount now carries a "?"
in its header linking to the guide (opens in a new tab so drawer and
editor state survive): the All Books drawer and the collection rules
page — the two mounts sharing FilterBuilderRows. The docs home
indexes the manual (guide, cookbook, generated field reference).
Two docs-loader fixes surfaced by pinning this:
- Folder URLs resolve to their index page: /docs/user/filters now
serves user/filters/index.md (the manual is a folder: guide +
cookbook + generated reference); previously only root /docs had
index handling.
- The docs folder path is resolved by walking upward (bounded):
`go test` runs test binaries from the PACKAGE directory, so the
relative "docs" path resolved to a folder that didn't exist there
and every /docs route 404'd in tests while working in prod —
found by the new pin, fixed so tests exercise the docs routes
exactly as prod serves them.
Pins: TestFilterHelpAffordance (folder URL serves the guide; drawer
and rules pages carry the link). internal/docs suite + full suite
green (239s).
This commit is contained in:
1 parent
1ed1602e70
commit
a4dc03984c
7 files changed
+118
-24
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"bookhoard/templates"
|
||||
@@ -24,7 +25,23 @@ type DocsHandler struct {
|
||||
docsFS fs.FS
|
||||
}
|
||||
|
||||
// resolveDocsPath — the docs folder lives at the repo root, but `go test`
|
||||
// runs test binaries from the PACKAGE directory (/cmd/server/tests), so a
|
||||
// plain relative "docs" resolves to a folder that doesn't exist there and
|
||||
// every /docs route 404s in tests (2026-11-01). Walk upward, bounded,
|
||||
// until the folder is real; prod (/app) matches on the first try.
|
||||
func resolveDocsPath(p string) string {
|
||||
for i := 0; i < 6; i++ {
|
||||
if fi, err := os.Stat(filepath.Join(p, "index.md")); err == nil && !fi.IsDir() {
|
||||
return p
|
||||
}
|
||||
p = filepath.Join("..", p)
|
||||
}
|
||||
return "docs"
|
||||
}
|
||||
|
||||
func NewDocsHandler(docsPath string) *DocsHandler {
|
||||
docsPath = resolveDocsPath(docsPath)
|
||||
// Create goldmark renderer with syntax highlighting
|
||||
md := goldmark.New(
|
||||
goldmark.WithExtensions(
|
||||
@@ -74,10 +91,23 @@ func (h *DocsHandler) LoadDocument(docPath string) (*templates.Document, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Read the markdown file
|
||||
// Read the markdown file. Folder URLs resolve to their index page:
|
||||
// /docs/user/filters → user/filters/index.md (2026-11-01 — the
|
||||
// filter manual is a folder: guide + cookbook + generated field
|
||||
// reference).
|
||||
content, err := fs.ReadFile(h.docsFS, docPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read document: %w", err)
|
||||
if !strings.HasSuffix(docPath, "index.md") {
|
||||
alt := strings.TrimSuffix(docPath, ".md") + "/index.md"
|
||||
if altContent, altErr := fs.ReadFile(h.docsFS, alt); altErr == nil {
|
||||
docPath = alt
|
||||
content = altContent
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to read document: %w", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to read document: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert markdown to HTML
|
||||
|
||||
Reference in new issue
Block a user