Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol
diffdown.com
AGENTS.md - Agentic Coding Guidelines for Diffdown#
This file provides guidelines for AI agents operating in this repository.
Project Overview#
Diffdown is a collaborative Markdown editor with ATProto (Bluesky) authentication. It uses Go for the backend and vanilla JavaScript/CodeMirror for the frontend.
Build, Lint, and Test Commands#
Makefile Targets#
| Command | Description |
|---|---|
make build |
Build the Go application to bin/server |
make run |
Run the server directly with go run |
make test |
Run all tests with verbose output |
make test-coverage |
Run tests with coverage report (coverage.html) |
make lint |
Run golangci-lint |
make fmt |
Format code with go fmt |
make dev |
Run with hot reload using air |
make migrate-up |
Run database migrations |
make migrate-down |
Rollback last migration |
make migrate-create name=<name> |
Create new migration |
Running a Single Test#
# Run tests in a specific package
go test -v ./internal/handler
# Run a specific test function
go test -v -run TestFunctionName ./internal/handler
# Run tests matching a pattern
go test -v -run "Test.*Handler" ./...
Environment Variables#
export DIFFDOWN_SESSION_SECRET="your-secret-here"
export BASE_URL="http://127.0.0.1:8080"
export PORT=8080
export DB_PATH="./diffdown.db"
Code Style Guidelines#
Go Conventions#
- Go Version: 1.22+
- Module:
github.com/limeleaf/diffdown - Formatting: Use
go fmtorgofumports- run before committing
Imports#
Organize imports in three groups with blank lines between:
- Standard library
- Third-party packages
- Internal packages
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/sessions"
"golang.org/x/crypto/bcrypt"
"github.com/limeleaf/diffdown/internal/auth"
"github.com/limeleaf/diffdown/internal/db"
"github.com/limeleaf/diffdown/internal/model"
)
Naming Conventions#
- Files:
snake_case.go(e.g.,auth.go,handler.go) - Types/Interfaces:
PascalCase(e.g.,Handler,User,DB) - Functions/Methods:
PascalCaseexported,camelCaseunexported - Constants:
PascalCasefor exported,camelCasefor unexported - Variables:
camelCase, prefer short names (db,err,w,r)
Error Handling#
- Always handle errors explicitly; avoid bare
err != nilchecks - Log meaningful context with
log.Printfbefore returning errors - Return user-friendly error messages in HTTP handlers
- Use wrapped errors with
%wfor context:fmt.Errorf("open db: %w", err)
HTTP Handlers#
Follow the existing pattern in internal/handler/handler func (h *Handler) Handler.go:
// 1. Authenticate/authorize
user := h.currentUser(r)
if user == nil {
http.Redirect(w, r, "/auth/login", http.StatusSeeOther)
return
}
// 2. Extract request data
data := r.FormValue("field")
// 3. Process (call DB, external API, etc.)
result, err := h.DB.Method(data)
if err != nil {
log.Printf("HandlerName: operation: %v", err)
h.render(w, "page.html", PageData{Error: "Friendly message"})
return
}
// 4. Respond
h.render(w, "page.html", PageData{Content: result})
}
Database#
- Use SQLite with WAL mode:
_journal_mode=WAL&_foreign_keys=on - Use ULID for IDs:
db.NewID()(usesoklog/ulid/v2) - Use migrations in
migrations/directory (SQL files) - Apply migrations on startup:
database.Migrate()
Authentication#
- Sessions: Gorilla
sessions.CookieStore - Passwords: bcrypt (via
golang.org/x/crypto/bcrypt) - ATProto OAuth: DPoP tokens with JWT
Testing#
- Tests should live in
*_test.gofiles alongside the code they test - Use table-driven tests when testing multiple cases
- Mock external dependencies (database, HTTP clients) where practical
Project Structure#
diffdown/
├── cmd/
│ ├── server/main.go # Application entry point
│ └── migrate/main.go # Migration tool
├── internal/
│ ├── atproto/ # ATProto OAuth, DPoP, XRPC client
│ ├── auth/ # Session management, password hashing
│ ├── db/ # Database operations, migrations
│ ├── handler/ # HTTP handlers
│ ├── middleware/ # HTTP middleware (logging, auth injection)
│ ├── model/ # Data models
│ └── render/ # Markdown rendering
├── migrations/ # SQL migration files
├── static/ # Frontend assets
├── templates/ # HTML templates
├── Makefile # Build commands
└── go.mod # Go dependencies
Common Tasks#
Adding a New Handler#
- Add method to
Handlerstruct ininternal/handler/handler.go - Register route in
cmd/server/main.go - Add template to
templates/if needed
Adding a Database Query#
- Add method to
DBstruct ininternal/db/db.go - Follow naming:
GetX,CreateX,UpdateX,DeleteX - Use
db.scanUserhelper for scanning rows
Adding a Migration#
make migrate-create name=create_users_table
Then edit the generated SQL file in migrations/.
Dependencies#
Key dependencies (see go.mod):
github.com/gorilla/sessions- Session managementgithub.com/mattn/go-sqlite3- SQLite drivergithub.com/yuin/goldmark- Markdown parsinggithub.com/golang-jwt/jwt/v5- JWT tokensgolang.org/x/crypto- bcrypt, cryptography
Frontend (JavaScript)#
The frontend uses vanilla JavaScript with CodeMirror 6 for the editor. Bundling is done with esbuild:
npm install
npx esbuild --bundle --format=esm --outfile=static/vendor/codemirror.js ...
Static files are served from static/ at /static/.