# History Graph A browser extension that shows your navigation history as a tree ![Screenshot of a Wikipedia page with History Graph open](./docs/screenshot.png) ## Build and Package Requires Node 24 and npm ```sh 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-.zip` — drag onto `chrome://extensions`, or just **Load unpacked** the `dist/` folder. - `release/history-graph-firefox-.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 ```sh 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 ```sh 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 - [ ] **Dwell time** (`durationMs`) — measure time on page (tab activation + navigation-away) so nodes can be weighted/sized. - [ ] **Session grouping** (`sessionId`) — group branches by browsing session. - [ ] **Favicon cache** — store favicons by origin instead of per-visit. - [ ] Decide subframe policy; optionally capture meaningful iframe navigations. - [ ] Optional **seed from `chrome.history`** on install (reconstruct a partial tree from `referringVisitId`) for a non-empty day one. - [ ] Treat back/forward as an explicit "revisit" edge if we want to show it. - [x] Collapse client-side redirects/canonicalisation (e.g. /wiki/Juneau → /wiki/Juneau,_Alaska) into one node via the History-API timing heuristic. ### UX & product - [x] **Side panel** that follows your browsing: toolbar icon opens it, it solos the active tab, refreshes live, and uses a compact (container-query) layout. - [x] Persist view prefs (mode + direction) across sessions. - [x] **Pause capture** + per-site ignore list; define incognito behavior. - [ ] Date-range picker (beyond the preset dropdown) and per-domain filtering. - [ ] Export / import the graph (JSON). - [ ] Onboarding + privacy explanation; eventual store-publish polish. ### Tooling & quality - [x] Automated **end-to-end extension tests** — `npm run test:e2e` loads the unpacked extension in real Chromium, drives navigations, asserts the tree. - [x] `npm test` for the pure-logic checks in `tests/logic.test.ts`. - [ ] Performance pass if forests get large (virtualization beyond `content-visibility`). ### 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`: - [x] **List** — the original indented tree. - [x] **Git-graph lanes** — GitKraken/gitamine straight-branch layout, lane colors, interchange-station fork nodes. - [x] **Tree** — file-tree layout (subtrees contiguous) on the same canvas. - [x] **Collapse a fork** — hide all but the most-recently-active branch. - [x] **Solo a tab** + cross-tab jump buttons (new-tab opens become links between per-tab graphs). - [ ] **Timeline + branches** — time axis with navigations branching off. - [ ] **Force / radial graph** — for exploring large forests. - [x] Reversible time direction (newest- or oldest-at-top; newest default). - [x] Forbidden-columns lane allocation (interval-graph coloring) so concurrent branches never share a column — no overlapping lanes/connectors. - [x] Right-angle (elbow) connectors with rounded junctions. - [ ] Node detail panel, search highlighting, jump-to-now.