Files
gitmost/docs/dev-stand.md
T
claude code agent 227 ef173f022d docs: add "Running a local dev stand" guide + reference it from AGENTS.md
Captures the non-obvious gotchas that make bringing up a local instance
painful: the collaboration server is a THIRD process (pnpm dev starts only
API + client) that must be built before running (tsx/ts-node fail on NestJS
DI); APP_SECRET must be identical between the API and collab servers or every
realtime connection is rejected with "Invalid collab token"; Vite binds
localhost so LAN access needs --host; a stale @docmost/editor-ext white-
screens the client; pgvector is mandatory; migrations don't auto-run in dev.
Also documents that demo/test passwords should be a simple one-word
alphanumeric (no special chars, which get mangled through shells/JSON/URLs).

Referenced from AGENTS.md (Commands + Two-server-processes sections).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 03:21:41 +03:00

5.8 KiB

Running a local dev stand

How to bring up a working local instance (API + client + realtime collaboration) and the non-obvious gotchas that will otherwise eat an hour. Written from real setup pain — read the Gotchas section before you start.

Prerequisites

  • Node 20+ / pnpm 10+.
  • Postgres with pgvector. Use the pgvector/pgvector image (e.g. pgvector/pgvector:pg18). The stock postgres image will FAIL the CREATE EXTENSION vector migration — the RAG feature stores embeddings in page_embeddings.
  • Redis — backs caching, BullMQ queues, the Socket.IO adapter, and collab sync.

1. Environment (.env)

The client (apps/client/vite.config.ts) and both server processes read env via envPath → the workspace root .env. Keep a single source of truth. Minimum:

APP_URL=http://localhost:3000
PORT=3000
APP_SECRET=<one long secret — SAME value everywhere, see gotcha #3>
DATABASE_URL="postgresql://<user>:<pass>@localhost:5432/<db>?schema=public"
REDIS_URL=redis://127.0.0.1:6379
COLLAB_URL=http://localhost:3001      # where the CLIENT connects for realtime
COLLAB_PORT=3001                      # where the COLLAB server listens
STORAGE_DRIVER=local
DISABLE_TELEMETRY=true

If you also keep an apps/server/.env, its APP_SECRET must match the root one (see gotcha #3).

2. Migrations

Migrations do not auto-run in local dev. After a fresh checkout or switching branches, apply them yourself or endpoints touching a new column/table will 500:

pnpm --filter server migration:latest

3. Bring it up — THREE processes, not two

pnpm dev starts only the API server (Nest, :3000) and the client (Vite). Realtime collaboration is a separate process and pnpm dev does NOT start it. You need all three:

# 1) API + client (from the repo root)
pnpm dev
#    → API   http://localhost:3000
#    → client http://localhost:5173  (Vite; localhost-only by default)

# 2) Collaboration server — SEPARATE process. Build first (see gotcha #2), then:
pnpm --filter server build          # produces dist/collaboration/server/collab-main.js
pnpm collab:dev                     # node dist/.../collab-main → listens on :3001 (0.0.0.0)

Without step 2 the editor shows "Real-time editor connection lost. Retrying…", stays in read-only static mode, and anything that only mounts in the live editor won't appear.

Seeding a login

Register through the UI, or reset an existing user's password directly in the DB (the server hashes with bcrypt):

// node -e '...'  with pg + bcrypt from the repo's node_modules
const bcrypt = require("bcrypt");
const { Client } = require("pg");
(async () => {
  const hash = await bcrypt.hash("demopass", 10);
  const c = new Client({ /* DATABASE_URL parts */ });
  await c.connect();
  await c.query("update users set password=$1 where email=$2", [hash, "admin@example.com"]);
  await c.end();
})();

Use a simple one-word password with no special characters (e.g. demopass, not Str0ng!Pass@2026). Demo/test credentials get passed through shells, JSON payloads, and URLs by scripts and automation, where ! @ $ & etc. get mangled or need escaping — a plain alphanumeric word avoids a whole class of "wrong password" confusion.

Gotchas (the грабли)

  1. Collaboration is a third process. pnpm dev runs API + client only. Start pnpm collab:dev (on :3001) separately or the live editor never connects. The client connects to COLLAB_URL directly (default http://localhost:3001), NOT through the Vite /collab proxy — the API server on :3000 does not serve the collab websocket.

  2. The collab server must be built — you can't run it from source. collab:dev runs node dist/collaboration/server/collab-main.js, so run pnpm --filter server build first. Running the entry via tsx/ts-node fails with a NestJS DI error ("dependency … appears to be undefined at runtime") because direct TS execution doesn't emit the decorator metadata the built output has.

  3. APP_SECRET must be identical for the API server and the collab server. The API issues a collab-token (JWT signed with APP_SECRET); the collab server validates it with APP_SECRET. If they load different values (e.g. a root .env and an apps/server/.env with different secrets), every realtime connection is rejected with [onAuthenticate] Invalid collab token and the editor shows "connection lost". Keep one secret everywhere.

  4. Vite binds localhost only. To reach the stand from another machine on the LAN, start the client with --host (pnpm --filter client exec vite --host) and use the box's LAN IP. The /api, /socket.io, and /collab Vite proxies forward to APP_URL, so the API just works over the LAN; realtime needs COLLAB_URL reachable from the browser (point it at the LAN IP:3001, and run collab on 0.0.0.0 — it does by default).

  5. A stale @docmost/editor-ext white-screens the client. The client imports from @docmost/editor-ext (a workspace package). If that package's source is behind (missing a newer export, e.g. Spoiler), the client dies at load with "The requested module … does not provide an export named 'Spoiler'" → blank page. Make sure the workspace packages/editor-ext is current for the branch you're running (a stale sibling checkout resolved through a shared node_modules symlink is the usual cause).

  6. pgvector, not stock postgres (see Prerequisites) — the vector extension migration fails otherwise.

  7. Migrations don't auto-run in dev — run migration:latest after every pull or branch switch.

See also the Commands and Architecture → Two server processes sections in AGENTS.md.