This document is both an end-user operational guide (how to use the extension) and a functional test plan (how to verify every moving part works end-to-end). Read Part 1 to install, Part 2 to use it, Part 3 to verify it.
- Node.js 20+ and npm on PATH.
- A Chromium-based browser (Chrome, Edge, Brave, Arc). Firefox is not supported yet — the manifest is MV3 Chromium-flavor.
- ~200 MB of free disk for
node_modules/.
git clone https://github.com/Mintsolester/Chronocluster.git
cd Chronocluster
npm install # ~30s, pulls Vite, Pixi, d3-force, Dexie, CRXJS
npm run build # ~6s, type-checks then emits dist/After the build, dist/ contains the loadable extension:
manifest.jsonservice-worker-loader.js(CRXJS shim →assets/background.ts-*.js)index.html+assets/index-*.js(the constellation page)assets/worker-*.js(d3-force physics)icons/icon.png
- Visit
chrome://extensions. - Toggle Developer mode on (top right).
- Click Load unpacked.
- Select the
dist/folder inside your clone. - You should see ChronoCluster Live in the list with no red error badge and the default icon visible in the Chrome toolbar.
npm run devCRXJS emits into dist/ with HMR. Load the same dist/ folder as above. Edits to src/App.tsx, src/renderer.ts, etc. will hot-reload the constellation page without needing a rebuild.
Click the ChronoCluster icon in your Chrome toolbar. A dedicated full tab opens at chrome-extension://<id>/index.html. Clicking the icon again focuses the existing tab rather than opening a duplicate.
- Glowing blue circle (
#00A3FF, alpha 1.0) — a tab that is currently open. - Dim gray circle (
#555555, alpha 0.45) — a tab that has been closed. It remains in the graph as a historical shadow; links connecting to/from it stay drawn. - Thin blue line — a parent → child link. A parent is the tab from which a child was opened (via click, middle-click, or
target="_blank"). Typed/bookmark/reload navigations have no parent and appear as roots. - Clustering — every domain has a deterministic "gravity point" on a golden-angle spiral. Tabs from the same domain drift toward each other, forming a solar system. A brand-new domain is assigned the next available slot without disturbing existing clusters.
| Row | Meaning |
|---|---|
| Nodes | Total tabs seen (open + closed). |
| Open | Currently open tabs (isOpen: true). |
| Edges | Parent/child links recorded. |
| FPS | Render-loop frames per second. Expect ~60. |
| LOD | on when nodes > 2000 (labels hidden, radii shrink). |
| Button | Action |
|---|---|
| Export | Downloads a JSON snapshot of nodes + edges for backup / transport. |
| Import | Reads a JSON snapshot and bulkPuts it into Dexie. Existing rows with matching ids are overwritten; others preserved. |
| Seed 500 | Dev-only — visible when npm run dev or an unminified build is loaded. Inserts 500 synthetic nodes + edges across 4 domains so you can stress the physics + LOD. Hidden in production builds. |
| Clear | Sends { type: "clearAll" } to the service worker, which truncates nodes, edges, meta, and wipes chrome.storage.session. Asks for confirmation first. |
- Drag (left mouse button) — pan the viewport.
- Scroll / pinch — zoom toward the cursor.
- Release after drag — momentum; the viewport decelerates naturally.
Every 5 seconds the page flushes the current (x, y) of every node back into Dexie. When you reload the constellation tab, nodes warm-start at their persisted positions and the simulation reaches steady state in ~100 ms instead of re-laying out from scratch.
ChronoCluster observes:
chrome.tabs.onCreated— new tab (with optionalopenerTabIdfor parentage).chrome.tabs.onUpdated— URL or title change, patches the existing node.chrome.tabs.onRemoved— flipsisOpen: false, setsclosedAt.chrome.webNavigation.onCreatedNavigationTarget— Ctrl/Cmd-click, middle-click,target="_blank"open-in-new-tab. Records parent fromsourceTabId.chrome.webNavigation.onCommitted— same-tab navigation. IftransitionType === "link", parent = previous node in that tab; otherwise the new node is a root.
ChronoCluster does not request host_permissions and does not inject content scripts. It never reads page content — only URLs, titles, and tab-level events that Chrome already hands to extensions with the tabs + webNavigation permissions.
- Node / edge graph: IndexedDB database
ChronoClusterGraph(visible in DevTools → Application → IndexedDB). - Per-tab routing caches:
chrome.storage.sessionunder keyschronocluster.lastNodeByTabandchronocluster.nodeForTab. Session storage is wiped on browser restart; this is intentional — history in Dexie persists, only the live "which tab maps to which node" cache resets.
Nothing leaves your machine. There are no network calls.
Run through this checklist after any meaningful change. Each test is independent; the Expect line is the pass criterion.
- T1.1 — Clean build.
rm -rf dist node_modules && npm install && npm run build. Expect:tsc --noEmitpasses silently; Vite build completes;dist/manifest.json,dist/service-worker-loader.js,dist/index.html, at least onedist/assets/worker-*.js, anddist/assets/background.ts-*.jsall exist. - T1.2 — Manifest sanity.
cat dist/manifest.json. Expect:manifest_version: 3;permissions: ["tabs","webNavigation","storage"]; nohost_permissions;background.service_workerpoints toservice-worker-loader.js. - T1.3 — Load unpacked. In
chrome://extensions→ Developer mode → Load unpacked → pickdist/. Expect: Extension appears with no errors; the Service worker link is active (click it to open DevTools on the background).
- T2.1 — Action click opens new tab. Click the toolbar icon. Expect: A new tab opens at
chrome-extension://<id>/index.html. The HUD showsNodes: 0 | Open: ~1(the constellation tab itself is a node). Empty-state text "Open a few tabs…" is visible. - T2.2 — Singleton tab. Click the action icon again while the constellation tab is open. Expect: Chrome switches focus to the existing constellation tab; no duplicate is opened.
- T3.1 — Typed URL → root node. In a fresh tab, type
https://example.cominto the omnibox. Expect: In the constellation, a new glowing node appears with no incoming link. The HUDNodesandOpencounters both increase by 1. - T3.2 — Link click in same tab → child node. On example.com, click the "More information…" link. Expect: A new glowing node appears, connected to the previous node by a thin line. Edge count +1.
- T3.3 — Ctrl-click / middle-click → child in new tab. On any page with links, Ctrl-click (Cmd-click on Mac) a link. Expect: The new tab appears as a glowing child of the source tab with a visible link line. This hits
onCreatedNavigationTarget, which is the priority-1 parent resolver. - T3.4 —
target="_blank"link. Openhttps://www.w3.org/TR/html52/semantics.html#the-a-element-attributes(or any page withtarget="_blank"links) and click one. Expect: Same as T3.3 — parent correctly resolved viasourceTabId. - T3.5 — Title updates. Wait for a page to finish loading. Expect: Hover the node (or zoom in) — the label beneath the disc shows the page title, not the raw URL. (Labels hidden once LOD triggers; see T6.1.)
- T3.6 — Close tab → dims. Close one of your open test tabs. Expect: Its node transitions to gray at alpha ~0.45. Link lines remain.
Opencounter decreases;Nodesunchanged. - T3.7 — Reload same tab. Reload a test tab (
Ctrl+R). Expect: A new root node is created (transition typereload→parentId: null). This is intentional — a reload breaks the "link" chain.
- T4.1 — Solar system forms. Open 5+ tabs on one domain (e.g. several Wikipedia articles across different paths: Ctrl-click from a seed Wikipedia page 5 times). Expect: All 5 nodes drift toward the same region of the canvas, forming a visible cluster, while tabs from other domains sit in a different region.
- T4.2 — New domain does not disturb existing clusters. After T4.1, open 3 tabs on a brand-new domain. Expect: The existing Wikipedia cluster stays roughly where it was. The new domain's nodes form their own cluster in a different spiral slot.
- T5.1 — 5-second flush writes positions. Watch nodes settle for ~10 seconds. In DevTools → Application → IndexedDB →
ChronoClusterGraph→nodes, pick any row. Expect:xandyfields are numbers, not null. - T5.2 — Warm-start on reload. Reload the constellation tab (
Ctrl+R). Expect: Nodes appear at their saved positions within ~100 ms; the simulation does not visibly re-layout from scratch. FPS stays high throughout. - T5.3 — Survives browser restart. Quit Chrome fully, reopen, click the ChronoCluster icon. Expect: All historical nodes (open and closed) still present; positions persisted. Note:
chrome.storage.sessionclears on restart, so new navigations in old tabs will create new root nodes — this is correct behavior.
Only the dev build exposes the Seed 500 button.
- T6.1 — Seed below threshold. Run
npm run dev, reloaddist/inchrome://extensions, open the constellation. Click Seed 500 four times. Expect: ~2000 nodes visible; LOD indicator flips toon; labels disappear; node radii shrink by ~40 %. - T6.2 — FPS holds under load. After T6.1, watch the FPS counter for 30 seconds while panning. Expect: FPS stays ≥ 55. If it drops below 45, inspect the Worker in DevTools → Sources →
worker-*.jsfor long tick times.
- T7.1 — Drag to pan. Mouse-down on empty canvas, drag. Expect: Viewport follows smoothly; on release, momentum deceleration.
- T7.2 — Wheel to zoom. Scroll wheel over a cluster. Expect: Zoom centers on the cursor; labels re-layout cleanly.
- T7.3 — Pinch on trackpad. Two-finger pinch on a trackpad. Expect: Zoom in/out responsively.
- T8.1 — Export. With at least 10 nodes present, click Export. Expect: A
chronocluster.jsonfile downloads. Open it — confirmversion: 1,exportedAttimestamp, and arraysnodesandedgeswith populated entries. - T8.2 — Import. Click Clear to wipe, then Import and pick the JSON from T8.1. Expect: All nodes/edges return, positions included. HUD counts match the pre-clear state.
- T9.1 — Confirm prompt. Click Clear. Expect: Browser
confirm()dialog. Cancel → nothing happens. - T9.2 — Nuclear wipe. Click Clear again, confirm. Expect: HUD resets to
Nodes: 0 | Edges: 0 | Open: 0. IndexedDB →ChronoClusterGraphtables are empty.chrome.storage.sessionkeyschronocluster.lastNodeByTabandchronocluster.nodeForTabare gone (inspect in the service worker DevTools Console:await chrome.storage.session.get(null)).
Open the service worker DevTools (from chrome://extensions) and run:
const db = await (async () => {
const all = indexedDB.databases ? await indexedDB.databases() : [];
return all.find(d => d.name === "ChronoClusterGraph");
})();
console.log(db);Expect: A database entry with name: "ChronoClusterGraph" and a numeric version.
- Open
chrome://extensions. Is the extension enabled? Any red error badge? - Click the Service worker link to open its DevTools. Console errors there usually point to a failed import or a Dexie migration issue.
- Try rebuilding:
rm -rf dist && npm run build, then reload the extension fromchrome://extensions.
chrome.webNavigationpermission may have been dropped. Checkdist/manifest.jsoncontains"webNavigation"inpermissions.- Certain pages (e.g.
chrome://URLs, PDF viewer) are filtered out byhandleNavCommitted. This is intentional.
- Open the constellation tab's DevTools (F12). In the Performance tab, record ~5 seconds. Look for long tasks in the main thread — most likely suspects are
useLiveQuerydiffing or Pixi label creation. - Try disabling strict mode temporarily (
src/main.tsx) — StrictMode doubles effects in dev, which can mask FPS.
- Expected.
chrome.storage.sessionis cleared on restart, so new navigations in tabs that were already open will appear as roots. Fresh tabs opened from links after restart will still resolve correctly viaonCreatedNavigationTarget.
- Check the service worker DevTools Console for
Error in chrome.runtime.onMessage. If the worker was suspended, theonMessagehandler may have missed the event — reload the constellation tab and try again.
- Graph data: Constellation tab DevTools → Application → IndexedDB →
ChronoClusterGraph. - Tab-routing cache: Service worker DevTools Console →
await chrome.storage.session.get(null). - Message traffic: Constellation tab DevTools → Sources →
worker-*.js, set breakpoints in themessagehandler.
- Soft reset (keep installed, drop all data): click Clear in the toolbar.
- Full uninstall:
chrome://extensions→ Remove. Dexie database persists until Chrome garbage-collects the extension origin; to delete immediately, also use DevTools → Application → Clear storage → Clear site data onchrome-extension://<id>. - Wipe and rebuild from source:
rm -rf dist node_modules && npm install && npm run build, then reload unpacked.
Quick architectural reference:
| Context | File | Allowed to touch | Forbidden |
|---|---|---|---|
| Service worker | src/background.ts |
chrome.*, Dexie writes, chrome.storage.session |
DOM, Pixi, Worker |
| Full-tab page | src/main.tsx, src/App.tsx, src/hooks/* |
DOM, Pixi, spawns physics Worker, Dexie reads via useLiveQuery |
chrome.tabs.* writes |
| Physics worker | src/worker.ts |
d3-force, transferable buffers |
DOM, Dexie, chrome.* |
Parent resolution priority (in src/lib/observers.ts):
onCreatedNavigationTarget.sourceTabId→ last node of source tab.onCreated.openerTabId→ last node of opener tab.onCommittedwithtransitionType === "link"→ last node of same tab.- Anything else →
null(root).
Domain gravity (golden-angle spiral, src/lib/domainGravity.ts): angle = i · 2.39996, radius = 140 · √(i+1), i = lexically-sorted domain index.
LOD (src/lib/lod.ts): labels hidden and radii × 0.6 once nodeCount > 2000.