Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol diffdown.com
at main 219 lines 6.1 kB view raw view rendered
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