From 1035202e3624ef318f971e98bcf0b58d73809d17 Mon Sep 17 00:00:00 2001 From: joesobo Date: Thu, 9 Jul 2026 11:33:09 -0700 Subject: [PATCH 01/10] docs: capture rust core webgpu architecture brainstorm --- ...ust-core-webgpu-architecture-brainstorm.md | 820 ++++++++++++++++++ ...26-07-09-webgpu-graph-renderer-research.md | 550 ++++++++++++ ...6-07-09-webgpu-renderer-source-research.md | 241 +++++ 3 files changed, 1611 insertions(+) create mode 100644 docs/plans/2026-07-09-rust-core-webgpu-architecture-brainstorm.md create mode 100644 docs/plans/2026-07-09-webgpu-graph-renderer-research.md create mode 100644 docs/plans/2026-07-09-webgpu-renderer-source-research.md 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..7c1e19c3f --- /dev/null +++ b/docs/plans/2026-07-09-rust-core-webgpu-architecture-brainstorm.md @@ -0,0 +1,820 @@ +# 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. + +## 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..064ac7991 --- /dev/null +++ b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md @@ -0,0 +1,550 @@ +# 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; +- time-travel or timeline graph replay from git/history; +- 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: + +- Keeping a `d3-force`-like force interface could make migration easier. +- 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 + CPU d3 layout; +- WebGPU rendering + workerized CPU layout; +- WebGPU rendering + WebGPU compute layout. + +## Migration Levels + +### Level 1: WebGPU Renderer, Existing CPU Layout + +Keep `d3-force` or force-graph-like CPU simulation. Replace the 2D canvas +drawing path with WebGPU. + +Pros: + +- smaller first step; +- preserves force behavior; +- 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. + +## 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. + +## 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 + +- Should 3D remain `react-force-graph-3d` while 2D goes custom WebGPU? +- Should layout be d3-compatible at first or should we jump to a new force model? +- How much of DAG mode is essential? +- Do we keep Canvas plugin renderers through a compatibility overlay, remove + direct graph-renderer plugin hooks, or migrate only selected capabilities to + declarative styling? +- Where should persistent layout coordinates live in the Rust/SQLite future? +- What is the target scale: 10k nodes, 100k nodes, or "whatever the machine can + reasonably show"? +- What should dense graph mode hide first: labels, icons, particles, arrows, or + low-importance edges? 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..e75798773 --- /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 should probably remain separate or legacy while the WebGPU effort focuses on the 2D graph. + +## 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. From d9569d760471c0087b29983e87095d61916bb497 Mon Sep 17 00:00:00 2001 From: joesobo Date: Thu, 9 Jul 2026 11:54:09 -0700 Subject: [PATCH 02/10] =?UTF-8?q?docs:=20fill=20architecture=20gaps=20?= =?UTF-8?q?=E2=80=94=20multi-process=20SQLite,=20remote=20hosts,=20binary?= =?UTF-8?q?=20payload=20path,=20plugin=20migration,=20perf=20budgets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- ...ust-core-webgpu-architecture-brainstorm.md | 165 ++++++++++++++++++ ...26-07-09-webgpu-graph-renderer-research.md | 86 +++++++-- 2 files changed, 240 insertions(+), 11 deletions(-) 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 index 7c1e19c3f..60c1a309e 100644 --- a/docs/plans/2026-07-09-rust-core-webgpu-architecture-brainstorm.md +++ b/docs/plans/2026-07-09-rust-core-webgpu-architecture-brainstorm.md @@ -122,6 +122,171 @@ 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: there is no process spawn at all. Options are a + wasm build of the core running in a web worker (Tree-sitter and SQLite both + have wasm stories), or simply declaring web unsupported initially. Decide + deliberately; do not discover this later. +- 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. diff --git a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md index 064ac7991..f5cc353ca 100644 --- a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md +++ b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md @@ -471,6 +471,60 @@ 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). +- Suggested 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: @@ -535,16 +589,26 @@ Rust core computes compact graph projection/diffs -> React owns controls, panels, plugin UI slots ``` -## Open Questions +## Open Questions With Leanings - Should 3D remain `react-force-graph-3d` while 2D goes custom WebGPU? -- Should layout be d3-compatible at first or should we jump to a new force model? -- How much of DAG mode is essential? -- Do we keep Canvas plugin renderers through a compatibility overlay, remove - direct graph-renderer plugin hooks, or migrate only selected capabilities to - declarative styling? -- Where should persistent layout coordinates live in the Rust/SQLite future? -- What is the target scale: 10k nodes, 100k nodes, or "whatever the machine can - reasonably show"? -- What should dense graph mode hide first: labels, icons, particles, arrows, or - low-importance edges? + Leaning: yes. 3D is already WebGL via Three.js and is not the performance + pain point. Freezing 3D as legacy keeps the WebGPU scope honest. +- Should layout be d3-compatible at first or should we jump to a new force + model? Leaning: keep the current d3 simulation verbatim behind the layout + interface for step 1, so renderer changes can be validated against pixel- + identical physics. New force models come only after the renderer is trusted. +- How much of DAG mode is essential? Needs usage data; if kept, DAG is a layout + adapter, which the interface split already accommodates. +- 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: recommend committing to a concrete tier — smooth at 100k + nodes / 300k edges, usable at 500k edges — because buffer formats, picking, + and LOD strategy all change shape depending on this answer. +- 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. From 5c8f4f41378b379d67ccd9404d3bfc05669b2b47 Mon Sep 17 00:00:00 2001 From: joesobo Date: Thu, 9 Jul 2026 12:00:30 -0700 Subject: [PATCH 03/10] docs: commit target scale tier, remove 3D mode, declare browser VS Code unsupported Co-Authored-By: Claude Fable 5 --- ...09-rust-core-webgpu-architecture-brainstorm.md | 10 ++++++---- .../2026-07-09-webgpu-graph-renderer-research.md | 15 ++++++++------- .../2026-07-09-webgpu-renderer-source-research.md | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) 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 index 60c1a309e..a766a5ad0 100644 --- a/docs/plans/2026-07-09-rust-core-webgpu-architecture-brainstorm.md +++ b/docs/plans/2026-07-09-rust-core-webgpu-architecture-brainstorm.md @@ -174,10 +174,12 @@ process: 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: there is no process spawn at all. Options are a - wasm build of the core running in a web worker (Tree-sitter and SQLite both - have wasm stories), or simply declaring web unsupported initially. Decide - deliberately; do not discover this later. +- 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. diff --git a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md index f5cc353ca..cd7848d47 100644 --- a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md +++ b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md @@ -514,7 +514,7 @@ 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). -- Suggested targets: 60fps pan/zoom at 100k nodes / 300k edges; < 16ms +- 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 @@ -591,9 +591,10 @@ Rust core computes compact graph projection/diffs ## Open Questions With Leanings -- Should 3D remain `react-force-graph-3d` while 2D goes custom WebGPU? - Leaning: yes. 3D is already WebGL via Three.js and is not the performance - pain point. Freezing 3D as legacy keeps the WebGPU scope honest. +- 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? Leaning: keep the current d3 simulation verbatim behind the layout interface for step 1, so renderer changes can be validated against pixel- @@ -606,9 +607,9 @@ Rust core computes compact graph projection/diffs - 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: recommend committing to a concrete tier — smooth at 100k - nodes / 300k edges, usable at 500k edges — because buffer formats, picking, - and LOD strategy all change shape depending on this answer. +- 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 index e75798773..f6c2c7ea0 100644 --- a/docs/plans/2026-07-09-webgpu-renderer-source-research.md +++ b/docs/plans/2026-07-09-webgpu-renderer-source-research.md @@ -223,7 +223,7 @@ This keeps the extension unaware of plugin handling while still letting the core - 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 should probably remain separate or legacy while the WebGPU effort focuses on the 2D graph. +- 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 From e04a4320275b7b68d4ad7f37bd3170602d7259c6 Mon Sep 17 00:00:00 2001 From: joesobo Date: Thu, 9 Jul 2026 12:04:56 -0700 Subject: [PATCH 04/10] docs: add full WebGPU replacement parity spec grounded in actual force-graph usage Co-Authored-By: Claude Fable 5 --- ...26-07-09-webgpu-graph-renderer-research.md | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md index cd7848d47..89516403b 100644 --- a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md +++ b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md @@ -425,6 +425,149 @@ Plugin-facing behavior: - 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 (the biggest hidden subsystem) + +CodeGraphy configures the d3 simulation directly, so the replacement needs a +full d3-force-compatible engine, not "some physics": + +- Integration: velocity Verlet with the d3 alpha lifecycle — `alpha`, + `alphaMin`, `alphaDecay` (currently 0.0228), `velocityDecay` (the damping + setting, 0.7), and `d3ReheatSimulation` semantics (reset alpha to 1). +- Forces in use (`root/settings/`): charge/many-body (Barnes-Hut quadtree, + `gravitationalConstant: -250`), link springs (per-link distance 80 / + strength 0.15), center + centroid gravity (0.1), and collision (iterative, + radius-aware). +- Pinning: `fx`/`fy` fixed positions — this is how node drag works (fix during + drag, optionally release on drag end) and how persisted layouts would load. +- Lifecycle: `warmupTicks` (currently 0), `cooldownTicks` (60 interactive / 50 + timeline), and `onEngineStop` — the app listens for stabilization. +- Plugin forces: `pluginForces.ts` installs adapters with + `initialize(nodes)` / `tick(alpha)` / `dispose()` under namespaced ids. This + exact contract must survive in the CPU layout; a GPU layout needs a + declarative replacement before plugin forces can move. + +Recommendation: do not rewrite the math initially. Run the actual `d3-force` +package (or a literal port) inside the layout module — main thread first, +worker later — so physics feel is bit-identical while the renderer changes +underneath. Custom/GPU physics is a later, separately validated step. + +### 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: identical d3 engine (or port) validated by running old and new + side-by-side on fixture graphs and diffing settled positions. +- 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 From 80706bc1b6151ad8a9c45a841df6aa946e8d813e Mon Sep 17 00:00:00 2001 From: joesobo Date: Thu, 9 Jul 2026 12:11:10 -0700 Subject: [PATCH 05/10] docs: replace d3-force reuse with custom typed-array physics engine design Co-Authored-By: Claude Fable 5 --- ...26-07-09-webgpu-graph-renderer-research.md | 124 +++++++++++++----- 1 file changed, 88 insertions(+), 36 deletions(-) diff --git a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md index 89516403b..95d78b5de 100644 --- a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md +++ b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md @@ -245,7 +245,8 @@ dependency. CodeGraphy lesson: -- Keeping a `d3-force`-like force interface could make migration easier. +- 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. @@ -315,21 +316,22 @@ separate leaves room for these adapters: - current `react-force-graph` adapter; - custom Canvas2D adapter; -- WebGPU rendering + CPU d3 layout; +- WebGPU rendering + custom CPU layout engine; - WebGPU rendering + workerized CPU layout; - WebGPU rendering + WebGPU compute layout. ## Migration Levels -### Level 1: WebGPU Renderer, Existing CPU Layout +### Level 1: WebGPU Renderer, Custom CPU Layout -Keep `d3-force` or force-graph-like CPU simulation. Replace the 2D canvas -drawing path with WebGPU. +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; -- preserves force behavior; +- 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. @@ -433,31 +435,77 @@ 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 (the biggest hidden subsystem) - -CodeGraphy configures the d3 simulation directly, so the replacement needs a -full d3-force-compatible engine, not "some physics": - -- Integration: velocity Verlet with the d3 alpha lifecycle — `alpha`, - `alphaMin`, `alphaDecay` (currently 0.0228), `velocityDecay` (the damping - setting, 0.7), and `d3ReheatSimulation` semantics (reset alpha to 1). -- Forces in use (`root/settings/`): charge/many-body (Barnes-Hut quadtree, - `gravitationalConstant: -250`), link springs (per-link distance 80 / - strength 0.15), center + centroid gravity (0.1), and collision (iterative, - radius-aware). -- Pinning: `fx`/`fy` fixed positions — this is how node drag works (fix during - drag, optionally release on drag end) and how persisted layouts would load. -- Lifecycle: `warmupTicks` (currently 0), `cooldownTicks` (60 interactive / 50 - timeline), and `onEngineStop` — the app listens for stabilization. -- Plugin forces: `pluginForces.ts` installs adapters with - `initialize(nodes)` / `tick(alpha)` / `dispose()` under namespaced ids. This - exact contract must survive in the CPU layout; a GPU layout needs a - declarative replacement before plugin forces can move. - -Recommendation: do not rewrite the math initially. Run the actual `d3-force` -package (or a literal port) inside the layout module — main thread first, -worker later — so physics feel is bit-identical while the renderer changes -underneath. Custom/GPU physics is a later, separately validated step. +### 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 / 50 timeline today), 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 @@ -553,8 +601,10 @@ code), `onLinkHover`, `linkPointerAreaPaint`, `cooldownTime`, `dagNodeFilter` ### 9. Parity Verification -- Physics: identical d3 engine (or port) validated by running old and new - side-by-side on fixture graphs and diffing settled positions. +- 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. @@ -739,9 +789,11 @@ Rust core computes compact graph projection/diffs 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? Leaning: keep the current d3 simulation verbatim behind the layout - interface for step 1, so renderer changes can be validated against pixel- - identical physics. New force models come only after the renderer is trusted. + 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? Needs usage data; if kept, DAG is a layout adapter, which the interface split already accommodates. - Plugin Canvas renderers: remove raw hooks, keep DOM slots, add declarative From 817954a873f8588d3512754828ba07e025835e42 Mon Sep 17 00:00:00 2001 From: joesobo Date: Fri, 10 Jul 2026 08:43:26 -0700 Subject: [PATCH 06/10] docs: add master plan with phased tracks and deterministic checkpoints Co-Authored-By: Claude Fable 5 --- ...2026-07-10-rust-core-webgpu-master-plan.md | 451 ++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 docs/plans/2026-07-10-rust-core-webgpu-master-plan.md diff --git a/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md b/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md new file mode 100644 index 000000000..40a1bf0e6 --- /dev/null +++ b/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md @@ -0,0 +1,451 @@ +# CodeGraphy Rust Core + WebGPU Master Plan + +> **For agentic workers:** This is the umbrella/handoff plan for the whole +> program. Each phase below 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. Execute via +> superpowers:subagent-driven-development or superpowers:executing-plans. + +**Goal:** Replace CodeGraphy's TypeScript indexing core with an installable +Rust binary (`codegraphy`) serving CLI/MCP/VS Code, and replace the +`react-force-graph` surface with a CodeGraphy-owned WebGPU 2D renderer plus a +custom typed-array physics engine — keeping every user-visible graph feature +working throughout. + +**Architecture:** One installed Rust binary (`codegraphy stdio` machine mode, +JSON-RPC control + binary graph frames) owns indexing, SQLite cache, query, +projection/diffs, and the plugin registry. The VS Code extension becomes a +thin protocol client; the webview hosts React UI around a WebGPU renderer and +a headless struct-of-arrays physics engine. One flat binary payload format +flows Rust → extension (opaque) → `postMessage` transfer → GPU buffers. + +**Tech Stack:** Rust (tree-sitter, rusqlite/WAL, wasmtime later), TypeScript +webview (React, WebGPU/WGSL, worker layout), JSON-RPC 2.0 with LSP-style +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. +- [WebGPU renderer research](2026-07-09-webgpu-graph-renderer-research.md) + — renderer/layout seam, migration levels, **Full Replacement Parity Spec** + (§ by subsystem: physics, nodes, links, camera, picking, render loop, DAG), + perf budgets, decided open questions. +- [WebGPU source research](2026-07-09-webgpu-renderer-source-research.md) + — source-backed constraints (VS Code webview/CSP/workers, WebGPU support), + precedent library notes, renderer module shape. + +## Global Constraints (locked decisions — do not relitigate) + +- **No d3-force dependency.** Custom engine; d3/Obsidian are study material. +- **3D mode is removed** (`react-force-graph-3d` + Three.js deleted). +- **Browser-hosted VS Code (vscode.dev) unsupported**; show a clear message. + Remote SSH/WSL/containers **are** supported (binary runs on remote host). +- **Target scale:** smooth 60fps pan/zoom/drag at 100k nodes / 300k edges; + usable with degraded decoration at 500k edges. +- **End state removes `react-force-graph`**; the adapter is a migration rail. +- Payload rule: **the extension host never parses graph payloads** — binary + frames pass through untouched via `postMessage` ArrayBuffer transfer. +- Plugins never get raw WebGPU/renderer access; UI via React/DOM slots, data + via core. +- WebGPU is runtime-probed; a Canvas fallback path must always exist until + the deprecation checkpoint (C3) says otherwise. +- 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. + +## Program Shape + +Two parallel tracks that meet in a convergence phase: + +```text +Track A (webview): A0 baseline → A1 seam → A2 remove 3D → A3 physics engine + → A4 WebGPU MVP → A5 parity → A6 scale/LOD → A7 remove RFG +Track B (core): B0 protocol skeleton → B1 indexer MVP → B2 concurrency + → B3 query/projection/diffs → B4 extension sidecar + → B5 wasm plugin host → B6 CLI/MCP surfaces +Convergence: C1 binary path end-to-end → C2 full-loop perf → C3 cleanup +``` + +Tracks A and B are independent until C1. A0/A1 and B0/B1 can start today, in +parallel. Within a track, phases are sequential. + +--- + +## Track A — Graph Surface + +### A0. Benchmark Baseline And Fixture Harness + +Nothing else in Track A can be validated without this. + +**Deliverables** + +- Synthetic fixture graph generators (seeded): 1k/10k/50k/100k nodes with + realistic degree distribution (power-law-ish, like import graphs), plus 2–3 + real-repo snapshots exported from the current extension. +- A benchmark harness that loads the built webview standalone (reuse the + existing screenshot harness pattern: build webview, serve over HTTP, mock + `acquireVsCodeApi`, inject `GRAPH_DATA_UPDATED`) and records: FPS during + scripted pan/zoom, time-to-settle after load, frame time percentiles, + interaction latency (hover hit-test), and heap usage. +- A committed baseline report for the current react-force-graph renderer at + each fixture size. + +**Checkpoints (deterministic)** + +- [ ] `pnpm bench:graph --fixture 10k --renderer current` produces a JSON + report with fps/settle/latency fields; running twice with the same seed + produces node counts and identical fixture hashes. +- [ ] Baseline report committed at `docs/plans/benchmarks/baseline-.json` + for 1k/10k/50k (100k allowed to fail/crawl on current renderer — record + that fact; it is the motivation). + +### A1. Renderer/Layout Seam + +Introduce the interfaces from the research docs; wrap the current library. + +**Deliverables** + +- `GraphRenderer` and `GraphLayout` interfaces (shape per the research doc's + "Target Interface Shape" / "Proposed Renderer Module Shape" sections). +- `ReactForceGraphAdapter` implementing both, wired where `Surface2d` + + physics runtime are used today (`rendering/surface/`, `runtime/physics/`). +- Callers (interaction runtime, fit controls, accessibility, debug snapshot, + marquee, viewport pan) talk only to the interfaces. + +**Checkpoints** + +- [ ] Full existing test suite and acceptance scenarios pass **unchanged**. +- [ ] Debug snapshot protocol output for a seeded fixture is identical + before/after the seam (byte-diff the snapshot JSON). +- [ ] `grep -r "react-force-graph" packages/extension/src/webview` matches + only inside the adapter directory. + +### A2. Remove 3D Mode + +**Deliverables** + +- Delete `threeDimensional.tsx`, the 2D/3D mode toggle setting + UI, 3D + branches (`graphMode: '3d'` paths), `react-force-graph-3d`, + `three-spritetext` deps, and 3D test mocks. +- Settings migration: persisted `3d` mode value falls back to `2d` silently. + +**Checkpoints** + +- [ ] `grep -ri "force-graph-3d\|three" packages/extension/package.json` → no + matches; `pnpm build:webview` bundle size recorded and reduced vs A1. +- [ ] Acceptance suite green; a stored `3d` settings value loads as 2D + without error (regression test exists). + +### A3. Custom Physics Engine + +Headless typed-array engine per the parity spec §1. No renderer coupling. + +**Deliverables** + +- New package `packages/graph-engine` (pure TS, zero DOM imports): + - state: `Float32Array` positions/velocities, `Uint32Array` edge endpoints, + `Uint8Array` flags (pinned/hidden); + - forces: grid- or quadtree-based repulsion (Barnes-Hut on CPU), link + springs with degree bias, center/centroid gravity, iterative collision; + - integrator with temperature (alpha) annealing, damping, reheat, jiggle; + - lifecycle: `tick(dt)`, warmup/cooldown, stabilization callback, seeded + deterministic mode; + - custom-force API replacing the object-based plugin force adapters + (positions in, velocity deltas out, temperature-scaled). +- Worker host wrapper (single-file bundle, blob-URL loading per VS Code + webview worker rules) — may land in A6 if A4 needs to start sooner. +- Tuning bench: side-by-side run against the old engine on fixtures. + +**Checkpoints** + +- [ ] Determinism: same seed + fixed timestep ⇒ identical position buffer + hash across two runs (unit test). +- [ ] Stability: 10k-node fixture with 100 coincident nodes settles with no + NaN/Inf and bounded energy (unit test asserts). +- [ ] Settle time on 10k fixture ≤ current engine's baseline from A0. +- [ ] Blind feel check: owner cannot reliably distinguish old vs new engine + on the 1k fixture (or prefers the new one) — recorded in the PR. +- [ ] Plugin-force API: existing graph-view force contributions re-expressed + on the new API in a spike, behavior verified on their fixture. + +### A4. WebGPU Renderer MVP + +Behind the seam, feature-detected, Canvas fallback intact. + +**Deliverables** + +- `WebGpuGraphRenderer`: device/adapter acquisition with loss recovery; + owned 2D camera (zoom/centerAt/zoomToFit/min-max clamp/coords conversion, + tweened); instanced SDF node bodies with selection/hover/mute states; + instanced link strips with bezier curvature; CPU spatial-index picking + (node + link + marquee range query); pointer state machine (hover, click + vs drag threshold, right-click, background events, drag with pin+reheat); + damage-model render loop (draw only on tick/camera/interaction/animation). +- Feature detection: `navigator.gpu` probe → fall back to the A1 adapter. + +**Checkpoints** + +- [ ] With WebGPU force-disabled (probe stubbed false), the app runs the + fallback adapter — acceptance suite green in that mode. +- [ ] Screenshot diff harness: seeded 1k fixture at 3 zoom levels renders + within an agreed pixel-diff threshold of the fallback (layout pinned to + identical positions for the comparison). +- [ ] Bench: 50k fixture ≥ 60fps scripted pan/zoom on the dev machine; + hit-test < 16ms (from A0 harness, `--renderer webgpu`). +- [ ] Hit-test parity: table of (fixture, screen point) → picked id matches + the fallback renderer on 20 sampled points (automated). + +### A5. Full Feature Parity + +Work through the parity spec §2–§8 until the WebGPU renderer is the default. + +**Deliverables** + +- Labels: top-N visible Canvas2D/DOM overlay with zoom-gated fade (MSDF atlas + only if the overlay proves insufficient); icons via texture atlas with LOD + cutoff; collapse indicators/badges layer; directional arrows and + GPU-animated particles on curved links; `onRenderFramePost`-equivalent + overlay pass (marquee rect); accessibility projector, tooltips, and debug + snapshot fed from the renderer's position/camera data; DAG mode decision + executed (port as constraint force, or remove the setting — decide at phase + start, record in this doc). +- WebGPU renderer becomes default-on where supported; fallback demoted to + probe-failure path. + +**Checkpoints** + +- [ ] Full acceptance suite green with WebGPU as default renderer. +- [ ] Parity checklist derived from the parity spec §2–§7 completed in the + phase PR (every line: done / explicitly dropped with reason). +- [ ] Platform matrix run recorded: macOS (Metal), Windows (D3D12), Linux + (may fall back — must do so cleanly), device-loss recovery (suspend or + `device.destroy()` test) — manual checklist in the PR. + +### A6. Scale And LOD + +**Deliverables** + +- Worker-hosted layout (if not landed in A3); dense-graph mode hiding in + order: particles → icons → labels (keep hovered/selected) → arrows → + low-weight edges; viewport culling where measurable; frame budgeting. + +**Checkpoints** + +- [ ] Committed targets met on bench harness: 60fps pan/zoom/drag at 100k + nodes / 300k edges; usable at 500k edges; results committed to + `docs/plans/benchmarks/`. +- [ ] Main thread: no frame > 33ms from layout work during interactive + simulation on the 100k fixture (trace recorded). + +### A7. Remove react-force-graph + +**Checkpoints** + +- [ ] `react-force-graph-2d` (and `d3-force`, if now unused) gone from + `package.json`; adapter directory deleted; suite green; bundle size + recorded. + +--- + +## Track B — Rust Core + +### B0. Protocol Spec + Skeleton + +**Deliverables** + +- Crates: `codegraphy-cli` (binary), `codegraphy-core`, `codegraphy-protocol`. +- `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`. +- **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. + +**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. + +**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 at phase start per plugin): typescript + + markdown fold into core as built-ins; godot/unity/vue/svelte → wasm or JS + compat host (evaluate then); particles stays webview-only. + +**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 + +### C1. Binary Path End-To-End + +Wire B3's binary projections through the extension (opaque pass-through) into +A4/A5'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 A0 harness, now driven by the real core on real repos, meets every + committed budget (60fps at 100k/300k; 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 (A7 done); docs updated; decision on + keeping the Canvas fallback long-term recorded (based on A5 platform + matrix + telemetry if available). + +--- + +## Risk Register (watch these; each has a checkpoint above) + +1. **Physics feel** (A3) — highest subjective risk; mitigated by A0 baseline, + side-by-side tuning bench, blind feel check, and keeping the adapter rail + until A7. +2. **WebGPU availability on Linux/VMs** (A4/A5) — fallback is a permanent + requirement until C3 says otherwise. +3. **Fact parity of the Rust indexer** (B1/B5) — the differential harness is + the safety net; no phase advances with untriaged discrepancies. +4. **Payload path regressions** (C1) — any hop that reintroduces per-node JS + objects silently erases the win; the zero-parse checksum test guards it. +5. **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 relevant research 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 + program's single source of progress truth. From 65cb93c249f28a62520c80cc9289fefbfeea6ecb Mon Sep 17 00:00:00 2001 From: joesobo Date: Fri, 10 Jul 2026 08:49:04 -0700 Subject: [PATCH 07/10] docs: decide DAG port and wasm-only plugins, cover timeline/collapse/settings/shipping gaps Co-Authored-By: Claude Fable 5 --- ...26-07-09-webgpu-graph-renderer-research.md | 6 +- ...2026-07-10-rust-core-webgpu-master-plan.md | 64 ++++++++++++++++--- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md index 95d78b5de..4278a7c06 100644 --- a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md +++ b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md @@ -794,8 +794,10 @@ Rust core computes compact graph projection/diffs 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? Needs usage data; if kept, DAG is a layout - adapter, which the interface split already accommodates. +- 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. diff --git a/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md b/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md index 40a1bf0e6..54e082c18 100644 --- a/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md +++ b/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md @@ -208,15 +208,25 @@ Work through the parity spec §2–§8 until the WebGPU renderer is the default. cutoff; collapse indicators/badges layer; directional arrows and GPU-animated particles on curved links; `onRenderFramePost`-equivalent overlay pass (marquee rect); accessibility projector, tooltips, and debug - snapshot fed from the renderer's position/camera data; DAG mode decision - executed (port as constraint force, or remove the setting — decide at phase - start, record in this doc). + snapshot fed from the renderer's position/camera data; DAG modes ported + (decided): all current directions (td/bu/lr/rl/radial) implemented as + constraint forces in the custom engine — per-node depth is computed from + the directed graph (cycles broken deterministically, matching current + behavior), and a positional force pulls each node toward its + depth × `dagLevelDistance` coordinate on the mode's axis. +- Timeline replay parity: the timeline data path (`ITimelineData` messages, + timeline cooldown behavior) works on the WebGPU renderer + custom engine. - WebGPU renderer becomes default-on where supported; fallback demoted to probe-failure path. **Checkpoints** - [ ] Full acceptance suite green with WebGPU as default renderer. +- [ ] DAG modes: each direction renders a layered/radial layout on a seeded + DAG fixture; a cyclic fixture degrades deterministically without error + (matching current behavior). +- [ ] Timeline replay runs visually correctly on the WebGPU renderer + (existing timeline acceptance scenarios pass). - [ ] Parity checklist derived from the parity spec §2–§7 completed in the phase PR (every line: done / explicitly dropped with reason). - [ ] Platform matrix run recorded: macOS (Metal), Windows (D3D12), Linux @@ -280,7 +290,9 @@ Work through the parity spec §2–§8 until the WebGPU renderer is the default. - 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`. + 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. @@ -325,6 +337,17 @@ Work through the parity spec §2–§8 until the WebGPU renderer is the default. 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. +- Feature semantics that must be first-class in the projection model (all + exist in the extension today and are easy to forget): collapse/folder-view + state (collapsed groups change the visible projection, not just styling); + timeline data (`ITimelineData` — decide whether timeline frames are served + by core or remain extension-computed, and record it); 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 stay extension/webview-side. **Checkpoints** @@ -343,7 +366,9 @@ Work through the parity spec §2–§8 until the WebGPU renderer is the default. 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. + 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** @@ -360,9 +385,14 @@ Work through the parity spec §2–§8 until the WebGPU renderer is the default. - 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 at phase start per plugin): typescript + - markdown fold into core as built-ins; godot/unity/vue/svelte → wasm or JS - compat host (evaluate then); particles stays webview-only. +- 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** @@ -422,6 +452,24 @@ A4/A5's GPU buffers. --- +## Shipping Strategy During Migration + +Phases ship to users continuously; nothing waits for the end state. + +- Track A phases ship behind a renderer setting: `experimental` (opt-in, + A4) → `default-on with fallback setting` (A5) → `only` (A7). The old + renderer is never broken while it is reachable. +- 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. +- CI grows two new legs when B0 lands: a Rust workspace job (fmt, clippy, + `cargo test`) and a cross-platform binary build matrix feeding the + platform VSIXes; A4 adds a WebGPU-capable browser bench job (or a + documented local-only bench policy if CI GPUs are unavailable). +- Version discipline: extension and binary versions are released in lockstep + while bundled; the protocol handshake makes any drift a clean error, not + silent corruption. + ## Risk Register (watch these; each has a checkpoint above) 1. **Physics feel** (A3) — highest subjective risk; mitigated by A0 baseline, From 0e7a9ad43647c6281660ed057d18bda906d43b79 Mon Sep 17 00:00:00 2001 From: joesobo Date: Fri, 10 Jul 2026 08:55:48 -0700 Subject: [PATCH 08/10] docs: strip timeline view, keep collapse extension-side, add release plan + Rust setup walkthrough Co-Authored-By: Claude Fable 5 --- ...26-07-09-webgpu-graph-renderer-research.md | 4 +- ...2026-07-10-rust-core-webgpu-master-plan.md | 198 ++++++++++++++++-- 2 files changed, 178 insertions(+), 24 deletions(-) diff --git a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md index 4278a7c06..7c5cd33a9 100644 --- a/docs/plans/2026-07-09-webgpu-graph-renderer-research.md +++ b/docs/plans/2026-07-09-webgpu-graph-renderer-research.md @@ -172,7 +172,6 @@ CodeGraphy equivalents worth thinking about: - search/query-backed visual filters; - zoom-dependent labels; - force controls that feel immediate; -- time-travel or timeline graph replay from git/history; - hover/click/right-click as navigation, not decoration. Source: @@ -489,7 +488,8 @@ Engine design requirements: - 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 / 50 timeline today), stabilization callback. + 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 diff --git a/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md b/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md index 54e082c18..e6ae0b956 100644 --- a/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md +++ b/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md @@ -122,19 +122,31 @@ Introduce the interfaces from the research docs; wrap the current library. - [ ] `grep -r "react-force-graph" packages/extension/src/webview` matches only inside the adapter directory. -### A2. Remove 3D Mode +### A2. Remove 3D Mode And Timeline View + +Both are product removals (owner decision): 3D is a gimmick next to the 2D +graph's navigation value, and the timeline view is being cut entirely. **Deliverables** -- Delete `threeDimensional.tsx`, the 2D/3D mode toggle setting + UI, 3D +- 3D: delete `threeDimensional.tsx`, the 2D/3D mode toggle setting + UI, 3D branches (`graphMode: '3d'` paths), `react-force-graph-3d`, `three-spritetext` deps, and 3D test mocks. -- Settings migration: persisted `3d` mode value falls back to `2d` silently. +- Timeline: delete the timeline view/UI, `ITimelineData` and the + `TIMELINE_DATA` protocol message, timeline contracts + (`shared/timeline/`), the timeline cooldown branch + (`TIMELINE_COOLDOWN_TICKS` / `timelineActive`), timeline acceptance + scenarios, and any extension-side timeline data computation. +- Settings migration: persisted `3d` mode value falls back to `2d` silently; + stale timeline settings/state are ignored without error. **Checkpoints** -- [ ] `grep -ri "force-graph-3d\|three" packages/extension/package.json` → no - matches; `pnpm build:webview` bundle size recorded and reduced vs A1. +- [ ] `grep -ri "force-graph-3d\|three-spritetext" packages/extension/package.json` + → no matches; `pnpm build:webview` bundle size recorded and reduced vs + A1. +- [ ] `grep -rin "timeline" packages/extension/src` → no matches (or only + deliberate migration-shim lines, listed in the PR). - [ ] Acceptance suite green; a stored `3d` settings value loads as 2D without error (regression test exists). @@ -214,8 +226,6 @@ Work through the parity spec §2–§8 until the WebGPU renderer is the default. the directed graph (cycles broken deterministically, matching current behavior), and a positional force pulls each node toward its depth × `dagLevelDistance` coordinate on the mode's axis. -- Timeline replay parity: the timeline data path (`ITimelineData` messages, - timeline cooldown behavior) works on the WebGPU renderer + custom engine. - WebGPU renderer becomes default-on where supported; fallback demoted to probe-failure path. @@ -225,8 +235,6 @@ Work through the parity spec §2–§8 until the WebGPU renderer is the default. - [ ] DAG modes: each direction renders a layered/radial layout on a seeded DAG fixture; a cyclic fixture degrades deterministically without error (matching current behavior). -- [ ] Timeline replay runs visually correctly on the WebGPU renderer - (existing timeline acceptance scenarios pass). - [ ] Parity checklist derived from the parity spec §2–§7 completed in the phase PR (every line: done / explicitly dropped with reason). - [ ] Platform matrix run recorded: macOS (Metal), Windows (D3D12), Linux @@ -337,14 +345,14 @@ Work through the parity spec §2–§8 until the WebGPU renderer is the default. 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. -- Feature semantics that must be first-class in the projection model (all - exist in the extension today and are easy to forget): collapse/folder-view - state (collapsed groups change the visible projection, not just styling); - timeline data (`ITimelineData` — decide whether timeline frames are served - by core or remain extension-computed, and record it); 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). +- Visibility boundary (decided): 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 applied to + core data before rendering — the core schema and protocol must not grow + collapse state. +- 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 stay extension/webview-side. @@ -452,24 +460,170 @@ A4/A5's GPU buffers. --- -## Shipping Strategy During Migration +## Shipping And Release Plan Phases ship to users continuously; nothing waits for the end state. +### Rollout Gates + - Track A phases ship behind a renderer setting: `experimental` (opt-in, A4) → `default-on with fallback setting` (A5) → `only` (A7). The old renderer is never broken while it is reachable. - 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. -- CI grows two new legs when B0 lands: a Rust workspace job (fmt, clippy, - `cargo test`) and a cross-platform binary build matrix feeding the - platform VSIXes; A4 adds a WebGPU-capable browser bench job (or a - documented local-only bench policy if CI GPUs are unavailable). - 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. +- A4 adds a WebGPU bench job if CI GPU runners are available; otherwise the + bench is a documented pre-release local step with results committed to + `docs/plans/benchmarks/`. + +### 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 (watch these; each has a checkpoint above) 1. **Physics feel** (A3) — highest subjective risk; mitigated by A0 baseline, From 6be9e8232497ebf218d180bb5b6e85556b21f0a2 Mon Sep 17 00:00:00 2001 From: joesobo Date: Fri, 10 Jul 2026 09:03:33 -0700 Subject: [PATCH 09/10] docs: split master plan into graph surface plan and rust core plan Co-Authored-By: Claude Fable 5 --- ...r-plan.md => 2026-07-10-rust-core-plan.md} | 340 +++--------------- .../2026-07-10-webgpu-graph-surface-plan.md | 289 +++++++++++++++ 2 files changed, 347 insertions(+), 282 deletions(-) rename docs/plans/{2026-07-10-rust-core-webgpu-master-plan.md => 2026-07-10-rust-core-plan.md} (52%) create mode 100644 docs/plans/2026-07-10-webgpu-graph-surface-plan.md diff --git a/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md b/docs/plans/2026-07-10-rust-core-plan.md similarity index 52% rename from docs/plans/2026-07-10-rust-core-webgpu-master-plan.md rename to docs/plans/2026-07-10-rust-core-plan.md index e6ae0b956..14547eea0 100644 --- a/docs/plans/2026-07-10-rust-core-webgpu-master-plan.md +++ b/docs/plans/2026-07-10-rust-core-plan.md @@ -1,279 +1,73 @@ -# CodeGraphy Rust Core + WebGPU Master Plan +# CodeGraphy Rust Core Plan -> **For agentic workers:** This is the umbrella/handoff plan for the whole -> program. Each phase below is too large for a single session; when starting a -> phase, write a detailed task-level implementation plan for that phase first +> **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. Execute via -> superpowers:subagent-driven-development or superpowers:executing-plans. +> 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`) serving CLI/MCP/VS Code, and replace the -`react-force-graph` surface with a CodeGraphy-owned WebGPU 2D renderer plus a -custom typed-array physics engine — keeping every user-visible graph feature -working throughout. - -**Architecture:** One installed Rust binary (`codegraphy stdio` machine mode, -JSON-RPC control + binary graph frames) owns indexing, SQLite cache, query, -projection/diffs, and the plugin registry. The VS Code extension becomes a -thin protocol client; the webview hosts React UI around a WebGPU renderer and -a headless struct-of-arrays physics engine. One flat binary payload format -flows Rust → extension (opaque) → `postMessage` transfer → GPU buffers. - -**Tech Stack:** Rust (tree-sitter, rusqlite/WAL, wasmtime later), TypeScript -webview (React, WebGPU/WGSL, worker layout), JSON-RPC 2.0 with LSP-style -framing, platform-specific VSIXes. +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. -- [WebGPU renderer research](2026-07-09-webgpu-graph-renderer-research.md) - — renderer/layout seam, migration levels, **Full Replacement Parity Spec** - (§ by subsystem: physics, nodes, links, camera, picking, render loop, DAG), - perf budgets, decided open questions. -- [WebGPU source research](2026-07-09-webgpu-renderer-source-research.md) - — source-backed constraints (VS Code webview/CSP/workers, WebGPU support), - precedent library notes, renderer module shape. + — 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) -- **No d3-force dependency.** Custom engine; d3/Obsidian are study material. -- **3D mode is removed** (`react-force-graph-3d` + Three.js deleted). - **Browser-hosted VS Code (vscode.dev) unsupported**; show a clear message. Remote SSH/WSL/containers **are** supported (binary runs on remote host). -- **Target scale:** smooth 60fps pan/zoom/drag at 100k nodes / 300k edges; - usable with degraded decoration at 500k edges. -- **End state removes `react-force-graph`**; the adapter is a migration rail. - Payload rule: **the extension host never parses graph payloads** — binary frames pass through untouched via `postMessage` ArrayBuffer transfer. -- Plugins never get raw WebGPU/renderer access; UI via React/DOM slots, data - via core. -- WebGPU is runtime-probed; a Canvas fallback path must always exist until - the deprecation checkpoint (C3) says otherwise. +- 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. -## Program Shape - -Two parallel tracks that meet in a convergence phase: +## Phase Sequence ```text -Track A (webview): A0 baseline → A1 seam → A2 remove 3D → A3 physics engine - → A4 WebGPU MVP → A5 parity → A6 scale/LOD → A7 remove RFG -Track B (core): B0 protocol skeleton → B1 indexer MVP → B2 concurrency - → B3 query/projection/diffs → B4 extension sidecar - → B5 wasm plugin host → B6 CLI/MCP surfaces -Convergence: C1 binary path end-to-end → C2 full-loop perf → C3 cleanup +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 ``` -Tracks A and B are independent until C1. A0/A1 and B0/B1 can start today, in -parallel. Within a track, phases are sequential. +B0/B1 can start today, in parallel with the sibling plan's A0/A1. --- -## Track A — Graph Surface - -### A0. Benchmark Baseline And Fixture Harness - -Nothing else in Track A can be validated without this. - -**Deliverables** - -- Synthetic fixture graph generators (seeded): 1k/10k/50k/100k nodes with - realistic degree distribution (power-law-ish, like import graphs), plus 2–3 - real-repo snapshots exported from the current extension. -- A benchmark harness that loads the built webview standalone (reuse the - existing screenshot harness pattern: build webview, serve over HTTP, mock - `acquireVsCodeApi`, inject `GRAPH_DATA_UPDATED`) and records: FPS during - scripted pan/zoom, time-to-settle after load, frame time percentiles, - interaction latency (hover hit-test), and heap usage. -- A committed baseline report for the current react-force-graph renderer at - each fixture size. - -**Checkpoints (deterministic)** - -- [ ] `pnpm bench:graph --fixture 10k --renderer current` produces a JSON - report with fps/settle/latency fields; running twice with the same seed - produces node counts and identical fixture hashes. -- [ ] Baseline report committed at `docs/plans/benchmarks/baseline-.json` - for 1k/10k/50k (100k allowed to fail/crawl on current renderer — record - that fact; it is the motivation). - -### A1. Renderer/Layout Seam - -Introduce the interfaces from the research docs; wrap the current library. - -**Deliverables** - -- `GraphRenderer` and `GraphLayout` interfaces (shape per the research doc's - "Target Interface Shape" / "Proposed Renderer Module Shape" sections). -- `ReactForceGraphAdapter` implementing both, wired where `Surface2d` + - physics runtime are used today (`rendering/surface/`, `runtime/physics/`). -- Callers (interaction runtime, fit controls, accessibility, debug snapshot, - marquee, viewport pan) talk only to the interfaces. - -**Checkpoints** - -- [ ] Full existing test suite and acceptance scenarios pass **unchanged**. -- [ ] Debug snapshot protocol output for a seeded fixture is identical - before/after the seam (byte-diff the snapshot JSON). -- [ ] `grep -r "react-force-graph" packages/extension/src/webview` matches - only inside the adapter directory. - -### A2. Remove 3D Mode And Timeline View - -Both are product removals (owner decision): 3D is a gimmick next to the 2D -graph's navigation value, and the timeline view is being cut entirely. - -**Deliverables** - -- 3D: delete `threeDimensional.tsx`, the 2D/3D mode toggle setting + UI, 3D - branches (`graphMode: '3d'` paths), `react-force-graph-3d`, - `three-spritetext` deps, and 3D test mocks. -- Timeline: delete the timeline view/UI, `ITimelineData` and the - `TIMELINE_DATA` protocol message, timeline contracts - (`shared/timeline/`), the timeline cooldown branch - (`TIMELINE_COOLDOWN_TICKS` / `timelineActive`), timeline acceptance - scenarios, and any extension-side timeline data computation. -- Settings migration: persisted `3d` mode value falls back to `2d` silently; - stale timeline settings/state are ignored without error. - -**Checkpoints** - -- [ ] `grep -ri "force-graph-3d\|three-spritetext" packages/extension/package.json` - → no matches; `pnpm build:webview` bundle size recorded and reduced vs - A1. -- [ ] `grep -rin "timeline" packages/extension/src` → no matches (or only - deliberate migration-shim lines, listed in the PR). -- [ ] Acceptance suite green; a stored `3d` settings value loads as 2D - without error (regression test exists). - -### A3. Custom Physics Engine - -Headless typed-array engine per the parity spec §1. No renderer coupling. - -**Deliverables** - -- New package `packages/graph-engine` (pure TS, zero DOM imports): - - state: `Float32Array` positions/velocities, `Uint32Array` edge endpoints, - `Uint8Array` flags (pinned/hidden); - - forces: grid- or quadtree-based repulsion (Barnes-Hut on CPU), link - springs with degree bias, center/centroid gravity, iterative collision; - - integrator with temperature (alpha) annealing, damping, reheat, jiggle; - - lifecycle: `tick(dt)`, warmup/cooldown, stabilization callback, seeded - deterministic mode; - - custom-force API replacing the object-based plugin force adapters - (positions in, velocity deltas out, temperature-scaled). -- Worker host wrapper (single-file bundle, blob-URL loading per VS Code - webview worker rules) — may land in A6 if A4 needs to start sooner. -- Tuning bench: side-by-side run against the old engine on fixtures. - -**Checkpoints** - -- [ ] Determinism: same seed + fixed timestep ⇒ identical position buffer - hash across two runs (unit test). -- [ ] Stability: 10k-node fixture with 100 coincident nodes settles with no - NaN/Inf and bounded energy (unit test asserts). -- [ ] Settle time on 10k fixture ≤ current engine's baseline from A0. -- [ ] Blind feel check: owner cannot reliably distinguish old vs new engine - on the 1k fixture (or prefers the new one) — recorded in the PR. -- [ ] Plugin-force API: existing graph-view force contributions re-expressed - on the new API in a spike, behavior verified on their fixture. - -### A4. WebGPU Renderer MVP - -Behind the seam, feature-detected, Canvas fallback intact. - -**Deliverables** - -- `WebGpuGraphRenderer`: device/adapter acquisition with loss recovery; - owned 2D camera (zoom/centerAt/zoomToFit/min-max clamp/coords conversion, - tweened); instanced SDF node bodies with selection/hover/mute states; - instanced link strips with bezier curvature; CPU spatial-index picking - (node + link + marquee range query); pointer state machine (hover, click - vs drag threshold, right-click, background events, drag with pin+reheat); - damage-model render loop (draw only on tick/camera/interaction/animation). -- Feature detection: `navigator.gpu` probe → fall back to the A1 adapter. - -**Checkpoints** - -- [ ] With WebGPU force-disabled (probe stubbed false), the app runs the - fallback adapter — acceptance suite green in that mode. -- [ ] Screenshot diff harness: seeded 1k fixture at 3 zoom levels renders - within an agreed pixel-diff threshold of the fallback (layout pinned to - identical positions for the comparison). -- [ ] Bench: 50k fixture ≥ 60fps scripted pan/zoom on the dev machine; - hit-test < 16ms (from A0 harness, `--renderer webgpu`). -- [ ] Hit-test parity: table of (fixture, screen point) → picked id matches - the fallback renderer on 20 sampled points (automated). - -### A5. Full Feature Parity - -Work through the parity spec §2–§8 until the WebGPU renderer is the default. - -**Deliverables** - -- Labels: top-N visible Canvas2D/DOM overlay with zoom-gated fade (MSDF atlas - only if the overlay proves insufficient); icons via texture atlas with LOD - cutoff; collapse indicators/badges layer; directional arrows and - GPU-animated particles on curved links; `onRenderFramePost`-equivalent - overlay pass (marquee rect); accessibility projector, tooltips, and debug - snapshot fed from the renderer's position/camera data; DAG modes ported - (decided): all current directions (td/bu/lr/rl/radial) implemented as - constraint forces in the custom engine — per-node depth is computed from - the directed graph (cycles broken deterministically, matching current - behavior), and a positional force pulls each node toward its - depth × `dagLevelDistance` coordinate on the mode's axis. -- WebGPU renderer becomes default-on where supported; fallback demoted to - probe-failure path. - -**Checkpoints** - -- [ ] Full acceptance suite green with WebGPU as default renderer. -- [ ] DAG modes: each direction renders a layered/radial layout on a seeded - DAG fixture; a cyclic fixture degrades deterministically without error - (matching current behavior). -- [ ] Parity checklist derived from the parity spec §2–§7 completed in the - phase PR (every line: done / explicitly dropped with reason). -- [ ] Platform matrix run recorded: macOS (Metal), Windows (D3D12), Linux - (may fall back — must do so cleanly), device-loss recovery (suspend or - `device.destroy()` test) — manual checklist in the PR. - -### A6. Scale And LOD - -**Deliverables** - -- Worker-hosted layout (if not landed in A3); dense-graph mode hiding in - order: particles → icons → labels (keep hovered/selected) → arrows → - low-weight edges; viewport culling where measurable; frame budgeting. - -**Checkpoints** - -- [ ] Committed targets met on bench harness: 60fps pan/zoom/drag at 100k - nodes / 300k edges; usable at 500k edges; results committed to - `docs/plans/benchmarks/`. -- [ ] Main thread: no frame > 33ms from layout work during interactive - simulation on the 100k fixture (trace recorded). - -### A7. Remove react-force-graph - -**Checkpoints** - -- [ ] `react-force-graph-2d` (and `d3-force`, if now unused) gone from - `package.json`; adapter directory deleted; suite green; bundle size - recorded. - ---- - -## Track B — Rust Core - ### B0. Protocol Spec + Skeleton **Deliverables** -- Crates: `codegraphy-cli` (binary), `codegraphy-core`, `codegraphy-protocol`. +- 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 @@ -345,17 +139,13 @@ Work through the parity spec §2–§8 until the WebGPU renderer is the default. 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. -- Visibility boundary (decided): 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 applied to - core data before rendering — the core schema and protocol must not grow - collapse state. - 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 stay extension/webview-side. + purely visual settings (including collapse state) stay + extension/webview-side. **Checkpoints** @@ -427,12 +217,12 @@ Work through the parity spec §2–§8 until the WebGPU renderer is the default. --- -## Convergence +## 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 -A4/A5's GPU buffers. +the WebGPU renderer's GPU buffers. **Checkpoints** @@ -446,29 +236,23 @@ A4/A5's GPU buffers. ### C2. Full-Loop Performance Validation -- [ ] The A0 harness, now driven by the real core on real repos, meets every - committed budget (60fps at 100k/300k; index 5× baseline; interactive - force sliders reflected next frame). Results committed as the program's - exit report. +- [ ] 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 (A7 done); docs updated; decision on - keeping the Canvas fallback long-term recorded (based on A5 platform - matrix + telemetry if available). + react-force-graph adapter deleted (sibling plan A7 done); docs updated; + decision on keeping the Canvas fallback long-term recorded. --- ## Shipping And Release Plan -Phases ship to users continuously; nothing waits for the end state. - ### Rollout Gates -- Track A phases ship behind a renderer setting: `experimental` (opt-in, - A4) → `default-on with fallback setting` (A5) → `only` (A7). The old - renderer is never broken while it is reachable. - 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. @@ -522,9 +306,6 @@ depends on them. packaging step. - The differential harness (B1) runs on every release build until the old TS core is deleted (C3); a regression blocks release. -- A4 adds a WebGPU bench job if CI GPU runners are available; otherwise the - bench is a documented pre-release local step with results committed to - `docs/plans/benchmarks/`. ### Rust Setup Walkthrough (for a Rust newcomer) @@ -624,18 +405,13 @@ 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 (watch these; each has a checkpoint above) +## Risk Register -1. **Physics feel** (A3) — highest subjective risk; mitigated by A0 baseline, - side-by-side tuning bench, blind feel check, and keeping the adapter rail - until A7. -2. **WebGPU availability on Linux/VMs** (A4/A5) — fallback is a permanent - requirement until C3 says otherwise. -3. **Fact parity of the Rust indexer** (B1/B5) — the differential harness is +1. **Fact parity of the Rust indexer** (B1/B5) — the differential harness is the safety net; no phase advances with untriaged discrepancies. -4. **Payload path regressions** (C1) — any hop that reintroduces per-node JS +2. **Payload path regressions** (C1) — any hop that reintroduces per-node JS objects silently erases the win; the zero-parse checksum test guards it. -5. **Scope creep in plugin host** (B5) — WIT surface starts minimal +3. **Scope creep in plugin host** (B5) — WIT surface starts minimal (parse-once queries + facts + UI descriptors); anything more is a new decision. @@ -647,7 +423,7 @@ idiomatic way to read Rust library documentation. 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 relevant research 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 - program's single source of progress truth. +- 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. diff --git a/docs/plans/2026-07-10-webgpu-graph-surface-plan.md b/docs/plans/2026-07-10-webgpu-graph-surface-plan.md new file mode 100644 index 000000000..6c3dc1880 --- /dev/null +++ b/docs/plans/2026-07-10-webgpu-graph-surface-plan.md @@ -0,0 +1,289 @@ +# CodeGraphy WebGPU Graph Surface Plan + +> **For agentic workers:** This is the umbrella/handoff plan for the graph +> surface 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: [Rust core plan](2026-07-10-rust-core-plan.md) (Track B + +Convergence). The tracks are independent until that plan's C1 phase wires the +core's binary projections into this plan's renderer. + +**Goal:** Replace the `react-force-graph` surface with a CodeGraphy-owned +WebGPU 2D renderer plus a custom typed-array physics engine — keeping every +user-visible graph feature working throughout, and removing the 3D and +timeline views along the way. + +**Architecture:** The webview hosts React UI around a `GraphRenderer` / +`GraphLayout` seam. Behind it: a WebGPU renderer (instanced SDF nodes, bezier +link strips, CPU spatial-index picking, owned 2D camera) and a headless +struct-of-arrays physics engine (no d3-force), designed so the same buffers +later accept binary projections from the Rust core and a WGSL compute port of +the same physics passes. + +**Tech Stack:** TypeScript, React, WebGPU/WGSL, web worker layout, Canvas +fallback via the react-force-graph adapter during migration. + +## Source Documents (read these first) + +- [WebGPU renderer research](2026-07-09-webgpu-graph-renderer-research.md) + — renderer/layout seam, migration levels, **Full Replacement Parity Spec** + (§ by subsystem: physics, nodes, links, camera, picking, render loop, DAG), + perf budgets, decided open questions. +- [WebGPU source research](2026-07-09-webgpu-renderer-source-research.md) + — source-backed constraints (VS Code webview/CSP/workers, WebGPU support), + precedent library notes, renderer module shape. + +## Global Constraints (locked decisions — do not relitigate) + +- **No d3-force dependency.** Custom engine; d3/Obsidian are study material. +- **3D mode is removed** (`react-force-graph-3d` + Three.js deleted). +- **The timeline view is removed entirely** (view, protocol message, + contracts, physics branch). +- **Target scale:** smooth 60fps pan/zoom/drag at 100k nodes / 300k edges; + usable with degraded decoration at 500k edges. +- **End state removes `react-force-graph`**; the adapter is a migration rail. +- Collapse/folder-view grouping is an extension/webview feature applied to + core data before rendering (the core never learns view visibility). +- Plugins never get raw WebGPU/renderer access; UI via React/DOM slots, data + via core. +- WebGPU is runtime-probed; a Canvas fallback path must always exist until + the sibling plan's C3 deprecation checkpoint says otherwise. +- 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 +A0 baseline → A1 seam → A2 remove 3D + timeline → A3 physics engine +→ A4 WebGPU MVP → A5 parity → A6 scale/LOD → A7 remove react-force-graph +``` + +A0/A1 can start today, in parallel with the sibling plan's B0/B1. This track +has no dependency on the Rust core; it runs entirely against the current +extension data layer until the sibling plan's C1. + +### Rollout Gates + +Phases ship to users continuously behind a renderer setting: `experimental` +(opt-in, A4) → `default-on with fallback setting` (A5) → `only` (A7). The old +renderer is never broken while it is reachable. A4 adds a WebGPU bench job to +CI if GPU runners are available; otherwise the bench is a documented +pre-release local step with results committed to `docs/plans/benchmarks/`. + +--- + +### A0. Benchmark Baseline And Fixture Harness + +Nothing else in this track can be validated without this. + +**Deliverables** + +- Synthetic fixture graph generators (seeded): 1k/10k/50k/100k nodes with + realistic degree distribution (power-law-ish, like import graphs), plus 2–3 + real-repo snapshots exported from the current extension. +- A benchmark harness that loads the built webview standalone (reuse the + existing screenshot harness pattern: build webview, serve over HTTP, mock + `acquireVsCodeApi`, inject `GRAPH_DATA_UPDATED`) and records: FPS during + scripted pan/zoom, time-to-settle after load, frame time percentiles, + interaction latency (hover hit-test), and heap usage. +- A committed baseline report for the current react-force-graph renderer at + each fixture size. + +**Checkpoints (deterministic)** + +- [ ] `pnpm bench:graph --fixture 10k --renderer current` produces a JSON + report with fps/settle/latency fields; running twice with the same seed + produces node counts and identical fixture hashes. +- [ ] Baseline report committed at `docs/plans/benchmarks/baseline-.json` + for 1k/10k/50k (100k allowed to fail/crawl on current renderer — record + that fact; it is the motivation). + +### A1. Renderer/Layout Seam + +Introduce the interfaces from the research docs; wrap the current library. + +**Deliverables** + +- `GraphRenderer` and `GraphLayout` interfaces (shape per the research doc's + "Target Interface Shape" / "Proposed Renderer Module Shape" sections). +- `ReactForceGraphAdapter` implementing both, wired where `Surface2d` + + physics runtime are used today (`rendering/surface/`, `runtime/physics/`). +- Callers (interaction runtime, fit controls, accessibility, debug snapshot, + marquee, viewport pan) talk only to the interfaces. + +**Checkpoints** + +- [ ] Full existing test suite and acceptance scenarios pass **unchanged**. +- [ ] Debug snapshot protocol output for a seeded fixture is identical + before/after the seam (byte-diff the snapshot JSON). +- [ ] `grep -r "react-force-graph" packages/extension/src/webview` matches + only inside the adapter directory. + +### A2. Remove 3D Mode And Timeline View + +Both are product removals (owner decision): 3D is a gimmick next to the 2D +graph's navigation value, and the timeline view is being cut entirely. + +**Deliverables** + +- 3D: delete `threeDimensional.tsx`, the 2D/3D mode toggle setting + UI, 3D + branches (`graphMode: '3d'` paths), `react-force-graph-3d`, + `three-spritetext` deps, and 3D test mocks. +- Timeline: delete the timeline view/UI, `ITimelineData` and the + `TIMELINE_DATA` protocol message, timeline contracts + (`shared/timeline/`), the timeline cooldown branch + (`TIMELINE_COOLDOWN_TICKS` / `timelineActive`), timeline acceptance + scenarios, and any extension-side timeline data computation. +- Settings migration: persisted `3d` mode value falls back to `2d` silently; + stale timeline settings/state are ignored without error. + +**Checkpoints** + +- [ ] `grep -ri "force-graph-3d\|three-spritetext" packages/extension/package.json` + → no matches; `pnpm build:webview` bundle size recorded and reduced vs + A1. +- [ ] `grep -rin "timeline" packages/extension/src` → no matches (or only + deliberate migration-shim lines, listed in the PR). +- [ ] Acceptance suite green; a stored `3d` settings value loads as 2D + without error (regression test exists). + +### A3. Custom Physics Engine + +Headless typed-array engine per the parity spec §1. No renderer coupling. + +**Deliverables** + +- New package `packages/graph-engine` (pure TS, zero DOM imports): + - state: `Float32Array` positions/velocities, `Uint32Array` edge endpoints, + `Uint8Array` flags (pinned/hidden); + - forces: grid- or quadtree-based repulsion (Barnes-Hut on CPU), link + springs with degree bias, center/centroid gravity, iterative collision; + - integrator with temperature (alpha) annealing, damping, reheat, jiggle; + - lifecycle: `tick(dt)`, warmup/cooldown, stabilization callback, seeded + deterministic mode; + - custom-force API replacing the object-based plugin force adapters + (positions in, velocity deltas out, temperature-scaled). +- Worker host wrapper (single-file bundle, blob-URL loading per VS Code + webview worker rules) — may land in A6 if A4 needs to start sooner. +- Tuning bench: side-by-side run against the old engine on fixtures. + +**Checkpoints** + +- [ ] Determinism: same seed + fixed timestep ⇒ identical position buffer + hash across two runs (unit test). +- [ ] Stability: 10k-node fixture with 100 coincident nodes settles with no + NaN/Inf and bounded energy (unit test asserts). +- [ ] Settle time on 10k fixture ≤ current engine's baseline from A0. +- [ ] Blind feel check: owner cannot reliably distinguish old vs new engine + on the 1k fixture (or prefers the new one) — recorded in the PR. +- [ ] Plugin-force API: existing graph-view force contributions re-expressed + on the new API in a spike, behavior verified on their fixture. + +### A4. WebGPU Renderer MVP + +Behind the seam, feature-detected, Canvas fallback intact. + +**Deliverables** + +- `WebGpuGraphRenderer`: device/adapter acquisition with loss recovery; + owned 2D camera (zoom/centerAt/zoomToFit/min-max clamp/coords conversion, + tweened); instanced SDF node bodies with selection/hover/mute states; + instanced link strips with bezier curvature; CPU spatial-index picking + (node + link + marquee range query); pointer state machine (hover, click + vs drag threshold, right-click, background events, drag with pin+reheat); + damage-model render loop (draw only on tick/camera/interaction/animation). +- Feature detection: `navigator.gpu` probe → fall back to the A1 adapter. + +**Checkpoints** + +- [ ] With WebGPU force-disabled (probe stubbed false), the app runs the + fallback adapter — acceptance suite green in that mode. +- [ ] Screenshot diff harness: seeded 1k fixture at 3 zoom levels renders + within an agreed pixel-diff threshold of the fallback (layout pinned to + identical positions for the comparison). +- [ ] Bench: 50k fixture ≥ 60fps scripted pan/zoom on the dev machine; + hit-test < 16ms (from A0 harness, `--renderer webgpu`). +- [ ] Hit-test parity: table of (fixture, screen point) → picked id matches + the fallback renderer on 20 sampled points (automated). + +### A5. Full Feature Parity + +Work through the parity spec §2–§8 until the WebGPU renderer is the default. + +**Deliverables** + +- Labels: top-N visible Canvas2D/DOM overlay with zoom-gated fade (MSDF atlas + only if the overlay proves insufficient); icons via texture atlas with LOD + cutoff; collapse indicators/badges layer; directional arrows and + GPU-animated particles on curved links; `onRenderFramePost`-equivalent + overlay pass (marquee rect); accessibility projector, tooltips, and debug + snapshot fed from the renderer's position/camera data; DAG modes ported + (decided): all current directions (td/bu/lr/rl/radial) implemented as + constraint forces in the custom engine — per-node depth is computed from + the directed graph (cycles broken deterministically, matching current + behavior), and a positional force pulls each node toward its + depth × `dagLevelDistance` coordinate on the mode's axis. +- WebGPU renderer becomes default-on where supported; fallback demoted to + probe-failure path. + +**Checkpoints** + +- [ ] Full acceptance suite green with WebGPU as default renderer. +- [ ] DAG modes: each direction renders a layered/radial layout on a seeded + DAG fixture; a cyclic fixture degrades deterministically without error + (matching current behavior). +- [ ] Parity checklist derived from the parity spec §2–§7 completed in the + phase PR (every line: done / explicitly dropped with reason). +- [ ] Platform matrix run recorded: macOS (Metal), Windows (D3D12), Linux + (may fall back — must do so cleanly), device-loss recovery (suspend or + `device.destroy()` test) — manual checklist in the PR. + +### A6. Scale And LOD + +**Deliverables** + +- Worker-hosted layout (if not landed in A3); dense-graph mode hiding in + order: particles → icons → labels (keep hovered/selected) → arrows → + low-weight edges; viewport culling where measurable; frame budgeting. + +**Checkpoints** + +- [ ] Committed targets met on bench harness: 60fps pan/zoom/drag at 100k + nodes / 300k edges; usable at 500k edges; results committed to + `docs/plans/benchmarks/`. +- [ ] Main thread: no frame > 33ms from layout work during interactive + simulation on the 100k fixture (trace recorded). + +### A7. Remove react-force-graph + +**Checkpoints** + +- [ ] `react-force-graph-2d` (and `d3-force`, if now unused) gone from + `package.json`; adapter directory deleted; suite green; bundle size + recorded. + +--- + +## Risk Register + +1. **Physics feel** (A3) — highest subjective risk; mitigated by A0 baseline, + side-by-side tuning bench, blind feel check, and keeping the adapter rail + until A7. +2. **WebGPU availability on Linux/VMs** (A4/A5) — fallback is a permanent + requirement until the sibling plan's C3 says otherwise. + +## 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 relevant research 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 + graph surface track's single source of progress truth. From 18a08efa72e4b261250604461f5060c82337c3f6 Mon Sep 17 00:00:00 2001 From: joesobo Date: Fri, 10 Jul 2026 09:09:56 -0700 Subject: [PATCH 10/10] docs: move webgpu graph surface plan to its own PR Co-Authored-By: Claude Fable 5 --- .../2026-07-10-webgpu-graph-surface-plan.md | 289 ------------------ 1 file changed, 289 deletions(-) delete mode 100644 docs/plans/2026-07-10-webgpu-graph-surface-plan.md diff --git a/docs/plans/2026-07-10-webgpu-graph-surface-plan.md b/docs/plans/2026-07-10-webgpu-graph-surface-plan.md deleted file mode 100644 index 6c3dc1880..000000000 --- a/docs/plans/2026-07-10-webgpu-graph-surface-plan.md +++ /dev/null @@ -1,289 +0,0 @@ -# CodeGraphy WebGPU Graph Surface Plan - -> **For agentic workers:** This is the umbrella/handoff plan for the graph -> surface 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: [Rust core plan](2026-07-10-rust-core-plan.md) (Track B + -Convergence). The tracks are independent until that plan's C1 phase wires the -core's binary projections into this plan's renderer. - -**Goal:** Replace the `react-force-graph` surface with a CodeGraphy-owned -WebGPU 2D renderer plus a custom typed-array physics engine — keeping every -user-visible graph feature working throughout, and removing the 3D and -timeline views along the way. - -**Architecture:** The webview hosts React UI around a `GraphRenderer` / -`GraphLayout` seam. Behind it: a WebGPU renderer (instanced SDF nodes, bezier -link strips, CPU spatial-index picking, owned 2D camera) and a headless -struct-of-arrays physics engine (no d3-force), designed so the same buffers -later accept binary projections from the Rust core and a WGSL compute port of -the same physics passes. - -**Tech Stack:** TypeScript, React, WebGPU/WGSL, web worker layout, Canvas -fallback via the react-force-graph adapter during migration. - -## Source Documents (read these first) - -- [WebGPU renderer research](2026-07-09-webgpu-graph-renderer-research.md) - — renderer/layout seam, migration levels, **Full Replacement Parity Spec** - (§ by subsystem: physics, nodes, links, camera, picking, render loop, DAG), - perf budgets, decided open questions. -- [WebGPU source research](2026-07-09-webgpu-renderer-source-research.md) - — source-backed constraints (VS Code webview/CSP/workers, WebGPU support), - precedent library notes, renderer module shape. - -## Global Constraints (locked decisions — do not relitigate) - -- **No d3-force dependency.** Custom engine; d3/Obsidian are study material. -- **3D mode is removed** (`react-force-graph-3d` + Three.js deleted). -- **The timeline view is removed entirely** (view, protocol message, - contracts, physics branch). -- **Target scale:** smooth 60fps pan/zoom/drag at 100k nodes / 300k edges; - usable with degraded decoration at 500k edges. -- **End state removes `react-force-graph`**; the adapter is a migration rail. -- Collapse/folder-view grouping is an extension/webview feature applied to - core data before rendering (the core never learns view visibility). -- Plugins never get raw WebGPU/renderer access; UI via React/DOM slots, data - via core. -- WebGPU is runtime-probed; a Canvas fallback path must always exist until - the sibling plan's C3 deprecation checkpoint says otherwise. -- 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 -A0 baseline → A1 seam → A2 remove 3D + timeline → A3 physics engine -→ A4 WebGPU MVP → A5 parity → A6 scale/LOD → A7 remove react-force-graph -``` - -A0/A1 can start today, in parallel with the sibling plan's B0/B1. This track -has no dependency on the Rust core; it runs entirely against the current -extension data layer until the sibling plan's C1. - -### Rollout Gates - -Phases ship to users continuously behind a renderer setting: `experimental` -(opt-in, A4) → `default-on with fallback setting` (A5) → `only` (A7). The old -renderer is never broken while it is reachable. A4 adds a WebGPU bench job to -CI if GPU runners are available; otherwise the bench is a documented -pre-release local step with results committed to `docs/plans/benchmarks/`. - ---- - -### A0. Benchmark Baseline And Fixture Harness - -Nothing else in this track can be validated without this. - -**Deliverables** - -- Synthetic fixture graph generators (seeded): 1k/10k/50k/100k nodes with - realistic degree distribution (power-law-ish, like import graphs), plus 2–3 - real-repo snapshots exported from the current extension. -- A benchmark harness that loads the built webview standalone (reuse the - existing screenshot harness pattern: build webview, serve over HTTP, mock - `acquireVsCodeApi`, inject `GRAPH_DATA_UPDATED`) and records: FPS during - scripted pan/zoom, time-to-settle after load, frame time percentiles, - interaction latency (hover hit-test), and heap usage. -- A committed baseline report for the current react-force-graph renderer at - each fixture size. - -**Checkpoints (deterministic)** - -- [ ] `pnpm bench:graph --fixture 10k --renderer current` produces a JSON - report with fps/settle/latency fields; running twice with the same seed - produces node counts and identical fixture hashes. -- [ ] Baseline report committed at `docs/plans/benchmarks/baseline-.json` - for 1k/10k/50k (100k allowed to fail/crawl on current renderer — record - that fact; it is the motivation). - -### A1. Renderer/Layout Seam - -Introduce the interfaces from the research docs; wrap the current library. - -**Deliverables** - -- `GraphRenderer` and `GraphLayout` interfaces (shape per the research doc's - "Target Interface Shape" / "Proposed Renderer Module Shape" sections). -- `ReactForceGraphAdapter` implementing both, wired where `Surface2d` + - physics runtime are used today (`rendering/surface/`, `runtime/physics/`). -- Callers (interaction runtime, fit controls, accessibility, debug snapshot, - marquee, viewport pan) talk only to the interfaces. - -**Checkpoints** - -- [ ] Full existing test suite and acceptance scenarios pass **unchanged**. -- [ ] Debug snapshot protocol output for a seeded fixture is identical - before/after the seam (byte-diff the snapshot JSON). -- [ ] `grep -r "react-force-graph" packages/extension/src/webview` matches - only inside the adapter directory. - -### A2. Remove 3D Mode And Timeline View - -Both are product removals (owner decision): 3D is a gimmick next to the 2D -graph's navigation value, and the timeline view is being cut entirely. - -**Deliverables** - -- 3D: delete `threeDimensional.tsx`, the 2D/3D mode toggle setting + UI, 3D - branches (`graphMode: '3d'` paths), `react-force-graph-3d`, - `three-spritetext` deps, and 3D test mocks. -- Timeline: delete the timeline view/UI, `ITimelineData` and the - `TIMELINE_DATA` protocol message, timeline contracts - (`shared/timeline/`), the timeline cooldown branch - (`TIMELINE_COOLDOWN_TICKS` / `timelineActive`), timeline acceptance - scenarios, and any extension-side timeline data computation. -- Settings migration: persisted `3d` mode value falls back to `2d` silently; - stale timeline settings/state are ignored without error. - -**Checkpoints** - -- [ ] `grep -ri "force-graph-3d\|three-spritetext" packages/extension/package.json` - → no matches; `pnpm build:webview` bundle size recorded and reduced vs - A1. -- [ ] `grep -rin "timeline" packages/extension/src` → no matches (or only - deliberate migration-shim lines, listed in the PR). -- [ ] Acceptance suite green; a stored `3d` settings value loads as 2D - without error (regression test exists). - -### A3. Custom Physics Engine - -Headless typed-array engine per the parity spec §1. No renderer coupling. - -**Deliverables** - -- New package `packages/graph-engine` (pure TS, zero DOM imports): - - state: `Float32Array` positions/velocities, `Uint32Array` edge endpoints, - `Uint8Array` flags (pinned/hidden); - - forces: grid- or quadtree-based repulsion (Barnes-Hut on CPU), link - springs with degree bias, center/centroid gravity, iterative collision; - - integrator with temperature (alpha) annealing, damping, reheat, jiggle; - - lifecycle: `tick(dt)`, warmup/cooldown, stabilization callback, seeded - deterministic mode; - - custom-force API replacing the object-based plugin force adapters - (positions in, velocity deltas out, temperature-scaled). -- Worker host wrapper (single-file bundle, blob-URL loading per VS Code - webview worker rules) — may land in A6 if A4 needs to start sooner. -- Tuning bench: side-by-side run against the old engine on fixtures. - -**Checkpoints** - -- [ ] Determinism: same seed + fixed timestep ⇒ identical position buffer - hash across two runs (unit test). -- [ ] Stability: 10k-node fixture with 100 coincident nodes settles with no - NaN/Inf and bounded energy (unit test asserts). -- [ ] Settle time on 10k fixture ≤ current engine's baseline from A0. -- [ ] Blind feel check: owner cannot reliably distinguish old vs new engine - on the 1k fixture (or prefers the new one) — recorded in the PR. -- [ ] Plugin-force API: existing graph-view force contributions re-expressed - on the new API in a spike, behavior verified on their fixture. - -### A4. WebGPU Renderer MVP - -Behind the seam, feature-detected, Canvas fallback intact. - -**Deliverables** - -- `WebGpuGraphRenderer`: device/adapter acquisition with loss recovery; - owned 2D camera (zoom/centerAt/zoomToFit/min-max clamp/coords conversion, - tweened); instanced SDF node bodies with selection/hover/mute states; - instanced link strips with bezier curvature; CPU spatial-index picking - (node + link + marquee range query); pointer state machine (hover, click - vs drag threshold, right-click, background events, drag with pin+reheat); - damage-model render loop (draw only on tick/camera/interaction/animation). -- Feature detection: `navigator.gpu` probe → fall back to the A1 adapter. - -**Checkpoints** - -- [ ] With WebGPU force-disabled (probe stubbed false), the app runs the - fallback adapter — acceptance suite green in that mode. -- [ ] Screenshot diff harness: seeded 1k fixture at 3 zoom levels renders - within an agreed pixel-diff threshold of the fallback (layout pinned to - identical positions for the comparison). -- [ ] Bench: 50k fixture ≥ 60fps scripted pan/zoom on the dev machine; - hit-test < 16ms (from A0 harness, `--renderer webgpu`). -- [ ] Hit-test parity: table of (fixture, screen point) → picked id matches - the fallback renderer on 20 sampled points (automated). - -### A5. Full Feature Parity - -Work through the parity spec §2–§8 until the WebGPU renderer is the default. - -**Deliverables** - -- Labels: top-N visible Canvas2D/DOM overlay with zoom-gated fade (MSDF atlas - only if the overlay proves insufficient); icons via texture atlas with LOD - cutoff; collapse indicators/badges layer; directional arrows and - GPU-animated particles on curved links; `onRenderFramePost`-equivalent - overlay pass (marquee rect); accessibility projector, tooltips, and debug - snapshot fed from the renderer's position/camera data; DAG modes ported - (decided): all current directions (td/bu/lr/rl/radial) implemented as - constraint forces in the custom engine — per-node depth is computed from - the directed graph (cycles broken deterministically, matching current - behavior), and a positional force pulls each node toward its - depth × `dagLevelDistance` coordinate on the mode's axis. -- WebGPU renderer becomes default-on where supported; fallback demoted to - probe-failure path. - -**Checkpoints** - -- [ ] Full acceptance suite green with WebGPU as default renderer. -- [ ] DAG modes: each direction renders a layered/radial layout on a seeded - DAG fixture; a cyclic fixture degrades deterministically without error - (matching current behavior). -- [ ] Parity checklist derived from the parity spec §2–§7 completed in the - phase PR (every line: done / explicitly dropped with reason). -- [ ] Platform matrix run recorded: macOS (Metal), Windows (D3D12), Linux - (may fall back — must do so cleanly), device-loss recovery (suspend or - `device.destroy()` test) — manual checklist in the PR. - -### A6. Scale And LOD - -**Deliverables** - -- Worker-hosted layout (if not landed in A3); dense-graph mode hiding in - order: particles → icons → labels (keep hovered/selected) → arrows → - low-weight edges; viewport culling where measurable; frame budgeting. - -**Checkpoints** - -- [ ] Committed targets met on bench harness: 60fps pan/zoom/drag at 100k - nodes / 300k edges; usable at 500k edges; results committed to - `docs/plans/benchmarks/`. -- [ ] Main thread: no frame > 33ms from layout work during interactive - simulation on the 100k fixture (trace recorded). - -### A7. Remove react-force-graph - -**Checkpoints** - -- [ ] `react-force-graph-2d` (and `d3-force`, if now unused) gone from - `package.json`; adapter directory deleted; suite green; bundle size - recorded. - ---- - -## Risk Register - -1. **Physics feel** (A3) — highest subjective risk; mitigated by A0 baseline, - side-by-side tuning bench, blind feel check, and keeping the adapter rail - until A7. -2. **WebGPU availability on Linux/VMs** (A4/A5) — fallback is a permanent - requirement until the sibling plan's C3 says otherwise. - -## 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 relevant research 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 - graph surface track's single source of progress truth.