diff --git a/docs/plans/2026-07-09-rust-core-webgpu-architecture-brainstorm.md b/docs/plans/2026-07-09-rust-core-webgpu-architecture-brainstorm.md new file mode 100644 index 000000000..a766a5ad0 --- /dev/null +++ b/docs/plans/2026-07-09-rust-core-webgpu-architecture-brainstorm.md @@ -0,0 +1,987 @@ +# Rust Core And WebGPU Architecture Brainstorm + +## Status + +Exploratory brainstorm, not an implementation plan. + +The goal is to imagine CodeGraphy as a program you install first, with VS Code, +MCP, and other surfaces acting as clients of the same local core. + +## Rust Crates And Installation Shape + +Rust crates are roughly equivalent to packages. A Rust project can have several +crates internally while still shipping one installed program. + +The desired product shape is still one user-facing core install: + +```text +codegraphy +``` + +Internally, that single installed binary can be built from multiple crates: + +```text +codegraphy-core internal Rust library crate +codegraphy-protocol internal request/response type crate +codegraphy-cli binary crate that produces the `codegraphy` executable +``` + +Users should not need to think about those crates. They install and run +`codegraphy`. + +## Chosen Direction So Far + +Use the main Rust CLI binary as both the human CLI and the machine interface. + +```text +Human CLI: + codegraphy index + codegraphy query + codegraphy status + codegraphy plugin install + +Machine mode: + codegraphy stdio +``` + +`codegraphy stdio` is not a daemon and not a network server. It is a long-lived +child process started by a client, such as the VS Code extension. The client +sends JSON or JSON-RPC-style messages over stdin and receives responses over +stdout. + +This keeps one core program while avoiding a fresh process spawn for every UI +interaction. + +## High-Level Architecture + +```text + one installed program + `codegraphy` + | + v + Rust CodeGraphy core engine + - indexing + - SQLite cache + - graph query + - search/filter/sort/projection + - plugin registry/runtime + - plugin extension contributions + - graph diffs + + +------------------------+------------------------+ + | | | + v v v + Human CLI Machine stdio mode MCP binary/adapter + `codegraphy query` `codegraphy stdio` agent tools + ^ + | + VS Code extension + TypeScript shell + - VS Code API + - settings UI + - graph controls + - plugin UI host + - webview wiring + | + v + Webview graph UI + - WebGPU 2D renderer + - interactions + - panels/menus +``` + +The Rust core is the source of truth. The VS Code extension provides UI for core +features rather than reimplementing core behavior in TypeScript. + +## VS Code Process Model + +When a VS Code window opens: + +```text +VS Code window + -> TypeScript extension activates + -> extension starts `codegraphy stdio` + -> extension sends requests over stdin/stdout + -> Rust core reads/writes SQLite and computes graph/query results + -> extension/webview renders the returned projection or diff +``` + +If multiple VS Code windows are open, the simplest model is one `codegraphy +stdio` process per window: + +```text +Window A -> codegraphy stdio process A +Window B -> codegraphy stdio process B + | + v + same workspace SQLite cache +``` + +This is acceptable if the processes are lightweight and SQLite write +coordination is handled correctly. Architecturally there is still one core: one +Rust codebase and one local cache format. There may simply be multiple running +instances, just like multiple terminals can run the same CLI. + +## SQLite Multi-Process Coordination + +"SQLite write coordination is handled correctly" is doing a lot of work in the +multi-window model and needs a concrete design: + +- Open the cache in WAL mode with a generous `busy_timeout`. WAL allows many + readers concurrent with one writer, which matches the desired shape. +- Elect a single indexer per workspace. If Window A and Window B both start + `codegraphy stdio` against the same workspace, only one process should run + the watcher/indexer; the other should be a reader. A simple election is an + advisory lock (a locked row or a separate lock file with the writer's PID and + a heartbeat timestamp). When the writer exits, a reader promotes itself. +- Readers need a change signal. A monotonically increasing `revision` value in + a metadata table plus either polling on a slow timer or a filesystem watch on + the WAL file is enough for readers to know "re-run my active projection and + send a diff." +- Migrations must be guarded by the same writer election so two processes never + race a schema upgrade. + +This also answers "are per-window processes wasteful": readers are cheap; only +the elected writer pays for watching and indexing. + +## File Watching Ownership + +`watchWorkspace` appears in the protocol list but the watcher's owner is +undecided, and it matters: + +- If the Rust core owns watching (e.g. the `notify` crate), the CLI and MCP + surfaces get incremental indexing for free, but each core process needs the + writer election above to avoid N watchers per workspace. +- If the VS Code extension forwards its own file events into the core, the + extension inherits VS Code's efficient platform watcher and remote support, + but then CLI/MCP long-lived sessions need their own watch path anyway. + +Recommendation: the core owns watching (single code path for all surfaces), +gated behind the writer election, with debounced incremental re-index. The +extension may additionally forward save events for lower latency on the file +the user is editing. + +## Where The Binary Runs: Remote, Containers, Web + +This is the biggest gap in the current draft. VS Code is not always one local +process: + +- Remote SSH / WSL / dev containers: the extension host runs on the remote + machine, next to the workspace files. `codegraphy stdio` must run there too, + which means the extension needs the right binary for the *remote* platform, + not the user's laptop. The webview (and therefore the WebGPU renderer) runs + on the local client — this split is actually favorable: indexing happens + where the files are, rendering happens where the GPU is. The graph payload + crosses the remote channel, which strengthens the case for compact binary + projections and diffs. +- vscode.dev / github.dev (VS Code in a browser): there is no process spawn at + all, so a native binary cannot run. Decision: browser-hosted VS Code is + unsupported. The extension should detect the web environment and show a + clear "CodeGraphy requires desktop VS Code" message rather than failing + silently. (Remote SSH/WSL/containers remain supported — those have a real + machine to run the binary on.) +- Platform matrix: macOS arm64/x64, Windows x64/arm64, Linux gnu/musl + x64/arm64. Biome and esbuild both ship platform-specific packages; + platform-specific VSIXes are the VS Code equivalent. + +## Binary Distribution And Version Skew + +- Prefer bundling the binary in platform-specific VSIXes (the repo already + builds platform VSIXes for Tree-sitter natives, so this pipeline exists), + with a `codegraphy.path` setting to override with a user-installed binary, + Deno/Biome style. +- The `initialize` handshake must exchange protocol versions and refuse or + degrade cleanly on mismatch. Version skew is guaranteed once the CLI can be + installed independently of the extension. +- Windows file locking of a running binary during extension update needs the + Biome-style copy-to-temp workaround. + +## Protocol Details Worth Deciding Early + +- Do not invent framing. JSON-RPC 2.0 with LSP-style `Content-Length` framing + gets mature client/server libraries on both sides, plus a well-understood + model for requests, notifications, cancellation (`$/cancelRequest`), and + progress (`$/progress`). Indexing is long-running; cancellation and progress + are day-one requirements, not extensions. +- JSON is fine for control messages but wrong for graph payloads: typed arrays + become base64 or number arrays, and the cost is paid again at every hop. + Reserve a binary frame type (length-prefixed, referenced from a JSON-RPC + result by id) for projections and diffs. +- Define revision semantics for diffs: every projection and diff carries the + revision it was computed from; a client that misses a diff or reconnects + requests a full snapshot. Snapshot + ordered diff log is the model. + +## One Flat Binary Path End To End + +The single biggest cross-cutting performance idea, and the reason the Rust +core and the WebGPU renderer belong in the same conversation: + +```text +Rust core builds struct-of-arrays projection + (positions?, node ids, sizes, colors as flat typed buffers) + -> binary frame over stdio (no JSON, no base64) + -> extension holds it as a Uint8Array, does not parse it + -> webview.postMessage(msg, [buffer]) transfers the ArrayBuffer + -> webview writes it straight into GPU buffers (queue.writeBuffer) +``` + +VS Code added ArrayBuffer transfer to webview messaging precisely because +JSON-serializing typed arrays was pathologically slow +([issue #115807](https://github.com/microsoft/vscode/issues/115807), +[PR #148429](https://github.com/microsoft/vscode/pull/148429) extended it to +webview views). Do not count on `SharedArrayBuffer` in webviews — it requires +cross-origin isolation headers the webview host does not guarantee. + +The payoff: node and edge data is encoded once in Rust and decoded zero times. +The extension host is a dumb pipe. If any hop reintroduces per-node JS objects, +most of the Rust rewrite's rendering benefit is lost. This constraint should +shape the projection format before either the core or the renderer is built. + +## Plugin Migration Cost + +The draft describes the plugin end state but not what happens to the seven +existing TS plugins (typescript, vue, svelte, godot, unity, markdown, +particles). Looking at their actual shape: + +- They are imperative TS with hooks (`analyzeFile`, `onPreAnalyze`, + `onFilesChanged`) and real logic — e.g. the TypeScript plugin implements + tsconfig path-alias resolution. A purely declarative "tree-sitter queries + + mapping manifest" format would cover the simple analyzers but not these. +- Realistic options per plugin: (a) fold into the Rust core as built-in + analyzers (likely right for typescript/markdown — they are near-core), (b) + rewrite as Wasm plugins, (c) run under a JS compatibility host (Oxc-style), + (d) stay webview-only (particles needs no core at all). +- Whatever the path, build a differential harness first: run the Rust indexer + and the current TS indexer over fixture repos and diff emitted facts. That + harness is also the rewrite's acceptance test. + +## Wasm Runtime Specifics + +- Runtime: Wasmtime with the component model and WIT-defined interfaces is the + current best practice for typed plugin ABIs (this is where the ecosystem + SWC/Zed pointed at is converging). +- Resource limits: use epoch interruption or fuel so a misbehaving plugin + cannot hang indexing — this is the rust-analyzer proc-macro lesson applied + to Wasm. +- Parse once: plugins must not each bundle their own Tree-sitter and re-parse + files. The host should parse once and expose an AST/query API through the + plugin interface. This is both a performance and a plugin-size decision, and + it constrains the WIT interface early. + +## Migration Sequencing + +The end state is well described but there is no strangler path. A workable +order, each step shippable: + +```text +1. Renderer seam in the webview (GraphRenderer interface, react-force-graph + behind it). Pure TS, no Rust dependency, immediately useful. +2. WebGPU 2D renderer behind the seam, CPU layout. Biggest visible win, + independent of the core rewrite. +3. Rust indexer as a sidecar: `codegraphy stdio` produces graph facts, the + existing extension consumes them behind the current data layer, validated + by the differential harness. +4. Move query/search/filter/projection into the core; extension data layer + shrinks to a protocol client. +5. Plugin runtime (Wasm host), migrate plugins per the table above. +6. CLI/MCP surfaces over the now-proven core. +``` + +Steps 1–2 and 3–4 are independent tracks and can proceed in parallel. + +## Why Not A Server + +This direction intentionally avoids an always-on CodeGraphy server. + +- No port. +- No global daemon. +- No separate background service. +- No user-visible server lifecycle. + +`codegraphy stdio` lives only while the client that started it needs it. It is a +local process mode of the main binary. + +## Core Responsibilities + +The Rust core should own durable and computational behavior: + +- workspace indexing; +- Tree-sitter parsing and language analysis; +- plugin loading and execution; +- plugin manifests, registry, install, enablement, and capability discovery; +- SQLite cache reads/writes/migrations; +- graph query; +- search; +- filtering; +- sorting; +- visible graph projection; +- graph diff generation; +- node/edge detail lookup; +- plugin installation and registry state; +- plugin command dispatch for core-owned plugin behavior. + +The important idea is that CLI, MCP, and VS Code all use the same query and data +processing engine. + +## VS Code Extension Responsibilities + +The TypeScript extension should stay at the VS Code seam: + +- activation; +- VS Code commands; +- settings UI; +- webview lifecycle; +- translating UI events into core requests; +- forwarding core results to the webview; +- editor/file opening behavior; +- host-specific affordances; +- hosting plugin-provided extension/webview contributions. + +The extension should not own indexing, durable graph facts, graph query +semantics, or heavy data processing. + +The extension can still be extended by plugins. The distinction is that the +extension hosts plugin UI contributions while the Rust core owns plugin identity, +installation, enablement, data, and core behavior. + +## Protocol Shape + +The machine interface can be a small typed protocol over stdio. + +Example request: + +```json +{ "id": 1, "method": "queryGraph", "params": { "search": "auth", "edges": ["import"] } } +``` + +Example response: + +```json +{ "id": 1, "result": { "nodes": [], "edges": [], "revision": 42 } } +``` + +Likely methods: + +```text +initializeWorkspace +getStatus +indexWorkspace +watchWorkspace +queryGraph +getVisibleGraph +getGraphDiff +searchNodes +getNodeDetails +listPlugins +installPlugin +listPluginContributions +invokePluginCommand +shutdown +``` + +The protocol should be versioned and diff-oriented so the extension does not +constantly move huge JSON graphs across the process boundary. + +## SQLite Cache + +The Rust rewrite does not need to keep LadybugDB. SQLite is a good default for +the Rust core because it is local, durable, inspectable, portable, and good at +querying indexed facts. + +Possible cache shape: + +```text +.codegraphy/ + cache.sqlite +``` + +SQLite can store: + +- files; +- symbols; +- relationships; +- plugin facts; +- revisions; +- settings relevant to indexed data; +- migration state. + +The extension can keep visual state such as pan, zoom, selection, and temporary +view state outside the durable graph cache unless there is a reason to persist +it. + +## Plugin Direction + +Plugins are a key part of the architecture. They should extend both the Rust +core and the VS Code/webview experience without becoming separate VS Code +extensions by default. + +The plugin should have one identity: + +```text +codegraphy.godot +codegraphy.unity +acme.architecture-tools +``` + +That one plugin identity can declare multiple contribution surfaces: + +```text +Core contributions + - language analyzers + - graph enrichers + - query extensions + - exporters + - plugin commands + - plugin-owned SQLite facts/data + +Extension/webview contributions + - Graph View toolbar actions + - context menu entries + - settings panels + - graph side panels + - viewport overlays + - custom node/edge display metadata + - optional webview assets +``` + +The core installs and enables the plugin. The extension asks the core what UI +contributions are active for the current workspace. + +```text +codegraphy plugin install acme.graph-tools + | + v +Rust core registry + | + +--> core loads plugin analyzer/query/export behavior + | + +--> VS Code extension receives plugin UI contribution descriptors + | + v + webview renders plugin UI slots/overlays/actions +``` + +### Plugin Package Shape + +A future plugin package could look like this: + +```text +plugin manifest +plugin.wasm +webview/ + bundle.js + styles.css +``` + +The manifest declares both core and client-facing capabilities: + +```text +id +name +version +core capabilities + analyzers + query extensions + exporters + commands +extension capabilities + toolbar actions + context menus + settings sections + webview slots + graph overlays +assets + webview bundle paths +``` + +Wasm plugins are the clean long-term shape for core behavior. They let Rust, +TypeScript, or other languages compile into a portable plugin format while +avoiding platform-specific native plugin binaries. Native Rust plugins are +possible, but Rust does not have a stable native ABI, so that route is likely +harder to distribute safely. + +Webview assets can remain JavaScript because they run in the webview/browser +surface. That does not weaken the Rust core model; it just means plugin UI code +lives at the UI seam while plugin facts and commands live at the core seam. + +### Extension Plugin Flow + +The VS Code extension does not discover plugins independently. It asks the core: + +```text +listPluginContributions(workspace) +``` + +The core responds with descriptors for the enabled plugins: + +```text +toolbar actions +context menu entries +settings sections +webview asset URIs or asset handles +command ids +``` + +When the user clicks a plugin action: + +```text +user clicks plugin toolbar action + -> webview sends action to TS extension + -> extension calls `invokePluginCommand` over `codegraphy stdio` + -> Rust core runs the plugin command + -> core returns graph diff, data update, or command result + -> extension/webview updates UI +``` + +That keeps plugin behavior central while still allowing rich extension UI. + +### Plugin Data Flow + +```text +Plugin analyzer + -> emits facts through Rust core + -> core writes plugin-owned rows into SQLite + -> query/projection includes enabled plugin facts + -> extension receives graph projection/diff + -> WebGPU graph updates visible buffers + +Plugin UI action + -> extension forwards command to core + -> core updates plugin data/facts + -> extension receives result or graph diff +``` + +### WebGPU Implication For Plugins + +The current plugin API can let plugins draw runtime graph UI through webview +slots and overlays. A WebGPU renderer makes that more deliberate, and the +preferred direction is to keep plugins out of the raw graph renderer. + +TS visual plugins should interface with the React/webview layout, not with the +WebGPU graph internals. A particles plugin can render a transparent animated +background by injecting into a specific HTML slot behind the graph. A panel +plugin can render into a side panel or toolbar slot. Neither needs direct node, +edge, picking, or shader access. + +Plugin rendering should split into: + +- React/webview UI slots for panels, controls, windows, and DOM overlays; +- background or foreground effect slots hosted by the webview shell; +- command descriptors that call back into core plugin behavior; +- optional future declarative graph styling metadata from core projection data. + +Avoid giving plugins direct raw access to the WebGPU renderer at first. That +would make renderer internals part of the plugin interface too early. + +If a plugin needs to change graph facts, filters, queries, or styling, the change +should flow through core and return to the webview as normal graph projection +data. The extension should still only render the active plugin UI descriptors +reported by core. + +### Companion VS Code Extensions + +The default should be core-installed CodeGraphy plugins, not separate VS Code +extensions. A separate VS Code companion extension could exist for extreme +host-specific integrations, but it should be the exception. + +Most plugins should be installable once through the core and usable from CLI, +MCP, and VS Code. + +## Similar Project Comparisons + +This brainstorm is not inventing a totally strange architecture. Several +projects use pieces of the same shape. + +### rust-analyzer + +Pattern: + +```text +Rust analysis engine + -> language server binary + -> editor clients through LSP +``` + +Useful precedent: + +- The core engine is Rust, but editor clients can be any editor that speaks LSP. +- The architecture separates internal analysis crates from the LSP-facing binary. +- The `ide` crate is treated as the useful client-facing interface, while the + LSP crate handles JSON/protocol translation. +- It keeps source/project input in memory, applies small deltas, and computes + derived analysis lazily. +- rust-analyzer also uses a separate process for proc macros because third-party + code can panic, crash, or behave nondeterministically. + +CodeGraphy lesson: + +- Keep a real Rust core interface behind the stdio protocol. +- Treat `codegraphy stdio` as a protocol adapter, not as the core itself. +- Keep plugin execution isolated enough that bad plugins cannot crash the whole + product when avoidable. + +Sources: + +- [rust-analyzer introduction](https://rust-analyzer.github.io/book/) +- [rust-analyzer architecture](https://rust-analyzer.github.io/book/contributing/architecture.html) + +### VS Code Language Server Pattern + +Pattern: + +```text +VS Code extension + -> starts a separate process + -> communicates over protocol + -> process can be written in any language +``` + +Useful precedent: + +- VS Code documents that a language client runs in the Node extension host, + starts a language server in another process, and communicates through LSP. +- VS Code calls out the two big wins: the server can be written in any language, + and it can be reused by multiple editors. + +CodeGraphy lesson: + +- `codegraphy stdio` is a natural fit. It can be CodeGraphy's protocol, even if + it is not literally LSP. +- The extension should remain a client/controller over a long-lived process. + +Source: + +- [VS Code Programmatic Language Features](https://code.visualstudio.com/api/language-extensions/programmatic-language-features) + +### Biome + +Pattern: + +```text +Rust toolchain binary + -> CLI + -> language server + -> VS Code extension talks to the binary/LSP +``` + +Useful precedent: + +- Biome's VS Code extension is a UI/editor integration over a real binary. +- Multi-root workspaces create a Biome instance per workspace folder. +- Users can override the binary path with `biome.lsp.bin`. +- Biome documents platform-specific binary package paths. +- It also has a Windows-specific option to run from a temporary location because + active LSP sessions can lock binaries. + +CodeGraphy lesson: + +- One `codegraphy stdio` process per VS Code window or workspace folder is not + weird. +- Binary discovery, version matching, and process lifecycle are product + features, not afterthoughts. +- Windows binary locking needs to be considered if the extension runs a bundled + or workspace-installed `codegraphy` binary. + +Source: + +- [Biome VS Code extension reference](https://biomejs.dev/reference/vscode/) + +### Deno + +Pattern: + +```text +Deno CLI + -> Deno language server + -> VS Code extension requires/uses the installed CLI +``` + +Useful precedent: + +- The Deno VS Code extension is powered by the Deno language server. +- The extension expects a Deno CLI executable and lets users set `deno.path`. +- Editor features intentionally align with CLI behavior: module resolution, + cache, lint, format, tasks, and config all come from Deno. + +CodeGraphy lesson: + +- CodeGraphy's VS Code extension can require or bundle the `codegraphy` binary + and treat the CLI/core as the product source of truth. +- Keeping editor behavior aligned with CLI behavior is a feature, not a + compromise. + +Source: + +- [Deno VS Code extension README](https://github.com/denoland/vscode_deno) + +### SWC + +Pattern: + +```text +Rust compiler platform + -> Rust/JS usage surfaces + -> plugins compiled to Wasm +``` + +Useful precedent: + +- SWC is a Rust-based platform used by JS tooling. +- SWC plugins are written in Rust and built as `.wasm`. +- The documented plugin flow targets Wasm, including WASI. + +CodeGraphy lesson: + +- Wasm plugins are a proven path for a Rust core that wants portable plugin + execution. +- Wasm works especially well for core facts/analysis/transforms, not direct UI. + +Sources: + +- [SWC overview](https://swc.rs/) +- [SWC plugin guide](https://swc.rs/docs/plugin/ecmascript/getting-started) + +### Oxc / Oxlint + +Pattern: + +```text +Rust JS toolchain + -> CLI/editor integrations + -> Rust core + -> JavaScript plugin compatibility for ecosystem reach +``` + +Useful precedent: + +- Oxc is a Rust toolchain with parser/linter/formatter pieces. +- Oxlint supports JavaScript plugins, including npm plugins, with an + ESLint-compatible API. +- The JS plugin support is currently alpha, but the direction is important: + a Rust core can still support an existing JS plugin ecosystem through a + compatibility host. + +CodeGraphy lesson: + +- The dream architecture does not have to choose between Rust/Wasm plugins and + TypeScript plugins forever. +- A Rust core could support Wasm plugins as the native future and a JS plugin + host as a compatibility/adoption layer. + +Sources: + +- [Oxc overview](https://oxc.rs/) +- [Oxlint JS plugins](https://oxc.rs/docs/guide/usage/linter/js-plugins) + +### Zed + +Pattern: + +```text +Rust editor + -> extensions written in Rust + -> compiled to WebAssembly + -> extension API exposed through Rust crate/WIT-style interface +``` + +Useful precedent: + +- Zed extensions are developed with Rust and a manifest. +- `zed_extension_api` exposes process, LSP, HTTP client, settings, and a + registration macro. +- Zed's own extension story shows Rust + Wasm + typed extension contracts can be + a serious product architecture. + +CodeGraphy lesson: + +- A typed plugin interface can be larger than pure analysis: it can include + process/LSP helpers, settings, and integration utilities. +- Wasm plugins are credible for editor-adjacent extensibility, but UI extension + scope should be designed carefully. + +Sources: + +- [Zed developing extensions](https://zed.dev/docs/extensions/developing-extensions) +- [zed_extension_api docs](https://docs.rs/zed_extension_api/latest/zed_extension_api/) + +### Lapce + +Pattern: + +```text +Rust editor + -> GPU rendering through wgpu + -> Tree-sitter and LSP + -> plugins through WASI +``` + +Useful precedent: + +- Lapce advertises a WASI plugin system where plugins can be written in any + language that compiles to WASI. +- It also combines Tree-sitter, LSP, and a Rust-native performance-oriented UI + stack. + +CodeGraphy lesson: + +- WASI is a reasonable target if the goal is language-agnostic portable plugins. +- CodeGraphy's WebGPU graph direction pairs naturally with a Rust/Wasm core + ecosystem, but the VS Code webview still imposes its own constraints. + +Source: + +- [Lapce overview](https://lap.dev/lapce/) + +### Tauri + +Pattern: + +```text +Rust backend + -> web frontend + -> command/message bridge + -> plugins extend Rust and webview behavior +``` + +Useful precedent: + +- Tauri apps use web UI technologies while relying on Rust-side logic. +- Tauri plugins can hook lifecycle, expose Rust code that uses webview APIs, + and handle commands. +- Tauri explicitly keeps core smaller by adding external functionality through + plugins. + +CodeGraphy lesson: + +- A Rust core plus web UI bridge is a mature pattern. +- Plugins that span backend behavior and webview-facing behavior are plausible, + but their interface must be deliberate. + +Sources: + +- [Tauri overview](https://v2.tauri.app/start/) +- [Tauri plugin development](https://v2.tauri.app/develop/plugins/) + +### Comparison Takeaway + +The architecture we are discussing is on a well-trodden path: + +```text +Rust core binary + + protocol mode for editors/tools + + durable local cache + + thin clients + + Wasm/native plugin system + + optional JS compatibility layer +``` + +The most relevant combo for CodeGraphy is: + +- rust-analyzer/Biome/Deno for the `codegraphy stdio` process model; +- SWC/Zed/Lapce for Wasm plugin design; +- Oxc/Oxlint for preserving a JS/TS plugin path while moving the core to Rust; +- Tauri for Rust backend plus webview client/plugin split. + +## Tree-Sitter In Rust + +A Rust core can still use Tree-sitter. Tree-sitter has Rust bindings and grammar +crates. This makes a Rust indexing engine plausible, including parallel parsing +and language analysis. + +This is a better fit for CPU multithreading than GPU acceleration. Indexing is +mostly file IO, parsing, branching logic, strings, and graph fact generation. + +## WebGPU Direction + +WebGPU is most relevant to the graph view, especially the 2D graph rendering and +possibly force/layout simulation. + +If CodeGraphy seriously adopts WebGPU, it probably replaces the +`react-force-graph` graph surface rather than merely tweaking it. + +Possible levels: + +```text +Level 1: Rust/core or JS computes positions, WebGPU renders nodes/edges. +Level 2: WebGPU renders and handles viewport-scale buffers/culling. +Level 3: WebGPU compute handles force/layout iterations too. +``` + +React can still own the surrounding UI: + +- panels; +- toolbar; +- settings; +- search; +- context menus; +- plugin controls. + +The custom WebGPU surface owns the actual 2D graph drawing. + +Hard parts to account for: + +- labels; +- icons/images; +- hit testing; +- selection; +- hover; +- context menus; +- edge arrows/particles; +- plugin overlays; +- accessibility; +- screenshots/export; +- stable layout persistence. + +The strongest architecture is for the Rust core to return compact projections or +diffs, and for the WebGPU renderer to update GPU buffers rather than rebuilding a +large object graph every time. + +## Performance Intent + +Rust core targets: + +- faster indexing; +- better parallelism; +- faster graph query/search/filter/sort/projection; +- less TypeScript main-thread work; +- a single query engine shared by CLI, MCP, and VS Code. + +WebGPU targets: + +- faster 2D rendering for large graphs; +- smoother pan/zoom/hover; +- less Canvas draw-call pressure; +- possible GPU-accelerated layout later. + +The two ideas complement each other: + +```text +Rust core makes the graph facts and projections faster. +WebGPU makes the visible graph surface faster. +``` + +## Current Dream Version + +```text +Install CodeGraphy core: + codegraphy + +Use as a CLI: + codegraphy index + codegraphy query + +Use from VS Code: + extension starts `codegraphy stdio` + extension provides UI over core features + webview renders graph with WebGPU + +Use from agents: + MCP adapter uses the same Rust core/protocol + +Extend with plugins: + codegraphy plugin install + plugins add analyzers, graph facts, query behavior, exporters, maybe UI +``` + +This makes CodeGraphy feel like a local program first. VS Code becomes one +excellent visualization client, not the center of the architecture. diff --git a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md new file mode 100644 index 000000000..7c5cd33a9 --- /dev/null +++ b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md @@ -0,0 +1,812 @@ +# WebGPU Graph Renderer Research + +## Status + +Exploratory architecture research. This is not an implementation plan yet. + +The goal is to understand what it would mean to replace CodeGraphy's current +`react-force-graph` graph surface with a custom 2D WebGPU renderer that can keep +large graphs fast, dynamic, and force-driven. + +## Current Renderer + +CodeGraphy currently depends on: + +- `react-force-graph-2d` +- `react-force-graph-3d` +- `d3-force` + +Local usage is concentrated around the Graph View surface: + +- `packages/extension/src/webview/components/graph/rendering/surface/view/twoDimensional.tsx` +- `packages/extension/src/webview/components/graph/rendering/surface/view/threeDimensional.tsx` +- `packages/extension/src/webview/components/graph/rendering/useGraphCallbacks.ts` +- `packages/extension/src/webview/components/graph/runtime/physics/` +- `packages/extension/src/webview/components/graph/runtime/physics/pluginForces.ts` + +`react-force-graph` is doing more than drawing. It bundles several jobs: + +- force simulation; +- canvas/WebGL rendering; +- zoom and coordinate transforms; +- node/link pointer picking; +- drag handling; +- hover/click/right-click callbacks; +- imperative graph controls such as `zoom`, `centerAt`, `zoomToFit`, + `screen2GraphCoords`, `graph2ScreenCoords`, `d3Force`, `d3Alpha`, and + `d3ReheatSimulation`; +- optional DAG layout behavior; +- directional arrows and particles; +- custom 2D canvas node/link drawing. + +That means WebGPU is not a simple package swap. It is a renderer-interface +replacement. + +Sources: + +- `packages/extension/package.json` +- `packages/extension/src/webview/components/graph/rendering/surface/view/twoDimensional.tsx` +- `packages/extension/src/webview/components/graph/rendering/surface/sharedProps.ts` +- `packages/extension/src/webview/components/graph/runtime/physics/pluginForces.ts` +- [react-force-graph README](https://github.com/vasturiano/react-force-graph) + +## What React Force Graph Gives Us Today + +The package exports React components for 2D, 3D, VR, and AR force-directed +graphs. Its README describes the 2D path as HTML Canvas rendering and the 3D +path as Three.js/WebGL, with `d3-force-3d` as the underlying physics engine. + +CodeGraphy currently uses these capabilities: + +- `graphData` with `nodes` and `links`; +- node id mapping through `nodeId`; +- force settings through `d3VelocityDecay`, `d3AlphaDecay`, `d3Force`, and + `d3ReheatSimulation`; +- cooldown and warmup ticks; +- DAG mode and DAG level distance; +- custom node drawing with `nodeCanvasObject`; +- custom link drawing with `linkCanvasObject`; +- hidden pointer-paint canvas behavior through `nodePointerAreaPaint`; +- directional arrows; +- moving directional particles; +- link colors, widths, curvature, and particle color; +- node click, node hover, node drag, node right-click; +- link click and link right-click; +- background click and right-click; +- `screen2GraphCoords` and `graph2ScreenCoords`; +- `centerAt`, `zoom`, and `zoomToFit`. + +The biggest hidden dependency is not rendering. It is the force-graph runtime +object that callers use as the Graph View control surface. + +Source: + +- [react-force-graph README](https://github.com/vasturiano/react-force-graph) + +## What WebGPU Changes + +WebGPU gives a webview/browser surface lower-level access to modern GPU +graphics and compute. MDN describes it as a successor to WebGL with better +compatibility with modern GPUs, support for general-purpose GPU computation, +and faster operations. + +Chrome's WebGPU overview calls out reduced JavaScript workload and access to +advanced GPU capabilities. It also notes WebGPU is available across Chrome, +Firefox, and Safari implementations, though exact support depends on the +runtime and platform. + +For CodeGraphy, this points to two separate opportunities: + +```text +WebGPU rendering + -> draw many nodes and edges with instancing and GPU buffers + -> reduce canvas draw-call pressure + -> avoid per-frame React/object churn + +WebGPU compute + -> maybe run force/layout iterations on GPU + -> maybe keep positions/velocities in GPU buffers + -> hard but powerful for giant dynamic graphs +``` + +Sources: + +- [MDN WebGPU API](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API) +- [Chrome WebGPU overview](https://developer.chrome.com/docs/web-platform/webgpu/overview) +- [Navigator.gpu MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/gpu) +- [WorkerNavigator.gpu MDN](https://developer.mozilla.org/en-US/docs/Web/API/WorkerNavigator/gpu) + +## VS Code Webview Constraints + +The renderer runs inside a VS Code webview, so it must obey webview lifecycle and +security rules: + +- WebGPU needs feature detection through `navigator.gpu` and adapter/device + creation. +- WebGPU may be unavailable because of runtime, OS, driver, blocklist, hardware + acceleration settings, or secure-context constraints. +- VS Code webviews should use strict CSP and minimum capabilities. +- Webview content can be destroyed when hidden unless state is persisted or the + panel uses heavier retention behavior. +- Web workers in VS Code webviews have bundling/resource constraints. + +Architecture implication: CodeGraphy should keep a fallback path. The fallback +could be the current Canvas renderer during migration or a simpler Canvas2D +renderer later. + +Sources: + +- [VS Code Webview API](https://code.visualstudio.com/api/extension-guides/webview) +- [Chrome WebGPU troubleshooting](https://developer.chrome.com/docs/web-platform/webgpu/troubleshooting-tips) +- [Navigator.gpu MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/gpu) + +## Obsidian Inspiration + +Obsidian's Graph View is useful inspiration because it treats the graph as a +navigation and filtering surface, not only a visual toy. + +Official Graph View features include: + +- global graph of vault relationships; +- local graph centered on the active note; +- hover to highlight connections; +- click to open a note; +- right-click context menu; +- zoom and pan by mouse or keyboard; +- filters/search; +- tag, attachment, existing-file, and orphan toggles; +- groups with colors; +- display controls for arrows, text fade threshold, node size, and link + thickness; +- force controls for center force, repel force, link force, and link distance; +- time-lapse animation; +- local graph depth. + +CodeGraphy equivalents worth thinking about: + +- global workspace graph; +- local graph around current file/symbol; +- graph depth around a selected node; +- graph scope filters; +- groups by language, plugin, package, folder, or symbol kind; +- search/query-backed visual filters; +- zoom-dependent labels; +- force controls that feel immediate; +- hover/click/right-click as navigation, not decoration. + +Source: + +- [Obsidian Graph View help](https://obsidian.md/help/plugins/graph) + +## Similar Renderer And Layout Projects + +### Sigma.js + +Sigma.js is a WebGL graph renderer. Its docs say WebGL lets it draw larger +graphs faster than Canvas or SVG, while making custom rendering harder. + +CodeGraphy lesson: + +- GPU rendering is a good direction for scale. +- Custom rendering and plugin extensibility get harder, so the plugin rendering + interface should be declarative. + +Source: + +- [Sigma.js](https://www.sigmajs.org/) + +### Cosmograph / cosmos.gl + +cosmos.gl is a GPU-accelerated WebGL force-graph engine. Its README says +computation and drawing happen on the GPU and that it targets real-time +simulation of hundreds of thousands of points and links on modern hardware. +Cosmograph's docs frame the central challenge exactly like CodeGraphy's: both +layout computation and rendering become hard at large scale. + +CodeGraphy lesson: + +- Use numeric ids and typed arrays instead of object-heavy per-frame data. +- Keep simulation state close to GPU buffers. +- Separate simulation control from rendering control. +- Consider link blending and dense-edge rendering modes for performance. + +Sources: + +- [cosmos.gl GitHub](https://github.com/cosmosgl/graph) +- [Cosmograph concept docs](https://cosmograph.app/docs-general/concept/) + +### GraphWaGu + +GraphWaGu is an academic WebGPU graph visualization system. Its abstract says it +uses WebGPU compute shaders for modified Fruchterman-Reingold and Barnes-Hut +layout plus fast parallel rendering. + +CodeGraphy lesson: + +- WebGPU layout is possible and directly relevant. +- It is also a serious renderer/layout project, not a small React migration. + +Source: + +- [GraphWaGu publication page](https://www.willusher.io/publications/graphwagu/) + +### d3-force And d3-force-webgpu + +`d3-force` uses a velocity Verlet integrator and lets callers render ticks in a +separate graphics system. Its many-body force uses a quadtree and Barnes-Hut +approximation for performance. + +`d3-force-webgpu` is a small experimental project that tries to keep a +`d3-force`-like interface while moving force computation into WebGPU compute +shaders. Its README advertises a CPU fallback and API compatibility, but the +project looks young, so it is better as a research reference than an immediate +dependency. + +CodeGraphy lesson: + +- d3-force is a reference implementation to learn from (integrator, annealing, + Barnes-Hut), not a dependency: CodeGraphy builds its own engine. +- The real performance win comes when position and velocity buffers avoid + round-tripping back to JavaScript every frame. + +Sources: + +- [d3-force docs](https://d3js.org/d3-force) +- [d3 many-body force docs](https://d3js.org/d3-force/many-body) +- [d3-force-webgpu GitHub](https://github.com/jamescarruthers/d3-force-webgpu) + +### Graphology ForceAtlas2 + +Graphology's ForceAtlas2 package supports synchronous layout and a web worker +utility. It is not WebGPU, but it is useful as a CPU/worker layout reference and +as a reminder that graph layout can be independent from graph rendering. + +CodeGraphy lesson: + +- Layout should be its own module. +- Renderer should not own the only possible simulation algorithm. + +Source: + +- [Graphology ForceAtlas2](https://graphology.github.io/standard-library/layout-forceatlas2.html) + +## Target Interface Shape + +The future renderer should be a deep module with a small interface. + +It should hide WebGPU pipelines, buffers, picking textures, shader code, resize +handling, and draw scheduling. + +The extension/webview should mostly know this: + +```text +GraphRenderer + setGraph(projection) + applyGraphDiff(diff) + setAppearance(appearance) + setViewport(viewport) + setSelection(selection) + setHover(hover) + fitToBounds(options) + screenToGraph(point) + graphToScreen(point) + start() + stop() + dispose() +``` + +The layout side should be separate: + +```text +GraphLayout + setGraph(projection) + applyGraphDiff(diff) + setForces(settings) + pinNode(nodeId, position) + unpinNode(nodeId) + reheat() + tick(frameBudget) + getPositions() + dispose() +``` + +The two can initially live in the same package, but keeping the interface +separate leaves room for these adapters: + +- current `react-force-graph` adapter; +- custom Canvas2D adapter; +- WebGPU rendering + custom CPU layout engine; +- WebGPU rendering + workerized CPU layout; +- WebGPU rendering + WebGPU compute layout. + +## Migration Levels + +### Level 1: WebGPU Renderer, Custom CPU Layout + +Build the custom typed-array simulation engine (see the physics section of +the parity spec) on the CPU, and replace the 2D canvas drawing path with +WebGPU. + +Pros: + +- smaller first step; +- force feel can be tuned against the current renderer side-by-side; +- performance win for draw-heavy graphs; +- easier to compare visually against current renderer. + +Cons: + +- layout still burns CPU; +- positions still move through JavaScript; +- not enough for truly huge dynamic graphs. + +### Level 2: Workerized Layout, WebGPU Renderer + +Move layout ticks off the main webview thread. Render with WebGPU on the main +thread or explore WebGPU in a dedicated worker if VS Code webview constraints +allow it. + +Pros: + +- keeps UI responsive; +- avoids freezing React and pointer handling; +- can still use known CPU algorithms. + +Cons: + +- worker bundling in VS Code webviews needs care; +- transferring large positions every frame can erase gains unless using compact + typed arrays and throttled updates. + +### Level 3: WebGPU Compute Layout + +Positions, velocities, edges, and forces live mostly in GPU buffers. Rendering +and simulation share buffers. + +Pros: + +- best match for giant dynamic graphs; +- avoids JavaScript per-node simulation loops; +- can keep dense graphs moving. + +Cons: + +- hardest implementation; +- debugging shader/compute logic is slower; +- plugin force adapters need a new declarative model; +- hit testing and labels need deliberate design. + +## What We Need To Support + +Rendering: + +- nodes with size, shape, color, opacity, selection, hover, muted state; +- links with color, width, opacity, curvature, arrows, direction markers; +- moving particles or equivalent direction animation; +- labels with zoom-based visibility and fade threshold; +- file icons/images or a reduced high-performance substitute; +- collapse indicators and badges; +- highlighted neighbors; +- dense-edge mode for large graphs; +- screenshot/export path. + +Layout and physics: + +- repel force; +- link force; +- link distance; +- center force; +- collision; +- damping; +- reheating; +- engine stop/stabilization events; +- pinned/fixed node positions; +- node drag updates; +- optional DAG/tree mode or a replacement layout mode; +- plugin-owned forces or a replacement contribution model. + +Interaction: + +- pan and zoom; +- keyboard zoom/pan; +- click, right-click, hover; +- node drag and drag-end policies; +- marquee/multi-selection; +- context menu coordinates; +- `screenToGraph` and `graphToScreen`; +- fit to graph and focus node; +- accessibility overlay. + +Plugin-facing behavior: + +- graph toolbar and panel slots stay React/webview; +- world/viewport overlays need a renderer-aware contract; +- TS-only visual plugins like particles can remain webview bundles; +- core/plugin analyzers like Vue or Godot can feed graph facts through core; +- raw renderer access should stay private; +- plugin graph styling should become declarative enough for GPU buffers. + +## Full Replacement Parity Spec + +What a WebGPU renderer must actually implement for `react-force-graph-2d` to +be removed, grounded in what the codebase uses today (not the library's full +API). Sources: `sharedProps.ts`, `twoDimensional.tsx`, the physics runtime +(`runtime/physics/root/settings/*`, `pluginForces.ts`), and the interaction +runtime (`interactionRuntime/*`, `runtime/use/interaction/*`). + +### 1. Physics Engine: Our Own, Not d3-force + +Decision: the replacement does not depend on `d3-force` (or any port of it). +CodeGraphy builds its own simulation engine, designed for GPU-friendly data +from day one. This is the Obsidian path: Obsidian originally used d3 for both +simulation and drawing, abandoned it for performance, moved rendering to +Pixi/WebGL, and built a custom undisclosed force engine +([tech stack thread](https://forum.obsidian.md/t/what-is-the-tech-stack-currently/833/5), +[graph core thread](https://forum.obsidian.md/t/understanding-the-graph-view-core/41020), +[physics discussion](https://forum.obsidian.md/t/graph-view-physics-and-force-directed-graphs/72586)). + +What we take from reading d3-force's implementation (the ideas, not the code): + +- Integrator: semi-implicit / velocity-Verlet-style — accumulate force into + velocity, apply a velocity damping multiplier, add velocity to position. + Simple, stable, and trivially parallel per node. +- Annealing: a global "temperature" (d3's alpha) that decays each tick and + scales all force applications, so the graph settles instead of oscillating. + Reheat = reset temperature (this is what makes drag/filter changes feel + alive). Stabilization event fires when temperature crosses a floor. +- Repulsion at scale: never all-pairs. d3 uses a Barnes-Hut quadtree + (theta ~0.9, min/max distance clamps); that is the right CPU algorithm. For + the GPU compute path, a uniform spatial grid is the better-shaped + approximation (regular memory access, no tree traversal in shaders). +- Details that make sims feel good and are easy to miss: jiggle (tiny random + displacement when two nodes are exactly coincident, avoids NaN/explosions), + link-force degree bias (a link moves the lower-degree endpoint more, so + hubs stay put), and collision solved iteratively over a few passes. + +What we take from Obsidian's product behavior: + +- The whole force model reduces to four user-facing knobs — repel force, link + force, link distance, center force — plus damping. CodeGraphy's settings + already match this shape; the engine should treat these as the public + contract and keep them hot-adjustable (slider moves re-scale forces next + tick, with a gentle reheat). +- Settle fast: aggressive early cooling, so even large graphs reach a + readable layout in a couple of seconds, then go quiet (no CPU/GPU burn at + idle). + +Engine design requirements: + +- State is struct-of-arrays typed arrays from the start (`Float32Array` + positions, velocities; `Uint32Array` edge endpoints) — the same buffers the + renderer uploads, and the same layout a WGSL compute port consumes later. + No per-node objects anywhere in the sim. +- Runs headless: no DOM, no renderer dependency, tick-driven + (`tick(dt)` under a frame budget), so it can live on the main thread, in a + worker, or eventually in Rust/WGSL without interface changes. +- Deterministic option: seeded initial placement and fixed-timestep mode, so + fixture tests and screenshot diffs are reproducible. +- Pinning: fixed-position flags per node (drag grabs a node by pinning it; + persisted layouts load as pinned-then-released or as initial positions). +- Lifecycle parity with current behavior: warmup ticks, cooldown ticks (60 + interactive today; the timeline variant is removed with the timeline view), + stabilization callback. +- Plugin forces: the existing adapter contract (`initialize(nodes)` / + `tick(alpha)` / `dispose()`) is object-based and cannot survive as-is — + typed arrays are the new interface. Provide a small custom-force API + (read positions, add to velocity/force accumulators, scaled by + temperature) for the CPU path, and treat GPU-path plugin forces as a later + declarative feature. +- Migration consequence: dropping d3 means the current tuned constants + (charge -250, spring 80/0.15, damping 0.7, centralGravity 0.1) are + reference behavior, not reusable values. Tune the new engine side-by-side + against the current renderer on fixture graphs until the feel matches or + beats it — "beats" is allowed; feel-parity is a floor, not the goal. + +Staging still applies: the engine starts as TypeScript-in-a-worker operating +on the shared typed arrays, and the WGSL compute version is a port of the +same passes (springs, grid repulsion, center/damping, integrate/pin) — not a +second engine. + +### 2. Node Rendering + +Current drawing is fully custom (`nodeCanvasObjectMode: 'replace'`) via +`rendering/node/*`: body shapes, labels, media/icons, collapse indicators, +pointer areas. WebGPU equivalents: + +- Bodies: one instanced draw of quads shaded as SDF circles (and any other + body shapes as SDF variants); per-instance position, radius (`nodeVal` -> + radius mapping), fill color, border, and state flags (selected, hovered, + muted, search hit) driving halo/dim effects in the shader. +- File icons/media (`node/media.ts`): a texture atlas sampled per instance; + LOD rule to drop icons below a zoom threshold. +- Collapse indicators/badges: a second small instanced layer positioned + relative to the parent node. +- Labels (`node/label.ts`): the hardest rendering problem. Two viable paths: + (a) MSDF glyph atlas rendered in-GPU — best scaling, most work; (b) a + Canvas2D/DOM overlay that draws only the top-N visible labels (hovered, + selected, largest on screen) with zoom-gated fade, Obsidian-style. Path (b) + is recommended first: at the committed 100k-node scale, only a tiny fraction + of labels are legible at once anyway. + +### 3. Link Rendering + +- Lines: instanced segments expanded to screen-space width in the vertex + shader; per-instance color, width, opacity. +- Curvature (`linkCurvature`): quadratic bezier evaluated in the vertex shader + over a small subdivided strip; curvature is per-link data already + (`link.curvature` for multi-edges/self-loops). +- Directional arrows: instanced triangles placed at `arrowRelPos` along the + (possibly curved) link, oriented by the curve tangent, shortened by target + node radius. +- Directional particles: currently the only always-animating feature + (`autoPauseRedraw` is disabled in particles mode). On GPU this is nearly + free: particle position = bezier evaluated at a per-link phase advanced by + time; one instanced draw, no per-particle JS state. +- Link text/dash/custom draws that today go through `linkCanvasObject` need an + inventory pass — each becomes either per-instance data or a shader feature. + +### 4. Viewport And Camera + +Replace the library's zoom/pan transform with an owned 2D camera (translate + +scale), providing: `zoom(k, ms)`, `centerAt(x, y, ms)` with tweened +animation, `zoomToFit(ms, padding, filter)`, `getGraphBbox`, min/max zoom +clamps, `screen2GraphCoords`/`graph2ScreenCoords` (trivial matrix math), and +an `onZoom` event stream. Note CodeGraphy already disables the library's pan +(`enablePanInteraction={false}`) and implements its own Ctrl-drag pan +(`runtime/use/interaction/viewportPan/`) — the custom camera actually +simplifies this code. + +### 5. Picking And Pointer Model + +Replace `nodePointerAreaPaint` (hidden hit-canvas) with a CPU spatial hash or +quadtree rebuilt from positions each tick (cheap for 100k points): + +- node hit test: nearest node within radius (+ pointer slop); +- link hit test: point-to-segment/bezier distance with hover precision; +- marquee selection (`interaction/marquee/`): a range query on the same index; +- pointer state machine reproducing the library's semantics: hover + (enter/leave with null), click vs drag threshold, right-click, background + click/right-click, drag start/move/end with `fx`/`fy` pinning and reheat. + +The same position data feeds the accessibility projector +(`viewport/accessibility.ts` needs `graph2ScreenCoords` + node radii), tooltip +rects, and the debug snapshot protocol (`graph/debug/`), so positions must +remain CPU-readable — one more reason CPU/worker layout comes before GPU +layout. + +### 6. Render Loop And Damage Model + +Reproduce `autoPauseRedraw`: draw only when the simulation ticked, the camera +moved, interaction state changed, or a continuous animation (particles, tween) +is active. Add a frame budget so simulation ticks and buffer uploads cannot +starve pointer handling. `onRenderFramePost` (used today for overlays like +marquee rectangles) becomes an explicit overlay pass or a DOM/Canvas overlay +layer above the WebGPU canvas. + +### 7. DAG Mode + +`dagMode`/`dagLevelDistance` are wired through settings today +(td/bu/lr/rl/radial variants in the library). The library implements DAG as +constraint forces on top of the same simulation, so it ports as one more +force. Keep it as a layout-module feature, or explicitly drop it — but decide; +it is user-visible settings surface. + +### 8. Explicitly Not Needed + +Force-graph API surface CodeGraphy does not use and the replacement should not +build: `nodeAutoColorBy`/`linkAutoColorBy`, `emitParticle`, particle offset / +custom particle draws, `linkLineDash` (dashes, if any, live in custom draw +code), `onLinkHover`, `linkPointerAreaPaint`, `cooldownTime`, `dagNodeFilter` +/ `onDagError`, and all 3D/VR/AR surfaces (3D is being removed). + +### 9. Parity Verification + +- Physics: the custom engine is validated by running old and new side-by-side + on fixture graphs — not for identical positions (different engine), but for + settle time, stability (no oscillation/explosion), and interaction feel; + seeded/fixed-timestep mode makes each engine's own runs reproducible. +- Interaction: the existing acceptance specs plus the debug snapshot protocol + give a comparison harness; extend snapshots to include camera transform and + hit-test results. +- Visuals: screenshot-diff fixture graphs at fixed seeds/zoom levels between + the react-force-graph adapter and the WebGPU renderer. + +### Effort Ranking + +Hardest first: labels at scale; physics feel parity (drag/reheat/settle); +curved-link arrows and particles; picking semantics parity; DAG mode; +everything else (bodies, lines, camera, halos) is standard instanced +rendering. + +## Plugin Implications + +The current plugin API includes Canvas-oriented hooks such as node renderers and +overlay renderers. A WebGPU renderer should avoid exposing raw WebGPU state to +plugins early. + +The preferred direction is simpler than full renderer extensibility: + +```text +Core owns: + plugin install/enable/disable + plugin metadata + graph facts/query behavior + renderer-facing graph projection data + +React/webview shell owns: + plugin windows + settings panels + toolbar slots + side panels + DOM overlays + transparent background/foreground effect slots + +WebGPU renderer owns: + GPU pipeline + buffer format + batching strategy + picking strategy + level-of-detail behavior +``` + +TS visual plugins should generally interface with the React layout and DOM slot +system, not with the WebGPU graph internals. Particles are the model example: the +plugin can render a transparent animated background by injecting into a specific +HTML slot behind the graph, while the WebGPU graph continues to own nodes, edges, +picking, and layout. + +This means CodeGraphy does not need to support arbitrary plugins mutating graph +interaction or raw graph rendering. Plugins can add their own React UI where the +host exposes slots. If a plugin needs to affect graph facts, filters, styling, or +queries, that should flow through the core plugin system and come back to the +renderer as normal graph projection data. + +Future declarative graph styling can still exist, but it should be a narrow core +or projection feature rather than a general "plugin gets the renderer" interface. + +## The Extension-Host Hop + +The renderer does not receive data from the core directly. Every payload +crosses: core stdio -> extension host (Node) -> `webview.postMessage` -> +webview. Two facts govern this path: + +- VS Code webview messaging JSON-serializes by default, which is pathological + for typed arrays; VS Code added explicit ArrayBuffer transfer support + ([issue #115807](https://github.com/microsoft/vscode/issues/115807), + [PR #148429](https://github.com/microsoft/vscode/pull/148429)). The renderer + payload format must be flat binary buffers passed via the transfer parameter, + never per-node JSON objects. +- `SharedArrayBuffer` requires cross-origin isolation and should not be assumed + available inside webviews. + +Design rule: the extension host never parses graph payloads. It forwards +binary frames from the core to the webview untouched. In VS Code Remote +scenarios the core runs on the remote host while the webview (and GPU) run on +the client, so the payload also crosses the network — one more reason diffs +and compact binary formats are non-negotiable. + +## GPU-Resident Positions Have Consumers + +Level 3 (compute layout) keeps positions in GPU buffers, but several features +need positions on the CPU: + +- hit testing via spatial index; +- node drag (write user position back into the sim); +- context menu placement, `graphToScreen`, fit-to-bounds; +- persisting layout snapshots to the core cache. + +So Level 3 still needs a throttled async readback (`mapAsync` on a staging +buffer, e.g. every N frames), or GPU color-picking plus GPU-computed bounds to +avoid readback for interaction. Budget this as part of Level 3's design, not +as a surprise; naive per-frame synchronous readback would erase the win. + +## Performance Budgets And Test Matrix + +The plan should commit to numbers before code exists, and measure the current +renderer first so wins are provable: + +- Baseline: measure react-force-graph FPS and interaction latency at 1k / 10k + / 50k nodes on the current stack (the screenshot harness can host this). +- Committed targets: 60fps pan/zoom at 100k nodes / 300k edges; < 16ms + hit-test; < 100ms full projection swap at 100k nodes; force-control changes + reflected next frame. +- Platform matrix: macOS (Metal), Windows (D3D12), Linux (Vulkan — Electron + has a history of `requestAdapter()` returning null on Linux, + [electron#41763](https://github.com/electron/electron/issues/41763)), plus + VS Code Remote (client GPU) and a software-rendering/VM case that must hit + the Canvas fallback cleanly. +- Device loss: suspend/resume and driver-reset recovery is a test case, not an + afterthought. + +## Performance Strategy + +For the "giant graphs moving fast" dream, prioritize: + +- stable numeric node/link ids; +- struct-of-arrays or typed-array graph projections; +- compact graph diffs from core; +- append/update GPU buffers instead of rebuilding whole JS objects; +- level-of-detail for labels, icons, edge arrows, and particles; +- dense-edge rendering modes; +- zoom-dependent label selection; +- viewport culling where it helps; +- GPU or spatial-index picking; +- animation frame budgeting; +- avoid React state updates per simulation tick; +- keep force settings immediately interactive; +- local graph/depth graph mode for focused exploration; +- optional precomputed/cached layout snapshots in the core cache. + +The renderer should be optimized around the normal loop: + +```text +core query/projection/diff + -> typed visible graph payload + -> layout positions/velocities update + -> GPU buffers update + -> renderer draws + -> interaction returns node/link ids + -> extension asks core for details/actions +``` + +## Recommended Direction + +The best near-term architecture is: + +```text +React UI shell + -> GraphRenderer interface + -> current react-force-graph adapter + -> future WebGPU 2D adapter + -> GraphLayout interface + -> current d3/force-graph layout adapter + -> future worker/GPU layout adapter +``` + +Do not start with full GPU compute layout. Start with the renderer seam, because +that lets CodeGraphy replace the hot drawing path while preserving current graph +behavior. Then move layout from CPU main-thread to worker or GPU once the +renderer can consume typed position buffers efficiently. + +The product direction is to remove `react-force-graph` rather than wrap it +forever. The adapter step is only a migration safety rail while CodeGraphy builds +its own 2D graph engine from the ground up. + +Long-term dream: + +```text +Rust core computes compact graph projection/diffs + -> VS Code webview receives typed payload + -> WebGPU renderer owns high-volume drawing + -> WebGPU or worker layout keeps graph alive and dynamic + -> React owns controls, panels, plugin UI slots +``` + +## Open Questions With Leanings + +- 3D mode: decided — remove it. It is a gimmick relative to the 2D graph's + navigation value, and dropping `react-force-graph-3d`/Three.js removes a + large dependency, shrinks the webview bundle, and keeps the WebGPU effort + focused on a single renderer. +- Should layout be d3-compatible at first or should we jump to a new force + model? Decided: build our own engine, no d3-force dependency. d3's + internals (velocity integration, alpha annealing, Barnes-Hut, degree bias, + jiggle) and Obsidian's four-knob force model are the study material; the + engine itself is CodeGraphy's, typed-array-native and GPU-portable. The + react-force-graph adapter remains only as the migration comparison rail. +- How much of DAG mode is essential? Decided: port all current DAG modes + (td/bu/lr/rl/radial) as constraint forces in the custom engine — depth + assignment from the directed graph plus a positional force toward + depth × level distance on the mode's axis. +- Plugin Canvas renderers: remove raw hooks, keep DOM slots, add declarative + styling later (per the plugin sections above). A compatibility Canvas overlay + layered on the WebGPU canvas is a plausible bridge if a real plugin needs it. +- Persistent layout coordinates: SQLite table in the core cache keyed by stable + node id. Note the hard sub-problem is node identity across renames/moves — + positions keyed by path evaporate on refactors. +- Target scale: decided — smooth (60fps pan/zoom/drag) at 100k nodes / 300k + edges; usable (interactive, degraded decoration) at 500k edges. Buffer + formats, picking, and LOD strategy should be designed against this tier. +- Dense graph mode hiding order: particles first (pure decoration, per-frame + cost), then icons, then labels (keep zoom-gated labels for hovered/selected + neighborhoods), then arrows, then low-importance edges by weight. diff --git a/docs/plans/2026-07-09-webgpu-renderer-source-research.md b/docs/plans/2026-07-09-webgpu-renderer-source-research.md new file mode 100644 index 000000000..f6c2c7ea0 --- /dev/null +++ b/docs/plans/2026-07-09-webgpu-renderer-source-research.md @@ -0,0 +1,241 @@ +# WebGPU Graph Renderer Source Research + +Status: source-backed research for architecture decisions, not an implementation plan. + +## Short Read + +Replacing `react-force-graph-2d` with WebGPU is plausible, but it is not a small renderer swap. CodeGraphy currently depends on `react-force-graph-2d` for four things at once: Canvas drawing, D3-style force simulation, viewport transforms, and pointer interaction. A WebGPU replacement should become a CodeGraphy-owned graph renderer module behind a small interface, with React still owning surrounding UI. + +The best path is staged: + +1. Introduce a renderer interface and keep the current `react-force-graph-2d` implementation behind it. +2. Build a WebGPU 2D renderer that consumes CodeGraphy graph buffers and uses CPU or worker layout first. +3. Add WebGPU compute layout as an experimental fast path once drawing, picking, labels, drag, fit, and plugin rendering descriptors are stable. + +## Current CodeGraphy Surface To Replace + +Current dependency: `packages/extension/package.json` uses `react-force-graph-2d`, `react-force-graph-3d`, `d3-force`, and `graphology`. + +Relevant local seams: + +- `packages/extension/src/webview/components/graph/rendering/surface/view/twoDimensional.tsx` +- `packages/extension/src/webview/components/graph/rendering/surface/sharedProps.ts` +- `packages/extension/src/webview/components/graph/rendering/useGraphCallbacks.ts` +- `packages/extension/src/webview/components/graph/runtime/physics/pluginForces.ts` +- `packages/plugin-api/src/graphView.ts` +- `packages/plugin-api/src/webview.ts` + +Feature surface currently supplied by `react-force-graph-2d`: + +- Graph data input and incremental updates through `graphData`. +- Node/link drawing through per-frame Canvas callbacks: `nodeCanvasObject`, `linkCanvasObject`, plus custom pointer hit areas. +- Direction indicators: arrows, particles, particle size, speed, color, and curved links. +- View control: `zoom`, `centerAt`, `zoomToFit`, `screen2GraphCoords`, `graph2ScreenCoords`. +- Event model: node/link/background click, right-click, hover, drag, drag end, zoom events. +- Simulation control: `d3AlphaDecay`, `d3VelocityDecay`, warmup/cooldown ticks, pause/resume animation, `d3Force`, and `d3ReheatSimulation`. +- Plugin force adapters that currently inject D3-compatible forces through `d3Force`. +- Plugin/webview rendering hooks that currently expose raw `CanvasRenderingContext2D`. + +Source: `react-force-graph` README documents Canvas/WebGL rendering, D3 physics, zoom/pan, dragging, node/link interactions, Canvas draw callbacks, directional particles/arrows, render hooks, viewport methods, D3 force access, and pointer controls: +https://github.com/vasturiano/react-force-graph + +Source: the underlying `force-graph` package describes the 2D version as HTML5 Canvas rendering with `d3-force`, plus zoom/pan, dragging, hover/click interactions, and large graph examples: +https://github.com/vasturiano/force-graph + +## WebGPU Support And VS Code Webview Constraints + +WebGPU is a real fit for this problem because it supports both rendering and GPU compute. The W3C spec describes WebGPU as exposing GPU hardware to the web, mapping to modern native GPU APIs, and providing `GPUDevice`, `GPUQueue`, buffers, textures, render pipelines, and compute pipelines: +https://www.w3.org/TR/webgpu/ + +Important constraints: + +- WebGPU access starts at `navigator.gpu`, then `requestAdapter()`, then `requestDevice()`. +- The WebGPU spec exposes `navigator.gpu` in both `Window` and `WorkerGlobalScope` contexts, gated by `SecureContext`. +- Chrome shipped WebGPU by default in Chrome 113 on ChromeOS, macOS, and Windows; VS Code webviews ride on Electron/Chromium, so CodeGraphy should runtime-probe rather than hardcode assumptions. +- Electron major releases track Chromium releases, so VS Code support will drift with the VS Code/Electron version. +- GPU adapters/devices can be absent, denied, lost, or limited. A production renderer needs fallback and device-loss recovery. + +Sources: + +- W3C WebGPU spec: https://www.w3.org/TR/webgpu/ +- Chrome WebGPU release: https://developer.chrome.com/blog/webgpu-release +- Electron release cadence: https://www.electronjs.org/docs/latest/tutorial/electron-timelines + +VS Code webview constraints matter as much as browser support: + +- A webview is an iframe-like surface controlled by the extension and communicates by message passing. +- Webviews are resource-heavy relative to native VS Code UI. +- JavaScript is disabled by default and must be enabled with `enableScripts: true`. +- Webviews should use restrictive `localResourceRoots` and a Content Security Policy. +- Web workers are supported, but VS Code says workers can only be loaded through `data:` or `blob:` URIs and cannot use dynamic `importScripts` or `import(...)`; worker code should be bundled into a single file. +- Webview state is destroyed when moved to the background unless state is persisted or `retainContextWhenHidden` is used. + +Source: VS Code Webview API: +https://code.visualstudio.com/api/extension-guides/webview + +CodeGraphy's current webview CSP allows local scripts/styles and images but does not currently declare a worker source. If the renderer uses a worker for layout or WebGPU off-main-thread work, the webview HTML/CSP and bundling path need to be part of the renderer design. + +## Force Layout Source Facts + +`react-force-graph-2d` depends on the D3-style model: a simulation mutates node positions and velocities over ticks, then rendering reads those positions. + +D3 facts relevant to replacement: + +- `d3-force` uses a velocity Verlet numerical integrator. +- The simulation mutates node `x`, `y`, `vx`, and `vy` values. +- It can run automatically on a timer or manually through `simulation.tick`. +- D3 recommends computing static layouts for large graphs in a web worker to avoid freezing UI. +- The many-body force uses a quadtree and Barnes-Hut approximation, reducing repulsion from naive `O(n^2)` toward `O(n log n)`. + +Sources: + +- D3 force overview: https://d3js.org/d3-force +- D3 force simulation: https://d3js.org/d3-force/simulation +- D3 many-body force: https://d3js.org/d3-force/many-body + +Architecture implication: a WebGPU renderer should not try to preserve raw `d3Force` as the long-term plugin interface. It should expose CodeGraphy physics concepts: repel force, link force, link distance, center force, damping, collision radius, pinning, and optional plugin-owned layout effects. + +## GPU Graph Precedents + +No source found looks like a mature drop-in WebGPU replacement for `react-force-graph-2d` with CodeGraphy's exact surface. The useful precedents point toward a custom renderer. + +### Cosmograph / cosmos.gl + +`cosmos.gl` is the strongest browser precedent for the performance dream, but it is WebGL, not WebGPU. Its README says computation and drawing happen on the GPU in fragment and vertex shaders and targets real-time simulation of hundreds of thousands of points and links. Cosmograph docs say `cosmos.gl` uses GPU parallel processing to calculate force-directed layouts for hundreds of thousands or millions of nodes in seconds. + +Use it as a design reference and benchmark target, not as a direct WebGPU answer. + +Sources: + +- cosmos.gl: https://github.com/cosmosgl/graph +- Cosmograph concept docs: https://cosmograph.app/docs-general/concept/ + +### Sigma.js + Graphology + +Sigma.js is a good reference for architecture, not for dynamic force simulation. It separates graph data/algorithms into Graphology and rendering/interactions into Sigma. Sigma renders with WebGL and says that enables larger graphs than Canvas or SVG, with the tradeoff that custom rendering becomes harder. + +Graphology's ForceAtlas2 package provides synchronous and worker layout modes, and can use Barnes-Hut optimization. This is useful as a CPU/worker fallback or alternate layout mode, not as the high-end WebGPU layout. + +Sources: + +- Sigma.js: https://www.sigmajs.org/ +- Graphology ForceAtlas2: https://graphology.github.io/standard-library/layout-forceatlas2.html + +### GraphWaGu + +GraphWaGu is the most relevant WebGPU research precedent. The project page describes a WebGPU graph visualization system using parallel rendering and layout with modified Fruchterman-Reingold and Barnes-Hut algorithms implemented in WebGPU compute shaders. + +Use it as evidence that WebGPU compute layout is viable, but treat it as research guidance rather than a production dependency. + +Source: + +- GraphWaGu project page: https://www.willusher.io/publications/graphwagu/ + +### GraphGPU + +GraphGPU is a newer WebGPU graph visualization library. Its README claims WebGPU-native rendering for nodes, edges, selection halos, labels, CPU or GPU compute force layout, Barnes-Hut CPU repulsion, animated mode, pan/zoom/drag/select/hover, and Canvas2D label overlay. + +This is worth a prototype spike, but it should be treated as a candidate to evaluate, not assumed as the renderer foundation. + +Source: + +- GraphGPU: https://github.com/drkameleon/GraphGPU + +## Obsidian Graph View Inspiration + +Official Obsidian help frames Graph View around four control groups: + +- Filters: search files, tags, attachments, existing files, orphans. +- Groups: search-defined groups with colors. +- Display: arrows, text fade threshold, node size, link thickness, animate time-lapse. +- Forces: center force, repel force, link force, link distance. + +Local Graph adds depth around the active note. + +Source: + +- Obsidian Graph View help: https://obsidian.md/help/plugins/graph + +Architecture implication: Obsidian's graph feels useful because controls are immediate and graph-native. CodeGraphy should keep the renderer interactive while filters/scope/query updates stream in, rather than treating graph layout as a static export step. + +## Proposed Renderer Module Shape + +The replacement should be a deep module with a small interface: + +```ts +interface GraphRenderer { + mount(canvas: HTMLCanvasElement, options: RendererOptions): Promise; + setGraph(update: GraphSnapshot | GraphDiff): void; + setStyles(styles: GraphRenderStyles): void; + setPhysics(settings: GraphPhysicsSettings): void; + setInteractionState(state: GraphInteractionState): void; + start(): void; + stop(): void; + reheat(): void; + fitTo(nodes?: readonly string[]): void; + centerOn(nodeId: string): void; + screenToGraph(point: Point2D): Point2D; + graphToScreen(point: Point2D): Point2D; + hitTest(point: Point2D): GraphHit | null; + dispose(): void; +} +``` + +The implementation should be data-oriented: + +- Node buffers: id-to-index map in JS; GPU buffers for position, velocity, size, color, type/style index, flags. +- Edge buffers: source/target indexes, edge kind/style index, weight, curvature/direction flags. +- Dynamic buffers: selected, hovered, pinned, hidden, search result, plugin decorations. +- Render passes: edges, arrows, particles, nodes, halos, maybe labels. +- Compute passes later: link springs, center gravity, damping, repulsion approximation, collision approximation. + +## Recommended Architecture Direction + +Start with `WebGpuGraphRenderer` where GPU owns drawing and CPU/worker owns layout. This gives immediate rendering wins without forcing the hardest compute problem first. + +Then add `WebGpuForceLayout` as an optional layout adapter: + +- Pass 1: link springs over edges. +- Pass 2: center/gravity/damping over nodes. +- Pass 3: repulsion approximation. Do not begin with all-pairs `O(n^2)`. Use grid/tile approximation first, then Barnes-Hut or another hierarchy if quality demands it. +- Pass 4: integration and pin/drag constraints. + +Keep a `CanvasForceGraphRenderer` or current `react-force-graph-2d` adapter as fallback until WebGPU is proven across VS Code/Electron, remote/VM contexts, and weaker hardware. + +## Plugin Impact + +Current plugins can register raw Canvas node renderers and overlays. A WebGPU renderer should not expose raw GPU pipelines as the default plugin interface. + +Prefer React/webview slot contributions for TS visual plugins: + +- Plugin windows, panels, toolbar items, and settings live in React/webview slots. +- Transparent background or foreground effects can mount into named DOM slots around the graph. +- Particles are the model: a TS bundle injects an animated transparent background behind the graph without touching node/edge rendering. +- Graph facts, filters, queries, and styling changes flow through the core plugin system and return to the webview as graph projection data. +- Optional declarative graph styling may be added later, but raw renderer interaction should stay private. + +This keeps the extension unaware of plugin handling while still letting the core/plugin system describe renderer contributions to the webview. + +## Risks And Decisions + +- WebGPU availability must be runtime-probed in the VS Code webview with fallback. +- Worker-based layout is attractive, but VS Code webview worker loading requires blob/data bundling and likely CSP changes. +- Labels are expensive at giant scale. Use Obsidian-like text fade threshold, label culling, and possibly a Canvas2D or SDF overlay instead of full labels for every node. +- Picking can be CPU spatial-index based or GPU color-picking based. CPU spatial index is likely simpler and avoids GPU readback stalls. +- Plugin compatibility needs a migration story from raw Canvas callbacks to declarative render descriptors. +- 3D mode: decided — remove `react-force-graph-3d` entirely rather than maintaining it as legacy. The WebGPU effort targets a single 2D renderer. + +## Bottom Line + +The right track is not "replace React with WebGPU." It is: + +```text +React UI shell + -> CodeGraphy GraphRenderer interface + -> current react-force-graph adapter + -> WebGPU 2D renderer + -> CPU/worker layout first + -> WebGPU compute layout later +``` + +This matches the performance goal: giant graphs should keep drawing, panning, zooming, dragging, filtering, and force motion fluidly. The hard part is layout and interaction parity, not just drawing circles faster. diff --git a/docs/plans/2026-07-10-rust-core-plan.md b/docs/plans/2026-07-10-rust-core-plan.md new file mode 100644 index 000000000..14547eea0 --- /dev/null +++ b/docs/plans/2026-07-10-rust-core-plan.md @@ -0,0 +1,429 @@ +# CodeGraphy Rust Core Plan + +> **For agentic workers:** This is the umbrella/handoff plan for the Rust core +> track. Each phase is too large for a single session; when starting a phase, +> write a detailed task-level implementation plan for that phase first +> (superpowers:writing-plans style, checkbox tasks with failing-test steps), +> using this doc's checkpoints as that plan's acceptance criteria. + +Sibling plan: [WebGPU graph surface plan](2026-07-10-webgpu-graph-surface-plan.md) +(Track A). The two tracks are independent until the Convergence phases at the +bottom of this doc. + +**Goal:** Replace CodeGraphy's TypeScript indexing core with an installable +Rust binary (`codegraphy`) that serves CLI, MCP, and VS Code from one engine — +indexing, SQLite cache, query, projections/diffs, and the plugin registry. + +**Architecture:** One installed binary. `codegraphy stdio` is a long-lived +child process speaking JSON-RPC 2.0 (LSP-style framing) for control plus +length-prefixed binary frames for graph payloads. The VS Code extension +becomes a thin protocol client; the extension host never parses graph +payloads. SQLite (WAL) is the durable cache with a single-writer election per +workspace. + +**Tech Stack:** Rust (tree-sitter, rusqlite bundled, notify, wasmtime later), +JSON-RPC 2.0 + Content-Length framing, platform-specific VSIXes. + +## Source Documents (read these first) + +- [Rust core architecture brainstorm](2026-07-09-rust-core-webgpu-architecture-brainstorm.md) + — core/protocol/plugin/SQLite/remote/distribution design and precedents + (rust-analyzer, Biome, Deno, SWC, Oxc, Zed, Lapce, Tauri). + +## Global Constraints (locked decisions — do not relitigate) + +- **Browser-hosted VS Code (vscode.dev) unsupported**; show a clear message. + Remote SSH/WSL/containers **are** supported (binary runs on remote host). +- Payload rule: **the extension host never parses graph payloads** — binary + frames pass through untouched via `postMessage` ArrayBuffer transfer. +- Visibility boundary: the core serves graph facts and query/filter/search + results; it does not know about view-level visibility. Collapse/folder-view + grouping is an extension/webview feature — the core schema and protocol + must not grow collapse state. +- The timeline view is removed (Track A2 in the sibling plan); the core never + implements timeline data. +- **No JS plugin compatibility host in this program** (see B5). +- Plugins never get raw renderer access; plugin UI flows as descriptors from + core, plugin data as facts through core. +- Repo workflow rules apply to every phase: acceptance scenarios for + user-visible behavior, CRAP ≤ 8, scoped differential mutation testing + (never full-suite), pre-commit typecheck must pass. + +## Phase Sequence + +```text +B0 protocol skeleton → B1 indexer MVP + differential harness +→ B2 concurrency/watching → B3 query/projection/diffs +→ B4 extension sidecar → B5 wasm plugin host → B6 CLI/MCP surfaces +→ C1 binary path end-to-end → C2 full-loop perf → C3 cleanup +``` + +B0/B1 can start today, in parallel with the sibling plan's A0/A1. + +--- + +### B0. Protocol Spec + Skeleton + +**Deliverables** + +- Crates: `codegraphy-cli` (binary), `codegraphy-core`, `codegraphy-protocol` + (see the Rust Setup Walkthrough below for the workspace layout). +- `codegraphy stdio`: JSON-RPC 2.0 over LSP-style `Content-Length` framing; + `initialize` handshake exchanging protocol + binary-format versions; + `$/cancelRequest`, `$/progress`; binary frame channel (length-prefixed + frames referenced by id from JSON results). +- Written protocol spec in-repo (`docs/protocol.md`): every method from the + brainstorm doc's list with request/response schemas, revision semantics + (every projection/diff carries its source revision; missed diff ⇒ client + requests snapshot). + +**Checkpoints** + +- [ ] Integration test: spawn binary, initialize, mismatched version is + refused with a typed error; `cargo test` green in CI for the crate + workspace. +- [ ] A TS test client (in-repo) round-trips a request and receives a binary + frame ≥ 1MB intact (checksum verified). + +### B1. Indexer MVP + Differential Harness + +**Deliverables** + +- Tree-sitter parsing (start: TypeScript/JavaScript + Markdown — the near-core + languages), SQLite schema (WAL, migrations table), files/symbols/relations + tables, FTS index for symbol search; `codegraphy index`, `codegraphy + status`. This subsumes and eventually replaces the existing + `packages/core/src/graphCache` database layer — study its records/query + modules for the fact model before designing the schema. +- **Differential harness** (the phase's centerpiece): index the fixture/example + repos (`examples/`) with both the current TS core and the Rust core; diff + emitted nodes/edges; report format committed. + +**Checkpoints** + +- [ ] Differential report on all example repos: 100% node parity, edge parity + ≥ 99% with every discrepancy triaged in the report (bug vs accepted + improvement). +- [ ] Index throughput recorded; target: full index of the CodeGraphy repo + itself ≥ 5× faster than the current TS core on the same machine. +- [ ] `codegraphy index && codegraphy status` on a fixture repo exits 0 and + reports counts matching the harness. + +### B2. Concurrency, Watching, Incremental + +**Deliverables** + +- WAL + busy_timeout; single-writer election (advisory lock + PID/heartbeat, + reader promotion); `notify`-based watcher owned by the elected writer; + debounced incremental re-index; revision counter + change notification to + connected clients. + +**Checkpoints** + +- [ ] Test: two `codegraphy stdio` processes on one workspace — exactly one + indexes (assert via status); kill the writer; the reader promotes and + indexing resumes (integration test, deterministic with heartbeat + timeout override). +- [ ] Incremental: touching one file re-indexes only that file's scope; + end-to-end update visible to a subscribed client < 500ms on the fixture + repo (test with timing assertion, generous CI margin). +- [ ] Schema migration under contention: writer migrates, concurrent reader + never observes a half-migrated schema (test). + +### B3. Query, Projection, Diffs + +**Deliverables** + +- Query engine: search/filter/sort/scope/graph traversal matching the current + extension's data layer semantics; visible-graph projection as + struct-of-arrays binary frames (format doc committed — this format is the + C1 contract, co-designed with Track A's buffer layout); `getGraphDiff` + between revisions; node/edge detail lookup. +- Persisted layout positions keyed by stable node identity, with a defined + rename/move policy (content-assisted identity or explicit position + migration on rename — positions must survive common refactors). +- Settings ownership split, documented: settings that affect indexed data or + projections (filters, scopes, plugin enablement) live in/flow through core; + purely visual settings (including collapse state) stay + extension/webview-side. + +**Checkpoints** + +- [ ] Golden tests: projections for seeded fixtures byte-stable across runs. +- [ ] Diff soundness property test: snapshot(rev A) + diffs(A→B) == + snapshot(rev B) for randomized edit sequences. +- [ ] Query latency: filter+search+projection on 100k-node synthetic + workspace < 50ms (bench in CI, informational not blocking). + +### B4. Extension Sidecar Integration + +**Deliverables** + +- Extension spawns/discovers the binary (bundled per-platform VSIX using the + existing tree-sitter-natives pipeline; `codegraphy.path` override); process + lifecycle (restart on crash, version handshake surfacing); extension data + layer swapped to protocol client behind its existing interface; web + environment detection → "requires desktop VS Code" notice; Windows + binary-lock update workaround; diagnostics: core stderr/log stream surfaced + in a VS Code output channel, `codegraphy status --json` wired into an + extension "doctor" command for bug reports. + +**Checkpoints** + +- [ ] Acceptance suite green with the Rust core supplying graph data. +- [ ] Manual matrix recorded in PR: local macOS, local Windows, Remote SSH + (binary on remote), WSL, dev container; each loads a graph. +- [ ] Kill the core process mid-session: extension restarts it and recovers + the view via snapshot resync (integration test). + +### B5. Wasm Plugin Host + Plugin Migration + +**Deliverables** + +- Wasmtime component-model host; WIT interface exposing parse-once AST/query + access, fact emission, and manifest-declared UI contribution descriptors; + fuel/epoch limits; `codegraphy plugin install/list/enable`. +- Per-plugin migration (decided): **no JS compatibility host in this + program.** All plugins are first-party and in-repo, so embedding a JS + engine in the Rust core to run four of our own plugins is a large subsystem + with no payoff. typescript + markdown fold into core as built-ins; + godot/unity/vue/svelte are rewritten as Rust→wasm plugins, validated by the + differential harness; particles stays webview-only. An Oxc-style JS host + remains a documented future option if a third-party TS plugin ecosystem + materializes — it is explicitly out of scope here. + +**Checkpoints** + +- [ ] Differential parity per migrated plugin on its example repo (same + harness as B1). +- [ ] Sandbox test: a plugin that infinite-loops is terminated by + fuel/epoch limit without stalling indexing (integration test). +- [ ] Extension renders plugin UI contributions purely from core descriptors + (no plugin-specific extension code) for one migrated plugin. + +### B6. CLI + MCP Surfaces + +**Deliverables** + +- Human CLI (`index`, `query`, `status`, `plugin …`) with stable JSON output + mode; MCP adapter (`codegraphy mcp`) exposing the same tools the current + `packages/mcp` provides, backed by the core protocol. + +**Checkpoints** + +- [ ] Existing MCP package's tool-level tests pass against the new adapter + (or a mapped equivalent suite); old TS MCP package deleted. +- [ ] `codegraphy query --json` output schema documented + golden-tested. + +--- + +## Convergence (requires the sibling plan's A4+ renderer) + +### C1. Binary Path End-To-End + +Wire B3's binary projections through the extension (opaque pass-through) into +the WebGPU renderer's GPU buffers. + +**Checkpoints** + +- [ ] Assertion in code + test: extension host performs zero + parse/re-serialize of projection frames (frames pass through with only + envelope handling; verified by checksum at both ends). +- [ ] Trace on the 100k fixture: core-emit → GPU-buffer-written < 100ms for a + full projection swap; diffs < 16ms. +- [ ] Remote SSH: same flow over the remote channel; diff-driven update after + a file save < 1s end-to-end (manual, recorded). + +### C2. Full-Loop Performance Validation + +- [ ] The sibling plan's A0 harness, now driven by the real core on real + repos, meets every committed budget (60fps at 100k nodes / 300k edges; + index 5× baseline; interactive force sliders reflected next frame). + Results committed as the program's exit report. + +### C3. Cleanup And Deprecation + +- [ ] Old TS indexing/data-layer code deleted from `packages/core`/extension; + react-force-graph adapter deleted (sibling plan A7 done); docs updated; + decision on keeping the Canvas fallback long-term recorded. + +--- + +## Shipping And Release Plan + +### Rollout Gates + +- Track B ships dark first: B4 can ship with a `useRustCore` experimental + setting, running the differential harness in CI on every release until + cutover confidence is earned. +- Version discipline: extension and binary versions are released in lockstep + while bundled; the protocol handshake makes any drift a clean error, not + silent corruption. + +### Distribution: Install The Extension, Get Everything + +The ideal path — user installs the CodeGraphy extension and the core is just +there — is achievable, but not via an npm dependency. The core is a native +binary per OS/architecture; npm dependencies of an extension are JS code +bundled at packaging time and cannot deliver per-platform native executables +cleanly. The VS Code-native mechanism that gives the same result is +**platform-specific extensions**: the Marketplace lets one extension publish +separate VSIX payloads per target, and VS Code automatically picks the right +one at install time — including installing the matching build on the remote +host in SSH/WSL/container sessions. + +```text +vsce publish --target darwin-arm64 (VSIX contains bin/codegraphy, macOS arm64) +vsce publish --target darwin-x64 +vsce publish --target win32-x64 +vsce publish --target win32-arm64 +vsce publish --target linux-x64 +vsce publish --target linux-arm64 +vsce publish --target alpine-x64 (musl build, for containers) +``` + +The repo already builds platform VSIXes for the Tree-sitter natives, so this +pipeline exists — the Rust binary becomes one more per-platform artifact in +it. Extension activation then does: use `codegraphy.path` if set → else use +the bundled binary → verify with the protocol handshake → clear error UI on +failure. No download step, works offline, one-click install. (Fallback +option if VSIX size ever becomes a problem: download-on-first-activation +with checksum verification, the rust-analyzer model — not needed initially.) + +Independent CLI installs (`cargo install codegraphy`, Homebrew, npm wrapper +package with platform binaries à la esbuild/Biome) are optional extras for +CLI/MCP users, published from the same release pipeline — the extension never +depends on them. + +### Release Pipeline (CI) + +- On B0 landing, CI grows a Rust leg: `cargo fmt --check`, `cargo clippy -- + -D warnings`, `cargo test` on the workspace, on Linux/macOS/Windows + runners. +- Release builds cross-compile the target matrix above (GitHub Actions + matrix; `cross` or Zig-assisted linking for the musl/arm64 targets built + on x64 runners), strip the binaries, and hand them to the existing VSIX + packaging step. +- The differential harness (B1) runs on every release build until the old TS + core is deleted (C3); a regression blocks release. + +### Rust Setup Walkthrough (for a Rust newcomer) + +One-time machine setup: + +```bash +# Installs rustc (compiler), cargo (build tool + package manager), rustup (toolchain manager) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +rustup component add clippy rustfmt # linter + formatter +``` + +Mental model, mapped to what you know: **cargo** is pnpm+turbo in one, +**crates.io** is npm, **Cargo.toml** is package.json, **Cargo.lock** is the +lockfile (committed), a **crate** is a package, and a cargo **workspace** is +a pnpm monorepo. `clippy` is ESLint, `rustfmt` is Prettier, tests live next +to the code (`#[cfg(test)]` modules) or in `tests/` for integration tests — +no separate test-runner dependency. + +Repo layout (new top-level `crates/` directory beside `packages/`): + +```text +crates/ + Cargo.toml # workspace root (below) + codegraphy-cli/ # binary crate → produces the `codegraphy` executable + Cargo.toml + src/main.rs + codegraphy-core/ # library crate: indexing, SQLite, query, plugins + Cargo.toml + src/lib.rs + codegraphy-protocol/ # library crate: request/response types, framing + Cargo.toml + src/lib.rs +``` + +Workspace root `crates/Cargo.toml`: + +```toml +[workspace] +members = ["codegraphy-cli", "codegraphy-core", "codegraphy-protocol"] +resolver = "2" + +[workspace.dependencies] # shared version pins, like pnpm catalog +serde = { version = "1", features = ["derive"] } +serde_json = "1" +rusqlite = { version = "0.31", features = ["bundled"] } # compiles SQLite in — no system dep +tree-sitter = "0.22" +notify = "6" + +[profile.release] +lto = "thin" +strip = true # smaller binaries for the VSIX +``` + +A member crate, e.g. `codegraphy-cli/Cargo.toml`: + +```toml +[package] +name = "codegraphy-cli" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "codegraphy" # the executable's name, regardless of crate name +path = "src/main.rs" + +[dependencies] +codegraphy-core = { path = "../codegraphy-core" } +codegraphy-protocol = { path = "../codegraphy-protocol" } +serde_json = { workspace = true } +``` + +Daily commands (run from `crates/`): + +```bash +cargo build # dev build → target/debug/codegraphy +cargo run -- index # build + run with args (the -- separates cargo's args from yours) +cargo test # all workspace tests +cargo test -p codegraphy-core # one crate's tests (-p = --package) +cargo clippy -- -D warnings # lint, warnings as errors +cargo fmt # format everything +cargo build --release # optimized build → target/release/codegraphy +cargo add serde -p codegraphy-core # add a dependency (like pnpm add) +``` + +Cross-compiling for the release matrix (CI does this; locally you mostly +build your own platform): + +```bash +rustup target add aarch64-apple-darwin # one-time per target +cargo build --release --target aarch64-apple-darwin +``` + +Gotchas worth knowing on day one: the borrow checker will fight you for the +first weeks — prefer cloning small data over fighting lifetimes while +learning; `target/` is the (large) build cache, gitignore it; `rusqlite` +with the `bundled` feature avoids all system-SQLite version pain; and +`cargo doc --open` renders every dependency's API docs locally, which is the +idiomatic way to read Rust library documentation. + +## Risk Register + +1. **Fact parity of the Rust indexer** (B1/B5) — the differential harness is + the safety net; no phase advances with untriaged discrepancies. +2. **Payload path regressions** (C1) — any hop that reintroduces per-node JS + objects silently erases the win; the zero-parse checksum test guards it. +3. **Scope creep in plugin host** (B5) — WIT surface starts minimal + (parse-once queries + facts + UI descriptors); anything more is a new + decision. + +## Handoff Notes + +- Worktree/PR conventions, acceptance-spec ownership guard, and pre-commit + typecheck are enforced by hooks — commits will run them. +- Quality loop: acceptance scenarios first for user-visible behavior; CRAP ≤ 8 + on changed code; differential (scoped) mutation testing only. +- Benchmarks live in `docs/plans/benchmarks/`; every perf checkpoint commits + its JSON so trends are diffable. +- When a phase starts: (1) re-read the brainstorm doc section, (2) write the + task-level plan, (3) copy this doc's checkpoints in as acceptance criteria, + (4) tick checkboxes here as phases complete — this file is the Rust track's + single source of progress truth.