Docs

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 nameGitHub repoRole
tranquil-rpctranquillabs/tranquil-rpcguest↔host Cap’n Web RPC (bundled)
tranquil-automationstranquillabs/tranquil-automationsautomation runner + pane controls
tranquil-browsertranquillabs/tranquil-browserembedded browser
tranquil-configtranquillabs/tranquil-configsecrets/config utility (not activated)
tranquil-debugtranquillabs/tranquil-debugbreakpoints + step debugging for automations
tranquil-business-darktranquillabs/tranquil-business-darkactive dark theme
tranquil-business-lighttranquillabs/tranquil-business-lightactive light theme
tranquil-theme-iconstranquillabs/tranquil-theme-iconsicon theme
tranquil-window-colortranquillabs/tranquil-window-colorper-window accent
tranquil-drag-droptranquillabs/tranquil-drag-droptab/pane drag-and-drop
tranquil-tipstranquillabs/tranquil-tipsbackground 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 nameGitHub repoRole
language-csvtranquillabs/atom-language-csv (fork of ldez/atom-language-csv)CSV syntax highlighting
pdf-viewtranquillabs/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 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) 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.


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 packageDependenciesexcept 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-rpc is bundled. The app loads its committed dist/host.js (built from lib/ by esbuild), not lib/ directly — so editing tranquil-rpc/lib takes effect only after yarn build in that repo regenerates dist/. 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:

  1. Create the sibling repo ../tranquil-new-thing/ with a package.json whose main points at the entry file, e.g. "main": "./lib/main". (Atom loads <main>; if main is wrong or missing, activate() never runs.)

  2. Add the link: entry to both blocks in tranquil-client/package.jsondependencies and packageDependencies:

    "tranquil-new-thing": "link:../tranquil-new-thing"

    (Utility libraries Atom must not activate — like tranquil-config — go in dependencies only.)

  3. Create the node_modules/<name> symlink — this is the load-bearing step. Either run yarn install in tranquil-client, or create it directly to skip a full install:

    cd tranquil-client
    ln -sfn ../../tranquil-new-thing node_modules/tranquil-new-thing

    Verify it resolves into the sibling repo and exposes main:

    node -e "console.log(require.resolve('tranquil-new-thing/package.json'))"
  4. (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-thing
  5. Fully 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 no main, so reloading it won’t pick up the fix.

  6. Create the private GitHub repo tranquillabs/tranquil-new-thing and 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 in packageDependencies), but a stub: path points at .../node_modules/tranquil-new-thing, metadata is {name} only (no main), mainModulePath: undefined, mainModule: null, mainActivated: false. Atom found the name but the path didn’t exist, so it never required a main module and activate() never ran. → Do step 3, then fully restart.
  • HealthymainModule is your module object and mainActivated: 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 own lib/, 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 install inside it so the module lands in <pkg>/node_modules — this is what tranquil-browser does for atom-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-client remote stays clean