Multi-Repo Setup
This setup is for local development only. Production build configuration is handled separately.
Tranquil’s owned packages live in separate GitHub repos and are linked into tranquil-client for local development via Yarn 1’s link: feature, which creates live symlinks — no reinstall needed when you edit a package.
Repository Layout
~/Documents/Tranquil/Repos/
tranquil-client/ ← main app (this repo, forked from Pulsar)
tranquil-rpc/ ← guest↔host Cap'n Web RPC layer (bundled: dist/ committed)
tranquil-automations/ ← automation runner + tab-bar pane controls
tranquil-browser/ ← embedded browser
tranquil-config/ ← secrets/config utility (dependencies-only, not activated)
tranquil-debug/ ← breakpoints + step debugging for .ts automations
tranquil-business-dark/ ← active dark UI theme
tranquil-business-light/ ← active light UI theme
tranquil-theme-icons/ ← file/UI icon theme
tranquil-window-color/ ← per-window accent color
tranquil-drag-drop/ ← tab/pane drag-and-drop
tranquil-tips/ ← background tips shown when no editors are open
atom-language-csv/ ← forked third-party CSV grammar, not Tranquil-authored (see below) All directories must be siblings. The link:../ paths in tranquil-client/package.json resolve
relative to the repo root.
GitHub Repos
| npm name | GitHub repo | Role |
|---|---|---|
tranquil-rpc | tranquillabs/tranquil-rpc | guest↔host Cap’n Web RPC (bundled) |
tranquil-automations | tranquillabs/tranquil-automations | automation runner + pane controls |
tranquil-browser | tranquillabs/tranquil-browser | embedded browser |
tranquil-config | tranquillabs/tranquil-config | secrets/config utility (not activated) |
tranquil-debug | tranquillabs/tranquil-debug | breakpoints + step debugging for automations |
tranquil-business-dark | tranquillabs/tranquil-business-dark | active dark theme |
tranquil-business-light | tranquillabs/tranquil-business-light | active light theme |
tranquil-theme-icons | tranquillabs/tranquil-theme-icons | icon theme |
tranquil-window-color | tranquillabs/tranquil-window-color | per-window accent |
tranquil-drag-drop | tranquillabs/tranquil-drag-drop | tab/pane drag-and-drop |
tranquil-tips | tranquillabs/tranquil-tips | background tips |
All repos are on the main branch.
Forked Third-Party Packages
Not every entry in packageDependencies is Tranquil-authored. When a fix or feature lives in a
small, unmaintained community package, forking it under tranquillabs and wiring it in with the
same link:../ mechanism as an owned package is simpler than working around it at runtime.
A fork like this keeps its upstream repo name, even though the npm name inside its own package.json — the name packageDependencies actually loads by — is the package’s original
published name. The two don’t match, unlike every owned package above:
| npm name | GitHub repo | Role |
|---|---|---|
language-csv | tranquillabs/atom-language-csv (fork of ldez/atom-language-csv) | CSV syntax highlighting |
pdf-view | tranquillabs/atom-pdf-view (fork of izuzak/atom-pdf-view) | PDF preview pane |
Setup is the same link: mechanism, just with mismatched names — clone each alongside the owned
repos, add its link: entry, and symlink it into dev/packages/ under its npm name, not its
repo name:
cd /Users/david/Documents/Tranquil/Repos
git clone https://github.com/tranquillabs/atom-language-csv.git
git clone https://github.com/tranquillabs/atom-pdf-view.git "language-csv": "link:../atom-language-csv",
"pdf-view": "link:../atom-pdf-view" ln -s /Users/david/Documents/Tranquil/Repos/atom-language-csv ~/.tranquil/dev/packages/language-csv
ln -s /Users/david/Documents/Tranquil/Repos/atom-pdf-view ~/.tranquil/dev/packages/pdf-view language-csv is a grammar-only package with no main and no code to activate, so — like the
theme, icon, window-color, drag-drop, and examples packages below — it needs no yarn install step of its own. pdf-view is different: it pulls in real runtime dependencies (pdfjs-dist, atom-space-pen-views, and others), so it needs the same per-sibling install as tranquil-rpc/ tranquil-automations/etc. below, run before installing tranquil-client itself:
cd atom-pdf-view
source ~/.nvm/nvm.sh && nvm use 20.16.0 # no .nvmrc of its own — pin the version explicitly
yarn install
cd .. A
yarn installat thetranquil-clientroot can silently break native modules.better-sqlite3is rebuilt for Electron’s Node ABI, not the system Node’s — a plain root-levelyarn install(e.g. to pick up a newlink:entry) can leave it compiled for the wrong ABI, which surfaces as aNODE_MODULE_VERSIONmismatch crash on next launch (blank window, no obvious cause). Runyarn build(which runselectron-rebuild) after any root-levelyarn installif the app fails to launch afterward.
First-Time Setup
Clone all owned repos alongside tranquil-client, then install:
cd /Users/david/Documents/Tranquil/Repos
for repo in tranquil-rpc tranquil-automations tranquil-browser tranquil-config tranquil-debug
tranquil-business-dark tranquil-business-light tranquil-theme-icons
tranquil-window-color tranquil-drag-drop tranquil-tips; do
git clone "https://github.com/tranquillabs/$repo.git"
done
cd 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 modules, so their
# require()s resolve from the real path (see Module Resolution below).
cd ../tranquil-rpc && yarn install && yarn build # bundled — rebuilds dist/host.js
cd ../tranquil-automations && yarn install # link deps: tranquil-config, tranquil-rpc
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, and examples packages have no dependencies of their
own, so they need no yarn install.
After installing, verify the symlinks exist:
ls -la node_modules/tranquil-rpc
node_modules/tranquil-automations
node_modules/tranquil-browser
node_modules/tranquil-business-dark
node_modules/tranquil-business-light
node_modules/tranquil-theme-icons Each should point to ../../tranquil-* (two levels up, into the sibling directory).
Dev packages symlinks
Symlink the owned packages into ~/.tranquil/dev/packages/ so Tranquil discovers them as dev packages rather than core packages in the Settings view:
mkdir -p ~/.tranquil/dev/packages
REPOS=/Users/david/Documents/Tranquil/Repos
for repo in tranquil-rpc tranquil-automations tranquil-browser tranquil-config tranquil-debug
tranquil-business-dark tranquil-business-light tranquil-theme-icons
tranquil-window-color tranquil-drag-drop tranquil-tips; do
ln -s "$REPOS/$repo" ~/.tranquil/dev/packages/$repo
done Pulsar/Atom scans dev/packages/ before processing packageDependencies, so packages found there get isBundled: false and appear under Dev Packages instead of Core Packages. In a production build the symlinks won’t exist, so the packages fall through to packageDependencies and load from node_modules as bundled — which is the correct behavior for a shipped binary.
How link: Works
Yarn 1’s link: creates a symlink in node_modules pointing to the given path. Unlike file:, which copies the package into .yarn-cache on install, link: is a live reference — editing files in ../tranquil-automations/lib/ takes effect immediately in the running app without any reinstall.
package.json entries (one per owned package):
"tranquil-rpc": "link:../tranquil-rpc",
"tranquil-automations": "link:../tranquil-automations",
"tranquil-browser": "link:../tranquil-browser",
"tranquil-config": "link:../tranquil-config",
"tranquil-business-dark": "link:../tranquil-business-dark",
"tranquil-business-light": "link:../tranquil-business-light",
"tranquil-theme-icons": "link:../tranquil-theme-icons",
"tranquil-window-color": "link:../tranquil-window-color",
"tranquil-drag-drop": "link:../tranquil-drag-drop",
"tranquil-tips": "link:../tranquil-tips" These entries appear in both dependencies and packageDependencies — except one that is dependencies-only, so Atom never activates it as a package:
tranquil-config— a utility library (secrets/config), not an Atom plugin.
Exception to “no reinstall”:
tranquil-rpcis bundled. The app loads its committeddist/host.js(built fromlib/by esbuild), notlib/directly — so editingtranquil-rpc/libtakes effect only afteryarn buildin that repo regeneratesdist/. Every other owned package loads its source directly, so their edits are live on a window reload.
Module Resolution
Sibling repos live outside tranquil-client. When Node.js resolves a symlink to its real path, upward traversal for node_modules starts from that real path — so packages inside tranquil-automations/ can’t find deps in tranquil-client/node_modules/.
The fix is to run yarn install inside each sibling package that has its own dependencies. Yarn creates a local node_modules/ there (e.g. tranquil-automations/node_modules/ gets tranquil-config and tranquil-rpc symlinks for its own link: deps), so all require() calls resolve correctly from the real path.
This is why the First-Time Setup section runs yarn install inside the siblings with their own deps — tranquil-rpc, tranquil-automations, tranquil-browser, tranquil-config, tranquil-tips — not just tranquil-client.
A corollary that bites when adding a new package: a top-level require() for a dependency that only exists in tranquil-client/node_modules will throw at load and silently abort the package’s activation. See Adding a New Owned Package for the safe options (vendor the dep in the package, or require only atom + your own files).
tranquil-config Setup
tranquil-config provides API keys (GitHub) via a generated file that is never committed. After cloning tranquil-config, you must generate this file before yarn start will work.
One-time setup:
cd /Users/david/Documents/Tranquil/Repos/tranquil-config
# Copy the example and fill in real values
cp .env.example .env
# edit .env with actual keys
# Write config.json from .env
node write_config.js --staging # or --production
# Generate src/generated/index.js from config.json
node generate.js The generated file is gitignored and must be re-run whenever keys change. If you just need the app to start without real keys (e.g. after a fresh clone), you can create a stub:
cd /Users/david/Documents/Tranquil/Repos/tranquil-config
echo '{"github_token": ""}' > config.json
node generate.js Updating an Owned Package
Work directly in the sibling directory, commit, and push to its own repo:
cd /Users/david/Documents/Tranquil/Repos/tranquil-automations
# ... make changes ...
git add -p
git commit -m "fix: ..."
git push No changes to tranquil-client are needed unless the package’s npm name or API changes.
Adding a New Owned Package
The one fact that keeps costing time: under yarn start an owned package is loaded as a bundled package from tranquil-client/node_modules/<name>. That node_modules/<name> symlink
is what actually loads the package. The ~/.tranquil/dev/packages/<name> symlink is cosmetic —
it only moves the package from “Core Packages” to “Dev Packages” in the Settings view; on its own it
does not load the package under yarn start. Create the package with only the dev/packages
symlink and it silently never loads.
Steps, in order:
Create the sibling repo
../tranquil-new-thing/with apackage.jsonwhosemainpoints at the entry file, e.g."main": "./lib/main". (Atom loads<main>; ifmainis wrong or missing,activate()never runs.)Add the
link:entry to both blocks intranquil-client/package.json—dependenciesandpackageDependencies:"tranquil-new-thing": "link:../tranquil-new-thing"(Utility libraries Atom must not activate — like
tranquil-config— go independenciesonly.)Create the
node_modules/<name>symlink — this is the load-bearing step. Either runyarn installintranquil-client, or create it directly to skip a full install:cd tranquil-client ln -sfn ../../tranquil-new-thing node_modules/tranquil-new-thingVerify it resolves into the sibling repo and exposes
main:node -e "console.log(require.resolve('tranquil-new-thing/package.json'))"(Optional, cosmetic) Symlink into
~/.tranquil/dev/packages/so it appears under Dev Packages rather than Core Packages in Settings:ln -s /Users/david/Documents/Tranquil/Repos/tranquil-new-thing ~/.tranquil/dev/packages/tranquil-new-thingFully restart
yarn start— not just a window reload. Package discovery re-reads the symlinks at startup; a window that was already open when the symlink was missing has cached a stub package with nomain, so reloading it won’t pick up the fix.Create the private GitHub repo
tranquillabs/tranquil-new-thingand push.
Verifying it loaded (and diagnosing when it didn’t)
In the editor DevTools console (⌘⌥I):
atom.packages.getActivePackage('tranquil-new-thing') - Missing
node_modules/<name>symlink → you still get a package object (its name is inpackageDependencies), but a stub:pathpoints at.../node_modules/tranquil-new-thing,metadatais{name}only (nomain),mainModulePath: undefined,mainModule: null,mainActivated: false. Atom found the name but the path didn’t exist, so it never required a main module andactivate()never ran. → Do step 3, then fully restart. - Healthy →
mainModuleis your module object andmainActivated: true; commands/features are registered.
Gotcha — what an owned package may require
A package loaded via the node_modules/<name> symlink resolves its own require()s from its real
sibling-repo path, not from tranquil-client/node_modules. So a top-level require('some-app-dependency') that lives only in tranquil-client/node_modules throws at module
load, and a throw at load silently aborts activation — no error surfaced, no commands
registered. (Same silent-crash failure mode as a missing dependency in a main-process file.) Safe
options:
- Require only
atom, your ownlib/, and Node built-ins; prefer a design with zero external deps (e.g. register one command per option instead of pulling in a picker library); or - Give the package its own dependency and run
yarn installinside it so the module lands in<pkg>/node_modules— this is whattranquil-browserdoes foratom-space-pen-views.
When “the commands don’t show up,” suspect a throwing top-level require first, then check getActivePackage(...) as above.
Why Separate Repos?
tranquil-client is forked from Pulsar (open source). Keeping owned packages in their own repos means:
- No proprietary code in the Pulsar fork’s history
- Each package can be versioned and released independently
- The
tranquil-clientremote stays clean