Docs
How it works
A static analysis pipeline writes a canonical graph to SQLite. A read-only server projects that graph onto a stable spatial layout, and the browser draws it on a single canvas.
The analyzer pipeline
| Analyzer | What it adds |
|---|---|
| Filesystem | The tree of applications, folders and files, with language, lines, bytes and content hashes. |
| Git metrics | Commits, authors, churn and last change per file, from one bounded git log. |
| TypeScript & JavaScript | One type-checked program per application: imports, components, functions, calls, renders, references, file-based routes and handlers, HTTP requests. |
| Frameworks | Classes, methods and inheritance; routes; commands and schedules; migrations replayed into tables; models with their reads and writes; server-driven pages linked to their components. |
| API matcher | Frontend requests matched to endpoints, with ambiguity, origin, shadowing and constraint checks. |
Nothing in the target repository is executed or installed. The TypeScript program covers the indexed files and the compiler’s own library files only, never node_modules, so the live index and every history snapshot resolve the same way. Results are cached per application: if no file in an application changed, its analysis is replayed.
An evidenced graph
Entities (applications, files, classes, methods, routes, endpoints, commands, tables…) have stable IDs that do not depend on where the repository is checked out. Every relationship must carry at least one piece of evidence: the analyzer and its version, a confidence, a file and source range, and an explanation.
Where proof is missing, the analyzers record a diagnostic instead of an edge: an unresolved import, an HTTP call whose base URL cannot be proven, an ambiguous route. Name-only matching never creates an edge. Call sites that stay unresolved are counted per symbol, so the gaps are measurable.
A layout that holds still
Coordinates come from the server, never the browser: integer rectangles from a deterministic packing with bucketed sizes. Slot order is saved per state directory in layout.json, so new children are appended and removed ones leave holes. The same graph gives the same map, and selection, search or loading order never move anything.
The map is one <canvas> with Canvas 2D: it loads children on demand, culls what is off screen and keeps a budget per frame. On a 5,000-entity application it pans at 60 fps.
The history store
history.db holds content-addressed versions of entities, relationships and findings. A snapshot is a set of pointers to them, so an entity that never changes is stored once for the whole history. Comparisons classify entities by source hash, facts, signature, kind and parent, and follow identities across renames and moves through a separate lineage, without weakening the IDs.
What it cannot see yet
- No runtime behavior: tables are what migrations declare, not the live database.
- Calls through callbacks, props, untyped values and interface dispatch stay unresolved and are counted.
- Middleware is drawn by name, not followed; events, listeners, observers, notifications and ORM relationships are not linked yet.
- Routes registered inside arbitrary service providers or conditions cannot be proven statically.
- Many ecosystems are detected and mapped, but their calls are not analyzed yet. Tell us which one you need.
The full architecture document is in the repository: docs/architecture-visualizer.md.