ADR-0004: HAR capture and offline .har replay in the browser
Status: Active · Date: 2026-07-04
Context
Dragging an open browser tab into the tree view copies only the backing .html file — deliberately,
since that is useful for opening the markup in an editor. It is not meant to produce a working
offline copy of a rendered page: the page’s stylesheets, fonts, images, and scripts are left
behind, so the copy renders unstyled (and remote http:// pages are only saved as .url shortcuts,
which still require the network).
The concrete trigger: a mockup dragged into a Test Project folder rendered unstyled because its mockup.css — and the fonts that CSS pulls in — never came along. We want a way to snapshot a
browser tab with everything it loaded and reopen that snapshot offline.
Decision
Add a tranquil-browser:save-har command that captures a faithful HAR of the active tab via the
Chromium DevTools Protocol — recording in a hidden background window that loads the same URL, so the
user’s live tab is never touched — and writes it into the tree view; and make .har files open in
the browser as an offline replay, served by a per-archive session partition that intercepts the
page’s original http/https/file requests and returns the archived response for each URL.
The replay tab navigates to the archived page’s original URL inside a dedicated partition
(har-replay-<id>); because the origin is unchanged, both relative and absolute references resolve
to archived bytes with zero HTML/CSS rewriting. Interception is scoped to that partition, so the
default session — normal browsing — is never affected. file-scheme misses fall through to disk (so
the webview’s own file:// preload keeps working); http/https misses stay offline (404).
Everything lives in the owned tranquil-browser package plus Tranquil’s own main-process init
(src/main-process/har.js, wired from cz-init.js). No packages/, third-party, or app-shell
(start.js) edits — a privileged har:// scheme proved unnecessary once interception served by
original URL.
Options considered
Capture:
- DevTools Protocol in a hidden window (chosen) — attach a debugger to a hidden background
window, enable the Network domain, load the same URL fresh, record requests/responses + bodies.
Faithful (real bodies, transitive + dynamic resources), non-destructive to the live tab, same
session so auth carries over. Cons: loads the page a second time; needs an
about:blankwarm-up soNetwork.enabledoesn’t hang on a rendererless window. - DevTools Protocol on the live tab (reload) — same, but reload the user’s visible tab.
Rejected: reloading the live
<webview>disposed its frame and crashed the renderer. - Assemble without reload — read
performance.getEntriesByType('resource')+ the DOM, then fetch/read each body. Rejected: cross-origin bodies blocked by CORS; misses dynamic resources.
Replay:
- Protocol handler serving archived responses by URL (chosen) — a per-archive session partition intercepts the original schemes and returns archived responses. Highest fidelity; handles CSS-referenced fonts/images and nested refs cleanly, with no page rewriting.
- Inline into a self-contained page — rewrite resource refs to
data:URIs. Rejected: fiddly for nested CSSurl()refs and imperfect for complex pages.
Consequences
- Easier: a browser tab (local or remote) becomes a portable, inspectable HAR that reopens offline and renders exactly what it loaded — the original “copied page is unstyled” problem disappears, and it works in standard HAR tooling.
- No core/third-party edits and no override of the default session; replay isolation is a dedicated partition.
- Harder / new risks:
- The page loads a second time in a hidden window (CDP is not retroactive), so capture costs an extra fetch and a moment; state built up by in-tab interaction (unsaved input, SPA navigation past the entry URL) is not reflected — the entry URL is what gets archived.
- Only one debugger may attach per webContents; a 45s guard prevents a silent hang.
- Re-evaluate if: Electron changes per-session
protocol.handlesemantics for standard schemes, or we need to snapshot live in-tab state rather than a fresh load of the URL.
Advice
Direction set by David during the session: keep drag-and-drop copying just the .html; make HAR an
explicit command; .har opens in the browser like .url; and choose faithful CDP capture +
protocol-served replay over the lighter-weight alternatives.