Docs

Local Dev Setup

This is a start-to-finish runbook for building and launching Tranquil Studio from source on macOS. It is written to be executed top to bottom — you can follow it yourself, or point an LLM agent harness (e.g. Claude Code) at this page and let it run each step.

Tranquil Studio (tranquil-client) is a fork of Pulsar (an Atom/Electron editor). Its owned features live in eleven sibling packages, plus two forked third-party packages, symlinked into the app for live editing. This runbook clones all fourteen repos, wires them together, and launches the real Electron GUI.

Prerequisites

Before starting, make sure the shell has:

  • git, with access to the private tranquillabs/* repos — an SSH key or a token that can clone them. All the git clone commands below fail without it.
  • Node via nvm — each repo pins its version in .nvmrc, so nvm use selects it. Node ≥ 18 (nvm typically resolves to 20.x here).
  • Yarn 1 (classic)yarn --version should report 1.x. Yarn 1’s link: feature is what creates the live sibling symlinks; Yarn 2+ won’t work.
  • Deno — automations run as Deno subprocesses (ADR-0022), so without it the app launches normally but no automation runs. It is also the only tooling tranquil-test-suite uses. No binary is bundled yet, so the app resolves one from the tranquil-automations.denoPath setting, then PATH, then ~/.deno/bin/deno, /opt/homebrew/bin/deno and /usr/local/bin/deno. Install and verification are in Run the Test Suite.

Everything is cloned under one workspace root. This runbook uses ~/Documents/Tranquil/Repos — substitute your own path consistently if you prefer another.

Clone the repos

Clone tranquil-client (the app) plus its eleven owned packages and two forked third-party packages as siblings in the same directory. They must sit next to each other — the link:../ paths in tranquil-client/package.json resolve relative to the repo root.

mkdir -p ~/Documents/Tranquil/Repos
cd ~/Documents/Tranquil/Repos

git clone https://github.com/tranquillabs/tranquil-client.git
git clone https://github.com/tranquillabs/tranquil-rpc.git
git clone https://github.com/tranquillabs/tranquil-automations.git
git clone https://github.com/tranquillabs/tranquil-browser.git
git clone https://github.com/tranquillabs/tranquil-config.git
git clone https://github.com/tranquillabs/tranquil-debug.git
git clone https://github.com/tranquillabs/tranquil-drag-drop.git
git clone https://github.com/tranquillabs/tranquil-window-color.git
git clone https://github.com/tranquillabs/tranquil-tips.git
git clone https://github.com/tranquillabs/tranquil-theme-icons.git
git clone https://github.com/tranquillabs/tranquil-business-dark.git
git clone https://github.com/tranquillabs/tranquil-business-light.git
git clone https://github.com/tranquillabs/atom-language-csv.git
git clone https://github.com/tranquillabs/atom-pdf-view.git

The resulting layout:

~/Documents/Tranquil/Repos/
  tranquil-client/          ← main app (Pulsar fork)
  tranquil-rpc/             ← guest↔host RPC layer (Cap'n Web)
  tranquil-automations/     ← automation runner + tree-view/tab UI
  tranquil-browser/         ← embedded Chromium browser
  tranquil-config/          ← config, defaults, and the settings tab
  tranquil-debug/           ← breakpoints and step debugging for .ts automations
  tranquil-drag-drop/       ← drag tabs to the tree to save .url files
  tranquil-window-color/    ← per-window accent color
  tranquil-tips/            ← background tips when no editors are open
  tranquil-theme-icons/     ← semantic file-type icons
  tranquil-business-dark/   ← default dark UI theme
  tranquil-business-light/  ← default light UI theme
  atom-language-csv/        ← forked third-party CSV grammar (npm name: language-csv)
  atom-pdf-view/            ← forked third-party PDF preview pane (npm name: pdf-view)

All repos are on the main branch.

Install dependencies

Install tranquil-client, initialize the ppm submodule, then install each sibling package that has its own dependencies. The per-sibling installs matter: a symlinked package resolves its own require()s from its real path, so it needs a local node_modules/ to find them.

pdf-view pulls in real runtime dependencies (pdfjs-dist, atom-space-pen-views, and others) and has no .nvmrc of its own, so install it first, with the Node version pinned explicitly rather than relying on whatever nvm last had active:

cd ~/Documents/Tranquil/Repos/atom-pdf-view
source ~/.nvm/nvm.sh && nvm use 20.16.0
yarn install
cd ..
cd ~/Documents/Tranquil/Repos/tranquil-client
source ~/.nvm/nvm.sh && nvm use
yarn install
git submodule update --init ppm
cd ppm && yarn install && cd ..

# Install deps inside each sibling package that has its own (so it can resolve them)
cd ../tranquil-rpc && yarn install
cd ../tranquil-automations && yarn install
cd ../tranquil-browser && yarn install
cd ../tranquil-config && yarn install
cd ../tranquil-tips && yarn install
cd ../tranquil-client

The theme, icon, window-color, drag-drop, debug, language-csv, and examples packages have no dependencies of their own, so they need no yarn install. (tranquil-debug reaches the automation runner through an Atom service, not an npm dependency, which is why it needs none; language-csv is a grammar-only package with no code to run at all.)

tranquil-rpc is bundled. The app loads its committed dist/host.js (built from lib/ by esbuild), not lib/ directly. A fresh clone already ships dist/, so it launches as-is — but if you later edit tranquil-rpc/lib/, run yarn build in that repo to regenerate dist/. Every other owned package loads its source directly, so their edits are live on a window reload.

A yarn install at the tranquil-client root can silently break native modules. better-sqlite3 is rebuilt for Electron’s Node ABI, not the system Node’s — a plain root-level yarn install (e.g. to pick up a new link: entry, as above) can leave it compiled for the wrong ABI, which surfaces as a NODE_MODULE_VERSION mismatch crash on next launch (blank window, no obvious cause). Run yarn build (which runs electron-rebuild) after any root-level yarn install if the app fails to launch afterward.

Verify the sibling symlinks resolved — every owned package must appear, each pointing at ../../tranquil-* (or, for the forked packages, ../../atom-language-csv / ../../atom-pdf-view):

ls -la node_modules | grep -E "tranquil|language-csv|pdf-view"

You should see one line per owned package, plus language-csv and pdf-view. A package missing from this list will not load, no matter what else is wired up — see the box below.

A new owned package needs yarn install, not just a dev/packages symlink.

Adding "tranquil-x": "link:../tranquil-x" to tranquil-client/package.json does nothing on its own: the entry only becomes a real node_modules/tranquil-x symlink when yarn install runs. Pulsar resolves packageDependencies from node_modules, so until then the package is invisible — no commands in the palette, no menu items, no keybindings, no styles, and no activation error to explain it. It simply is not there.

Symlinking into ~/.tranquil/dev/packages/ does not substitute for this. That directory is scanned only in dev mode, and it controls whether a package is listed as a Dev Package rather than a Core Package — it is not how the package is found in the first place.

So after adding a package, run yarn install in tranquil-client and confirm the symlink exists. The symptom of skipping it is indistinguishable from “my code never ran”.

Then fully restart the app — a window reload is not enough. A window that was already open while the symlink was missing has cached a stub package (name only, no main), and reloading that window keeps the stub. Adding a New Owned Package has the full checklist and a DevTools one-liner for telling a stub from a healthy package.

Configure tranquil-config

tranquil-config supplies API keys through a generated file that is never committed. The app won’t start until that file exists. For a first launch you don’t need real keys — write a stub:

cd ~/Documents/Tranquil/Repos/tranquil-config
echo '{"github_token": ""}' > config.json
node generate.js
cd ../tranquil-client

When you need real keys later, copy .env.example to .env, fill it in, then run node write_config.js --staging (or --production) followed by node generate.js. Re-run generate.js whenever keys change.

Launch

Start the app. This opens a real Electron GUI:

cd ~/Documents/Tranquil/Repos/tranquil-client
source ~/.nvm/nvm.sh && nvm use
yarn start

Launching from an agent shell

yarn start works from an agent/IDE shell as well as a terminal — background it so the agent keeps control:

cd ~/Documents/Tranquil/Repos/tranquil-client && source ~/.nvm/nvm.sh && nvm use && yarn start

The ELECTRON_RUN_AS_NODE gotcha. An agent/IDE shell (e.g. the VSCode extension host Claude runs inside) exports ELECTRON_RUN_AS_NODE=1. If that leaks into the environment Electron is spawned with, the binary runs as a plain Node interpreter instead of a GUI app, rejects the Chromium flags, and dies in ~0.3s with bad option: --no-sandbox. Tranquil’s scripts/dev.js strips the variable from the child env before spawning, so yarn start works from any shell — no env -u wrapper needed. If you ever see that error, confirm the binary’s mode directly:

electron --version                          # Node's version (e.g. v20.x) → running as Node
env -u ELECTRON_RUN_AS_NODE electron --version   # Electron's version (e.g. v30.x)

Prefer a throwaway instance — yarn verify. For agent work that shouldn’t touch your real window layout or open your actual source tree, yarn verify launches a fully isolated instance (its own --user-data-dir and ATOM_HOME seeded from ~/.tranquil, opening the dedicated tranquil-test-suite repo instead of tranquil-client) and holds it open:

yarn verify   # background this; override the project with: yarn verify <path>

Stop either one by stopping the backgrounded launcher — scripts/dev.js forwards SIGINT/ SIGTERM to the Electron child, so the whole app tears down with no orphans.

Verify it’s running

The dev build exposes the Chromium DevTools Protocol on localhost:9222. Confirm a live GUI without touching the UI:

curl -s http://localhost:9222/json/version

A running instance returns the Electron/Chrome versions and a Tranquil/<version> user agent. To go further — list every renderer/webview target and evaluate JavaScript inside a specific tab — see Agent Debugging.

Next steps

For deeper reference on how the pieces fit together:

  • Multi-Repo Setup — the symlink model, link: mechanics, and how to add a new owned package.
  • ppm and Core Packages — how ppm and the bundled Pulsar core packages load in a dev build.
  • Agent Debugging — inspect and script a live instance over CDP, and know what a reload actually reloads.