Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol
diffdown.com
1# AGENTS.md - Agentic Coding Guidelines for Diffdown
2
3This file provides guidelines for AI agents operating in this repository.
4
5## Project Overview
6
7Diffdown is a collaborative Markdown editor with ATProto (Bluesky) authentication. It uses Go for the backend and vanilla JavaScript/CodeMirror for the frontend.
8
9---
10
11## Build, Lint, and Test Commands
12
13### Makefile Targets
14
15| Command | Description |
16|---------|-------------|
17| `make build` | Build the Go application to `bin/server` |
18| `make run` | Run the server directly with `go run` |
19| `make test` | Run all tests with verbose output |
20| `make test-coverage` | Run tests with coverage report (`coverage.html`) |
21| `make lint` | Run golangci-lint |
22| `make fmt` | Format code with `go fmt` |
23| `make dev` | Run with hot reload using air |
24| `make migrate-up` | Run database migrations |
25| `make migrate-down` | Rollback last migration |
26| `make migrate-create name=<name>` | Create new migration |
27
28### Running a Single Test
29
30```bash
31# Run tests in a specific package
32go test -v ./internal/handler
33
34# Run a specific test function
35go test -v -run TestFunctionName ./internal/handler
36
37# Run tests matching a pattern
38go test -v -run "Test.*Handler" ./...
39```
40
41### Environment Variables
42
43```bash
44export DIFFDOWN_SESSION_SECRET="your-secret-here"
45export BASE_URL="http://127.0.0.1:8080"
46export PORT=8080
47export DB_PATH="./diffdown.db"
48```
49
50---
51
52## Code Style Guidelines
53
54### Go Conventions
55
56- **Go Version**: 1.22+
57- **Module**: `github.com/limeleaf/diffdown`
58- **Formatting**: Use `go fmt` or `gofumports` - run before committing
59
60### Imports
61
62Organize imports in three groups with blank lines between:
631. Standard library
642. Third-party packages
653. Internal packages
66
67```go
68import (
69 "context"
70 "encoding/json"
71 "fmt"
72 "log"
73 "net/http"
74 "time"
75
76 "github.com/gorilla/sessions"
77 "golang.org/x/crypto/bcrypt"
78
79 "github.com/limeleaf/diffdown/internal/auth"
80 "github.com/limeleaf/diffdown/internal/db"
81 "github.com/limeleaf/diffdown/internal/model"
82)
83```
84
85### Naming Conventions
86
87- **Files**: `snake_case.go` (e.g., `auth.go`, `handler.go`)
88- **Types/Interfaces**: `PascalCase` (e.g., `Handler`, `User`, `DB`)
89- **Functions/Methods**: `PascalCase` exported, `camelCase` unexported
90- **Constants**: `PascalCase` for exported, `camelCase` for unexported
91- **Variables**: `camelCase`, prefer short names (`db`, `err`, `w`, `r`)
92
93### Error Handling
94
95- Always handle errors explicitly; avoid bare `err != nil` checks
96- Log meaningful context with `log.Printf` before returning errors
97- Return user-friendly error messages in HTTP handlers
98- Use wrapped errors with `%w` for context: `fmt.Errorf("open db: %w", err)`
99
100### HTTP Handlers
101
102Follow the existing pattern in `internal/handler/handler
103func (h *Handler) Handler.go`:
104
105```goName(w http.ResponseWriter, r *http.Request) {
106 // 1. Authenticate/authorize
107 user := h.currentUser(r)
108 if user == nil {
109 http.Redirect(w, r, "/auth/login", http.StatusSeeOther)
110 return
111 }
112
113 // 2. Extract request data
114 data := r.FormValue("field")
115
116 // 3. Process (call DB, external API, etc.)
117 result, err := h.DB.Method(data)
118 if err != nil {
119 log.Printf("HandlerName: operation: %v", err)
120 h.render(w, "page.html", PageData{Error: "Friendly message"})
121 return
122 }
123
124 // 4. Respond
125 h.render(w, "page.html", PageData{Content: result})
126}
127```
128
129### Database
130
131- Use SQLite with WAL mode: `_journal_mode=WAL&_foreign_keys=on`
132- Use ULID for IDs: `db.NewID()` (uses `oklog/ulid/v2`)
133- Use migrations in `migrations/` directory (SQL files)
134- Apply migrations on startup: `database.Migrate()`
135
136### Authentication
137
138- Sessions: Gorilla `sessions.CookieStore`
139- Passwords: bcrypt (via `golang.org/x/crypto/bcrypt`)
140- ATProto OAuth: DPoP tokens with JWT
141
142### Testing
143
144- Tests should live in `*_test.go` files alongside the code they test
145- Use table-driven tests when testing multiple cases
146- Mock external dependencies (database, HTTP clients) where practical
147
148---
149
150## Project Structure
151
152```
153diffdown/
154├── cmd/
155│ ├── server/main.go # Application entry point
156│ └── migrate/main.go # Migration tool
157├── internal/
158│ ├── atproto/ # ATProto OAuth, DPoP, XRPC client
159│ ├── auth/ # Session management, password hashing
160│ ├── db/ # Database operations, migrations
161│ ├── handler/ # HTTP handlers
162│ ├── middleware/ # HTTP middleware (logging, auth injection)
163│ ├── model/ # Data models
164│ └── render/ # Markdown rendering
165├── migrations/ # SQL migration files
166├── static/ # Frontend assets
167├── templates/ # HTML templates
168├── Makefile # Build commands
169└── go.mod # Go dependencies
170```
171
172---
173
174## Common Tasks
175
176### Adding a New Handler
177
1781. Add method to `Handler` struct in `internal/handler/handler.go`
1792. Register route in `cmd/server/main.go`
1803. Add template to `templates/` if needed
181
182### Adding a Database Query
183
1841. Add method to `DB` struct in `internal/db/db.go`
1852. Follow naming: `GetX`, `CreateX`, `UpdateX`, `DeleteX`
1863. Use `db.scanUser` helper for scanning rows
187
188### Adding a Migration
189
190```bash
191make migrate-create name=create_users_table
192```
193
194Then edit the generated SQL file in `migrations/`.
195
196---
197
198## Dependencies
199
200Key dependencies (see `go.mod`):
201- `github.com/gorilla/sessions` - Session management
202- `github.com/mattn/go-sqlite3` - SQLite driver
203- `github.com/yuin/goldmark` - Markdown parsing
204- `github.com/golang-jwt/jwt/v5` - JWT tokens
205- `golang.org/x/crypto` - bcrypt, cryptography
206
207---
208
209## Frontend (JavaScript)
210
211The frontend uses vanilla JavaScript with CodeMirror 6 for the editor. Bundling is done with esbuild:
212
213```bash
214npm install
215npx esbuild --bundle --format=esm --outfile=static/vendor/codemirror.js ...
216```
217
218Static files are served from `static/` at `/static/`.
219