View your browser history as a graph
README.md

History Graph #

A browser extension that shows your navigation history as a tree

Screenshot of a Wikipedia page with History Graph open

Build and Package #

Requires Node 24 and npm

npm install       # if you haven't already
npm run build     # just build the extension
npm run package   # build + zip → release/

Produces two loadable artifacts:

  • release/history-graph-chrome-<version>.zip — drag onto chrome://extensions, or just Load unpacked the dist/ folder.
  • release/history-graph-firefox-<version>.zip — built from a Firefox-flavored manifest written to dist-firefox/ (event-page background instead of a service worker, native sidebar_action instead of side_panel, plus a gecko add-on id).

To try it in Firefox: open about:debugging#/runtime/this-firefox → Load Temporary Add-on → pick dist-firefox/manifest.json (temporary add-ons are unsigned and clear on restart). The Chrome side panel becomes Firefox's native sidebar — open it from the toolbar button. Requires Firefox 140+.

Develop #

npm install
npm run dev     # vite build --watch → writes ./dist on every change

Then load it in Chrome:

  1. Visit chrome://extensions, enable Developer mode.
  2. Load unpacked → select the dist/ folder.
  3. Browse around. Open the service-worker console (the service worker link on the extension card) to watch [history-graph] visit … logs.
  4. Click the toolbar icon to open the side panel (it solos the active tab and updates live). Use the ⤢ button in the panel to pop out the full-tab view.

After code changes, npm run dev rebuilds automatically — just hit the reload icon on the extension card to pick up the new build.

Test #

npm test        # pure-logic checks (URL matching, forest assembly)
npm run test:e2e  # builds, loads the extension in a real headed Chromium via
                  # Playwright, drives navigations, then reads the extension's
                  # own IndexedDB and asserts the captured tree (titles, edges,
                  # no same-URL dupes)

test:e2e (scripts/capture-check.mjs) is the real end-to-end check: it launches Chromium with --load-extension=dist, so the actual service worker / chrome.webNavigation capture path runs — not a mock.

How it works #

Capture (service worker)  →  IndexedDB  →  Query (getForest)  →  Viewer (GraphView)
 webNavigation + tabs         visits store    {roots, children}    indented tree (v1)
  • Capture (src/background): listens to webNavigation/tabs, builds a Visit per navigation, and reconstructs the parent edge from Chrome's transition metadata + which tab opened which. Ephemeral per-tab state lives in chrome.storage.session because the MV3 worker is killed between events.
  • Storage (src/storage/db.ts): IndexedDB, indexed by time/tab/parent.
  • Query (src/query/getForest.ts): the only thing renderers call. Turns stored visits into a normalized forest.
  • Viewer (src/viewer): a React app. App.tsx owns view state and derives a view model (graph/viewModel.ts) from the forest; pure layout functions (graph/layout.ts) place nodes; views/GraphCanvas.tsx draws lanes (SVG) + rows (HTML). Three views share that pipeline: Git graph (chronological, branches as vertical lanes), Tree (file-tree), and List. The layout/view-model code is framework-free and unit-tested.

The data model (src/model/types.ts) is flat and additive so it can grow without migrations.

Status #

Work in progress. I'm pretty happy with it so far, but the history capturing heuristics need work wrt what counts as a separate entry.

Roadmap #

Loosely ordered, no promises I get to any of this.

Data model & capture fidelity #

UX & product #

Tooling & quality #

Visualizations #

I was originally going to play with other visualization techniques (and feel free to if you want to), but I think I'm quite happy with the git graph it currently uses.

Each is a layout strategy feeding the shared GraphCanvas: