Files
pad/docs/architecture.md
T
xarmian afe721d202 feat(cli): add Cloud mode to pad init, drop Docker option (TASK-837, TASK-838) (#272)
Merging despite Go (PostgreSQL) red — those failures (TestListItems_FTS_HyphenatedSearchTerm/task-five + TestAdminBillingStats_SidecarSidecarError_DegradesToLocalOnly TempDir cleanup race) are pre-existing on main and tracked in BUG-842.

Codex reviewed in 3 rounds (round 1 clean → round 2 found a real semantic bug → fix → round 3 clean). Tests, vet, and lint all green; remaining check failures are documented pre-existing.
2026-04-28 09:41:50 -04:00

8.4 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. Every pad <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.go is the main router; handlers_*.go group endpoints by resource.
  • internal/store/ — SQL abstractions and migrations. Each resource (workspaces, items, users, webhooks, sessions, …) has a <resource>.go file; migrations/ is the golang-migrate style migration sources. Backed by SQLite by default, PostgreSQL when PAD_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.toml parsing, environment-variable loading.

Request flow

  1. CLI or web UI sends an HTTP request to /api/v1/….
  2. Middleware chain in internal/server/middleware_*.go handles auth, rate limiting, metrics, audit logging.
  3. The handler in internal/server/handlers_*.go parses the request, calls one or more internal/store/* methods, and writes the JSON response.
  4. If the mutation is observable (item change, comment, etc.), the handler publishes an event to internal/events which 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 — mirrors internal/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 fields JSON schema that declares field keys (e.g. status, priority, due_date) with types and options.
  • Items have structured fields JSON validated against the collection's schema, plus optional rich Markdown content.
  • 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 Remote or Cloud mode, the same client targets a network-served Pad instance instead of a local daemon.

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 when PAD_TEST_POSTGRES_URL is set (see Makefile targets test and test-pg).
  • Web: cd web && npm run build to catch type / build errors; npm run check runs svelte-check.
  • CI: .github/workflows/ci.yml runs 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 install runs killall -9 pad system-wide, so any other pad process 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.