Grouped nice-to-haves called out in the pre-launch audit. 1. docs/architecture.md — new contributor-focused architecture doc. CLAUDE.md covers the same ground but is agent-oriented; this is the human companion. Covers backend layout, request flow, frontend / data model / CLI↔daemon model / agent integration / testing. 2. .env.example — extended to document every PAD_* variable in docs/deployment.md (core, database, real-time events, security, email). Existing Postgres/Redis + encryption secrets kept at the top; new variables grouped by concern with inline comments and safe defaults commented out. 3. .gitattributes — normalize LF line endings repo-wide, mark binary assets, and flag web/build + web/.svelte-kit as generated so they don't pollute GitHub linguist stats or PR diffs. 4. Makefile — CAUTION comment on `make install` noting that the `killall -9 pad` step is system-wide; anyone else's pad daemon on the same machine gets killed too. Designed for single-developer local setups; not for shared hosts. Parent: PLAN-644.
8.3 KiB
Architecture
High-level map of the Pad codebase for contributors. CLAUDE.md covers the same ground but is written for AI agents working in the repo — this doc is the human-readable companion.
Shape
Pad ships as one Go binary. The SvelteKit web UI is built into static
assets and embedded into the binary at compile time via go:embed, so a
deployed Pad has exactly one moving piece on the filesystem. SQLite is the
default backing store; PostgreSQL + Redis is the alternate mode for
multi-node production use.
┌────────────┐ ┌────────────┐ ┌───────────────┐
│ CLI (pad) │ │ Web UI │ │ AI agents │
│ │ │ (Svelte 5)│ │ via /pad │
└─────┬──────┘ └──────┬─────┘ └───────┬───────┘
│ HTTP │ HTTP/SSE │ HTTP
▼ ▼ ▼
┌─────────────────────────────┐
│ Pad HTTP server │
│ (internal/server/*.go) │
└─┬────────┬───────────────┬──┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌────────────┐
│ SQLite / │ │ EventBus│ │ Webhooks + │
│ Postgres │ │ (SSE) │ │ Email │
└──────────┘ └─────────┘ └────────────┘
Backend (Go)
cmd/pad/main.go— Cobra CLI entry point. Everypad <command>is registered here. The same binary also serves as the daemon (pad server start) and as the CLI client that talks to it.internal/server/— HTTP router (chi), middleware, handlers, SSE hub.server.gois the main router;handlers_*.gogroup endpoints by resource.internal/store/— SQL abstractions and migrations. Each resource (workspaces, items, users, webhooks, sessions, …) has a<resource>.gofile;migrations/is the golang-migrate style migration sources. Backed by SQLite by default, PostgreSQL whenPAD_DB_DRIVER=postgres.internal/models/— shared Go structs that flow through the API response boundary (Collection,Item,User,View, etc.).internal/items/— field-schema validation: collection schemas declare typed fields (select, text, date, number, …) and this package validates item fields against them.internal/collections/— default collection definitions per template (startup,scrum,hiring,interviewing,product) and workspace bootstrap logic.internal/cli/— HTTP client used by the CLI to talk to the local daemon, plus formatting helpers for terminal output.internal/events/— in-process EventBus that fans SSE updates to connected clients. In Redis mode, a pub/sub bridge replaces the in-memory bus so multiple Pad replicas stay in sync.internal/webhooks/— outbound webhook dispatcher with HMAC signing, retries, and delivery log.internal/email/— transactional email via Maileroo. Used for workspace invitations and password resets; nil-safe if unconfigured.internal/diff/— per-item version history storage + diff rendering.internal/links/— resolver for[[wiki-link]]syntax across items.internal/config/— workspace detection,.pad.tomlparsing, environment-variable loading.
Request flow
- CLI or web UI sends an HTTP request to
/api/v1/…. - Middleware chain in
internal/server/middleware_*.gohandles auth, rate limiting, metrics, audit logging. - The handler in
internal/server/handlers_*.goparses the request, calls one or moreinternal/store/*methods, and writes the JSON response. - If the mutation is observable (item change, comment, etc.), the
handler publishes an event to
internal/eventswhich fans out to connected SSE clients at/api/v1/events.
Frontend (SvelteKit + Svelte 5)
web/src/routes/— page routes. File-based: a folder corresponds to a URL segment.[username]/[workspace]/...is the main workspace-scoped tree;console/is the server-admin UI.web/src/lib/components/— reusable UI (BottomSheet, FieldEditor, ReactionPicker, NestedChildren, etc.).web/src/lib/stores/— Svelte 5 rune-based stores for cross-route state (current workspace, page title, current user).web/src/lib/api/client.ts— typed HTTP client. Every REST endpoint the backend exposes has a method here; adding an endpoint means adding a client method too.web/src/lib/types/index.ts— mirrorsinternal/models/as TypeScript types.
The web UI is built with npm run build (static adapter) and the
build/ output is embedded into the Go binary via //go:embed in
internal/server/embed.go. npm run dev runs a Vite dev server on
:5173 that proxies API requests to the running Pad daemon on :7777
— fast iteration without rebuilding the binary.
Data model
Workspaces
└── Collections (typed by a JSON schema)
└── Items (structured fields + optional markdown content)
├── parent/child links
├── blocks / blocked-by dependency links
└── comments, reactions, tags
- Collections have a
fieldsJSON schema that declares field keys (e.g.status,priority,due_date) with types and options. - Items have structured
fieldsJSON validated against the collection's schema, plus optional rich Markdowncontent. - Parent/child links power progress tracking and burndown; any item type can be a parent of any item type.
[[wiki-link]]syntax resolves across all items in a workspace and renders as clickable links in the UI.
CLI ↔ daemon model
There is only one binary, pad. Some commands run purely client-side
(pad item show REF), but most go through the daemon:
pad server start— run the daemon foreground (normal dev mode).pad auth configure— first-run credential setup, auto-starts the local daemon on first use.- All other
pad <verb>commands are CLI → HTTP → daemon → SQLite.
The CLI discovers the daemon via ~/.pad/credentials.json (see
internal/cli/client.go). In Docker/Kubernetes mode, a different
client targets the network-served Pad instance.
Agent integration
skills/pad/SKILL.md ships inside the binary and gets installed into
an AI agent's configuration by pad agent install. The skill is
natural-language — it documents the CLI well enough that any
Claude/Cursor/Copilot-style agent can drive Pad via terminal calls.
Testing
- Go:
go test ./...covers the backend;internal/store/tests run against real SQLite by default and against PostgreSQL whenPAD_TEST_POSTGRES_URLis set (seeMakefiletargetstestandtest-pg). - Web:
cd web && npm run buildto catch type / build errors;npm run checkruns svelte-check. - CI:
.github/workflows/ci.ymlruns the full matrix — Go (SQLite + PostgreSQL + race), govulncheck, golangci-lint (new-issues mode), web build, npm audit, svelte-check.
Build and install
See CLAUDE.md for the day-to-day commands (make install is the one
you'll run most). The short version:
make build— build web UI + Go binary to./pad.make install— build, kill running daemon, install to$HOME/.local/bin/pad, restart. Heads-up:make installrunskillall -9 padsystem-wide, so any otherpadprocess on the host (including other users' daemons) gets killed.make dev-web— SvelteKit hot-reload dev server.
Further reading
CLAUDE.md— agent-focused development guide (identical scope, different audience).docs/deployment.md— full environment-variable reference, production deployment shapes.docs/backup.md— backup and restore procedures.SECURITY.md— reporting a vulnerability, threat model, hardening tips.