Docs

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:blank warm-up so Network.enable doesn’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 CSS url() 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.handle semantics 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.