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.
| Piece | State |
|---|---|
| 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— theAtomWindowconstructor mintsthis.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 viathis.loadSettings.windowSessionId(read withatom.getLoadSettings().windowSessionId).src/main-process/atom-application.jssaveCurrentWindowOptions/loadPreviousWindowOptions— persist and restorewindowSessionIdper window inapplication.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
openWithOptions→openPath/openPaths→createWindowinto theAtomWindowsettings. openUrlInNewWindow(url)+ anipcMainhandler forapplication:new-window-open-url— opens a fresh (per-window-isolated) window and, on itsloadedPromise, tells its renderer toatom.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— webviewpartitionnow falls back to the per-window session: an explicit per-tab partition (HAR replay) still wins; otherwisepersist:tb-window-<windowSessionId>; otherwise (no id) the Electron default session.lib/tranquil-browser.js— registers a single, once-per-windowopen-url-in-windowIPC listener at package activation (deliberately not in the per-viewaddIpcInstanceEvents, which would never fire in an empty new window and would duplicate per tab).
Gotchas found live
ELECTRON_RUN_AS_NODE=1in a headless/agent shell forces the Electron binary into plain-Node mode (electron --versionprints the Node version,--no-sandboxerrors 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
openPathsis path-oriented and won’t route an http URL through thetranquil-browser://opener; hence the “open an empty window, then have its rendereratom.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 fromapplication.json— is a future item. - The
feat/container-tabsbranches created intranquil-theme-dark/tranquil-theme-lightfor the abandoned colored-chip idea are unused.