Contributing
Thanks for hacking on Paddock. This is a short operational guide — how to get the stack running, the conventions we follow, and the environment gotchas that will otherwise cost you an afternoon. For deeper material, follow the links rather than re-reading it here:
- DEV.md — running the full stack locally (production-like and hot-reload modes), voice dictation setup.
- docs/CONFIGURATION.md — every
PADDOCK_*env var with its default. - docs/API.md — the REST + WebSocket contract.
- docs/TESTING.md — the test strategy and layers.
- docs/DESIGN.md —
read this before touching anything visual. The token contract, the
type/space/radius/elevation/motion scales, the shared
ui/primitives, how to add a new theme, and a “Reject this” section aimed at AI coding agents. Its rules are enforced bypackages/web/src/styles/tokens.test.ts, which fails the build. The user-facing half is Appearance. - RELEASING.md — the changesets + release pipeline.
- AUTH.md — authentication modes and secret handling.
Prerequisites
Section titled “Prerequisites”- Node 22+, and the
claudeCLI on yourPATH(claude --version) for the post-turn sweeper and for any turn resolved todriveMode: batch. Chats resolve the SDK’s own bundled binary and never consultPATH, so they run without it — and so do triggers, which resolve their drive mode exactly like a chat (project override, else the instance default, which issession). Only the sweeper goes through the CLI unconditionally. If you are reading the source, note that several comments still claim all triggers are CLI turns — they are wrong, and tracked as #771. - A Claude Max OAuth token (
CLAUDE_CODE_OAUTH_TOKEN) or anANTHROPIC_API_KEYin your environment. Never print or commit it — load it into the environment, don’t hardcode it (see DEV.md).
Getting started
Section titled “Getting started”npm install # install all workspacesnpm run build # build server (tsc) + web (vite)npm run dev # server on :7233 (API + WS) — terminal 1npm run dev:web # Vite dev server, proxies to :7233 — terminal 2See DEV.md for the two run modes and their tradeoffs.
Tests & checks
Section titled “Tests & checks”Run these before opening a PR — CI runs the same set:
npm run check:nul # guard: no raw NUL bytes in JS/TS sourcesnpm run typecheck # tsc on both packagesnpm test # server (unit + integration) + web (component)npm run test:e2e # Playwright journeys against the real server + a fake `claude`npm run check:nulruns beforenpm ciin CI — it needs no dependencies, and a raw0x00in a source file is invisible in a diff while makinggrep/ripgrepskip the whole file as binary, so nothing later in the run would catch it.- If you touched
website/oropenapi-site/, CI also builds the docs site (cd website && npm install && npm run build). That job is path-filtered, so it only runs when those directories change — but it is the only thing that catches an unresolvable sidebarslugor a broken relative image path. npm run test:server/npm run test:webrun one side only.npm run test:e2edrives the real server, FleetManager, and CLI runtime; only the LLM is swapped for a fakeclaudeon PATH (zero Anthropic calls). Opt into a real-Claude run withnpm run test:e2e:live.- More on the test layers: docs/TESTING.md.
Environment gotchas
Section titled “Environment gotchas”These bite everyone at least once:
-
NODE_ENV=productionprunes dev dependencies. If your shell exportsNODE_ENV=production(some servers/CI images do), a plainnpm installsilently dropstsc,vitest, Playwright, etc., and you’ll see “command not found” for the tools above. Install dev tooling explicitly:Terminal window NODE_ENV=development npm install --include=dev -
Run tests/builds with
NODE_ENVunset.NODE_ENV=productionalso breaks Reactact()in the web component tests. Unset it for the run:Terminal window env -u NODE_ENV npm run typecheckenv -u NODE_ENV npm testenv -u NODE_ENV npm run build -
Use a throwaway data dir so local runs don’t touch real projects:
export PADDOCK_DATA_DIR="$(mktemp -d /tmp/paddock-dev.XXXXXX)".
Branch, commit & PR conventions
Section titled “Branch, commit & PR conventions”- Branch for every non-trivial change — never commit to
maindirectly, and never force-push a shared branch. - Commits follow Conventional Commits:
type(scope): summary, e.g.feat(web): fork button,fix(server): drain queue on stop,docs(config): env-var reference. Common types:feat,fix,docs,refactor,test,chore,ci. - Open a PR against
main. Keep PRs small and focused; describe what changed and how you verified it. CI (NUL guard + typecheck + tests + E2E, plus the docs-site build whenwebsite/changes) must be green.
Changesets (release notes)
Section titled “Changesets (release notes)”Paddock uses changesets for versioning and changelogs. When your PR makes a user-facing change, add a changeset in the same PR:
npm run changeset # pick patch/minor/major + write a one-line summarygit add .changeset && git commit -m "chore: add changeset"No changeset is needed for pure-internal changes (tests, CI, refactors with no
observable effect, or docs-only changes to root/docs/ files that don’t ship
in a package). The full release flow — how the “chore: version packages” PR cuts a
Docker image and a tarball, and publishes @edspencer/paddock to npm via OIDC
trusted publishing (with a provenance attestation the workflow then verifies) — is in
RELEASING.md. That npm
package is what every npx @edspencer/paddock install path on this site resolves to.