summaryrefslogtreecommitdiffstats
path: root/CLAUDE.md
blob: aaede3c109e3a6d86ea4940d3fb98833e91e4f3c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# 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
<!-- component:site-greeting {"name": "visitor"} -->
```

becomes:

```html
<site-greeting name="visitor"></site-greeting>
```

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.