Skip to content

Repository files navigation

Notion Atlas - Browser Extension and Website

Access the project here: https://notion-atlas-extension.onrender.com/

A companion layer that turns a Notion workspace into a living map: it learns which pages matter from how you actually work, discovers connections nobody wrote down, and brings forgotten knowledge back exactly when it becomes useful again.

The Map

The problem

Notion is excellent at helping people create and organize information. The problem appears after a workspace grows. With hundreds or thousands of pages, you start losing track of information you already wrote: how pages relate, what you actually use, what you were working on last month, which old notes just became relevant again. The workspace becomes an archive you search instead of a map you navigate.

Atlas answers one question: what does my workspace actually look like as a brain, and which part of it is useful to me right now? It infers structure instead of asking you to maintain it. Your workspace already contains a map of how you think; Atlas reveals it.

What it is

A TypeScript monorepo containing a browser extension, a background sync and analysis engine, and a companion web app. Atlas connects over Notion's official OAuth API, indexes pages and their relationships, learns from real reading behavior recorded by the extension, and serves four hero experiences:

Adaptive Home. Opens on what matters right now: current work ranked by a blend of recency, sustained engagement, thread context, personalized graph rank, and upcoming dates, never a plain ORDER BY last_edited. Threads (active regions), recent changes grouped by region, and one resurfaced page complete the start screen.

Map. The workspace as cartography, not a particle simulation. Communities detected in the graph become tinted regions with place names; important pages read as cities; navigation habits, semantic similarity, links, and hierarchy draw as distinguishable roads. Semantic zoom reveals labels progressively; selecting a page fades everything unrelated to its k-hop neighborhood, and every connection can explain itself.

Resurface. Finds pages that are historically important, quiet for weeks, and strongly related to what you are doing today, then says why in plain terms with the actual graph path ("System Design Interview Prep → Distributed Systems Notes, last opened 24 days ago, 85% similar"). Exposure history and feedback keep it from nagging.

Wren, the navigator. A cute agentic chatbot with zero API keys, zero cloud calls, and zero cost to run. Wren classifies your question with the same feature-hashed n-gram embeddings the semantic layer uses, then plans visible tool calls over the product's real machinery — search fusion, weighted-Dijkstra path tracing, the importance and resurface rankers, Louvain regions — and every step it shows carries real numbers. Ask "how is X connected to Y?" and it walks the actual strongest path, hop by hop. An online logistic model trained on your recommendation feedback personalizes its suggestions and says exactly what it learned ("active reading time weighs more for you").

Around them: fused instant search (⌘K), mined navigation Trails with honest confidences, quiet workspace Insights (landmarks, emerging and dormant regions, orphans, probable duplicates), and privacy controls that actually delete things.

Adaptive Home Resurface
Home Resurface
Focus mode + explanations Insights
Detail Insights
Wren, the navigator — agentic answers from your own graph and notes
Wren, the navigator

Try it in two minutes

npm install
npm run build
npm run dev          # http://127.0.0.1:8792

No Docker, no accounts, no API keys. Atlas boots on an embedded PostgreSQL (PGlite + pgvector) stored under ~/.cache/notion-atlas, and the first screen offers a sample workspace: 77 pages and eight weeks of scripted history that exercise the entire production pipeline (embeddings, semantic edges, community detection, layout, ranking). Nothing in the demo is hand-placed; delete the activity history in Settings and watch the product degrade honestly.

To connect a real workspace, create a public integration at notion.so/profile/integrations, set NOTION_CLIENT_ID and NOTION_CLIENT_SECRET in .env (see .env.example; the OAuth redirect URI is derived from the request), restart, and choose "Connect your Notion".

The extension: cd apps/extension && node build.mjs, then load apps/extension/dist/chrome unpacked at chrome://extensions (Firefox: about:debugging → Load Temporary Add-on → any file in dist/firefox), open its options page, and click "Connect to Atlas".

Architecture

                         Notion
                     REST API   Webhooks (signed, deduped, treated as hints)
                        │           │
   Browser extension    ▼           ▼
   (page opens, active  ┌──────────────────────┐
    reading, paths) ──▶ │  Fastify API          │  typed routes, zod at the
                        │  + durable job queue  │  boundaries, TTL caches
                        └──────────┬───────────┘  keyed by graph version
                                   │ SKIP LOCKED claims, idempotency keys,
                                   │ backoff, dead-letter, heartbeat reaping
                        ┌──────────▼───────────┐
                        │  Workers              │ sync → embed → semantic
                        │  (inline or separate) │ edges → PageRank+importance
                        └──────────┬───────────┘ → Louvain regions → cached
                                   │               force layout → trails,
                        ┌──────────▼───────────┐   metrics
                        │  PostgreSQL + pgvector│
                        │  (embedded PGlite or  │
                        │   docker-compose)     │
                        └──────────┬───────────┘
                                   ▼
                        React web app (custom Canvas2D map renderer)

One process runs everything by default. Set DATABASE_URL (see docker-compose.yml) to move onto a real PostgreSQL, where workers scale out as separate processes (npm run worker); the CI matrix runs the storage suite against both drivers.

The knowledge graph

Pages are nodes. Five heterogeneous edge types carry different meaning and different weight:

Type Source Notes
parent Notion hierarchy structural background, weighted lowest
mention page links and mentions in content a deliberate authorial act
relation database relation properties typed, bidirectional
semantic embedding similarity discovered, capped, explains itself with a %
navigation repeated page-to-page moves behavioral, time-decayed

Density is controlled deliberately: semantic edges need to clear a calibrated similarity threshold, get a stronger bar when either page is tiny (unstable vectors), skip pairs hierarchy already explains, and respect a per-page degree cap applied strongest-first. Navigation edges require repeated transitions (decayed count, not raw), so an accidental click never becomes structure, and old habits fade with a three-week half-life.

Semantic layer

Embeddings are provider-pluggable: hash (default) is a local hashed n-gram embedder (feature hashing with signed projections, sublinear TF, L2 norm) that is deterministic, offline, and free; openai swaps in text-embedding-3-small with one env var and a re-embed job. Both store into the same pgvector column with an HNSW index.

Similarity uses retrieve-then-rerank: candidates come from approximate nearest neighbors on raw vectors, then pairs are re-scored with the workspace centroid removed (anisotropy correction: short notes share a baseline of common vocabulary that inflates every raw cosine; subtracting the corpus mean leaves only distinctive overlap). Search queries, which are keyword-shaped and nearly baseline-free, deliberately use the raw space instead. The thresholds are calibrated against the seeded corpus in the pipeline test.

Ranking and recommendations

packages/ranking is pure data-in/data-out, so every scoring decision is unit-tested:

  • Importance is a transparent blend of percentile-normalized signals (PageRank, decayed visit frequency, decayed active reading time, connectivity, edit and visit recency). The per-page breakdown is stored and inspectable (GET /api/dev/importance/:id); no invented coefficients pretending to be machine learning.
  • Continue ranks current work: visit and edit recency, engagement, thread context, personalized PageRank from the active set, and upcoming dates, so a deadline two days out can outrank something touched an hour ago.
  • Resurface multiplies importance × current context relevance × staleness × novelty. It requires real usage history and a quiet period, names its anchor page, and suppresses itself through exposure records and feedback ("not relevant" silences a page for a month; "don't show again" is forever).
  • Related blends direct edges, a two-hop expansion, personalized PageRank from the page, cluster co-membership, and engagement, with each contribution kept as a typed reason. The blend function is one small module away from being replaced by learning-to-rank.
  • Search is reciprocal rank fusion over four independent lists: token-based title match, tsvector full-text relevance, semantic similarity, and a behavioral prior.

Every recommendation carries its reasons; every surface exposes a "Why?".

Wren, the navigator (apps/api/src/agent)

Wren, the navigator

An agentic chat over the workspace that costs nothing to run: no API keys, no cloud model, no tokens. Wren classifies questions with the same feature-hashed n-gram embeddings the semantic layer uses (raw-cosine matching against intent exemplars, surface patterns as priors), then plans visible tool calls over the product's own machinery — search fusion, weighted-Dijkstra path tracing, the importance and resurface rankers, Louvain regions — and answers with the numbers to back every claim. A small online learner (logistic regression over the six named importance signals, trained on your recommendation feedback) personalizes its suggestions and says exactly what it learned ("active reading time weighs more for you"). Every step Wren takes is shown in the transcript; nothing it says can't be traced to a query.

Graph algorithms (packages/graph, all hand-implemented and tested)

  • Weighted PageRank via power iteration (dangling mass redistribution, weight-proportional link following)
  • Personalized PageRank seeded by recent activity, the connective tissue behind Home, Resurface, Related, and search boosts
  • Louvain community detection (two-phase modularity optimization) with stable cluster matching across recomputes, so manual renames survive
  • Strongest-path Dijkstra with cost 1/(0.15+w), powering "how are these connected?" with paths a person recognizes
  • Capped k-hop neighborhoods (strongest edges first) for focus mode
  • Barnes-Hut force layout with cluster gravity, seeded and deterministic, computed server-side and cached so the Map only ever draws

Synchronization

Webhook events are signals that state may have changed, never state itself: verified (HMAC over the exact raw body), deduped by event id, they only enqueue a page_sync that re-fetches truth from the API. Sync jobs carry idempotency keys that include last_edited_time, so event bursts collapse; a fetched page older than what is stored is ignored (out-of-order guard); missing pages become tombstones; child pages discovered inside block content are crawled recursively because the search endpoint is not assumed to enumerate the workspace. The queue gives at-least-once execution with exponential backoff, dead-lettering, and heartbeat-based crash recovery, and the db test suite proves the duplicate/out-of-order/retry behaviors.

The Map renderer

A custom Canvas2D renderer (evaluated Sigma.js, Cytoscape, and D3 first; the cartographic look needed full pipeline control, and at the target scale a well-culled Canvas2D beats a WebGL dependency):

  • draw-on-demand: zero rendering while idle; a ticker that races requestAnimationFrame against a timeout runs only during camera animation (and survives environments where rAF never fires)
  • positions precomputed by the layout job; the client never runs physics
  • viewport culling, batched edge strokes grouped by style, spatial-grid picking
  • semantic zoom: labels place greedily by importance into a screen-space occupancy grid, region names set in a serif fade out as page labels take over, emoji icons appear up close
  • regions render as convex hulls stroked thick with round joins (a soft padded blob for the price of one path)

npm run seed:large replaces the workspace with 2,000 synthetic pages across 12 topical clusters and runs the same pipeline, which is how the smooth-at-2k claim is validated rather than asserted: on an M-series laptop the full pipeline (embeddings, semantic edges, PageRank, Louvain, layout, trails, metrics) settles in about 43 seconds on embedded PGlite, and the resulting map (2,000 nodes, ~7,500 edges) renders at about 2.9 ms per frame fully zoomed out and under 1 ms zoomed in, several times inside the 60 fps budget.

Privacy

Atlas handles private knowledge, so the rules are explicit:

  • The extension only ever observes notion.so / notion.site. It records page opens, page-to-page navigation, and active reading time, counted only while the tab is visible, focused, and recently interacted with; an idle tab overnight earns nothing (the tracker is unit-tested on exactly that).
  • Every control in Settings is enforced server-side, not hidden client-side: ingestion rejects what preferences forbid.
  • Deletion is real: activity history, the semantic index, or the whole workspace (including the stored OAuth token) each have a button.
  • Notion tokens are AES-256-GCM encrypted at rest; the activity endpoint requires a workspace-scoped ingest token; webhook payloads are signature-verified.
  • Everything lives on your machine by default. No telemetry.

Engineering notes

Things that turned out to matter:

  • Short-text embeddings are a calibration problem. Raw hashed n-gram cosines on note-sized documents are dominated by shared common vocabulary; the fix that survived measurement was centroid removal for document pairs, the raw space for keyword queries, a stronger bar for tiny pages, and thresholds locked in by an end-to-end pipeline test over the seeded corpus.
  • The demo is the product path. Demo mode does not mock anything; it seeds data and runs the same jobs. That decision caught real bugs (a workspace-scoped ETag miss, a canvas sizing loop) that mocks would have hidden.
  • Behavior needs half-lives. Visits, transitions, and navigation edges all decay (14 to 28 day half-lives) with periodic exact recomputes from the event log, so the product reflects your current life, not your permanent history.
  • Absolutely-positioned replaced elements do not stretch. A canvas with inset: 0 keeps its intrinsic size; combined with a ResizeObserver writing the backing store, that is a feedback loop to a 16-million-pixel canvas. Explicit width/height plus a change guard fixed it.

Repository layout

apps/api          Fastify server, routes, inline worker, integration tests
apps/web          React app, design system, custom map renderer
apps/extension    MV3 extension: tracker, queue, side panel, options
packages/graph    PageRank, Louvain, paths, layout (tested)
packages/ranking  importance, continue, resurface, related, threads (tested)
packages/search   embedding providers, chunking, RRF fusion (tested)
packages/notion   API client, OAuth, extraction, webhooks (tested)
packages/db       schema, migrations, durable queue, activity ledger (tested)
packages/shared   types, API contracts, config validation, decay math
workers           job handlers, pipeline, demo corpus, scale seeder

npm test runs 110+ tests across every layer, including a full pipeline integration test (seed → embeddings → edges → regions → layout → resurface) and API tests against the real stack via fastify.inject. CI runs typecheck, lint, tests, build, and the storage suite against real PostgreSQL with pgvector.

Future work

Learned ranking on the recommendation features (the scorer interface is already shaped for it); collaborative workspace intelligence; richer topic evolution over time; graph history and time-travel for the Map; an LLM pass for region naming and connection summaries where a key is configured, kept strictly optional, the way the rest of the product treats AI: a supporting tool, not the point.

About

Browser extension that turns your Notion workspace into a map of everything you know.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages