Space Tab: Behind the Scenes
How we built a real-time 3D codebase visualizer with Rust, WebGPU, and WASM — rendering thousands of file nodes at 60fps inside an Electron overlay.
Space Tab: Behind the Scenes
The Space tab is the most visually striking part of Callipso. It renders your entire codebase as a 3D star field, with AI coding agents appearing as ships that navigate between files as they work. It updates in real time — when Claude Code reads a file, you see the ship move toward it. When it writes a file, the node pulses.
Building this required solving a specific set of problems: high-performance rendering inside an Electron overlay, real-time data flow from IDE adapters, and a rendering pipeline that could handle thousands of nodes at 60fps on a laptop GPU.
This post covers how we built it.
Why Not Three.js?
The obvious choice for 3D in a web context is Three.js. We actually started there during early prototyping. But we hit fundamental limitations:
- Performance ceiling — Three.js abstracts over WebGL, which adds overhead. With thousands of nodes plus ships plus particle effects, we were struggling to maintain 60fps on integrated GPUs.
- Shader control — We needed custom shaders for the engine glow effect, additive mode transitions, and heat accumulation visuals. Three.js's material system is powerful but opinionated — fighting it costs more than working without it.
- Memory — Three.js's scene graph allocates aggressively. In an Electron overlay that runs alongside an IDE and multiple terminal processes, every megabyte matters.
The answer was to go lower: write the renderer in Rust, compile to WebAssembly, and use WebGPU directly.
The Rust/WASM Architecture
The Space tab's rendering core lives in rust-space/, a Cargo project that compiles to WASM via wasm-pack. The key modules are:
lib.rs— The entry point. Exposes theSpaceRendererstruct via#[wasm_bindgen], which owns the WebGPU device, surface, and all render state.entities/spaceship.rs— Ship geometry generation. Thegenerate_ship_geometry()function returns vertex and index buffers with a customShipVertexlayout: position, normal, andpart_id.ship_manager.rs— The ship rendering pipeline, including the WGSL shader. Part IDs in the shader map to different visual treatments: 0 = body, 1 = wing, 2 = cockpit, 3 = engine (with animated glow).codebase_viz.rs— The file node system. Handles layout (force-directed or hierarchical), visibility, and the additive mode where nodes start hidden and reveal as the AI discovers them.
The Bridge Pattern
JavaScript cannot call Rust methods directly. We use a bridge layer — space-wgpu-bridge.js — that wraps the WASM SpaceRenderer with hand-written JavaScript methods. This is deliberate: wasm_bindgen does not auto-expose new methods through a proxy. Every Rust method that the UI needs must have a corresponding bridge wrapper.
// space-wgpu-bridge.js
setAdditiveMode(enabled) {
return this.renderer?.set_additive_mode(enabled);
}
This explicit wrapping has a benefit: it serves as documentation of the public API surface. If a method is not in the bridge, the UI cannot call it.
WebGPU Rendering Pipeline
Each frame follows a standard WebGPU render loop:
- Update uniforms — Camera position, time (for animations), and per-node instance data are written to GPU buffers.
- Render pass — A single render pass draws nodes (instanced quads), ships (indexed meshes), and particle effects.
- Present — The frame is presented to the canvas surface.
The critical optimization is instanced rendering. Instead of issuing a draw call per file node, we pack all node data into a single instance buffer and draw them in one call. A codebase with 2,000 files becomes a single instanced draw of 2,000 quads — the GPU handles this trivially.
The Ship Shader
Ships are more complex than nodes. Each ship has four visual parts, identified by part_id in the vertex data:
if (part_id == 3u) {
// Engine: animated glow based on time uniform
let glow = sin(uniforms.time * 4.0) * 0.3 + 0.7;
color = vec4f(0.3, 0.6, 1.0, 1.0) * glow;
}
The engine glow pulses based on a time uniform, creating a subtle animation without any JavaScript involvement. The GPU handles it entirely.
Real-Time Data Flow
The Space tab needs to know what files exist in the codebase and what the AI is doing with them. This data flows through several layers:
- Worktree scanning — On startup, the codebase visualizer scans the git worktree to build the initial node set. Paths are repo-relative (e.g.,
callipso/src/stateManager.ts). - Tool activity routing — When Claude Code uses a tool (Read, Write, Edit), Callipso's hook system captures the event and routes it to the
ToolActivityRouter. This module maps the file path to a codebase node and triggers the appropriate ship animation. - Ship movement — Ships use a simple interpolation system. When a new target file is identified, the ship's destination is set, and each frame it moves a fraction of the remaining distance. This creates smooth, organic movement.
Additive Mode
Additive mode is a visualization technique where all nodes start fully transparent. As the AI discovers files (by reading or writing them), those nodes fade into visibility. Over time, the visualization reveals the "footprint" of the AI's work — showing exactly which parts of the codebase it touched.
This required coordination between Rust and JavaScript:
- Rust manages node visibility values (0.0 to 1.0) and applies them in the shader.
- JavaScript tracks which files have been discovered via
ToolActivityRouter. - When a file is discovered, JavaScript calls
bridge.onFileDiscovered(path), which sets the node's visibility to 1.0 with a fade-in transition.
A subtle gotcha: when files are loaded (e.g., after a hierarchy mode switch), the Rust side creates new nodes with default visibility: 1.0. If additive mode is active, these new nodes must be reset to 0.0. We handle this by checking the additive_mode flag after every node rebuild.
Performance Results
On a MacBook Pro M2 with the integrated GPU:
| Metric | Value |
|---|---|
| Nodes | 2,500 |
| Ships | 4 |
| Frame time | ~4ms (250fps cap) |
| WASM memory | ~8MB |
| GPU memory | ~12MB |
The Space tab adds virtually zero overhead to the system. Rust's zero-cost abstractions and WebGPU's efficient pipeline make it possible to run a real-time 3D visualizer inside an Electron overlay without impacting IDE performance.
Lessons Learned
- WASM bridge methods are not automatic — Every
#[wasm_bindgen]method needs a hand-written JavaScript wrapper in the bridge. Forgetting this is the most common source of "method not found" bugs. - Lazy-resolve external references — Modules that depend on the WASM bridge should use a getter function instead of storing a reference at init time. WASM loading is async, and init timing is unpredictable.
- Storage creates ghost state — Persisted settings (like additive mode enabled) can conflict with runtime state if the WASM bridge is not ready when the setting is restored. Always account for this.
- Repo-relative paths are the canonical format — Node paths look like
callipso/src/stateManager.ts, not/Users/you/project/callipso/src/stateManager.ts. The tool activity router strips the worktree prefix.
The Space tab started as an experiment — "what if you could see your codebase while the AI works on it?" It has become one of Callipso's most beloved features, and we are just getting started. Additive mode, heat maps, and commit-based animations are all on the roadmap.