Docs

HAR Capture & Replay — Delivery Notes

Delivery summary for snapshotting a browser tab to a HAR and reopening it as an offline replay. For the decision record, read ADR-0004: HAR capture & replay; this note says what shipped and where.

Status

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

PieceState
Save HAR command → writes a .har into the tree view✅ verified (4 resources + bodies archived)
.har opens in the browser as an offline replay✅ verified (served from archive, not disk)

Replay was proven to serve from the archive, not the on-disk file, by the cache-control: no-store header our handler emits — the replay tab’s mockup.css fetch returned no-store while the normal mockup tabs returned null.

The plan (as built)

The starting point was a different idea — copy an HTML file’s assets during drag-and-drop — which was rejected: it coupled two unrelated gestures and couldn’t capture dynamic or remote resources. Drag/drop stays as-is (copies just the .html, useful for opening in an editor). Instead:

  1. Capture — a tranquil-browser:save-har command records the active tab over the Chromium DevTools Protocol and writes a HAR into the tree view.
  2. Replay.har files open in the browser (like .url files), replaying the archived page offline.

Two design decisions (chosen by David): faithful CDP capture over a retroactive performance-timing assemble; and a protocol handler that serves archived responses by URL over inlining assets into a self-contained page.

Where everything lives

tranquil-client (main process)

  • src/main-process/har.js (new) — the whole feature’s main side:
    • capture-har — creates a hidden background window, warms it with about:blank, attaches the debugger, enables the Network domain, loads the real URL, and assembles a HAR 1.2 (request + response + base64 bodies). A 45s guard prevents silent hangs.
    • show-save-har-dialog — native save dialog (mirrors show-save-url-dialog).
    • register-har — parses a .har, indexes responses by URL, and stands up a per-archive session partition (har-replay-<id>) whose http/https/file requests are served from the archive.
  • src/main-process/cz-init.js — one require + registerHarIpc({ … }).
  • The decision record lives in these docs as ADR-0004, not in tranquil-client.

tranquil-browser (renderer)

  • lib/tranquil-browser.jssaveHar() + the save-har / open-har-file commands + a .har addOpener branch that returns a replay TranquilBrowserModel on the original page URL.
  • lib/tranquil-browser-view.js — passes opt.partition onto the <webview> for replay tabs.
  • menus/tranquil-browser.json.har tree-view entry + “Save HAR” on the browser-tab menu.
  • lib/notify.js — errors/warnings now linger 6s (was 1s) so capture failures are readable.

No packages/, node_modules/, or app-shell (start.js) edits — the partition-interception approach made a privileged har:// scheme unnecessary.

Gotchas found live

  1. Never capture off the live tab. The first cut attached the debugger to the visible <webview> and reloaded it — that disposed its frame and crashed the renderer. Fix: capture in a hidden window loading the same URL (same session, so cookies/auth carry over).
  2. Network.enable hangs on a fresh window. A brand-new BrowserWindow has no renderer process yet, so the CDP command waits forever for a DevTools agent that doesn’t exist. Fix: load about:blank first (it fetches nothing) to spin up the renderer, then attach + enable.
  3. Main-process code only reloads on a full quit. Pulsar runs a single main process and yarn start hands off to a running instance; a window reload refreshes only the renderer. Symptom while iterating: a stale har.js (renderer sending {url}, main still expecting webContentsId).
  4. No HTML/CSS rewriting needed. The replay tab navigates to the page’s original URL inside the archive partition, so relative and absolute refs both resolve to archived bytes.