diff --git a/TODO.md b/TODO.md index 081b08f..44a72a8 100644 --- a/TODO.md +++ b/TODO.md @@ -32,12 +32,132 @@ Dispatched from core/go orchestration. Pick up tasks in order. - [x] **Multi-word bonus** -- All query words present in topic adds +2.0. - [x] **Tests for all new features** -- Levenshtein, min3, extractPhrases, fuzzy search, phrase search, tag boost, multi-word bonus, scoring constants, phrase highlighting, section phrase matching. -## Phase 2: core.help Integration +## Phase 2: HTTP Server & Rendering -- [ ] Feed CLI docs into help catalog (parse `core` subcommand help text) -- [ ] Serve catalog via HTTP for the `core.help` domain -- [ ] Add Markdown rendering for topic bodies in HTTP responses -- [ ] Generate static site from catalog for BunnyCDN deployment +### 2.1 Markdown Rendering (`render.go`) + +- [ ] **Add `github.com/yuin/goldmark` dependency** — CommonMark-compliant Markdown renderer. Run `go get github.com/yuin/goldmark`. +- [ ] **Create `render.go`** — Markdown-to-HTML conversion: + - `func RenderMarkdown(content string) (string, error)` — converts Markdown to HTML using goldmark. Configure with: + - `html.WithUnsafe()` — allow raw HTML in source (needed for embedded code examples) + - `extension.GFM` — GitHub Flavoured Markdown (tables, strikethrough, autolinks) + - `extension.Typographer` — smart quotes and dashes + - Returns HTML fragment (no ``/`` wrapper — the server templates handle that) +- [ ] **Create `render_test.go`** — Tests: + - (a) heading hierarchy (H1-H6) produces correct `

`-`

` tags + - (b) fenced code blocks (```` ```go ````) produce `
`
+  - (c) inline code backticks produce ``
+  - (d) lists (ordered + unordered)
+  - (e) links and images
+  - (f) tables (GFM extension)
+  - (g) empty input returns empty string
+  - (h) special characters are properly escaped
+
+### 2.2 HTTP Server (`server.go`)
+
+- [ ] **Create `server.go`** — HTTP server for the help catalog:
+  - `type Server struct { catalog *Catalog; addr string; mux *http.ServeMux }` — holds catalog reference and HTTP mux
+  - `func NewServer(catalog *Catalog, addr string) *Server` — constructor. Creates mux with all routes registered.
+  - `func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)` — delegates to mux (implements `http.Handler`)
+  - `func (s *Server) ListenAndServe() error` — starts listening
+
+  **Routes:**
+  - `GET /` — Index page: rendered HTML listing all topics grouped by tags, sorted by Order then Title
+  - `GET /topics/{id}` — Topic page: rendered Markdown content + table of contents from Sections + related topics sidebar
+  - `GET /search?q={query}` — Search results page: rendered HTML with highlighted snippets, scores
+  - `GET /api/topics` — JSON array of all topics (without rendered HTML — raw content field)
+  - `GET /api/topics/{id}` — JSON single topic
+  - `GET /api/search?q={query}` — JSON array of SearchResult
+
+  **Response format:**
+  - HTML routes: render using Go `html/template` with embedded templates (see 2.3)
+  - JSON routes: `application/json` with `json.NewEncoder`
+  - All routes: set `Content-Type`, `X-Content-Type-Options: nosniff`
+  - Search routes: return 400 if query is empty
+
+- [ ] **Create `server_test.go`** — Tests using `httptest.NewServer`:
+  - (a) `GET /` returns 200 with HTML containing topic titles
+  - (b) `GET /topics/getting-started` returns 200 with rendered content
+  - (c) `GET /topics/nonexistent` returns 404
+  - (d) `GET /search?q=install` returns 200 with results
+  - (e) `GET /search` (no query) returns 400
+  - (f) `GET /api/topics` returns JSON array
+  - (g) `GET /api/topics/getting-started` returns JSON object
+  - (h) `GET /api/topics/nonexistent` returns 404
+  - (i) `GET /api/search?q=config` returns JSON results
+  - (j) Content-Type headers correct for HTML vs JSON routes
+
+### 2.3 HTML Templates (`templates.go`)
+
+- [ ] **Create `templates.go`** — Embedded HTML templates using `embed.FS`:
+  - Use `html/template` with a `//go:embed templates/*.html` directive
+  - Create `templates/` directory with:
+    - `base.html` — shared layout: ``, dark theme CSS (reuse go-session's colour palette: `--bg: #0d1117; --fg: #c9d1d9; --accent: #58a6ff`), nav bar with search input, footer
+    - `index.html` — topic listing: cards grouped by first tag, topic count, search bar
+    - `topic.html` — single topic: rendered Markdown body, table of contents (from Sections), related topics, prev/next navigation
+    - `search.html` — search results: query echo, result count, ranked list with snippets, "no results" state with fuzzy suggestions
+    - `404.html` — not found page with search suggestion
+  - Template functions: `renderMarkdown` (calls RenderMarkdown), `truncate`, `pluralise`
+  - All templates use the dark theme. Monospace font. Clean, minimal.
+
+- [ ] **Create `templates_test.go`** — Tests:
+  - (a) all templates parse without error
+  - (b) index template renders topic titles
+  - (c) topic template renders Markdown content
+  - (d) search template renders results with snippets
+  - (e) 404 template contains "not found"
+
+### 2.4 Static Site Generator (`generate.go`)
+
+- [ ] **Create `generate.go`** — Generate a static site from the catalog:
+  - `func Generate(catalog *Catalog, outputDir string) error` — writes static HTML files:
+    - `{outputDir}/index.html` — index page (rendered from index template)
+    - `{outputDir}/topics/{id}.html` — one file per topic (rendered from topic template)
+    - `{outputDir}/search.html` — search page with client-side JS search
+    - `{outputDir}/search-index.json` — JSON search index for client-side search:
+      ```json
+      [{"id":"getting-started","title":"Getting Started","tags":["intro"],"content":"...truncated..."}]
+      ```
+    - `{outputDir}/404.html` — not found page
+  - Client-side search JS: inline `