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 fmt or gofumports - run before committing

Imports#

Organize imports in three groups with blank lines between:

  1. Standard library
  2. Third-party packages
  3. 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: PascalCase exported, camelCase unexported
  • Constants: PascalCase for exported, camelCase for unexported
  • Variables: camelCase, prefer short names (db, err, w, r)

Error Handling#

  • Always handle errors explicitly; avoid bare err != nil checks
  • Log meaningful context with log.Printf before returning errors
  • Return user-friendly error messages in HTTP handlers
  • Use wrapped errors with %w for 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() (uses oklog/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.go files 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#

  1. Add method to Handler struct in internal/handler/handler.go
  2. Register route in cmd/server/main.go
  3. Add template to templates/ if needed

Adding a Database Query#

  1. Add method to DB struct in internal/db/db.go
  2. Follow naming: GetX, CreateX, UpdateX, DeleteX
  3. Use db.scanUser helper 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 management
  • github.com/mattn/go-sqlite3 - SQLite driver
  • github.com/yuin/goldmark - Markdown parsing
  • github.com/golang-jwt/jwt/v5 - JWT tokens
  • golang.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/.