Srcbook is a local-first TypeScript notebook. A React frontend talks to an Express server that executes code in child processes. pnpm workspaces, turbo, ESM throughout.
Start here rather than searching blind:
| Where | What |
|---|---|
packages/api/server/ws.mts |
Every notebook operation. Run, stop, edit, rename, deps, AI, tsserver |
packages/api/server/http.mts |
All REST routes, one file |
packages/api/server/ws-client.mts |
The channel/topic framework the websocket is built on |
packages/api/session.mts |
In-memory session store and cell CRUD, write-through to disk |
packages/api/srcmd/ |
The .src.md codec. decoding.mts defines the grammar |
packages/api/exec.mts |
Spawning node / tsx / npm |
packages/shared/src/schemas/ |
Zod schemas shared by client and server. The protocol lives here |
packages/web/src/routes/session.tsx |
The notebook page |
packages/components/src/ |
Presentational UI. use-cell.tsx is the client-side cell store |
srcbook/src/ |
The published CLI. Boots the server and serves the built frontend |
Two things that are not where you'd guess:
- Notebooks are not in sqlite. Each one is a real directory under
~/.srcbook/srcbooks/<id>/with its ownpackage.jsonandnode_modules. sqlite holds only config and secrets. packages/webimports@srcbook/componentsby deep source path (@srcbook/components/src/components/ui/button), not through its entry point. The package's builtdist/and barrel file are unused.
DECISIONS.md explains why the system is shaped this way. REVIEW.md has a current critique
and the known-broken list. tasks/ is the work queue — one file per task, status in
frontmatter. Read DECISIONS.md before proposing architectural changes; several obvious-looking
improvements were considered and rejected for reasons recorded there.
Node 22 (.nvmrc), pnpm 9.12.1 via corepack. If pnpm is missing, corepack enable.
pnpm build && pnpm lint && pnpm check-format && CI=true pnpm testCI=true matters — the api test script is bare vitest, which otherwise sits in watch mode.
Turbo caches aggressively; add --force when you need to be sure a rebuild actually happened.
Tests are vitest, in packages/api/test/, globals enabled (no import { describe } needed).
Only packages/api has any. If you touch anything else, you are the first person to test it.
To exercise the real app:
pnpm --filter srcbook run build && node srcbook/dist/bin/cli.mjs startIt binds 127.0.0.1:2150 and writes to ~/.srcbook. Remove that directory afterwards if it
didn't exist before you started.
Prefer verifying a claim over asserting it. The security findings in REVIEW.md were confirmed
with curl and a websocket client against a running server, and two of the fixes were wrong on
the first attempt in ways only a live test revealed.
Match the file you're editing. Prettier and eslint are enforced in CI, so run them rather than hand-formatting.
Comments have a high bar. Explain why, never what — if a comment restates the code, delete it. Good reasons to write one: a non-obvious constraint, an ordering that matters, a rejected alternative, an upstream quirk you had to work around. Keep them compact; two tight lines beat a paragraph. Don't leave comments addressed to a reviewer ("changed this because…") — that belongs in the PR.
Other conventions already in place, worth keeping:
.mtssource importing.mjsspecifiers, ESM everywhere- Zod at every trust boundary; shared schemas so client and server can't drift
- Result objects (
{ error: true, errors }) over exceptions in the codec noUncheckedIndexedAccessis on. Handle theundefined, don't cast it away
Stacked PRs with Graphite. The repo is initialized, trunk is main.
gt create -m "message" # new branch on top of current
gt submit --stack # push and open/update PRs
gt sync # after a merge: pull trunk, restack, offer to delete merged branches
gt ls # see the stackUse gt, not manual git rebase. The repo squash-merges, which breaks patch-id matching for
any branch with more than one commit — vanilla git rebase will replay already-merged changes
and conflict. gt sync handles it.
One commit per logical change. No Co-Authored-By trailers.
Every PR body must open with a filetree of the changes, because GitHub's flat file list is hard to navigate. Plain text in a fenced block, grouped by directory, with per-file diff counts:
packages/api/
server/
security.mts +118/-0 origin allowlist, shared by http + ws
http.mts +47/-58 global cors/verifyOrigin, drop 41 per-route calls
ws-client.mts +12/-1 reject disallowed origins at handshake
test/
security.test.mts +97/-0 12 tests
turbo.json +2/-0 declare HOST, SRCBOOK_ALLOWED_ORIGINS
Generate the numbers with git diff --numstat <base>...HEAD; the trailing note is a few words
on what changed, not a restatement of the filename.
After the tree: what changed and why, how you verified it, and anything you deliberately left undone. Call out things you found but didn't fix, with a task number if you filed one.