# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **nebbet** is a static site generator (SSG) with an integrated admin UI for managing blog posts. It compiles markdown content to HTML, maintains metadata and search indices in SQLite, and provides a web interface for post creation/editing without redeploying. ## Build & Run Commands ### Build a production site ```bash go run ./cmd/nebbet build ``` ### Watch mode (continuous rebuild on file changes) ```bash go run ./cmd/nebbet watch ``` ### Development server (watch + live admin UI) ```bash go run ./cmd/nebbet serve --port 8080 ``` Visit `http://localhost:8080/admin/` to manage posts. Admin requires a user; create one first: ### User management ```bash # Add a user (prompts for password) go run ./cmd/nebbet user add alice # List users go run ./cmd/nebbet user list # Change password go run ./cmd/nebbet user passwd alice # Delete user go run ./cmd/nebbet user delete alice ``` ## High-Level Architecture ### The Build Pipeline 1. **Source**: Markdown files in `content/` with YAML frontmatter 2. **Parse**: Extract frontmatter (title, date, tags, layout, draft flag) and body 3. **Render**: Convert markdown → HTML using Goldmark with GFM extensions 4. **Process**: Replace component directives with custom element tags 5. **Output**: Write HTML to `public/` using layout templates 6. **Index**: Update two SQLite databases: - **meta.db**: Page metadata for listing/filtering by tag - **search.db**: Full-text search index (FTS5) 7. **Watch**: Monitor content, templates, components for changes; rebuild with 150ms debounce ### Directory Structure ``` content/ # Source markdown files (mirrored in public/ as .html) index.md # Becomes /index.html → / admin/ # Skipped from build (admin UI only) posts/ # Blog posts (managed via admin UI) public/ # Generated static output templates/ # HTML layout templates base.html # Default page layout admin.html # Admin template (for future) components/ # JavaScript web components site-greeting.js # Example component lib/ # JavaScript import map + libraries styles/ # CSS files (served directly, not copied) data/ # SQLite databases (created on first build) meta.db # Page metadata search.db # Full-text search index ``` ### Component System Components are declared as HTML comments in markdown and converted to custom elements at build time: ```markdown ``` becomes: ```html ``` The `components/` directory contains JavaScript modules that define these custom elements. An importmap is auto-generated from `lib/` and injected into each page. ### Frontmatter Spec Supported frontmatter fields (YAML-style): ``` title: Page Title # Required date: 2024-03-31 # Optional; ISO date format tags: tag1, tag2 # Optional; comma-separated or JSON array layout: base # Optional; template name (default: base) draft: false # Optional; if true, page is skipped at build time ``` ## Key Modules ### `internal/builder/` - **builder.go**: Core orchestrator (BuildAll, BuildFile, Watch, RemovePage) - **frontmatter.go**: Parse YAML frontmatter from markdown - **markdown.go**: Markdown → HTML conversion (Goldmark + GFM) - **components.go**: Component directive processing (comments → custom elements) - **importmap.go**: Auto-generate ES module importmap from lib/ ### `internal/admin/` - **server.go**: HTTP handlers for listing, creating, editing, and deleting posts - Uses Basic Auth (htpasswd-compatible bcrypt passwords) - Serves form UI for post management - Triggers rebuilds via the builder after create/edit/delete ### `internal/auth/` Manages password file (compatible with nginx auth_basic): - AddUser, DeleteUser, ChangePassword, ListUsers - Verify passwords with bcrypt.CompareHashAndPassword - File format: `username:$2a$...` (one per line) ### `internal/db/` Two SQLite databases: **meta.db** (PageMeta): - Stores path, title, date, tags, html_path for each page - Indexed by path and date - Used for tag filtering and chronological listing **search.db** (SearchDB): - FTS5 virtual table for full-text search - Tokenizer: porter unicode61 - Stores path, title, searchable plain-text content ## Deployment ### systemd service See `nebbet.service` for running the watch daemon as a service. Update SITE_ROOT placeholder. ### nginx configuration See `nginx.conf` for: - Public site serving from `public/` - Admin UI with htpasswd auth (via `.passwords` file) - Static assets (styles/, components/, lib/) served directly from source - Clean URLs (/about → /about.html) Replace SITE_ROOT with absolute path to project directory. ## Testing & Debugging The watch debounce is 150ms. When developing: - Markdown changes only rebuild that file (fast) - Template/component/lib changes trigger full rebuild - Multiple simultaneous markdown changes trigger full rebuild (optimization) To test the admin UI locally: ```bash go run ./cmd/nebbet serve # Create a user first if .passwords doesn't exist go run ./cmd/nebbet user add admin # Visit http://localhost:8080/admin/ ``` Draft pages (draft: true) are silently skipped during builds but can be tested by removing the draft flag temporarily.