* chore(mcp): bump ToolSurfaceVersion 0.3 → 0.4; document v0.4 envelope (TASK-1418)
Final PR of PLAN-1410. The contractual announcement that the v0.4
bootstrap shape is stable.
## What
1. internal/mcp/version.go — ToolSurfaceVersion: "0.3" → "0.4".
The godoc on the constant gains a full v0.4 changelog entry
enumerating each shape change shipped by PLAN-1410's six
bootstrap PRs:
- BootstrapCollection projection (TASK-1412): drops id,
workspace_id, created_at, updated_at, settings; schema as
a nested JSON object.
- BootstrapRole projection (TASK-1423): drops id,
workspace_id, tools, created_at, updated_at.
- Convention slug dropped (TASK-1413).
- Top-level recent_activity duplicate removed (TASK-1413).
- BootstrapDashboard wrapper caps five sub-arrays (TASK-1413
+ TASK-1422): attention, recent_activity, active_items,
active_plans, by_role at 5 entries each, parallel
*_overflow_count fields. suggested_next deliberately
excluded — already capped to 3 upstream.
- Schema label omitted when label == TitleCase(key) (TASK-1424).
Plus an explicit compatibility note: all v0.4 changes are
additive or subtractive (no field renames); clients that read
the preserved field names keep working unchanged.
2. CLAUDE.md updates:
- "## MCP server" header: v0.3 catalog → v0.4 catalog, with a
one-paragraph summary of what v0.4 shipped.
- "Surface:" Tools bullet: v0.3 → v0.4, with a note that the
tool/action surface is unchanged — only the bootstrap JSON
these tools return has been trimmed.
- "Stability contract": ToolSurfaceVersion (currently "0.4"),
comprehensive single-paragraph description of the v0.4
envelope, cumulative size reduction (40% live / 54% fixture),
and explicit additive/subtractive note.
## Why the strategy worked
PLAN-1410's "version bump last" strategy paid off:
- Each individual shape PR (TASK-1412/1413/1422/1423/1424) was
reviewable in isolation against a stable v0.3 contract.
- The six skill-side PRs (TASK-1414/1415/1416) had no MCP-shape
impact and didn't need any version bump consideration.
- v0.4 is now announced as a single comprehensive contract change,
not five separate version bumps — easier for downstream MCP
consumers (Claude Desktop, Cursor, future Pad Cloud remote MCP)
to reason about.
## Verification
- `make check` — golangci-lint 0 issues, all Go tests pass
(including the version-tracking tests in catalog_meta_test.go
that auto-pin to whatever ToolSurfaceVersion is set to),
govulncheck clean, web build clean.
- MCP handshake (verified via `pad mcp serve` + an initialize
JSON-RPC request) advertises
capabilities.experimental.padToolSurface.version = "0.4".
padCmdhelp.version stays at "0.1" as expected.
## Post-merge follow-ups
After this lands:
- Update PLAN-1410's Result section with a "v0.4 announced" line
and the final post-everything measurement (taken against
docapp after `make install`).
- Flip PLAN-1410 status from `active` → `completed`.
These are pad-item operations, not git changes.
Parent: PLAN-1410. Closes the plan.
* fix(mcp): update stale v0.3 references after ToolSurfaceVersion bump (TASK-1418 follow-up)
Address Codex P2 + P3 findings on PR #544: bumping
ToolSurfaceVersion in version.go left four runtime/user-facing
docs still claiming v0.3:
P2 — runtime MCP docs:
- internal/mcp/instructions.md "## Tool surface (v0.3)" → v0.4
- internal/mcp/catalog_meta.go "v0.3 server-introspection tool" → "(v0.4 catalog)"
- internal/mcp/catalog_meta.go padMetaToolDescription twice:
* "the v0.3 tool catalog" → "the v0.4 tool catalog"
* "v0.3 catalog dump" → "v0.4 catalog dump"
- internal/mcp/catalog_meta.go actionMetaToolSurface godoc:
"v0.3 catalog" → "catalog" (de-versioned; the comment is
about scope, not version)
P3 — public README:
- README.md "Tool catalog (v0.3)" → "Tool catalog (v0.4)"
- README.md "tool_surface_version: '0.3'" → "'0.4'" with a
pointer to PLAN-1410's bootstrap-trim summary and
version.go's full v0.4 changelog.
Without these, agents reading the initialize-instructions blob or
pad_meta's tool description (both of which are part of the
runtime MCP surface, not just internal docs) would see v0.3 while
the handshake / pad_meta.action: version returned v0.4 — the
exact "contradictory metadata depending on what you read" failure
mode Codex flagged.
Same skill-↔-code sync pattern that has been a running theme
through PLAN-1410's review loops. The cluster of stale references
is a classic side effect of a version bump landing late in a
plan — the version constant is one string, but downstream prose
that names it lives in multiple places.
Verified no remaining "v0.3" claims that imply currency — `grep -rn
"v0\.3\|tool_surface_version" --include="*.{go,md}"` returns only
historical-context mentions in changelog godocs (correct) and the
runtime constant readback (correctly returns "0.4" now).
Parent: PLAN-1410 / TASK-1418.
* fix(mcp): correct schema-type-change disclosure + stale cmdhelp-walker description (TASK-1418 follow-up)
Address Codex round 2 P3 findings on PR #544:
## P3 — `cmd/pad/mcp.go` still described the retired leaf walker
The `pad mcp serve` command's Long description said "every leaf
command becomes an MCP tool, except the curated allow-list
exclusions" — that was true under v0.1 but the cmdhelp leaf
walker was retired in TASK-981 (PLAN-969's v0.2 rollout). The
v0.2/v0.3/v0.4 surface has always been the hand-curated catalog
of eight resource × action tools + pad_set_workspace.
Updated the Long description to:
- Name the v0.4 catalog explicitly.
- List the eight resource × action tools.
- Note that cmdhelp v0.1 still drives per-command arg schemas
at dispatch time (so it's not gone, just no longer drives
tool naming/count).
- Reference TASK-981 for the cutover.
## P3 — "additive/subtractive only" was misleading
The compatibility note in `version.go` and `CLAUDE.md` claimed
all v0.4 changes were additive or subtractive. That glossed over
one breaking change in TASK-1412: `collections[].schema` went
from a JSON-encoded string ("schema":"{\"fields\":...}") to a
nested JSON object ("schema":{"fields":...}). For any v0.3
consumer that read schema as a string and JSON.parse()'d it
themselves, that's a TYPE change, not a no-op.
Updated both godoc and CLAUDE.md to explicitly call this out
as the one breaking change, separately from the additive/
subtractive bucket. Better for downstream MCP consumers to see
the truth than to discover it via runtime failure.
The remaining v0.4 changes ARE additive (overflow counts on
BootstrapDashboard) or subtractive (dropped fields with named
canonical alternatives) — those parts of the original note
are accurate and kept.
Honesty about compatibility is more valuable than a tidy
narrative. Surfaced explicitly in the godoc + the public
contract doc; PLAN-1410's Result section was already honest
about the field-level deltas.
Parent: PLAN-1410 / TASK-1418.
26 KiB
Pad — Development Guide
What This Is
Pad is a project management tool for developers and AI agents. Single Go binary with embedded SvelteKit web UI, SQLite storage, and multi-agent skill support (Claude Code, Cursor, Windsurf, Codex, Copilot, Amazon Q, Junie).
Related repo: The marketing website (getpad.dev) lives at ../pad-web — a separate SvelteKit site deployed to Vercel.
Architecture
- Backend: Go (cmd/pad/main.go) → REST API (internal/server/) → SQLite (internal/store/)
- Frontend: SvelteKit 2 + Svelte 5 (web/src/) → static build embedded in Go binary
- Data model: Workspaces → Collections (typed with JSON schemas) → Items (structured fields + rich content)
- CLI: Cobra commands in cmd/pad/main.go, HTTP client in internal/cli/
- Agent skill: Single natural-language
/padskill in skills/pad/SKILL.md
Build & Install
make build # Build web UI + Go binary (./pad)
make install # Build, kill server, install to ~/.local/bin/pad, restart
make build-go # Build Go only (skip web — faster when only backend changes)
make test # Run Go tests
make web # Build web UI only
make dev-web # Run SvelteKit dev server (hot reload on :5173)
After making changes, always run make install to rebuild the binary, install it, and restart the server. The web UI at http://localhost:7777 will reflect the changes.
Quick iteration loop
- Backend only:
make install(skips web rebuild if no frontend changes — edit Makefile to usebuild-goinstead ofbuildin the install target) - Frontend only:
make web && make installor usemake dev-webfor hot reload during development - Full rebuild:
make install
Key Directories
cmd/pad/main.go — CLI entry point, all Cobra commands
internal/
server/ — HTTP API handlers, SSE, middleware
store/ — SQLite CRUD, migrations, FTS
models/ — Go types (Collection, Item, View, etc.)
items/ — Field validation against schemas
collections/ — Default definitions, workspace templates
cli/ — HTTP client, formatting helpers
events/ — EventBus for real-time SSE
config/ — Workspace detection, .pad.toml
diff/ — Version diff storage
webhooks/ — Webhook dispatcher with HMAC signing
email/ — Transactional email via Maileroo
links/ — Wiki-link parsing
web/src/
routes/ — SvelteKit pages
lib/api/client.ts — TypeScript API client
lib/types/index.ts — TypeScript types
lib/stores/ — Svelte 5 rune stores
lib/components/ — Reusable UI components
skills/pad/SKILL.md — Claude Code skill (embedded in binary)
API
REST API at /api/v1/. Key endpoints:
GET/POST /workspaces/{ws}/collections— collection CRUDGET/POST /workspaces/{ws}/collections/{coll}/items— item CRUDGET/PATCH/DELETE /workspaces/{ws}/items/{slug}— item by slugGET /workspaces/{ws}/dashboard— computed project overview (active items, plans, attention, blockers)GET /workspaces/{ws}/activity— workspace activity feed (enriched with item titles + change details)GET/POST/DELETE /workspaces/{ws}/webhooks— webhook managementGET /workspaces/{ws}/items/{slug}/children— child items linked to a parentGET /workspaces/{ws}/items/{slug}/progress— child item completion progressGET/POST /workspaces/{ws}/items/{slug}/links— item relationships (blocks/blocked-by, parent/child)GET /search?q=query&workspace=slug— full-text searchGET /api/v1/events?workspace=slug— SSE real-time eventsGET /api/v1/collab/{itemID}?schema_version=N— WebSocket upgrade for real-time collaborative editing (Yjs binary protocol; client must announce schema version)GET /workspaces/{ws}/members— list members + pending invitationsPOST /workspaces/{ws}/members/invite— invite user to workspaceGET /api/v1/auth/session— auth status (setup_required,setup_method,auth_method,authenticated,user)POST /api/v1/auth/bootstrap— create the first admin account from localhost on a fresh instancePOST /api/v1/auth/register— create account (admin-created or invitation-based after setup)POST /api/v1/auth/login— email/password login (returns session token)POST /api/v1/auth/logout— destroy sessionGET/PATCH /api/v1/auth/me— current user profile (GET) and update name/password (PATCH)POST /api/v1/auth/forgot-password— request password reset emailPOST /api/v1/auth/reset-password— reset password with tokenGET/POST/DELETE /api/v1/auth/tokens— user-scoped API tokensGET/PATCH /api/v1/admin/settings— platform settings (admin-only)POST /api/v1/admin/test-email— send test email (admin-only)POST /api/v1/invitations/{code}/accept— accept workspace invitation
Authentication
User-based authentication with email/password. When no users exist (fresh install), everything works without auth until the instance is initialized with pad auth setup. Once the first admin exists, all API requests require authentication.
# First-time setup
pad auth setup # Create the first admin account on the server host
# Subsequent logins
pad auth login # Email + password prompt
pad auth whoami # Show current user
pad auth logout # Sign out
pad auth reset-password user@example.com # Generate reset link (admin fallback)
# Credentials stored in ~/.pad/credentials.json (0600 permissions)
# CLI auto-attaches auth token to all API requests
After a startup-template workspace is created (via pad init or pad workspace init — note that pad auth setup only creates the admin account, not a workspace), the success output points new users at the seeded onboarding entry point. Open a fresh agent session in the workspace's directory and say:
use pad to get IDEA-1
startup-template workspaces seed IDEA-1 (plus PLAN-2, TASK-3, DOC-4) as a first-person note from the workspace owner's future self. Any of the four is a viable entry point for /pad let's discuss <REF>; IDEA-1 is the one the post-signup hint surfaces because "I want to start using Pad" is itself an idea. See internal/collections/templates_onboarding.go for the bodies, and PLAN-1131 for the design history.
Workspace membership
pad workspace members # List workspace members
pad workspace invite user@example.com # Invite (adds directly if user exists, creates join code if not)
pad workspace invite user@example.com --role viewer # Invite with specific role
pad workspace join <code> # Accept a workspace invitation
Roles: owner (full access), editor (CRUD items), viewer (read-only).
Email (optional)
Transactional email via Maileroo. When configured, workspace invitations are sent by email. Without it, everything works via CLI-based join codes.
# Environment variables (or ~/.pad/config.toml)
PAD_MAILEROO_API_KEY=your-sending-key # Required to enable email
PAD_EMAIL_FROM=noreply@yourdomain.com # Sender address (default: noreply@getpad.dev)
PAD_EMAIL_FROM_NAME=Pad # Sender display name (default: Pad)
CLI
Items are referenced by issue ID (e.g. TASK-5, BUG-8) wherever a <ref> argument appears.
Slugs also work but issue IDs are preferred.
pad item create <collection> "title" [--status X] [--priority X] [--parent REF]
pad item list [collection] [--status X] [--parent REF] [--all]
pad item show <ref> # e.g. pad item show TASK-5
pad item update <ref> [--status X] [--priority X]
pad item delete <ref>
pad item move <ref> <target-collection>
pad item search "query"
pad project dashboard # Project dashboard
pad project next # Recommended next task
pad project standup [--days N] # Daily standup report
pad project changelog [--days N] [--parent REF] # Release notes from completed items
pad item block <source> <target> # e.g. pad item block TASK-5 TASK-8
pad item blocked-by <item> <blocker>
pad item deps <ref> # Show dependencies
pad item unblock <source> <target>
pad collection list # List collections
pad collection create "Name" --fields "key:type[:opts]; ..." # compact DSL for simple schemas
pad collection create "Name" --schema '<json>' # full CollectionSchema (terminal_options, defaults, computed, relations)
pad item edit <ref> # Open in $EDITOR
pad workspace init [--template X] # Create workspace
pad agent install [tool] # Install /pad skill for AI tools
pad workspace onboard # Analyze codebase, suggest conventions
pad server open # Open web UI in browser
pad project watch # Real-time activity stream
pad github link [item-ref] # Link current branch's PR to item
pad github status [item-ref] # Show PR status for linked items
pad github unlink <item-ref> # Remove PR link from item
pad item bulk-update --status done TASK-5 TASK-8 # Batch operations
pad webhook list/create/delete/test # Webhook management
pad auth setup # Initialize a fresh instance with the first admin
pad auth login # Log in
pad auth logout # Sign out
pad auth whoami # Show current user
pad workspace members # List workspace members
pad workspace invite <email> [--role X] # Invite user to workspace
pad workspace join <code> # Accept workspace invitation
Collection names accept singular forms: task→tasks, idea→ideas, doc→docs.
MCP server
Pad runs as a local Model Context Protocol server so Claude Desktop / Cursor / Windsurf can call non-interactive pad commands as tools. As of PLAN-1410 the tool surface is a hand-curated v0.4 catalog in internal/mcp/catalog_*.go — one ToolDef per resource (pad_item, pad_workspace, pad_collection, pad_project, pad_role, pad_search, pad_meta, pad_playbook) with an action enum dispatching to underlying CLI commands. v0.2 introduced the catalog (PLAN-969 / TASK-981); v0.3 added pad_playbook, pad_meta.action: bootstrap, pad_set_workspace's embedded-bootstrap response, and the pad://workspace/{ws}/bootstrap resource (PLAN-1377 / TASK-1380); v0.4 trimmed the bootstrap payload by ~40% (PLAN-1410) — slim BootstrapCollection + BootstrapRole projections (no UUIDs/timestamps/settings; nested schema object; redundant labels omitted), removed top-level recent_activity duplicate, dropped convention slug, and added a BootstrapDashboard wrapper that caps five sub-arrays (attention, recent_activity, active_items, active_plans, by_role) at 5 entries each with parallel *_overflow_count fields. The pre-catalog v0.1 cmdhelp leaf walker is retired.
cmdhelp is still consumed at dispatch time — BuildCLIArgs reads individual command schemas to translate the catalog's snake_case input map into CLI args. cmdhelp no longer drives tool naming or count.
When adding a new pad command, decide whether it belongs on the MCP surface. If yes, add an action to the appropriate pad_<resource> ToolDef in internal/mcp/catalog_<resource>.go. The action's handler — usually passThrough([]string{"resource", "subcommand"}) — wires it through to dispatch. Don't expose interactive (prompts the user), destructive (mutates auth / filesystem state), long-running (streaming watcher), or recursive (would spawn another MCP server) commands.
pad mcp serve # JSON-RPC over stdio (called by clients)
pad mcp install <client> # Write the client's mcp.json entry
pad mcp uninstall <client> # Remove the entry
pad mcp status # Install state across supported clients
Surface:
- Tools: the v0.4 catalog — eight resource × action tools (
pad_item,pad_workspace,pad_collection,pad_project,pad_role,pad_search,pad_meta,pad_playbook) pluspad_set_workspace(takes aworkspaceslug only — no action enum). The eight resource × action tools takeaction: <verb>to choose what they do.pad_playbookis the playbook surface from PLAN-1377 —list/get/runmirror the CLI'spad playbooksubcommands;runis side-effect-free and returns the body + bound args for the agent to execute. v0.4 (PLAN-1410) didn't change the tool/action surface; it trimmed the bootstrap JSON those tools/resources return — see the Stability contract subsection below for details. - Resources:
pad://workspace/{ws}/items/{ref},pad://workspace/{ws}/items,pad://workspace/{ws}/dashboard,pad://workspace/{ws}/collections,pad://workspace/{ws}/bootstrap(one-shot workspace overview — user + collections + always-on conventions + roles + playbook metadata + dashboard + recent activity), plus the server-widepad://_meta/version. - Prompts:
pad_plan,pad_ideate,pad_retro,pad_onboard— multi-step workflows lifted fromskills/pad/SKILL.md.
pad_set_workspace pins the session default workspace; its response embeds the bootstrap blob so agents pin + load workspace context in one round-trip. The same payload is available on demand via pad_meta.action: bootstrap and the pad://workspace/{ws}/bootstrap resource.
Stability contract. Two version constants live in internal/mcp/version.go, advertised in the handshake under capabilities.experimental.padCmdhelp and capabilities.experimental.padToolSurface:
CmdhelpVersion(currently"0.1") — the cmdhelp CLI help-tree contract. Bump when CLI flag/arg schemas change incompatibly.ToolSurfaceVersion(currently"0.4") — the MCP tool catalog contract. Bump when tool names, action enums, or parameter shapes change incompatibly. v0.3 (PLAN-1377 / TASK-1380) introducedpad_meta.action: bootstrap,pad_set_workspace's embedded-bootstrap response, and thepad://workspace/{ws}/bootstrapresource. v0.4 (PLAN-1410) is a comprehensive bootstrap-payload trim — same tool catalog, slimmer JSON shape inside bootstrap responses:BootstrapCollectionprojection dropsid/workspace_id/timestamps/settingsand emitsschemaas a nested object;BootstrapRoleprojection drops UUIDs/timestamps/tools; conventionslugdropped; top-levelrecent_activity(a duplicate ofdashboard.recent_activity) removed; newBootstrapDashboardwrapper caps five sub-arrays (attention,recent_activity,active_items,active_plans,by_role) at 5 entries each with parallel*_overflow_countfields; redundant schema labels omitted whenlabel == TitleCase(key). Cumulative size reduction: ~40% on a representative workspace, ~54% on the fixture (see PLAN-1410's Result section for per-section deltas). Compatibility: most changes are subtractive (dropped fields) or additive (overflow counts), but one type change is breaking:collections[].schemawent from a JSON-encoded string to a nested JSON object — clients that JSON.parse()'d the string need to consume it directly as an object now. The dropped fields (UUIDs, timestamps, settings, duplicaterecent_activity, conventionslug) have canonical alternatives (slugs for addressing;pad collection list/pad role listfor the full models when needed).
Both are also returned by pad://_meta/version and pad_meta.action: version.
Dispatchers. Two ship in internal/mcp/:
ExecDispatcher— shells out to thepadbinary; subprocess inherits credentials from~/.pad/credentials.json. Used bypad mcp servefor local stdio MCP.HTTPHandlerDispatcher— calls pad-cloud's HTTP handlers in-process with the requesting user attached viaserver.WithCurrentUser. Used by the future/mcpendpoint (PLAN-943) where the dispatcher serves multiple OAuth users from a single process. Tools are wired into the route table atinternal/mcp/dispatch_http.go(routeTable); add aRouteMapperper command —mapItemCreateis the seed entry from TASK-965.
Code lives in internal/mcp/ (built on github.com/mark3labs/mcp-go). Public docs at getpad.dev/mcp/local.
Data Model
- Collections have JSON schemas defining typed fields (select, text, date, number, etc.)
- Items have structured
fieldsJSON + optional richcontent(markdown) - Parent/child links: Any item can be a parent of child items (
--parent REF). Children get progress tracking, burndown charts, and nested rendering. Plans are the most common parent, but Ideas, Docs, or Tasks can also have children. - Wiki-links
[[Title]]resolve across all items, rendered as clickable links - Default collections: Tasks, Ideas, Plans, Docs (software /
startuptemplate) - Templates are grouped by category so Pad supports more than just software workflows:
- Software:
startup(default),scrum,product - People:
hiring(company-side: Requisitions → Candidates → Loops → Feedback),interviewing(candidate-side: Applications, Interviews, Companies, Contacts) - Research / Content / Operations / Personal are reserved categories awaiting their first templates.
- Software:
- Each template ships a curated starter pack (conventions + playbooks + sample items) appropriate to its domain — trigger vocabularies vary (
on-commitvson-candidate-advancevson-interview-scheduled). - Set the template via
pad workspace init --template <name>. Runningpad initwith no flag in a TTY opens an interactive picker grouped by category. Runpad workspace init --list-templatesto see the current catalog. - See
PLAN-609andIDEA-583in this workspace for the design history.
Playbooks
Playbooks are first-class invokable procedures. They live in the playbooks collection (typed item, just like Tasks/Ideas/Plans) but carry two extra fields that make them user-callable:
invocation_slug— optional, workspace-unique, kebab-case (regex^[a-z0-9][a-z0-9-]*[a-z0-9]$, 2+ chars). When set, the agent dispatches/pad <slug>directly to this playbook (slug routing). Leave blank for trigger-only playbooks (e.g.trigger=on-releasethat auto-load on intent match).arguments— JSON array of{name, type, required, default, description, enum}entries. Types:ref,string,flag,enum,number. Mirrors the playbook body's## Argumentssection; the structured field is the queryable form (used bypad playbook run's strict parser) and the markdown is the human-readable mirror.
Invocation model. Three surfaces, one playbook:
- Claude Code (agent NL):
/pad ship PLAN-1377 stop-after-each— the/padskill matches the first token against the bootstrap's playbook slug list and binds the rest with flexible NL parsing. - CLI (strict positional):
pad playbook run ship TASK-10,TASK-11 merge-strategy=rebase— the server applies strict positional + bareword-flag +key=valueparsing. - MCP:
pad_playbooktool withaction: list | get | run.runaccepts either a pre-parsedargsmap or raw CLI tokens viaraw_args.
Bootstrap returns metadata at startup. pad bootstrap (CLI + GET /api/v1/workspaces/{ws}/agent/bootstrap + pad://workspace/{ws}/bootstrap resource + pad_set_workspace response embed) returns the workspace's playbook metadata in one round-trip — ref, title, slug, invocation_slug, trigger, scope, status, has_arguments, summary per entry. No bodies in the bootstrap blob; the agent loads the full body via pad playbook show <slug> only when invoking. Keeps context light while still letting the agent route /pad ship without a tool call.
Seeded ship playbook. The startup template ships a generic ship playbook (invocation_slug=ship) derived from the personal /ship-tasks slash command. Fresh pad workspace init --template startup workspaces get it as PLAYB-N out of the box. See internal/collections/templates_startup_ship.go for the body + de-personalization choices.
Library — discovery surface for invokable playbooks. Per PLAN-1397's invokable-first overhaul, the playbook library (web UI: /[username]/[workspace]/library?tab=playbooks; JSON: GET /api/v1/playbook-library) carries the three canonical invokable workflow playbooks — /pad ship, /pad plan, /pad decompose — under a single agent-workflows category. Each library card surfaces a /pad <slug> chip and an N args badge so the invocation model is visible before activation. Software templates auto-seed plan + decompose via softwareStarterPlaybookTitles; startup separately prepends ship so all three land together at workspace init. The pre-PLAN-1377 trigger-only checklist entries (Implementation Workflow, Code Review Process, Plan Creation, Bug Triage, Retrospective, Onboarding to a Project, Release Process, Deployment, Incident Response) are stashed in playbook_library_archive.go::archivedPlaybooks() — compiled but not surfaced; per-entry "convert / promote to convention / retire" decisions tracked in IDEA-1396.
Web UI editor. web/src/routes/[username]/[workspace]/playbooks/[slug]/+page.svelte is the dedicated playbook editor — kebab-case slug input with debounced uniqueness check, structured arguments builder that round-trips with the body's ## Arguments section, trigger selector with custom-trigger escape, and a "Test invocation" helper that renders /pad, pad playbook run, and pad_playbook MCP JSON forms from a slug + sample inputs. The reusable component lives at web/src/lib/components/playbooks/PlaybookFormFields.svelte and the shared parser/generator at web/src/lib/playbooks/arguments.ts.
Code map:
internal/server/handlers_playbooks.go—pad playbook list|show|runHTTP handlers;ParsePlaybookCLIArgs,resolvePlaybook.internal/server/handlers_bootstrap.go—pad bootstrap; embeds playbook metadata.internal/mcp/catalog_playbook.go—pad_playbookMCP tool catalog entry.internal/collections/templates.go— playbooks collection schema (invocation_slug+argumentsfields);softwareStarterPlaybookTitles(auto-seed lineup for software templates).internal/collections/templates_startup_ship.go— the seededshipplaybook (ShipPlaybook(),shipPlaybookBody,shipPlaybookArguments).internal/collections/playbook_library.go— the invokable-first library (PlaybookLibrary(),LibraryPlaybookstruct withInvocationSlug+Arguments).internal/collections/playbook_library_plan.go— theplanlibrary entry (PlanPlaybook()).internal/collections/playbook_library_decompose.go— thedecomposelibrary entry (DecomposePlaybook()).internal/collections/playbook_library_archive.go— retired pre-PLAN-1377 bodies; not surfaced, but compiled for future migrations (IDEA-1396).web/src/lib/playbooks/arguments.ts—## Argumentsparser/generator,INVOCATION_SLUG_PATTERN,buildTestInvocation.
See PLAN-1377 (invocation model) and PLAN-1397 (library overhaul) in this workspace for the design history.
Testing
go test ./... # All Go tests
go test ./internal/store/ # Store tests only
cd web && npm run build # Verify frontend compiles
Common Tasks
Add a new API endpoint
- Add handler in
internal/server/handlers_*.go - Register route in
internal/server/server.gosetupRouter() - Add store method in
internal/store/if needed - Add CLI client method in
internal/cli/client.go - Add TypeScript type in
web/src/lib/types/index.ts - Add API method in
web/src/lib/api/client.ts make install
Add a new CLI command
- Add function in
cmd/pad/main.go - Register in rootCmd.AddCommand()
make install
Modify the database schema
- Add migration file in
internal/store/migrations/ - Update models in
internal/models/ - Update store methods in
internal/store/ make install(migrations run automatically on server start)
Real-time collaboration (Yjs / Tiptap)
Collab is wired through /api/v1/collab/{itemID} (WebSocket, Yjs
binary protocol). The relevant code lives in:
internal/collab/— RoomManager, room lifecycle, dumb-relayinternal/store/yjs_updates.go— op-log persistenceweb/src/lib/collab/wsProvider.svelte.ts— client providerweb/src/lib/collab/schemaVersion.ts— client schema-version stamp
Collab requires no additional container deps; the single Go binary remains the self-hosted shape. The dumb-relay design (server persists raw Yjs binary updates without parsing them) means there's no Yjs Go port to vendor and no separate sync-server process to run. The op-log lives in the same SQLite/Postgres as everything else, and the WebSocket relay is part of the main HTTP listener. Multi-instance Redis fanout is deliberately out of scope for v1 (single-instance everywhere); when horizontal scaling is needed it lands as a separate IDEA, not a self-host complication.
Tiptap multi-package coordinated bumps
The Y.Doc/ProseMirror schema is shared across three Tiptap packages:
@tiptap/core@tiptap/extension-collaboration@tiptap/y-tiptap
Rule: bump all three together, exact-pinned to the same version.
Mixing minor versions across these can change the persisted Y.Doc
shape silently — peers running mismatched bundles produce divergent
ops that the relay can't reconcile. The web/package.json pins
each one explicitly (e.g. "@tiptap/extension-collaboration": "3.22.5")
rather than using ^ ranges so npm can't slide one out of sync.
A coordinated bump that changes the ProseMirror node-spec MUST also
bump web/src/lib/collab/schemaVersion.ts::SCHEMA_VERSION AND
internal/collab/manager.go::DefaultSchemaVersion in lockstep. The
client announces the version on every WS connect; mismatch returns
HTTP 400 and the room manager prunes the per-item op-log so the new
client doesn't replay incompatible old-schema ops. items.content is
canonical and untouched, so no edit history is lost.
Pure UI/CSS/behavioural changes that don't alter the persisted document shape DO NOT bump the schema version. When in doubt, load an item edited under the old version after your change and confirm the rendered tree is identical.