Skip to content

Repository files navigation

GptTree

A Chrome extension that turns ChatGPT conversations into a spatial map. Branching a chat normally scatters it across unrelated sidebar entries; GptTree reconstructs the tree and draws it as a git-graph style canvas, docked beside the live page.

It augments the visible chatgpt.com page from a closed Shadow Root. It stores metadata only — never message bodies, assistant text, or summaries.

GptTree docked beside a ChatGPT conversation, drawing the conversation tree as a git-style graph

The live chat stays on the left; the dock on the right is the map. Each coloured lane is one conversation, FORK POINT marks the message a branch left from, and HEAD is the one you are reading. Above, a single question about how a CPU works has fanned into five branches — cache, ALU, clock, registers — each still hanging off the exact message that spawned it, instead of five unrelated rows in ChatGPT's sidebar.

Install

No store listing; load it unpacked.

pnpm install
pnpm build          # -> .output/chrome-mv3

Then open chrome://extensions, enable Developer mode, and Load unpacked from .output/chrome-mv3.

Requires Node 22 and pnpm 10.14.0.

Commands

pnpm dev                  # WXT dev server
pnpm build                # -> .output/chrome-mv3 (load unpacked from here)
pnpm test                 # vitest run (unit)
pnpm test -- src/content/spine.test.ts       # single file
pnpm test -- -t 'name of the test'           # single test by name
pnpm typecheck            # wxt prepare && tsc --noEmit
pnpm test:e2e:install     # pinned Playwright Chromium, required once
pnpm test:e2e             # Playwright; requires a fresh `pnpm build` first
pnpm verify:release       # test + typecheck + build + e2e + artifact verifier

Architecture

A WXT + React Chrome MV3 extension. Chrome-only: it runs only on https://chatgpt.com/*, drives the visible site UI, and never calls private APIs or submits content without an explicit user action.

Two processes, one boundary:

  • Background coordinator (entrypoints/background.tssrc/background/start.tssrc/background/runtime.ts) is the sole reader/writer of graph storage. GraphRepository serializes all mutations through one promise queue, revalidates every command payload, and broadcasts the new snapshot to every chatgpt.com tab.
  • Content UI (entrypoints/content.tsxsrc/content/Dock.tsx) mounts a closed WXT Shadow Root and talks to the background only through RuntimeCommand (src/shared/runtime.ts). It never touches browser.storage.

Persisting something new means: a new RuntimeCommand union member → validation and handling in src/background/runtime.ts → a schema change in src/shared/schema.ts (bump GRAPH_SNAPSHOT_VERSION, keep older versions loadable) → the content call site.

src/shared/schema.ts is the trust boundary. Everything read from storage or arriving from a content script is re-validated there with exact-shape checks (exactObject), not casts. Anything malformed or version-incompatible loads as the current empty snapshot rather than throwing. The graph is versioned metadata held under a single key, gpttree.graph-snapshot (GRAPH_STORAGE_KEY), and storage models must never gain fields for message bodies, turn text, prompts, or summaries.

The adapter is a capability detector (src/content/chatgpt-adapter.ts). Every operation returns {status:'ready', …} or {status:'unavailable', reason:'ChatGPT UI changed'}. It only recognizes the canonical https://chatgpt.com/c/<id> URL — any query, hash, port, or credential makes it unavailable. It never guesses a selector or acts from an unrecognized state; it fails closed. getDiagnostics() reports selector match counts for debugging DOM drift.

Turn identity drives the tree. Turns are {key: 'turn:<message-id>', ordinal, role, fingerprint}, where the fingerprint is a SHA-256 of normalized visible text. sharedAssistantForkKey in src/content/graph-parent.ts decides parentage: a node is a child if the parent's turns are an exact key+ordinal+role+fingerprint prefix of the child's and the fork point is an assistant turn. mergeTurns exists because ChatGPT virtualizes the thread — an extraction is only a window, so stored turns merge with incoming ones and are never deleted by a scroll.

Layout is derived, never stored. src/content/spine.ts computes the lane layout from the tree on every render; GraphCanvas.tsx draws it.

Privacy

GptTree persists conversation IDs and URLs, short labels, relationships, job checkpoints, and a bounded preview of the user's own prompt (≤120 chars). It never persists assistant text, full message bodies, or summaries.

pnpm verify:release enforces that the built manifest carries exactly the storage permission and exactly the https://chatgpt.com/* host permission, that the production Shadow Root stays closed, and that no test canaries ship. The e2e suite asserts metadata-only storage.

Testing

  • Unit tests run in the node environment by default; React tests opt in with a // @vitest-environment jsdom docblock on line 1. Tests live beside their source as *.test.ts(x).
  • E2E runs the real unpacked build against a local HTTPS replay server serving the genuine https://chatgpt.com origin (tests/e2e/support/), so build before running e2e or you test a stale artifact. Specs run serially, and the harness refuses any Chromium revision other than the pinned one.
  • Behavior changes follow red-green-refactor: write the focused test first, run it to a meaningful failure, implement the smallest change, then run the focused suite followed by full verification.

Further reading

  • CONTEXT.md — domain language and product guardrails

About

A Chrome extension that turns branching ChatGPT conversations into a git-style spatial map, docked beside the live page. Metadata only — never message bodies.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages