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.
| Piece | State |
|---|---|
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:
- Capture — a
tranquil-browser:save-harcommand records the active tab over the Chromium DevTools Protocol and writes a HAR into the tree view. - Replay —
.harfiles open in the browser (like.urlfiles), 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 withabout: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 (mirrorsshow-save-url-dialog).register-har— parses a.har, indexes responses by URL, and stands up a per-archive session partition (har-replay-<id>) whosehttp/https/filerequests 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.js—saveHar()+ thesave-har/open-har-filecommands + a.haraddOpenerbranch that returns a replayTranquilBrowserModelon the original page URL.lib/tranquil-browser-view.js— passesopt.partitiononto the<webview>for replay tabs.menus/tranquil-browser.json—.hartree-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
- 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). Network.enablehangs on a fresh window. A brand-newBrowserWindowhas no renderer process yet, so the CDP command waits forever for a DevTools agent that doesn’t exist. Fix: loadabout:blankfirst (it fetches nothing) to spin up the renderer, then attach + enable.- Main-process code only reloads on a full quit. Pulsar runs a single main process and
yarn starthands off to a running instance; a window reload refreshes only the renderer. Symptom while iterating: a stalehar.js(renderer sending{url}, main still expectingwebContentsId). - 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.