Skip to content

Interactive Module Dependency Graph

Auto-generated. Data file: ./graph.json — produced by node scripts/generate-graph.mjs. The page below renders that JSON as a live, zoomable, pannable, clickable graph.

Click any node to open its detail panel (file path + link to its TypeDoc page when available). Drag to pan. Scroll to zoom. Use the search box to filter by path. Toggle the legend to hide/show buckets.

<!-- RELIABILITY NOTES FOR VITEPRESS RENDERING

VitePress runs Markdown output through Vue's template compiler, which happily interprets {{ ... }} and dynamic-looking attributes. To keep this page robust under a production vitepress build we:

1. Put ALL interactive markup inside <ClientOnly> so the graph never runs
   during SSR (vis-network needs `window` + a real canvas).
2. Externalize the bootstrap JS to `docs-site/.vitepress/public/graph.js`
   and load it via `<script src="/graph.js" defer>`. VitePress serves
   the `public/` folder from the site root, so the absolute `/graph.js`
   path is correct regardless of where this page is mounted in the
   sidebar tree.
3. Wrap the detail-panel container in `v-pre` so Vue does NOT attempt to
   compile the placeholder markup or any HTML the script later injects
   via `innerHTML` (mustache-style strings inside generated content
   would otherwise be parsed as Vue interpolation).
4. Fetch `./graph.json` with a path relative to the current page. The
   assemble step copies `docs/internal/architecture/generated/graph.json`
   to `docs-site/content/architecture/generated/graph.json`, sitting
   next to this page in the built site — so `./graph.json` resolves
   correctly under any VitePress base path.

-->

<ClientOnly> <div class="hch-graph-toolbar"> <input id="hch-graph-search" type="search" placeholder="Filter nodes by path…" autocomplete="off" /> <button id="hch-graph-fit" type="button">Fit</button> <button id="hch-graph-reset" type="button">Reset</button> <span id="hch-graph-stats" class="hch-graph-stats">loading…</span> </div>

<div id="hch-graph-canvas" style="width:100%;height:720px;border:1px solid var(--vp-c-divider);border-radius:8px;background:var(--vp-c-bg-soft);"

</div>

<!-- v-pre on the detail container: keeps Vue out of any HTML injected here at runtime by /graph.js (the script builds strings containing <dl><dt><dd> plus interpolated values, and we never want Vue to try to compile them). --> <div id="hch-graph-detail" class="hch-graph-detail" v-pre> <em>Click a node to see details.</em> </div>

<!-- vis-network UMD bundle from a pinned CDN version --> <link rel="stylesheet" href="https://unpkg.com/vis-network@9.1.9/styles/vis-network.min.css" /> <script src="https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js" defer

</script>

<!-- Bootstrap script lives in docs-site/.vitepress/public/graph.js and is served from the site root. defer ensures it runs after vis-network is parsed. --> <script src="/graph.js" defer></script>

<style> .hch-graph-toolbar { display: flex; gap: 0.5rem; align-items: center; margin: 0 0 0.75rem 0; flex-wrap: wrap; } .hch-graph-toolbar input[type='search'] { flex: 1 1 240px; padding: 0.4rem 0.6rem; border: 1px solid var(--vp-c-divider); border-radius: 6px; background: var(--vp-c-bg); color: var(--vp-c-text-1); } .hch-graph-toolbar button { padding: 0.4rem 0.8rem; border: 1px solid var(--vp-c-divider); border-radius: 6px; background: var(--vp-c-bg-soft); color: var(--vp-c-text-1); cursor: pointer; } .hch-graph-stats { margin-left: auto; font-size: 0.85rem; color: var(--vp-c-text-2); } .hch-graph-detail { margin-top: 0.75rem; padding: 0.75rem 1rem; border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); min-height: 4rem; } .hch-graph-detail dl { display: grid; grid-template-columns: max-content 1fr; gap: 0.25rem 1rem; margin: 0.5rem 0 0 0; } .hch-graph-detail dt { font-weight: 600; color: var(--vp-c-text-2); } .hch-graph-detail dd { margin: 0; } .hch-graph-path { font-family: var(--vp-font-family-mono); font-size: 0.85rem; color: var(--vp-c-text-2); word-break: break-all; margin-top: 0.25rem; } </style> </ClientOnly>

Legend

  • Circle — first-party TypeScript module (apps/api or packages)
  • Diamond — external package (anything under node_modules/)
  • Triangle — Node.js core module (fs, path, stream, …)
  • Red edge — part of a circular dependency (rule no-circular)
  • Orange edge — rule violation (e.g. cross-app, deep package import)
  • Gray edge — clean import

Node size scales with fan-in (how many other modules import it) — large nodes are your high-leverage shared modules; tiny nodes on the rim are leaves.

How to regenerate

bash
node scripts/generate-graph.mjs

This rewrites graph.json next to this page. The page itself never needs to be regenerated — it always renders the latest JSON on load.

Heritage Community Hub — Internal. Access restricted via Cloudflare Access + Entra ID.