Docs

Per-Window Session Isolation — Delivery Notes

Delivery summary for making each Tranquil window its own browser session, so the same domain can be logged in under different credentials in different windows. For the decision record, read ADR-0005: Per-window browser session isolation; this note says what shipped and where.

Status

Complete and manually verified end-to-end on branch feat/container-tabs (in both tranquil-client and tranquil-browser). Not yet committed.

PieceState
Every window gets its own persistent session partition✅ verified (3 windows → 3 tb-window-<uuid> partitions on disk)
windowSessionId persisted in application.json, restored on relaunch✅ verified live
Same domain, two windows, two different logins✅ verified manually
Logins persist across quit + relaunch✅ verified manually
“Open Link/Tab in New Window” (previously dead) opens URL in a fresh window✅ verified manually
HAR replay still uses its own partition (unaffected)✅ verified

Isolation was proven by opening three windows and confirming ~/Library/Application Support/Tranquil/Partitions/ held a tb-window-<uuid> directory matching each window’s windowSessionId in ~/.tranquil/storage/application.json, 1:1.

The plan (as built)

The starting point was a different idea — per-tab named containers (a picker/registry of named, colored session containers, launchable from five entry points). It was rejected in favor of window-scoping: “an Electron window is a session” needs essentially no UI and covers the real need (two accounts = two windows). A middle option — deriving the session from the window’s project roots — was also rejected because two windows on the same project would then share a session, the opposite of the requirement.

Chosen design: each window carries a durable windowSessionId (uuid), and every browser <webview> in that window uses persist:tb-window-<id>. The id is generated in the main process, persisted in application.json (additively — no schema-version bump), and threaded to the renderer via loadSettings.

Where everything lives

tranquil-client (main process)

  • src/main-process/atom-window.js — the AtomWindow constructor mints this.windowSessionId = settings.windowSessionId || crypto.randomUUID() (a restored window reuses its saved id; a new window mints a fresh one), and exposes it to the renderer via this.loadSettings.windowSessionId (read with atom.getLoadSettings().windowSessionId).
  • src/main-process/atom-application.js
    • saveCurrentWindowOptions / loadPreviousWindowOptions — persist and restore windowSessionId per window in application.json. Additive: entries written before this field simply lack it and the window mints a fresh id, so the schema version was not bumped and existing window layout isn’t wiped.
    • The id is threaded through openWithOptionsopenPath / openPathscreateWindow into the AtomWindow settings.
    • openUrlInNewWindow(url) + an ipcMain handler for application:new-window-open-url — opens a fresh (per-window-isolated) window and, on its loadedPromise, tells its renderer to atom.workspace.open(url). This revives the browser’s “Open link/tab in new window”, which previously sent to a channel nothing handled.

tranquil-browser (renderer)

  • lib/tranquil-browser-view.js — webview partition now falls back to the per-window session: an explicit per-tab partition (HAR replay) still wins; otherwise persist:tb-window-<windowSessionId>; otherwise (no id) the Electron default session.
  • lib/tranquil-browser.js — registers a single, once-per-window open-url-in-window IPC listener at package activation (deliberately not in the per-view addIpcInstanceEvents, which would never fire in an empty new window and would duplicate per tab).

Gotchas found live

  • ELECTRON_RUN_AS_NODE=1 in a headless/agent shell forces the Electron binary into plain-Node mode (electron --version prints the Node version, --no-sandbox errors as a “bad option”). Unset it to actually launch the app. Not a code issue — only affects launching from that environment.
  • The per-window listener must be global (package activation), not per-view — a freshly-opened empty window has no browser tab yet, so a per-view registration would never receive the new-window URL.
  • Main-process openPaths is path-oriented and won’t route an http URL through the tranquil-browser:// opener; hence the “open an empty window, then have its renderer atom.workspace.open(url)” sequencing.

Follow-ups (not done)

  • One-time re-login is expected for users after upgrade (existing logins were in the old shared default session).
  • Orphaned-partition cleanup — prune persist:tb-window-* partitions absent from application.json — is a future item.
  • The feat/container-tabs branches created in tranquil-theme-dark / tranquil-theme-light for the abandoned colored-chip idea are unused.