{"pages":[{"url":"/","title":"Orbit Code","section":"Home","description":"An open source, live 3D alternative to the Claude Code terminal. Watch every file Claude reads, edits and thinks about, in VS Code or on the desktop.","headings":["Read the view at a glance","One panel for the whole session","Built for how you work","Install or update Orbit Code","Open source, runs on your machine","Questions, answered"],"text":"An open source, live 3D alternative to Claude Code in the terminal. Your repository becomes a map, and you watch Claude read, edit and think across it. For vibecoders and programmers. Files are spheres. Colored by language and sized by the size of the file. Directories are bubbles. Nested inside their parents. Scroll toward one to look inside. Imports are lines. Drawn between whatever holds each end at the level you are looking at. Claude is the star. It flies to each file Claude Code reads or edits, and the camera can follow it. See what Claude touches. No more guessing which files changed. Reads glow cyan and edits pulse amber, right where those files live. Approve with a click. Permission requests arrive as cards with plain choices, so you never have to parse a terminal prompt. Prompt from the panel. Pick a model and effort, attach files or skills, and run several conversations side by side. Take a tour of your code. Let the camera fly through your project on its own and stop at the files that matter most. A real dependency graph. JS and TS through dependency-cruiser, plus Python, Go, Rust, Java, C#, Swift, PHP, Dart and more, resolved through each project’s own manifests. Live, incremental updates. Your edits, Claude’s writes and git checkouts show up within seconds without a full reindex, even in 20,000 file repositories. Your CLI, your settings. Orbit runs your own claude CLI, so CLAUDE.md, hooks, skills, MCP servers and permission rules apply exactly as in a terminal. Open and hackable. MIT licensed, no telemetry, and one TypeScript engine shared by the VS Code extension and the desktop app. Is Orbit Code free? Yes. Orbit Code is open source under the MIT license, and it runs on your own machine. Do I need Claude Code? For the live session, yes. Orbit starts your own claude CLI with your login and settings. Without it you still get the 3D dependency graph. Does it replace the terminal? It is a visual way to use it. The same CLI runs underneath, and Orbit shows every read, edit and thought on a live map of your code, with permissions answered in the panel. Which languages does it understand? JavaScript and TypeScript, Python, Go, Rust, Java, C#, Swift, PHP, Dart and more, with imports resolved through tsconfig, go.mod, Cargo.toml, pyproject.toml and other manifests. Which editors does it work with? VS Code 1.100 or newer, and editors built on it such as Cursor, Windsurf and VSCodium. The desktop app runs on macOS, Windows and Linux without an editor. Does Orbit send my code anywhere? Orbit itself has no telemetry and indexes your code locally. Claude Code sends what it needs to Anthropic, exactly as it does in a terminal. Is Orbit Code made by Anthropic? No. Orbit Code is an independent open source project. Claude and Claude Code are trademarks of Anthropic."},{"url":"/install/","title":"Install and update","section":"Install","description":"Install Orbit Code from the VS Code Marketplace, with one command for Cursor, Windsurf and VSCodium, or from source. Every option shows how to update.","headings":["Orbit Code for VS Code","Cursor, Windsurf and VSCodium","Orbit Code desktop app","Build the extension yourself","The indexer on its own"],"text":"Version 0.2.1 on the Visual Studio Marketplace, updated Sep 14, 2026. Needs VS Code 1.100.0 or newer. Install Claude Code Sign in once in a terminal. Orbit starts your own claude CLI, so skip this if you only want the graph. Install the extension Search for Orbit Code in the Extensions view, open the listing in VS Code, or run this in a terminal. code --install-extension imshaikot.orbit-code Open Orbit Reload the window, then run Orbit Code: Open from the Command Palette or click the Orbit icon in the activity bar. VS Code keeps Marketplace extensions up to date on its own, so there is nothing to do. These editors install from Open VSX, where Orbit Code is not listed yet. The install script puts version 0.2.1 into every one of them it finds. Install Claude Code Sign in once in a terminal. Orbit starts your own claude CLI, so skip this if you only want the graph. Install the extension To install into one editor only, add its name at the end: sh -s -- --editor cursor. curl -fsSL https://orbit-code.imshaikot.com/install.sh | sh irm https://orbit-code.imshaikot.com/install.ps1 | iex Open Orbit Reload the window, then run Orbit Code: Open from the Command Palette or click the Orbit icon in the activity bar. Run the same command again to install the newest version over the old one. A window of its own for any folder, on macOS, Windows and Linux, with no editor needed. There is no packaged build yet, so run it from source for now. Install Claude Code Sign in once in a terminal. Orbit starts your own claude CLI, so skip this if you only want the graph. Clone and install Needs git, Node 22 or newer and Yarn 4 through Corepack. git clone https://github.com/imshaikot/orbit-code.git cd orbit-code && corepack enable && yarn install Open a folder Builds the app and opens your project in it. yarn desktop --folder /path/to/your/project Pull the newest code, then start it again. Build the newest code on the main branch into a .vsix and install it in VS Code. Needs git, Node 22 or newer and Yarn 4 through Corepack. Clone and install git clone https://github.com/imshaikot/orbit-code.git cd orbit-code && corepack enable && yarn install Package and install Builds a production .vsix and installs it with the code command. yarn install-local Open Orbit Reload the window, then run Orbit Code: Open from the Command Palette or click the Orbit icon in the activity bar. Pull, then package and install again. Index any repository into the dependency graph JSON that Orbit draws. It is not on npm yet, so run it from a clone. Clone and build git clone https://github.com/imshaikot/orbit-code.git cd orbit-code && corepack enable && yarn install yarn build Index a folder yarn index /path/to/project --out graph.json Pull and build again."},{"url":"/docs/","title":"Orbit Code documentation","section":"Docs","description":"Documentation for Orbit Code: reading the 3D view, prompting Claude Code from the panel, settings, the desktop app, troubleshooting and how the engine works.","headings":["Get started","Editors and apps","Guides","How it works"],"text":"Using Orbit. Reading the view, navigating, prompting Claude, answering permissions, the file card, the Flat view and the tour. VS Code extension. An open-source, live 3D visual alternative to Claude Code in the terminal, for programmers and vibe coders alike: your codebase as a dependency graph in… Desktop app. Orbit Code as a desktop app: an open-source, live 3D visual alternative to Claude Code in the terminal, for programmers and vibe coders alike, in a window… Settings and commands. Every command and setting the VS Code extension adds, read from its manifest (version 0.2.1). The desktop app reads the same settings from its own… Indexer CLI. Indexes a workspace into the dependency graph Orbit Code draws: files are nodes, imports are edges, and directories group them. Claude in Orbit. How the `claude` CLI is run, conversations, permissions, questions, MCP, skills, files, history and subagents. Troubleshooting. Logs, caches, and the failures with a known cause. Architecture. The three bundles, the services, and how a graph, a layout and a Claude turn flow between them. The graph pipeline. Indexing, language coverage, the graph hash, live updates and the layout. The webview. The three.js scene, focus and levels, GPU picking, the frame loop, colours, the HUD, and the constraints it is built under. Hosts. What VS Code and the desktop app each provide, and what a third editor would have to. The monorepo. Projects, tags, boundaries, TypeScript per runtime, Nx targets and caching. Contributing. The dev loop, where a change goes, the checks, CI and the commit conventions. Releasing. Version plans, release groups, tags, and publishing to the Marketplace, Open VSX, GitHub releases and npm."},{"url":"/changelog/","title":"Changelog","section":"Project","description":"What changed in each release, synced from the changelogs in the orbit-code repository.","headings":["VS Code extension"],"text":""},{"url":"/press/","title":"Press kit","section":"Project","description":"Descriptions, social cards, screenshots, colors and ready to post copy for writing about Orbit Code, the open source 3D view of Claude Code.","headings":["Descriptions","Ready to post","Images","Colors and what they mean"],"text":"An open source, live 3D alternative to Claude Code in the terminal, for vibecoders and programmers. Watch Claude Code work in 3D. See every file Claude Code reads, edits and thinks about. Orbit Code is an open source VS Code extension and desktop app that shows your codebase as a live 3D map while Claude Code works through it. Orbit Code draws a repository as a 3D dependency graph, with directories as bubbles, files as spheres and imports as lines, and animates a live Claude Code session across it. Reads glow, edits pulse, imports fire while Claude thinks. MIT licensed."},{"url":"/agents/","title":"Orbit Code for AI agents","section":"Project","description":"This site answers agents directly: WebMCP tools in every page, plain text indexes, and each docs page as markdown.","headings":["WebMCP tools","Plain text for models"],"text":"orbit_install: Steps and exact commands to install or update Orbit Code for one target. orbit_latest_release: The newest version on the Marketplace, Open VSX, GitHub releases and npm. orbit_search_docs: The docs pages that best match a query, each with an excerpt. orbit_read_doc: One docs page as markdown. orbit_open_page: Opens a page of this site in the current tab."},{"url":"/docs/using-orbit/","title":"Using Orbit","section":"Get started","description":"Reading the view, navigating, prompting Claude, answering permissions, the file card, the Flat view and the tour.","headings":["What you are looking at ","Navigating ","Views: Nested and Flat ","Take a Tour ","Asking Claude ","Several conversations ","The conversation view ","Permissions and questions ","Following the star ","Subagents ","MCP servers ","The file card ","Live updates ","Around the panel ","Limits worth knowing "],"text":"This page reads the same whether Orbit runs in VS Code or as the desktop app: the panel is the same bundle in both. Where the two differ, the difference is called out. Installing and configuring each host is in apps/vscode/README.md and apps/desktop/README.md. What you are looking at The graph is your workspace after indexing: - A file is a sphere. Its colour is its language (the legend at the top left lists the kinds present); its size follows the file's size. - A directory is a bubble , inside its parent's bubble. A bubble's colour is the kind most of its files are. A directory that holds nothing but one sub-directory is folded into it, so src/main/java shows as one bubble named main/java . - An import is a line between the two things that contain its ends at the level you are looking at: file to file inside one directory, file to bubble or bubble to bubble across directories. Imports that land on the same pair share one line. - Claude is a star. While a turn runs it moves to each file Claude reads or edits. A file being read lights cyan as a comet leaves it; a file being edited pulses amber; while Claude thinks, every import line on screen fires. The view opens in the root's source directory when it has one ( src , lib , app , packages , …), else at the root. The breadcrumb at the top left says where you are, and each of its parts is a link back up. Navigating To Do Look inside a directory Scroll toward its bubble. It comes to the middle of the screen and its contents fade in; once it fills the view, you are inside it. Or click it Go back up Scroll out, or press Esc , or click a part of the breadcrumb Orbit Drag. The orbit is free all the way around, over the poles too Pan Right-drag. Panning out of a directory backs out of it See what a file is Hover it: the tooltip gives its path, kind and size, and how many files it imports and is imported by. A bubble's tooltip gives its file count, sub-directories and dominant kind Do something with a file Click it: see The file card One level up, a directory's own files, lines and sub-bubbles show through its bubble, fainter; two levels up, fainter still, so the import network stays visible from a distance. The directories beside the one you are in stay as faint rims, so the view keeps its place among its neighbours. Only what the directory in view shows takes clicks. Views: Nested and Flat The tabs at the top middle switch views. - Nested is the default: bubbles inside bubbles, one directory to look into at a time. - Flat flies every file out of its directory onto an orbit round the workspace's core. Files are grouped by project (the deepest directory holding a manifest) or by top-level directory, each group along its own arc, in path order; each file is a solid sphere carrying its file type's icon, with its name under it once you are close enough. Imports rise between the spheres as arcs that fire while Claude thinks. Drag to orbit, scroll toward a file to close in on it, click a file for its card. Switching back to Nested returns to the directory you were in. Take a Tour Take a Tour at the top right hands the camera over. It flies from stop to stop (a directory, then usually one of its files, then a neighbour or another directory), backs out and swings in between stops rather than cutting, and pauses at each. Some stops get a card of what the graph knows about the place: the most imported file in its directory, the largest, an entry point, a file nothing else imports, the imports crossing a directory's boundary. Scrolling, dragging, Esc and the tabs are off until you press Stop Tour , which leaves you wherever the tour got to. A tour is random; starting it again begins a new route. Asking Claude The tab at the bottom middle opens the drawer. Type a prompt and press Enter, or run Orbit Code: Ask Claude… in VS Code. Orbit runs your own claude command line, so your Claude Code login, settings, CLAUDE.md , hooks and MCP servers all apply, exactly as in a terminal. Claude runs only in a trusted workspace: VS Code's workspace trust, or the desktop app's per-folder trust dialog. The composer's bar holds: - Files opens the host's file dialog; the files chosen become chips and go with the prompt as @path mentions, which Claude Code reads into the turn itself. - Skills opens a panel of the skills Claude Code offers (the workspace's .claude/skills , your own, and installed plugins'). Drag one onto the composer, click it, or type / in the composer to filter them; the first attached skill is invoked as a slash command, the rest are named for Claude to load. - The model picker lists the models Claude Code reports; blank is Claude Code's default. - The effort meter is a ring for the default and a bar per level the chosen model offers. Haiku offers none, so the meter dims for it. - The permission mode : default (your settings decide, and anything gated is asked in the panel), accept edits, or plan. Bypass can only be set in the host's settings, never here. - History opens a timeline of the workspace's earlier conversations, from Claude Code's own transcripts. Narrow the time range with the slider, click a conversation to read what it touched, and Continue this conversation : the next prompt resumes it. - View conversation and New conversation , above the composer, open the current conversation's transcript and make the next prompt start a fresh one. - MCP (top right of the drawer) opens the MCP view, described below. A sent prompt flies into a Claude bubble at the bottom left, one per running conversation. The bubble shows what Claude is doing; a click opens the conversation. Several conversations A prompt sent from the drawer while the current conversation is busy starts another one beside it, with its own process, bubble and star. Each bubble opens its own transcript. Up to eight conversations are kept; when another starts, the oldest idle one that is not current is let go. Orbit Code: Stop Claude (or Stop in a conversation's view) interrupts. The conversation view A click on a bubble opens the transcript: your prompts, Claude's replies as Markdown, each tool call with the file it touched linked (a link opens the file in an editor tab in VS Code, in the editor sheet in the desktop app), and a line per finished turn with its duration and list-price cost. The composer at the bottom has the same controls as the drawer, so a follow-up can switch model or bring skills and files. While the turn runs, Stop stands in for Send; you can write the next prompt meanwhile, and it goes once the turn ends. Esc closes the view. Permissions and questions When Claude asks to use a gated tool, a card appears in the conversation view with Allow , the \"don't ask again\" choice Claude Code offers for that request (allow all edits this session, always allow this command in this project), and Deny . The bubble turns to \"waiting\" meanwhile. When Claude asks you questions instead, the card lists them: pick the options (or several, when the question allows) or type your own answer, then Answer , or Skip them. With the panel out of sight, a request becomes a notification: in VS Code with the same buttons, in the desktop app with buttons on macOS and a click that reveals the window elsewhere. A question can't be answered from a notification, which offers to open Orbit or skip it. Following the star Click Claude's star and choose Follow Spark : the camera eases to it and keeps it in the middle of the screen as it moves, while your own drags and scrolls still work. Click the star again for Stop Following . Following pauses while a directory move animates, and drops once the star is gone. Subagents When Claude hands work to a subagent (a Task call), a smaller star comes out of Claude's, waits beside it joined by a faint line, and moves over the files the subagent reads or edits, with comets and MCP stations of its own. Click it for the subagent's output as it comes; that popup stays up after the subagent has finished and its star has gone back. The conversation view shows only the Task call itself. MCP servers A call to an MCP server's tool brings a station out beside Claude's star, joined to it by a beam along which the call and the answer run; stations leave once the turn ends. The MCP view (the drawer's MCP button, which wears the state most in need of attention) shows every MCP server Claude Code loads as a glyph coloured by its status: connected, connecting, needs sign-in, failed, disabled. A click offers what /mcp would: reconnect, enable or disable, sign in or out. A toggle is saved in your Claude Code settings; a sign-in opens the server's page in your browser. Reload asks every server again. Each conversation picks up a change at its next prompt, not during a turn; a claude.ai connector authorised on claude.ai needs Reconnect. The file card A click on a file opens a card beside it and rings the file: Action Does View diff The file against HEAD , offered only when git says it has changes. VS Code opens a diff editor; the desktop app opens the editor sheet's Changes mode Open The editor sheet: a CodeMirror editor at the bottom of the panel with the file's language, Cmd/Ctrl+S to save, a Changes mode with the diff against HEAD , and edits made elsewhere taken in as long as you haven't changed anything. Files over 4 MB and binary files don't open there Open in a tab An editor tab beside the panel. VS Code only: the desktop app has no tabs and hides this item Attach to prompt Opens the drawer with the file as a chip Rename… The name is selected without its extension. A file renamed within its directory keeps its place in the graph Delete… The button arms after a moment; the file collapses inside a red ring and the next update removes it. VS Code deletes as a workspace edit (to the trash when files.enableTrash is on); the desktop app only ever moves to the trash Esc closes the card without moving the camera. Live updates The graph follows your edits, Claude's edits and git checkouts within a few seconds, without laying anything out again: kept files keep their position, a new file is placed in its directory's bubble near what it imports, a new directory gets a bubble inside its parent, and a file Claude writes pulses when it arrives with the star moving to it. Because updates re-read only the files a change affects, a few imports can go stale; Reindex (in the legend at the top left, or Orbit Code: Reindex Workspace ) indexes everything again and lays it out from scratch. Around the panel - VS Code. The Orbit icon in the activity bar opens the panel (and hands the side bar back to the Explorer). While the panel is hidden and a turn runs, the status bar shows how many run or wait for approval; a click opens the panel. Settings are the orbit. keys. - Desktop app. One window per folder; File › Open Folder… and Open Recent. The tray menu lists open folders and stops running turns; the dock (macOS) and launcher (Linux) badge counts running turns. Settings are settings.json in the app's user data (File › Settings…), never read from a folder. Limits worth knowing - Only the first workspace folder is indexed, and it is the session's working directory. - Conversations live in the host process: reloading the VS Code window or quitting the desktop app starts new ones. History continues earlier ones, without replaying their transcript into the view. - View diff compares with HEAD , not with a branch's merge base. - A rename made in the Explorer or by git reaches Orbit as a delete and a create, so the file is placed again; only renames made from the card keep a file's place. - The Flat layout is computed in the page and not cached, so a reload can place files a live update added elsewhere."},{"url":"/docs/vscode/","title":"VS Code extension","section":"Editors and apps","description":"An open-source, live 3D visual alternative to Claude Code in the terminal, for programmers and vibe coders alike: your codebase as a dependency graph in…","headings":["Features ","Requirements ","Install ","Commands ","Settings ","Development ","Limitations ","License "],"text":"An open-source, live 3D visual alternative to Claude Code in the terminal, for programmers and vibe coders alike: your codebase as a dependency graph in VS Code, with Claude working through it as you watch. Directories are bubbles, files are nodes, imports are lines. Files Claude reads light up cyan, files it edits pulse amber, and the import network fires while it thinks. Features - Dependency graph for most languages. JS/TS, Python, Go, Rust, Java, C , Swift, PHP, Dart and more. Imports resolve through each project's own config ( tsconfig , go.mod , Cargo.toml , pyproject.toml , …), so monorepos work too. - Live updates. The graph follows your edits, Claude's edits and git checkouts within seconds, and keeps the layout in place. - Nested or Flat. The tabs at the top switch views. Nested shows directories as bubbles inside bubbles, one to look into at a time. Flat puts every file on an orbit around the workspace, each project or top-level directory along its own arc, each file a sphere with its file type's icon and its name, with the imports arching between them. - Take a Tour. The button at the top right flies the camera from place to place round the workspace on its own, directory to file to directory, backing out and swinging in between, and pauses a moment at each, now and then with a card of what the graph knows about it: the most imported file in its directory, the largest, an entry point, a directory nothing outside imports from. Navigation is off until you press Stop Tour. - Claude in the panel. Prompt from the bottom drawer, pick model, effort and permission mode, and run several conversations at once. A conversation's own window has the same controls, so a follow-up can switch model or bring skills and files, and you can write it while Claude is still working. - Permissions. Allow, Deny or \"don't ask again\" from a card in the panel, or from a notification when Orbit is hidden. When Claude asks you questions, pick its options or type your own answer on the same card, or skip them. - Context. Attach skills (type / or drag them in), attach files, or continue an earlier conversation. - MCP servers. The MCP button at the top right of the drawer shows every MCP server Claude Code loads, and whether it is connected, needs sign-in, failed or is disabled. Reload them, or sign in, reconnect, enable or disable one; the change is saved in your Claude Code settings, as /mcp saves it. - File actions. Click a file to view its diff, edit it inline, rename it, delete it or attach it to a prompt. - Follow Spark. Click Claude's star and the camera follows it. - Subagents. When Claude hands work to a subagent, a smaller star comes out of Claude's and moves over the files the subagent reads and edits. Click it to read the subagent's output as it comes. Requirements - VS Code 1.100+ - Claude Code, installed and logged in. Without it, you still get the graph. Install yarn install yarn install-local Reload VS Code, then click the Orbit icon in the activity bar or run Orbit Code: Open . Navigating: scroll over a bubble to zoom into it (it comes to the middle of the screen and opens once it fills the view), click a bubble to enter it, drag to orbit all the way around, right-drag to pan (panning out of a directory backs out of it), and press Esc to go back up. The directories beside the one you are in stay as faint rims. In the Flat view, drag to orbit, scroll toward any file to zoom, and click a file as in the Nested view; switching back returns to the directory you were in. Take a Tour (top right) hands the camera over: it flies from stop to stop until you press Stop Tour , which leaves you wherever it got to. Commands Command Orbit Code: Open Open the graph Orbit Code: Ask Claude… Send a prompt Orbit Code: Stop Claude Stop every running turn Orbit Code: New Claude Conversation Start fresh Orbit Code: Reindex Workspace Index everything again and lay it out from scratch Settings Setting Default orbit.claude.path \"\" Path to claude . If empty, Orbit searches PATH and the usual install locations orbit.claude.model \"\" Model, e.g. opus or sonnet orbit.claude.effort \"\" low , medium , high , xhigh or max orbit.claude.permissionMode default default , acceptEdits , plan or bypassPermissions orbit.claude.extraArgs [] Extra arguments for every claude run orbit.maxFiles 20000 The most files Orbit indexes Claude only runs in trusted workspaces. Your own Claude Code settings, CLAUDE.md and MCP servers apply. Development The extension is apps/vscode in the Orbit Code monorepo, built from its packages with Nx. From the repository root: yarn self build and open the repository in an Extension Development Host (or press F5) yarn watch rebuild on change yarn typecheck yarn harness webview checks in headless Chrome yarn smoke end-to-end in a real VS Code yarn package a production .vsix in dist/apps/vscode/ CI ( .github/workflows/ci.yml ) runs typecheck, build, the package boundary check and an index of the repository on every push to main and pull request, then packages a .vsix and runs smoke and harness; the harness report and screenshots are an artifact to read. Releases come from Nx Release: a v<version tag attaches the .vsix to a GitHub release, and publishes it to the Marketplace and Open VSX once their tokens are set. The longer documentation is in the repository's docs/ directory: how to use the view, the architecture, the graph pipeline, how Claude Code is run, contributing, releasing and troubleshooting. Limitations - Only the first workspace folder is indexed. - Reloading the window ends your conversations. - Live updates are incremental, so a few imports can go stale. Reindex fixes them. - Enabling, disabling or signing in to an MCP server reaches each conversation at its next prompt, not during a turn. A claude.ai connector needs Reconnect after you connect it on claude.ai. License MIT"},{"url":"/docs/desktop/","title":"Desktop app","section":"Editors and apps","description":"Orbit Code as a desktop app: an open-source, live 3D visual alternative to Claude Code in the terminal, for programmers and vibe coders alike, in a window…","headings":["Requirements ","Run it from source ","Using it ","Settings ","Where it keeps things ","Development ","Packaging and releasing ","Limits "],"text":"Orbit Code as a desktop app: an open-source, live 3D visual alternative to Claude Code in the terminal, for programmers and vibe coders alike, in a window of its own. It shows a folder as a 3D dependency graph and animates a live Claude Code session moving through it, the same way the VS Code extension does. It runs the engine the extension runs ( @orbit-code/core , @orbit-code/agent ) and the same UI bundle ( @orbit-code/webview ), hosted by Electron instead of VS Code. Requirements - Claude Code installed and signed in ( claude --version works in a terminal). Orbit starts your own claude CLI, so your Claude Code settings, skills and MCP servers apply. - git, for the file card's View diff and the editor sheet's Changes. Without it the graph still indexes, by walking the folder. Run it from source yarn install also downloads Electron yarn desktop --folder . builds, then opens this repository yarn desktop --folder ../other --reindex A relative --folder is taken from the workspace root. With no folder, the app reopens the folders that were open when it last quit, or shows an empty window. Using it - Opening a folder. File › Open Folder… (Cmd/Ctrl+O) or File › Open Recent. Each folder gets its own window. Opening a folder that is already open brings its window forward. - Trust. The first time a folder opens, Orbit asks whether you trust its authors, because Claude Code runs there with the settings, hooks and MCP servers the folder configures. The graph works either way. Claude sessions start once the folder is trusted (File › Trust Folder). - Files. A click on a file opens its card: View diff, Open, Attach to prompt, Rename, Delete. Open and View diff use the editor sheet at the bottom of the window. There are no editor tabs, so links in the transcript open there too. Delete moves the file to the system's trash, and fails rather than deleting a file for good. - While the window is out of sight. A permission request or a question from Claude becomes a system notification. On macOS its buttons answer it. Everywhere, a click brings the window forward. The tray icon's menu lists open folders and stops running turns. The dock (macOS) and launcher (Linux) show how many turns run, or how many wait for approval first. Settings settings.json in the app's user data (File › Settings… opens it). The keys match the extension's orbit. settings, and changes apply as soon as the file is saved. Key Default What it does maxFiles 20000 The most files indexed (at least 100) claude.path \"\" The claude executable; empty looks in PATH and the usual install locations claude.model \"\" Model alias or id; empty is Claude Code's default claude.effort \"\" low , medium , high , xhigh or max ; empty is Claude Code's default claude.permissionMode \"default\" default , acceptEdits , plan or bypassPermissions claude.extraArgs [] Appended to every claude invocation Settings are never read from an opened folder, so a repository can't choose the executable, its arguments or the permission mode. bypassPermissions is accepted only from this file, never from the window. Where it keeps things The user data directory is ~/Library/Application Support/Orbit Code on macOS, %APPDATA%\\Orbit Code on Windows and ~/.config/Orbit Code on Linux. A run from source uses Orbit Code Dev instead, so it never shares state with an installed copy. Path Holds settings.json Settings recent.json Recent folders, and the ones open at the last quit trust.json Trusted folders workspaces/<hash /orbit-v1/ Each folder's graph.json and up to 4 layout-<hash .bin , as the extension keeps them logs/orbit.log The log, with the lines the extension writes to Orbit.log (File › Show Log) Development yarn nx run desktop:build dist/: main.js, preload.js, and the indexer and webview builds copied in yarn nx run desktop:watch rebuilds as sources change; restart the app, or View › Reload for the page yarn nx run desktop:typecheck yarn desktop:smoke launches the app on a small folder under .harness/desktop-smoke The main process is src/main/ : Module Owns main.ts App lifecycle, a window per folder, the menu, trust, recent folders, launch arguments workspace.ts One folder's engine: GraphService, SessionService and the shared OrbitController, with the desktop's dialogs and notifications as its HostUi window.ts The BrowserWindow as the controller's transport: ready , visibility, and messages only from its own top frame page.ts The orbit://app/ scheme: the page with a fresh nonce and the panel's CSP, and webview.js files.ts The file menu and editor sheet's requests, on the disk watcher.ts A recursive fs.watch feeding ChangeBatcher, for live updates settings.ts , recent.ts , trust.ts The JSON files in user data notifications.ts , tray.ts Permission notifications, the tray and the dock count shellPath.ts The login shell's PATH, for an app started from the Finder or a launcher src/preload/preload.ts exposes window.orbitHost.postMessage to the page and delivers the host's messages as window message events, which is the contract packages/webview/src/host.ts has with any host but VS Code. The window is sandboxed, with context isolation and no Node integration. Environment variables: ORBIT DESKTOP FOLDER (a folder to open), ORBIT REINDEX=1 , ORBIT DESKTOP USER DATA , ORBIT DESKTOP LOG , ORBIT DESKTOP SOFTWARE GL=1 (WebGL without a GPU). --disable-workspace-trust trusts every folder, as it does for VS Code. Everything the app runs is bundled into dist/ , so package.json lists the workspace packages as devDependencies: electron-builder would otherwise copy them into the package. docs/hosts.md describes what the desktop app provides next to what the VS Code extension does, and docs/using-orbit.md how to use the view. Packaging and releasing yarn desktop:package a production build, then electron-builder into dist/apps/desktop yarn desktop:package --linux AppImage arguments go to electron-builder This makes a dmg and zip on macOS, an NSIS installer on Windows and an AppImage on Linux ( electron-builder.yml ). electron-builder is pinned in scripts/builder.mjs and fetched with yarn dlx . Electron is pinned in package.json at 44.3.0, which runs Node 24.20.0. The desktop app is released with the VS Code extension: one version, one v<version tag. To release, add a version plan with the change ( yarn nx release plan minor --groups=apps -m \"…\" ), then run yarn nx release --skip-publish and push the tag. .github/workflows/release.yml builds a dmg for Apple silicon and one for Intel on macOS and puts them in the GitHub release beside the extension's .vsix , with that version's changelog. Windows and Linux installers aren't released yet; yarn desktop:package makes them on those platforms. docs/releasing.md has the whole procedure. The builds are not signed yet. macOS gets an ad hoc signature, so it runs on Apple silicon, but Gatekeeper asks before the first launch. Windows SmartScreen warns too. Signing needs, as follow-ups: - macOS: a Developer ID Application certificate ( CSC LINK , CSC KEY PASSWORD ), hardenedRuntime: true and notarization ( APPLE ID , APPLE APP SPECIFIC PASSWORD , APPLE TEAM ID ) in electron-builder.yml and the release job. Notifications from an unsigned build may not show on macOS. - Windows: a code signing certificate for electron-builder's win signing options. Limits - One folder per window; a multi-root workspace isn't supported. - Live updates watch the folder with a recursive fs.watch . On Linux that sets a watch on every directory, node modules included, and a very large folder can run out of inotify watches. The log then warns, and changes made outside Orbit show after Reindex. - The editor sheet picks a file's language by its name; files over 4 MB, and binary files, don't open. - Notification buttons are macOS only. Windows shows no count on the taskbar. - Conversations live in the app, so quitting starts new ones; History continues earlier ones."},{"url":"/docs/settings/","title":"Settings and commands","section":"Editors and apps","description":"Every command and setting the VS Code extension adds, read from its manifest (version 0.2.1). The desktop app reads the same settings from its own…","headings":["Commands ","Settings ","orbit.maxFiles ","orbit.claude.path ","orbit.claude.model ","orbit.claude.effort ","orbit.claude.permissionMode ","orbit.claude.extraArgs "],"text":"Every command and setting the VS Code extension adds, read from its manifest (version 0.2.1). The desktop app reads the same settings from its own settings.json , written without the orbit. prefix. Commands Run these from the Command Palette. Command ID Orbit Code: Open orbit.open Orbit Code: Ask Claude… orbit.prompt Orbit Code: Stop Claude orbit.interrupt Orbit Code: New Claude Conversation orbit.newSession Orbit Code: Reindex Workspace orbit.reindex Settings orbit.maxFiles Maximum number of source files to index. Larger workspaces are truncated. Type number , default 20000 , at least 100 . orbit.claude.path Path to the claude executable. Empty searches PATH and the default install locations. Type string , default \"\" , set per machine, never by a workspace. orbit.claude.model Model alias or id passed to Claude Code as --model , for example opus or sonnet . Empty uses Claude Code's own default. The picker in the Orbit Code panel overrides this until the window reloads. Type string , default \"\" . orbit.claude.effort Effort level passed to Claude Code as --effort : how much Claude thinks before answering. Empty uses Claude Code's own default. A model that takes no effort level, such as Haiku, ignores it. The effort meter in the Orbit Code panel overrides this until the window reloads. Type string , default \"\" , one of , low , medium , high , xhigh , max . orbit.claude.permissionMode Permission mode for new Claude sessions. Bypass can only be chosen here, never from the panel. Type string , default \"default\" , one of default , acceptEdits , plan , bypassPermissions , set per machine, never by a workspace. orbit.claude.extraArgs Extra arguments appended to every claude invocation, for example [\"--add-dir\", \"../shared\"] . Type array , default [] , set per machine, never by a workspace."},{"url":"/docs/indexer/","title":"Indexer CLI","section":"Editors and apps","description":"Indexes a workspace into the dependency graph Orbit Code draws: files are nodes, imports are edges, and directories group them.","headings":["CLI ","As a worker thread ","License "],"text":"Indexes a workspace into the dependency graph Orbit Code draws: files are nodes, imports are edges, and directories group them. - JS and TS through dependency-cruiser, once per tsconfig / jsconfig so each package's path aliases apply, with workspace packages, exports and imports maps resolved too. - Other languages (Python, Go, Rust, Java, C , Swift, PHP, Dart and more) through import scans that resolve with each project's own manifests: go.mod , Cargo.toml , pyproject.toml , composer.json , pubspec.yaml , .csproj , Package.swift . - Configuration files linked to the files and projects they name. Imports resolve only against files in the graph, never against the disk. The bundle carries dependency-cruiser and TypeScript, so nothing else needs installing. CLI npx @orbit-code/indexer <dir --out graph.json [--max N] npx @orbit-code/indexer <dir --out next.json --previous graph.json incremental, as a live update runs Files come from git ls-files in a work tree (so .gitignore applies), otherwise from a walk of the directory. The graph's hash covers file ids, sizes and edges, so an incremental run and a full index of the same files agree. As a worker thread Orbit's editor hosts run dist/indexer.mjs on a worker threads worker: workerData is an IndexerRequest and the messages back are IndexerResponse s, both defined in @orbit-code/protocol . License MIT"},{"url":"/docs/claude-session/","title":"Claude in Orbit","section":"Guides","description":"How the `claude` CLI is run, conversations, permissions, questions, MCP, skills, files, history and subagents.","headings":["The process ","Conversations ","What Claude does, and what Orbit shows ","Permissions ","Questions ","Interrupt, cost, exits ","The catalog ","The MCP view ","Skills, files, history ","Subagents ","Settings that reach the process ","Another backend "],"text":"Orbit embeds no model. It runs the user's own claude command line as a long-lived process that speaks stream-json, and turns what Claude does into three streams: session state (phase, permission request, cost), a transcript , and per-file activity on the graph. The page draws them as Claude's star and comets in the scene, and as bubbles, a conversation view and a permission card in the HUD. Two promises hold throughout: what lights up is what Claude asked to touch, on the file it named , and every question Claude asks reaches the user , even with Orbit hidden. The code is packages/agent (the session and the CLI) and packages/core (the projector and the controller); the shapes were last checked against Claude Code 2.1.267. The process claude --print --input-format stream-json --output-format stream-json --verbose --permission-prompt-tool stdio [--model M] [--effort E] [--permission-mode P] [--resume <session id ] [...claude.extraArgs] - Started in the first workspace folder, with ELECTRON RUN AS NODE removed from the environment, through no shell (except .cmd / .bat on Windows). - One process serves many turns of one conversation: each prompt is one {\"type\":\"user\"} line on stdin. - Option changes apply to the next prompt. Changing the model, effort or permission mode restarts the process with --resume <session id , so the conversation continues. A process that died while idle restarts the same way. - The probe. Before anything, the service runs claude --version on the configured path, or on claude from PATH plus ~/.local/bin , ~/.claude/local , /opt/homebrew/bin and /usr/local/bin , because editors started from the GUI often lack the shell's PATH . It logs agent: <version or agent unavailable: <reason ; without it, the graph still works. - Trust. Sessions stay unavailable until the workspace is trusted (VS Code's workspace trust; the desktop app's per-folder dialog). - Without --permission-prompt-tool stdio Claude Code denies gated tools outright instead of asking; the flag is what makes the permission card possible. Conversations SessionService keeps a Conversation per key ( c1 , c2 , …), each with its own process, and one of them is current : the one the drawer and Orbit Code: Ask Claude… continue. - A prompt without a key goes to the current conversation if it is idle, else opens a new one beside it. A prompt with a key continues that conversation, and is refused while it is busy. Whichever takes the prompt becomes current, and so does one started by New conversation or continued from History. - Only the current conversation keeps its process warm between turns; a former current lets its idle process go and restarts with --resume when prompted again. - Past eight conversations, the oldest idle non-current one is let go. - Stop interrupts one; Orbit Code: Stop Claude interrupts every running one. The page gets a sessions snapshot (every conversation's state and the current key) whenever the set changes, and session , transcript and activity messages carrying the conversation's key. What Claude does, and what Orbit shows Claude Transcript Scene HUD Starts a turn A prompt entry opens a turn Edges flow faster for the whole turn, and the frame loop stays awake The text flies into a new bubble Thinks Every import line on screen fires violet \"Thinking\" Writes Markdown, rendered as DOM nodes; a link without a scheme opens a workspace file \"Writing a reply\" Reads a graph file ( Read ) A tool entry linked to the file The star moves over the file; a comet leaves it; the file and its bubbles light cyan once the comet lands \"Reading path\" Edits a graph file ( Edit , MultiEdit , Write , NotebookEdit ) Linked The star moves over the file, which pulses amber; its neighbours at half strength \"Editing path\" Creates a source file Linked Nothing until the live update adds it; then it glows like an edit with the star moving to it Touches any other path Unlinked Nothing Calls an MCP tool ( mcp server tool ) An entry naming the server and tool The server's station comes out beside the star; a pulse runs out along the beam and the answer back, red on error \"Asking server\" Uses another tool (Bash, Grep, Glob, Task, WebFetch, …) The tool and its command, pattern, URL or description Nothing The tool name and detail A tool fails A warning notice Asks permission or questions The bubble waits; a card in the conversation view; a notification when the page is out of sight Runs a subagent ( Task ) The Task call; the subagent's own entries are kept apart A smaller star comes out of Claude's, moves over the subagent's files, and goes back when it finishes Its star's popup shows its output Finishes A turn line with duration and list-price cost Glows fade, the star goes home The bubble says how it went and leaves Is stopped \"Stopped\" As a finish \"Stopping\" Its process dies An error notice and a failed turn, with the last stderr line As a finish A failed bubble, which stays until opened Tool paths (absolute, or relative to the workspace) are resolved against the graph by SessionProjector ; a path outside the graph animates nothing and appears in the transcript without a link. Activity is batched per 100 ms tick and carries the graph's hash. The projector keeps the last 400 transcript entries per conversation, and 400 more of its subagents'. Permissions A gated tool arrives as a can use tool control request, with the permission suggestions Claude Code would apply for \"don't ask again\": switching the session to accept edits for an edit, an allow rule for a command. Orbit words them as the card's middle choice. - Allow answers with the request's own input echoed back. - The always choice sends those suggestions back as updatedPermissions , so Claude Code applies them itself; Orbit invents no rules of its own. - Deny carries a message. - An interrupt denies a pending request first. - Permission modes: default (the user's own settings decide), acceptEdits , plan , and bypassPermissions , which only a host setting can choose; the page never offers it and the controller refuses it. Questions An AskUserQuestion call arrives the same way. Its questions become a question card in place of the permission card: options as radios (or checkboxes when the question allows several) and a field for the user's own answer. The answer requires one per question and is sent as the tool's answers input, as Claude Code's own dialog does; Skip is a deny saying the questions were skipped. A notification can't hold the answers, so it offers to open Orbit or skip. Interrupt, cost, exits - An interrupt is an interrupt control request; the turn then ends with a result whose reason is an abort. If no result arrives within 5 s, the process is stopped (SIGTERM, then SIGKILL 3 s later) and the turn counts as interrupted. - total cost usd is cumulative per process, so the service subtracts the previous value and starts from zero with each new process. - A process that dies during a turn is logged as a warning (it isn't an Orbit bug), with a failed turn and a notice. Idle and non-zero: a warning, and the next prompt resumes the conversation. The catalog After a successful probe, in a trusted workspace with a folder open, the backend starts a short-lived process with the same arguments plus --no-session-persistence , sends it only the initialize and mcp status control requests, and ends it. - initialize gives the models (with the default marked) and their effort levels (none for a model such as Haiku), and the commands; the skill catalog keeps the commands that are skills on disk (the workspace's .claude/skills , ~/.claude/skills , installed plugins) with the skills each SKILL.md names. - mcp status gives each server's name, status, scope, tool count and names, transport, version and a scrubbed error. The answer also carries the server's configuration, credentials included, and initialize the account: only the declared fields leave the parser and neither answer is logged. - Each session's init message updates the MCP statuses. The MCP view Reload replaces the service's control process (the catalog's arguments, no prompt, let go after five minutes unused) and asks mcp status again while any server is pending. A click on a server offers reconnect, enable or disable, sign in or sign out; each is accepted only for a server the catalog lists and an action in the fixed set, and becomes the matching control request on that process. A toggle is saved in the user's Claude Code settings, as /mcp saves it. A sign-in's URL (http or https only) is opened by the host in the system browser and never reaches the page; for an OAuth server the process then waits for the callback while the service polls until it connects. Every action but reconnect makes each conversation start a new process at its next prompt, which loads the change. Skills, files, history - Skills. A prompt can carry up to eight catalog skill names; any other is dropped. The first is sent as a leading slash command ( /name text ) and the rest are named in the prompt for Claude to load. Built-in skills are left out of the panel, because initialize doesn't tell them from built-in commands. - Files. A prompt can carry up to twenty files as context: graph files, and absolute paths the host's own file dialog returned; anything else the page names is dropped. They are appended as @path mentions ( @\"a b.md\" with whitespace), Claude Code's own syntax, so Claude reads them into the turn itself rather than through a tool call Orbit is asked to allow. - History. ConversationHistory summarises the workspace's earlier conversations from Claude Code's transcripts under its config directory ( projects/<the workspace path with non-alphanumerics as dashes ), cached by size and mtime: title, prompts, model, branch, files touched, MCP servers and skills. Continuing one is accepted only for an id in the last list sent, because it becomes --resume <id : the conversation already holding that id becomes current, else a fresh one takes it. The earlier transcript isn't replayed into the view. Subagents A subagent's envelopes carry the id of the Task call running it. The conversation announces it on its first envelope (with the subagent's type and description) and ends it when that call's result arrives or the turn ends first. Its reads, edits and MCP calls are sent as activity with the subagent's id, and its transcript entries are kept in a history of their own, so a busy subagent doesn't push the conversation's entries out and the conversation view shows only the Task call. A subagent that sends nothing before its call returns gets no star. Settings that reach the process VS Code Desktop settings.json Effect orbit.claude.path claude.path The executable; empty searches PATH and the usual locations. A change probes again orbit.claude.model claude.model --model ; checked as a model name. Applies from the next prompt orbit.claude.effort claude.effort --effort , one of low , medium , high , xhigh , max ; empty passes none. Applies from the next prompt orbit.claude.permissionMode claude.permissionMode --permission-mode unless default . Applies from the next prompt orbit.claude.extraArgs claude.extraArgs Appended to every invocation, for example [\"--add-dir\", \"../shared\"] . A change probes again The path, the extra arguments and the permission mode change what runs and with which permissions, so VS Code scopes them to the machine and restricts them in untrusted workspaces, and the desktop app reads them only from user data. Another backend SessionBackend and AgentProcess in packages/agent/src/backend.ts are the seam. Another backend (the Agent SDK, a remote runner) implements them, emits the same AgentEvent s, and calls the sink's exit exactly once, including after disposal; nothing above the service would change."},{"url":"/docs/troubleshooting/","title":"Troubleshooting","section":"Guides","description":"Logs, caches, and the failures with a known cause.","headings":["Where to look ","Claude ","The graph ","The page ","Builds and checks ","Installing "],"text":"Where to look The log. Both hosts write the same lines. Host Where VS Code View › Output › Orbit . On disk: macOS ~/Library/Application Support/Code/logs/<session /window<N /exthost/imshaikot.orbit-code/Orbit.log ; Linux ~/.config/Code/logs/… ; Windows %APPDATA%\\Code\\logs\\… Desktop app File › Show Log, or <userData /logs/orbit.log (rotated to .1 past 5 MB); also stderr when started from a terminal The smoke test .harness/vscode-user/logs/ Lines to know: agent: <version or agent unavailable: <reason (the Claude Code probe), scene ready for graph <hash , [webview] layout of … (a layout worker run), graph updated <hash : +A −R ~C files, E edges in N ms (a live update), [webview] graph update applied , turn done in … . Webview errors arrive prefixed [webview] . The storage. orbit-v1/ holds graph.json and up to four layout-<hash .bin files. Host Where VS Code (macOS) ~/Library/Application Support/Code/User/workspaceStorage/<id /imshaikot.orbit-code/orbit-v1/ Desktop app ~/Library/Application Support/Orbit Code/workspaces/<hash of the path /orbit-v1/ on macOS; %APPDATA%\\Orbit Code\\… on Windows; ~/.config/Orbit Code/… on Linux. A run from source uses Orbit Code Dev Reindex (in the legend, or Orbit Code: Reindex Workspace ) rebuilds both; deleting the folder is the manual reset. The page. Developer: Open Webview Developer Tools in VS Code, View › Toggle Developer Tools in the desktop app. window. orbit has debug , world() , camera() and the rest (The webview). Claude - \"agent unavailable\" in the log, or the drawer says Claude isn't available. claude --version must work; Orbit looks on the configured path, then PATH plus ~/.local/bin , ~/.claude/local , /opt/homebrew/bin and /usr/local/bin . An editor started from the GUI often lacks the shell's PATH : set orbit.claude.path (or claude.path in the desktop settings), or start the editor from a terminal. The desktop app also asks the login shell for its PATH at start. - Sessions stay unavailable in a folder. The workspace isn't trusted: VS Code's trust prompt, or File › Trust Folder in the desktop app. The graph works either way. - A gated tool is denied without asking. Something removed --permission-prompt-tool stdio ; check claude.extraArgs for arguments that change the output or input format. - The process dies mid-turn. It is logged as a warning with the last stderr line, the turn fails, and the next prompt resumes the conversation. A Claude Code upgrade can change the stream-json shapes; the parser was last checked against 2.1.267. - An MCP change doesn't show in a running conversation. Enabling, disabling or signing in reaches each conversation at its next prompt, not during a turn. A claude.ai connector authorised on claude.ai needs Reconnect. - History is empty. History reads Claude Code's transcripts under <config /projects/<the workspace path with non-alphanumerics as dashes ; a workspace whose transcripts Claude Code keeps under another name (very long paths) shows none. - ELECTRON RUN AS NODE=1 is inherited by shells that VS Code starts, including Claude Code running inside VS Code. Orbit strips it for Claude and the desktop scripts delete it, but starting VS Code or Electron any other way with it set gives plain Node. Delete it first. The graph - An import is missing or stale. Live updates are incremental and some importers aren't re-read; The graph pipeline lists the cases. Reindex. - A file isn't in the graph. It is ignored by git, beyond maxFiles , in an always-excluded directory ( node modules , vendor , …), a lock file, or of an extension the language table doesn't know. A file that newly enters the listing without being created (a .gitignore change) needs some file event to be noticed. - The desktop app indexes a folder as empty ( listed 0 files via git ). The folder is inside a repository whose .gitignore covers it, so git ls-files lists nothing. Make it a repository of its own, or move it. - The layout looks old after a layout change. A load with an unchanged hash reuses the cached layout. Reindex, or bump LAYOUT VERSION in packages/core/src/store.ts to redo it everywhere. - An extractor change doesn't show. Live updates reuse the edges of unchanged files. Reindex. - Live updates stop in the desktop app on Linux. The recursive fs.watch sets a watch per directory and a very large folder can exhaust inotify's limit; the log warns. Raise fs.inotify.max user watches , or use Reindex. - A directory that gained many files looks crowded. New files are placed, not laid out, and sibling bubbles are packed close. Reindex gives a fresh layout. The page - The scene is frozen. Something moves without waking the frame loop; check the console for [webview] errors. While hidden, the page renders nothing on purpose. - Low frame rate. The stage steps the pixel ratio down (2, 1.5, 1) when frames keep missing 30 fps; the readout at the bottom right shows it. A graph with many thousands of imports draws each Flat arc with fewer segments. - The webview build fails on a URL in a comment. The constraint check is a plain regex over packages/webview/src/ ; no http:// or https:// anywhere, comments included. - Styles don't apply from code. The CSP blocks style attribute strings; set styles through element.style . Builds and checks - A stale dist/ after switching branches. Nx restores a cached build whose inputs haven't changed; the environment isn't an input. yarn nx reset after upgrading Node or changing a build script's environment. - @orbit-code/<name doesn't resolve right after adding a package: yarn install links it. A target that \"doesn't exist\": yarn nx reset . - A dependency-cruiser or TypeScript upgrade. Three of dependency-cruiser's modules are swapped for shims by path regex in packages/indexer/build.mjs ; if a regex stops matching, files fall back to the regex scanner. Compare yarn index . --out before.json and after.json : more depcruiseFallbacks or far fewer edges means a shim stopped matching. - The harness can't find Chrome. It expects /Applications/Google Chrome.app/… ; pass --chrome <path . On Ubuntu a snap Chromium can't see the paths written for it; prefer a deb Chrome. - A red harness check. The harness never fails on a bad result. Rerun, and compare with a run on main : a few checks depend on the graph under test. - The smoke test was killed mid-run. Delete packages/graph/src/ orbitSmoke.ts , orbitSmokeMenu.ts and orbitSmokeMoved.ts yourself. - yarn install downloads a 100 MB Electron. ELECTRON SKIP BINARY DOWNLOAD=1 skips it where the desktop app won't run; both workflows set it except in the desktop jobs. - A copy of the workspace builds the wrong sources. Its node modules/@orbit-code/ symlinks point into the original; run yarn install in the copy. Installing - Gatekeeper or SmartScreen warns about the desktop app. The builds are unsigned (macOS ad hoc); allow it once. Signing is a follow-up in apps/desktop/README.md . - Notifications don't show from the desktop app on macOS. An unsigned build's notifications may be suppressed; buttons on notifications are macOS only in any case. - The installed extension doesn't update after yarn install-local . Reload the open VS Code windows. The dev host ( yarn self ) never loads the installed copy."},{"url":"/docs/architecture/","title":"Architecture","section":"How it works","description":"The three bundles, the services, and how a graph, a layout and a Claude turn flow between them.","headings":["Three runtimes, three bundles ","The engine ","The page ","The protocol ","Data flow ","Storage and logs ","Security constraints "],"text":"Orbit is one engine, one UI and two hosts. The engine indexes a workspace into a graph and runs Claude Code conversations; the UI draws the graph and the conversations in a page; a host is an editor (VS Code) or an app (Electron) that gives the engine what it needs from the machine and gives the UI a page to show. Three runtimes, three bundles Bundle Runs in Built from May import apps/vscode/dist/extension.js (CommonJS) VS Code's extension host apps/vscode/src/ , with @orbit-code/core , agent , graph , protocol and common inlined vscode , Node, those packages, and @orbit-code/indexer/listFiles packages/indexer/dist/indexer.mjs (ESM) A worker threads worker the host starts; also the orbit-index CLI packages/indexer/src/ Node, dependency-cruiser, TypeScript, @orbit-code/graph and protocol packages/webview/dist/webview.js (IIFE) The page, under a strict Content Security Policy packages/webview/src/ three.js, CodeMirror, @orbit-code/graph and protocol The desktop app replaces the first with two of its own: apps/desktop/dist/main.js for Electron's main process (the same packages inlined) and dist/preload.js for the window's sandboxed preload. Both hosts copy indexer.mjs and webview.js into their dist/ and ship them. The indexer is its own bundle for three reasons: dependency-cruiser uses top-level await , so it can't be CommonJS; a large cruise would block the extension host; and TypeScript (about 9 MB) must not load at activation. Nothing the host bundles reach may import dependency-cruiser or typescript , directly or through any package, and the boundary check refuses any indexer module but listFiles outside the indexer. The packages tagged neutral ( protocol , graph , common ) compile without VS Code, Node or the DOM, because the webview, the hosts and the indexer all import them. The monorepo has the full table of projects, tags and allowed imports. The engine Each service owns one concern and publishes events through Emitter from @orbit-code/common . Services don't know each other; the controller wires them to the page. Service Package Owns GraphService core The cached graph.json , index runs and live updates on the worker, frozen and extended layouts, a path resolver per graph Store core orbit-v1/ in the host's storage directory: graph.json and up to four layout-<hash .bin files ChangeBatcher core File paths from the host's watcher, batched (1.5 s debounce, 5 s at most) into one refresh SessionService agent The workspace's conversations, the probe of the claude CLI, its availability, the catalog (models, skills, MCP servers), and which conversation is current Conversation agent One conversation and its process: prompts, turns, permission requests, interrupts, cost, restarts with --resume ClaudeCliBackend agent The one SessionBackend : spawns claude in stream-json mode and parses its output into AgentEvent s ConversationHistory agent The workspace's earlier conversations, summarised from Claude Code's own transcripts SessionProjector core One conversation's events plus the loaded graph, turned into per-file activity and transcript entries, batched per 100 ms tick OrbitController core The only module that knows all of the above: it checks the page's messages, routes them, forwards events, and sends a full snapshot whenever the page says ready The services take what they need from the editor as options: a logger, a storage directory, the indexer's path, the first folder, a file lister, whether the workspace is trusted. That is what lets a host be small. The controller takes three things from its host: a HostUi (capabilities, the folder, the file dialog, opening a URL or a file, permission notifications), a FileHost (what the file card and the editor sheet ask of a file) and, once there is one, a WebviewTransport (post a message, whether the page is in sight, and its ready , message and dispose events). The page packages/webview/src/main.ts wires the page's modules together with callbacks; none of them imports another's instance. HostBridge posts through acquireVsCodeApi() when the page has it and through window.orbitHost otherwise, and takes host messages as window message events either way, so the same bundle runs in VS Code, in the desktop window and in the harness. The webview describes the scene and the HUD. The protocol packages/protocol/src/protocol.ts defines every message that crosses a boundary: host to page and page to host, host to indexer worker, and page to layout worker. Host messages are coarse: the graph arrives as one reset or one update carrying the whole graph (never per-edge deltas), and activity and transcript entries come batched per host tick. Page messages are untrusted input: the controller checks each one's shape and values before a service sees it. A few rules run through the protocol: - Generations. Work a reindex can supersede carries a generation counter, and stale results are dropped. Activity carries the graph's hash and is dropped for any other graph. - Snapshots. Every ready (first load, reload, reopen) gets a full snapshot: the host's capabilities, the graph or the last indexing status, every conversation's state and the current one, the catalog, and each conversation's transcript (the last 400 entries). Host state the page displays has to be part of it, or a reload loses it. - Versions. PROTOCOL VERSION (13 at the time of writing) is sent with ready ; a mismatch is logged as a hint about stale bundles, not a gate. Data flow 1. Startup. The host creates the panel or window and attaches it to the controller. The page posts ready ; the controller sends the snapshot. 2. Graph load. GraphService.load() reads the cached graph.json , or lists the workspace's files ( git ls-files in a work tree, else the editor's search or a walk of the folder) and runs the indexer worker on them. A cached graph is followed by a background refresh, so edits made while the editor was closed show up. Reindex starts over: a full index, the cached layout ignored, a new scene. 3. Graph reset. The controller sends one columnar graph message: node index equals array position, edges as a flat Uint32Array , and the cached layout when there is one for that hash. 4. Layout. Without a cached layout, the page runs the layout worker: one force simulation per directory, bottom-up, each directory a bubble enclosing its files and its sub-directories' bubbles. The result goes back to the host as layoutComputed , which saves it as layout-<hash .bin ; the page then posts sceneReady . 5. A turn. A prompt from the page goes to SessionService , which hands it to a conversation's process as one stream-json line. Claude's output becomes session state (phase, permission request, cost), transcript entries and per-file activity, which the page draws. Claude in Orbit has the details. 6. Live updates. The host's watcher feeds changed paths into ChangeBatcher ; GraphService.refresh() re-lists the files, runs the worker on the previous graph plus the touched files, extends the current layout in place, writes the new graph and layout, and the controller sends an update delta (or a reset, if the page holds an older graph). The page builds a new World that adopts the old one's animation state through an index remap. The graph pipeline has the details. Storage and logs VS Code Desktop app Graph and layouts <workspaceStorage /imshaikot.orbit-code/orbit-v1/ <userData /workspaces/<hash of the path /orbit-v1/ Log The Orbit output channel ( Orbit.log on disk) <userData /logs/orbit.log , also stderr Settings The orbit. settings <userData /settings.json Both hosts write the same log lines, which the end-to-end checks read. Troubleshooting lists the paths per platform. Security constraints These hold in both hosts and in the harness, and the checks compare them: - The page's CSP is default-src 'none' with a nonce'd script and style and worker-src blob: (the layout worker starts from a Blob URL). No fonts, no remote images, no network: assets are bundled as text or data URIs, and the build refuses fetch , XMLHttpRequest , importScripts and any http:// or https:// in the webview's sources. - The page has no file access. VS Code's localResourceRoots is dist/ only; the desktop scheme serves only the page, webview.js and its source map, and its window is sandboxed, context-isolated and without Node. - The page never touches files. The file card and the editor sheet send requests that the controller accepts only for a normalised id of a file in the graph; VS Code applies them as workspace edits, the desktop app writes in place behind a revision check and deletes only to the trash. - Text goes into the DOM as textContent , never HTML. Markdown replies are rendered as DOM nodes. - Claude runs only in trusted workspaces. Settings that change what runs ( claude.path , claude.extraArgs , claude.permissionMode ) are machine-scoped in VS Code and read only from user data in the desktop app, so a repository can't set them. bypassPermissions is accepted only from settings, never from the page. Model names and effort levels are checked because they become command-line arguments, and a resumed conversation's id must be one the host listed. - MCP status answers carry server configurations with credentials, and initialize the account: only the fields the catalog declares leave the parser, and neither answer is logged. A sign-in URL goes from the host to the system browser and never reaches the page or the log."},{"url":"/docs/graph-pipeline/","title":"The graph pipeline","section":"How it works","description":"Indexing, language coverage, the graph hash, live updates and the layout.","headings":["Listing files ","Language coverage ","Indexing ","The cache ","Layout ","Live updates ","Known staleness ","Running the indexer alone "],"text":"A full index builds the workspace graph once; live updates keep it current. The promise the pipeline keeps: after a live update, the picture looks as if nothing was laid out again, and the graph is what a full index would produce. The pipeline spans five projects: packages/indexer (the worker and CLI), packages/graph (file kinds, the columnar graph, the directory tree, layout extension), packages/core ( GraphService , Store , ChangeBatcher ), the host (its file lister and watcher) and packages/webview (the layout worker and the World ). Listing files The host lists the files. In a git work tree that is git ls-files , so .gitignore applies; otherwise VS Code's findFiles with a fallback exclude list, or, in the desktop app, a walk of the folder with the same list. ALWAYS EXCLUDED DIRS ( node modules , .git , bower components , vendor , .harness ) are skipped even when tracked. The maxFiles setting (default 20,000, at least 100) truncates larger workspaces. Which files become nodes is decided by classify() in packages/graph/src/languages.ts , by basename, then basename pattern, then extension. Each class names the file's kind (its colour and legend entry) and its extractor : depcruise for JavaScript and TypeScript, a regex rule set for another language, config for configuration files, or none for a node without edges. Lock files and other generated output are left out. The same tables build the host's watcher glob, so a new extension is also watched. Language coverage Kind Extensions and files Imports read by TypeScript, JavaScript .ts .tsx .mts .cts .js .jsx .mjs .cjs ; .vue .svelte .astro dependency-cruiser, once per tsconfig / jsconfig so each package's path aliases apply; the regex scanner as its fallback Python .py .pyi .pyw .pyx .pxd , .ipynb Regex, resolved through pyproject.toml source roots Go .go Regex, through go.mod and go.work Rust .rs Regex, through Cargo.toml crates Java & Kotlin .java .kt .kts .scala .sc .groovy .gvy , .clj .cljs .cljc Regex C & C++ .c .h .cc .cpp .cxx .hpp .hh .m .mm .cu and more Regex C & .NET .cs .csx ( .fs .vb : nodes without edges) Regex, through .csproj root namespaces Swift .swift Regex, through SwiftPM targets Ruby .rb .rake .ru , Rakefile , Gemfile and friends Regex PHP .php .phtml , .blade.php Regex, through composer.json PSR-4 prefixes Dart .dart Regex, through pubspec.yaml names Elixir & Erlang .ex .exs .erl .hrl ( .gleam : no edges) Regex, Elixir modules under lib/<app HTML & templates .html .htm , Nunjucks, Jinja, Twig, Liquid, EJS, Pug, Razor Regex; some template languages are nodes without edges Styles .css .scss .sass .less .styl .pcss Regex Shell .sh .bash .zsh .fish , PowerShell Regex SQL & schemas .proto .graphql .gql ( .sql .prisma .thrift : no edges) Regex Infrastructure Terraform, HCL, Nix, Dockerfile , Compose files, Vagrantfile Regex, or the config scan Other code Haskell, Elm, Lua, Perl, R, Julia, Zig, Nim, Crystal, Solidity; OCaml, ReScript, Fortran, assembly, Verilog, Lisp and shader languages as nodes without edges Regex Project config .toml .yaml .yml .ini .gradle .cmake .csproj .sln and the manifests of every ecosystem ( package.json , tsconfig.json , go.mod , pom.xml , CMakeLists.txt , Makefile , Procfile , …) The config scan: entry points, extends and references, workspace members, modules, subdirectories, Dockerfiles Twenty kinds share eleven colours; kinds that seldom share a repository share one, and the legend lists them together. Indexing The worker ( packages/indexer/dist/indexer.mjs , started by IndexClient on a worker threads thread, or run as the orbit-index CLI): 1. Stats every file and reads the project manifests wherever they sit: package names, entry points and exports / imports maps, the tsconfig governing each file, Go modules, Cargo crates, Python source roots, PSR-4 prefixes, pubspec names, .csproj root namespaces and SwiftPM targets. 2. Runs dependency-cruiser over JavaScript and TypeScript in batches of 400, once per tsconfig or jsconfig. Bare specifiers it can't follow into the graph (a workspace package without node modules , an exports map, @/ ) go through Orbit's own resolver. 3. Runs the regex scanner over every other language, resolving through the same manifests, plus any batch dependency-cruiser threw on. The config scan links configuration files to what they name. 4. Resolves imports only against files in the graph, never against the disk. The graph ( graph.json ) holds the nodes (id, size, mtime, a count of unresolved local imports), the edges, the stats, projects (a fingerprint of what resolution took from the manifests) and a hash : sha1 over file ids, sizes and edges. The hash is computed the same way by a full index and an incremental run, so an unchanged hash means nothing drawn changed, and it keys the cached layout. Unresolved and external are different. An import a resolver can't match is either local but unresolved (counted on the node, and re-read when files are added) or external (never re-read). Which is which is decided by the manifests: a dotted Python import of a package under a source root is local, numpy is external. The cache Store keeps orbit-v1/ in the host's storage directory: graph.json and up to four layout-<hash .bin files, keyed only by the graph hash. Consequences: - After changing an extractor, run Reindex : live updates reuse the edges of files that didn't change, so they won't pick up an extractor change. - After changing the layout's output, Reindex lays out again, but a plain load with an unchanged hash reuses the old layout; bumping LAYOUT VERSION in packages/core/src/store.ts (2 at the time of writing) redoes it everywhere. - A layout-<hash .bin may be an extended layout written by a live update rather than a worker run; the format is the same. - GraphService keeps the loaded graph for the life of the host and updates it in place, so reopening the panel shows the current graph without re-reading the cache. Layout With no cached layout, the page runs the layout worker ( packages/webview/src/layout/nested.ts , inlined into the bundle and started from a Blob URL): - Every directory with files under it is a bubble inside its parent's bubble. Labels are in canonical order ( . first, parents before children, subtrees contiguous), and the hierarchy is derived from the labels alone. Past 65,535 bubbles the deepest levels are left out and their files sit in the nearest kept ancestor. - Bottom-up, one d3-force-3d simulation per directory places its own files and its already laid out sub-directory bubbles; the bubble's radius encloses them plus a margin. An import pulls at the one level where it is drawn. Bubbles sit on their parent's plane, a fixed gap apart; files in a directory without sub-directories spread in 3D. - Deterministic: fixed tick counts and a seeded random generator. The result goes back to the host as layoutComputed and is saved. The Flat view's layout ( flatLayout.ts ) is computed in the page when Flat is first shown and never cached. Live updates file created / changed / deleted / renamed │ the host's watcher (VS Code file system watchers; a recursive fs.watch in the desktop app) ▼ ChangeBatcher workspace ids, excluded directories dropped, 1.5 s debounce, 5 s at most; flushed when a turn ends ▼ GraphService refresh(): one update at a time, later requests merged, superseded by load() │ returns early when no path can matter; re-lists the files; stops if the ids are unchanged and no graph file was touched ▼ indexer worker gets the previous graph and the touched files; stats everything; re-extracts only what the change affects ▼ extendLayout kept files keep their position; bubbles keep centre and radius; new files and directories are placed ▼ GraphService writes graph.json and layout-<newhash .bin; logs graph updated <hash : +A −R ~C files, E edges in N ms ▼ controller an update delta if the page holds the base graph, else a reset carrying the layout ▼ the page a new World adopts the old one's animation state through the index remap; logs graph update applied What is re-read. New files; files whose size or mtime changed, or that the watcher reported; importers of removed files; files with unresolved imports when something was added; and everything when projects differs from the previous graph's (a package name, an exports map, a tsconfig or a source root changed) or when more than 30% would be read anyway. Other edges are reused. When the hash comes out unchanged, only the new mtimes and fingerprint are written and nothing is sent. Placing new things. extendLayout in packages/graph/src/layoutExtend.ts is pure and shared with the harness. A new file goes into its directory's bubble, near its placed import neighbours, else near that bubble's other files, else at its centre, at the nearest free spot. A directory without a bubble gets one inside its deepest existing ancestor, in a spot a sibling emptied by this update when that fits, else at the free spot nearest what its files import; short of room, it shrinks rather than overlap its siblings. A bubble with no room inside grows to take a spot just outside, and its parents with it as far as they must, unless that runs into anything beside them. Emptied bubbles are dropped. Renames. A rename or delete from the file card doesn't wait for the watcher: the controller calls refresh() as soon as the edit lands and passes the rename along, so a file renamed within its directory keeps its position and its animation state. A rename made in the editor or by git arrives as a delete and a create, and the file is placed like a new one. In the page. SceneController applies an update only on top of the graph it holds; otherwise it treats it as a reset (with the layout carried, so no worker run). The new World adopts shared uniforms, per-file state, comets in flight, Claude's stars and MCP stations, the directory the camera is inside of and any camera move under way. If that directory is gone, the camera backs out to its nearest remaining ancestor. Added files pulse: with an edit glow and the star moving to the last of them during a turn, with one self-fading pulse otherwise. Known staleness Live updates are incremental, so some imports go stale until Reindex: - An added file isn't linked from importers that treated the import as external rather than unresolved: single-name Python imports, JVM imports, Ruby require , C namespaces, Swift modules outside a SwiftPM package, Haskell, Elm, Lua, Perl and Clojure modules, and path strings in configuration files. - It isn't linked from importers whose import already resolved elsewhere (a closer candidate, the first files of a Go package, C namespace or Swift target). - A file that newly enters the listing without being created (a .gitignore or maxFiles change) needs some file event to be noticed. - dependency-cruiser returns no dependencies for some files depending on what else is in the same batch, so an incremental run can find edges a full index misses, or the reverse. - New files and directories are placed, not laid out. Sibling bubbles are packed close, so a directory that gains many files crowds them until Reindex. Running the indexer alone yarn index <dir --out graph.json [--max N] a full index; needs a prior build yarn index <dir --out next.json --previous graph.json an incremental run, as a live update does it npx @orbit-code/indexer <dir --out graph.json the published package Comparing an incremental run's hash with a full index of the same files is the check that the planner missed nothing."},{"url":"/docs/webview/","title":"The webview","section":"How it works","description":"The three.js scene, focus and levels, GPU picking, the frame loop, colours, the HUD, and the constraints it is built under.","headings":["Composition ","Layers ","Focus and levels ","GPU picking ","GPU animation ","The frame loop ","Colours ","Views and the tour ","The HUD ","Hard constraints ","Debugging "],"text":"packages/webview is the UI: a three.js scene and a DOM HUD, built into one dist/webview.js that VS Code's panel, the desktop window and the harness all load under the same Content Security Policy. It knows nothing about VS Code or Node: it talks to its host through HostBridge ( acquireVsCodeApi() when the page has it, else window.orbitHost ), and gets everything by message. Composition main.ts wires the modules together with callbacks; none of them imports another's instance. Module Owns host.ts HostBridge : typed post and on(type, handler) ; keep / kept for the little state a host persists (the chosen view) frameLoop.ts The visibility-aware, parking requestAnimationFrame loop at two paces: 30 fps for the ambient animation, up to 60 fps while the camera or the pointer moves scene.ts SceneController : a graph reset into a layout into a World ; activity into the World; live updates stage.ts The renderer, camera and controls (rotate and pan; the wheel is SmoothZoom 's). Steps the pixel ratio down (2, 1.5, 1) if frames keep missing 30 fps interaction.ts Hover and click through GPU picking; the tooltip; the grabbing cursor during a drag zoom.ts SmoothZoom : the wheel, eased over frames toward the point under the pointer, pulling a sub-directory bubble to the middle of the screen so that zooming into it opens it focus.ts What is on screen, derived from the camera alone: which directory is looked into, and the crossfade between a directory and the sub-directory being zoomed into dirView.ts What the view navigates: skipped directories (one holding nothing but one sub-directory), the root, and where the view starts world.ts and world/ Every layer for one graph and its frozen layout, and the state that must survive a live update layout/ The nested layout worker (inlined into the bundle as a string and started from a Blob URL) and its client flatLayout.ts , neurons.ts , orbits.ts The Flat view: files on orbits by project, the import arcs, the orbit rings and the core tour.ts Take a Tour hud/ The DOM: identity and breadcrumb, the view tabs, the tooltip, the drawer and composer, the Claude bubbles, the conversation view, the constellation (skills, history, MCP), the history panel, the file card, the editor sheet, the spark popup, the tour button and card, the status overlay and the performance readout constellation/ The skills, history and MCP panel's own WebGL canvas: a small force layout, instanced glow glyphs, a tesseract per skill, a gyroscope per conversation, a 16-cell per MCP server picking.ts , uniforms.ts , nodeState.ts , palette.ts GPU picking, the uniforms every material shares, the per-file state texture, the colours Layers World owns, for one graph: - NodeLayer : every file, one instanced mesh. - Bubbles : every directory. - EdgeLayer : every import, one draw object, with visibility and highlighting decided in the shader. - ParticleLayer : the comets of reads. - ClaudeLayer : a star per conversation with a turn under way (the first is always there; further ones fade in on a ring around its home), and a smaller star per subagent that comes out of its conversation's star and goes back into it. - McpLayer : a station per MCP server a conversation calls, orbiting that conversation's star on a beam. Everything keyed by node index (state texels, instance attributes, pick ids, label keys) is rebuilt for each World. State that must survive a live update is copied in World.adopt() through the index remap: the shared uniforms, per-file state, touched files, pending events, comets in flight, Claude's stars and their positions, MCP stations, the directory the camera is inside of, and which star is followed. Anything new that holds animation state needs an adopt too. Focus and levels The camera decides what is drawn. A directory is looked into while the middle of the screen is inside its bubble; zooming from framing the open directory toward framing a sub-directory crossfades from the one's contents to the other's on a log scale, and at the end the sub-directory opens. Zooming back out retraces the same curve, and so does panning until the middle of the screen leaves the bubble, so no way in or out jumps. A click, Esc or the breadcrumb eases the camera over 600 ms and opens only the directories on the way. In the shaders, a file shows while its own directory's contents are on screen (and wherever Claude is working on it), a bubble while its view parent's are, and the shown directory's own bubble is a faint frame. One level up, a directory's files, lines and sub-bubbles show through its bubble at reduced strength; two levels up, fainter still; so the import network stays visible and fires while Claude thinks, and the nesting reads while zoomed out. The directories beside the shown one keep a faint rim each. Each import is drawn in exactly one directory, the deepest holding both files, as a segment between the two things shown there that contain them. GPU picking There is no raycasting. Each pickable mesh has a visible material and a pick material built from the same shader source, and a click or hover reads the id under the pointer from a render target, asynchronously. Ids are 24-bit: 0 is nothing, node i is i + 1 , cluster c is a base plus c , and a Claude star is another base plus its stable id. Only what the directory dominating the screen shows is pickable: its own files and its sub-directory bubbles; Claude's stars are pickable regardless. Hover picks run one per frame while the pointer moves, none during a drag. GPU animation Every material shares the uniforms from uniforms.ts by reference. Per-file activity lives in one float data texture: when it was read, when edited, its weight, when it was added. Shaders derive glow, pulses and fades from the current time and the time of rest, so the CPU writes a few texels per event and nothing per frame. A read's texel is written when its comet lands, not at launch, or each read would put out the glow already there; landings still in flight when the turn ends are dropped. A thought writes only a burst window, and the edge shader fires every segment it doesn't clip with a rhythm seeded by the source end's position, so all lines out of one file fire together and keep their rhythm across a live update. The frame loop frame() renders one frame and returns whether another is needed and at which pace: smooth while the camera moves or a hover pick is wanted, ambient for everything else, parked when nothing moves. While a turn runs the edges keep flowing, so the loop runs for the whole turn; while the page is hidden it renders nothing at all. Anything that starts moving without going through an input or a host message has to wake the loop, or the scene freezes. Colours palette.ts holds sRGB triples that the shaders write straight to the framebuffer, never routed through THREE.Color , which converts to linear. Colour is file type: each file's kind, and each directory's dominant kind, come from the node names, and the legend in the HUD is built from the same table. The kind colours sit at mid lightness, clear of the bright read, edit and violet activity colours, and were checked in OKLab for the kinds that meet in one repository. Views and the tour World.setMode animates a shared mix from Nested (0) to Flat (1). The file mesh morphs rather than a second mesh appearing: the vertex shader flies each file on an arc once its delay has passed and prints its icon from an atlas drawn once on a canvas; bubbles and the Nested lines fade out as the mix rises, and the neurons and orbits fade in. In Flat, focus is frozen from above the orbits' plane, the breadcrumb is the root alone, and the wheel closes in on the file under the pointer. The tour picks each stop at random (hubs and large files weighted up, nowhere twice until every candidate has been) and flies there through Focus : out to the directory holding both places on the bearing the camera already has, then in to the stop on a new one. Some stops get a card with facts worked out from the graph alone. While it runs, hover, clicks, Esc, the breadcrumb, the wheel and the tabs are off; a live update re-finds the stop by id, and a reset ends the tour. orbit.startTour({ seed, cards }) makes a run repeatable for the harness. The HUD - Composer. The drawer and the conversation view each hold one: skill and file chips, the input, and a bar of Files, Skills, the model, the effort meter, the permission mode and Send (Stop, in the view, while the conversation works). The drawer adds History and the MCP button. A slash typed alone opens the Skills constellation filtered to the text after it; Enter attaches the pick in place of the command. - Constellation. Draws on its own transparent canvas over the whole viewport, only while open. Its glyphs take input through transparent buttons kept over them (a skill is dragged with pointer capture and dropped on the composer that opened the panel); nothing in it is a raycaster either. Under History, a two-thumb time range narrows the conversations shown. - Markdown replies are rendered as DOM nodes with text nodes, never HTML. - File card and editor sheet. The card is placed beside its file on every rendered frame; Rename and Delete morph the card into a prompt in place. The sheet is CodeMirror 6 with the page's nonce for its styles; Changes is a unified merge view against the base the host sent. A Mod shortcut the editor handled stops propagating, so VS Code's webview host doesn't undo or save a second time. - Esc order. One capturing listener on the window cancels a skill drag, else closes the constellation, the conversation view, the history panel or the drawer; the file card's listener runs before it, and the editor sheet's (on the document) after, before Interaction sees the key. Hard constraints The constraint check in packages/webview/build.mjs runs before every build and fails it if any source under packages/webview/src/ : - mentions Raycaster (picking is GPU only); - calls the synchronous readRenderTargetPixels( ; - contains http:// or https:// , comments included (the check is a plain regex); - uses fetch( , XMLHttpRequest or importScripts( (data arrives only by message). It also requires exactly one instanced mesh in nodes.ts and exactly one line, mesh or points object in edges.ts : all files are one draw call, and so are all lines. Also required, though no build checks them: three.js is bundled locally; styles are injected with the script's nonce (set styles from code through element.style , never a style attribute string, which the CSP blocks); the page never touches files; and the constellation is no exception to GPU picking of the graph. Debugging Developer: Open Webview Developer Tools in VS Code, View › Toggle Developer Tools in the desktop app, or the harness's Chrome. window. orbit exposes debug (frames, fps, CPU time per frame, draw calls, live updates applied), world() , project(x, y, z) (world to screen), camera() , view() , constellation() , editor() , fileMenu() , tour() , startTour() and endTour() . The performance readout at the bottom right shows the frame rate, CPU time, draw calls and pixel ratio. The harness ( yarn harness ) runs this bundle in headless Chrome against a simulated host and writes a report and screenshots; Contributing says how to read it."},{"url":"/docs/hosts/","title":"Hosts","section":"How it works","description":"What VS Code and the desktop app each provide, and what a third editor would have to.","headings":["What every host provides ","VS Code ","The desktop app ","Adding an editor "],"text":"A host is the program that runs the engine and shows the page. There are two: the VS Code extension ( apps/vscode ) and the Electron desktop app ( apps/desktop ). Everything else is shared: the services in packages/core and packages/agent , the controller, the protocol, the indexer and the UI bundle. What is host code is only what the editor or the app does that the other can't. What every host provides Duty VS Code Desktop app Shared piece it stands on Storage directory, first folder, trust, a logger extension.ts workspace.ts , trust.ts , logger.ts The options of GraphService and SessionService The file list graph/files.ts : git ls-files in a work tree, else workspace.findFiles listFilesOnDisk : git, else a walk @orbit-code/indexer/listFiles File events graph/watcher.ts : VS Code's file system watchers watcher.ts : one recursive fs.watch ChangeBatcher in core The indexer worker build.mjs copies dist/indexer.mjs The same, unpacked from the asar @orbit-code/indexer A page for the UI under the panel's CSP, with a transport panel/orbitPanel.ts : a webview panel page.ts (the orbit://app/ scheme), window.ts , the preload webview.js ; HostBridge in the page Dialogs, notifications, the browser, editor tabs controller.ts ( PanelController ) workspace.ts ( Workspace ), notifications.ts HostUi in packages/core/src/controller.ts File requests from the card and the sheet fileActions.ts : workspace edits, documents, diff editors files.ts : the disk, behind the same revision check FileHost , fileHelpers.ts in core Settings under the same trust rules config.ts and package.json settings.ts : settings.json in user data normalizeSettings and applySettings in core Status while the page is out of sight statusBar.ts , permission notifications tray.ts (tray, dock badge), notifications.ts summarizeSessions in agent The controller is OrbitController in packages/core/src/controller.ts . A host gives it a HostUi , a FileHost and, once there is one, a WebviewTransport , and sends { type: 'host', capabilities } for what it can't do: the desktop app sets tabs: false , so the file card hides \"Open in a tab\" and transcript links open the editor sheet. VS Code apps/vscode is the extension imshaikot.orbit-code . Its package.json is the extension manifest. - Activation on onStartupFinished . activate() builds GraphService (storage from storageUri , dist/indexer.mjs , the first folder, the file lister), SessionService over ClaudeCliBackend , the controller, the watcher, the status bar item and the activity bar launcher, pushes them all into the context's subscriptions, and registers the commands. In development mode it opens the panel by itself, and ORBIT REINDEX=1 reindexes. - Commands are the orbit. entries in contributes.commands : Open, Ask Claude…, Stop Claude, New Claude Conversation, Reindex Workspace. orbit.prompt takes the prompt text as an argument and returns whether it was accepted, which the smoke test relies on. - Settings are orbit.maxFiles and orbit.claude. , read once by readSettings() , checked by normalizeSettings , and applied by applySettings on change (a new path or extra arguments probe the CLI again; a new model, effort or mode applies from the next prompt). claude.path , claude.extraArgs and claude.permissionMode are machine-scoped and listed in restrictedConfigurations , so a repository's .vscode/settings.json can't set them. - Workspace trust is limited : the graph works in an untrusted workspace, and sessions stay unavailable until it is trusted. - The panel is a webview panel with scripts enabled, retainContextWhenHidden (so the WebGL context and layout survive tab switches; the frame loop parks instead) and localResourceRoots of dist/ only. Its HTML carries the nonce'd CSP and one script. Creating the panel collapses the side bar, and so do Orbit Code: Open and the activity bar icon for a panel already open. - The activity bar entry is a view container with one empty view whose becoming visible hands the side bar back to the Explorer and opens the panel. - The status bar shows how many turns run, or wait for approval, while the panel is hidden or closed; a click opens it. A permission request while the panel is hidden becomes a warning notification with Allow, the request's \"don't ask again\" choice and Deny. - Logs go to the Orbit output channel, on disk under VS Code's logs directory as Orbit.log . Webview errors and log calls arrive prefixed [webview] . - Storage is <workspaceStorage /imshaikot.orbit-code/orbit-v1/ . The desktop app apps/desktop composes the same services for each open folder in Electron's main process, one Workspace per folder in its own BrowserWindow . Module ( src/main/ ) Owns main.ts App lifecycle: launch arguments ( --folder , --reindex , --disable-workspace-trust ), the single-instance lock, a window per folder (an empty window takes the next folder opened), the menu, the trust dialog, recent folders reopened at launch, the indexer's path under app.asar.unpacked workspace.ts Workspace , the desktop's HostUi : the services, the controller attached to its window, the watcher flushed when a turn ends; pickFiles is the open dialog, openExternal the system browser for http(s) only window.ts OrbitWindow , the WebviewTransport : messages accepted only from its own top frame on orbit://app/ , ready re-armed on every load, visibility on show, hide, minimise and restore, no navigation or new windows page.ts The privileged orbit scheme: the page with a fresh nonce and the panel's CSP (as a header and a meta tag), webview.js and its map, and nothing else ../preload/preload.ts Exposes one object with one function, window.orbitHost.postMessage , and delivers host messages as window message events files.ts DesktopFileHost : the same file requests on the disk, with the same revisions; delete through the system trash and refused otherwise; no tabs watcher.ts A recursive fs.watch into ChangeBatcher settings.ts , trust.ts , recent.ts JSON files in user data; the settings file is watched and applied with applySettings notifications.ts , tray.ts , shellPath.ts Permission notifications (with buttons on macOS), the tray and badge count, the login shell's PATH for an app started from the GUI logger.ts <userData /logs/orbit.log , rotated past 5 MB, with the same lines the extension writes Invariants the desktop keeps: the window is sandboxed, context-isolated and without Node; the scheme serves the page and its script only; settings come from user data only, never from a folder; trust gates Claude per folder; delete is to the trash or not at all; typed arrays cross the IPC intact (the smoke's relaunch step proves a saved layout is reused). Electron 44.3.0 and electron-builder 26.15.3 are pinned; both CI workflows skip Electron's binary download except in the desktop jobs. apps/desktop/README.md covers running it, its settings, where it keeps things and packaging. Adding an editor An editor app hands the shared host its facts and the shared UI a page; nothing else is rewritten. The checklist: 1. Scaffold apps/<editor (see The monorepo) with a runtime tag of its own, and add that runtime to the boundary check's table. 2. Supply each duty in the table above. apps/vscode is the reference for every one, and the desktop app for a host that embeds Chromium with Node beside it. 3. Serve webview.js in a page under the same CSP, define window.orbitHost.postMessage before the script runs, and deliver host messages as window message events. 4. Implement HostUi , FileHost and WebviewTransport , and send the host capabilities message for what the editor can't do. 5. Targets: build (depending on ^build , so the webview and indexer are built), package , an end-to-end check, and nx-release-publish ; a publish:<registry tag and a release group; jobs in ci.yml and release.yml . By editor: VS Code's family (Cursor, Windsurf, VSCodium, Positron) needs no new app, since the same .vsix is published to Open VSX. An editor that can't host a page (JetBrains through a JCEF tool window, Zed, Neovim) would start a local server app running the engine in one Node process, speaking the protocol over a WebSocket or stdio, and open its page."},{"url":"/docs/monorepo/","title":"The monorepo","section":"How it works","description":"Projects, tags, boundaries, TypeScript per runtime, Nx targets and caching.","headings":["Projects ","Where code goes ","The boundary check ","Packages ","TypeScript per runtime ","Targets ","Adding a library ","Moving code between packages ","Gotchas "],"text":"An Nx workspace (package-based, a project.json per project) over Yarn 4 workspaces, cut along one line: what a module needs from where it runs . Code that needs nothing but JavaScript is neutral and goes everywhere; code that needs Node runs in a host or the indexer worker; code that needs the DOM is the UI; only an editor app touches an editor's API. Projects Project Package Tags Holds packages/protocol @orbit-code/protocol lib, neutral Every message that crosses a boundary; workspace ids; MCP tool names packages/graph @orbit-code/graph lib, neutral File kinds, the columnar graph, the directory tree, layout extension, node radius packages/common @orbit-code/common lib, neutral Emitter , Event , Disposable , Logger , TickBatcher packages/indexer @orbit-code/indexer lib, node, publish:npm The indexer worker and the orbit-index CLI; listFiles for hosts packages/agent @orbit-code/agent lib, node Claude Code conversations, the CLI backend, stream-json, permissions, the catalog, history packages/core @orbit-code/core lib, node The editor-agnostic host: GraphService , Store , the indexer client, ChangeBatcher , SessionProjector , the controller, settings packages/webview @orbit-code/webview lib, browser The UI, built to dist/webview.js apps/vscode orbit-code app, vscode, publish:vscode The VS Code extension apps/desktop @orbit-code/desktop app, electron, publish:desktop The Electron desktop app tools/harness @orbit-code/harness tool, browser The simulated host and the headless Chrome harness neutral protocol ◄──── graph common ▲ ▲ ▲ node indexer ─────────┘ (+ protocol) │ agent ── protocol ─────────────────┤ core ─── agent, graph, protocol, common, indexer/listFiles browser webview ─ graph, protocol harness ─ graph, protocol, common vscode apps/vscode ─ core, agent, graph, protocol, common, indexer/listFiles; ships the indexer and webview builds electron apps/desktop ─ the same Where code goes Ask, in order: 1. Does it call an editor's API? That editor's app. 2. Does it need Node? If it extracts imports or needs dependency-cruiser or TypeScript: packages/indexer , from which only listFiles is imported outside. If it is about Claude sessions and knows nothing of graphs: packages/agent . If it joins graphs, sessions and files on behalf of a host: packages/core . 3. Does it need the DOM? packages/webview , or tools/harness for test pages. 4. Otherwise it is neutral. Part of a message's shape: packages/protocol . Computing on graphs, paths or layouts: packages/graph . Plumbing any service needs: packages/common . A service in core or agent takes editor facts as options and never asks an editor. A neutral package doesn't gain a Node or DOM import to save a copy: if two runtimes need the same logic, the logic is neutral and each runtime wraps it. A new package is worth its manifest only when it holds a different runtime or a different reason to change. The boundary check yarn boundaries ( tools/scripts/check-boundaries.mjs , also in CI) reads each project's src/ imports and package.json and fails on: - Tags. Anything but exactly one type: (lib, app, tool) and one runtime: (neutral, browser, node, vscode, electron) per project. - Type. A lib depending on anything but libs; an app or tool depending on an app or tool. - Runtime. Neutral imports neutral; browser imports neutral and browser; node imports neutral and node; vscode and electron import neutral and node too. Only runtime:vscode imports vscode . Node builtins are refused in neutral and browser code. - Declared. An import of a package the project's package.json doesn't list, or a relative import leaving the project's src/ . - Indexer. Any @orbit-code/indexer import but listFiles outside the indexer, so dependency-cruiser and TypeScript stay in the worker. It can't see what a bundle pulls in transitively (the extension's wiring check follows the extension's imports into every package for that) or globals: a DOM or Node global in the wrong package fails that package's own typecheck instead, because its tsconfig doesn't declare it. Packages - A workspace package exports its sources module by module: \"./ \": \"./src/ .ts\" . Consumers import @orbit-code/graph/languages , and typecheck and bundle each other's sources directly; there is no build step between libraries and no barrel. - dependencies are what the code imports (workspace packages as workspace: , npm packages pinned exactly, the same version everywhere). devDependencies are types and the builds a project only ships ( @orbit-code/webview for the extension and the harness). The desktop app lists every workspace package as a devDependency, because electron-builder copies an app's dependencies into the package and everything is bundled already. - Workspace-wide tools ( nx , @nx/js , typescript , esbuild , @types/node ) are root devDependencies. - .yarnrc.yml keeps a node modules tree, because the build scripts and tools run under plain Node, which can't resolve Plug'n'Play packages. Workspace packages are symlinked into node modules/@orbit-code/ . - Names are @orbit-code/<dir ; the extension keeps orbit-code , its Marketplace identity. Manifests are private: true with a one-sentence description and license: MIT . TypeScript per runtime tsconfig.base.json holds the compiler options ( noEmit , moduleResolution: bundler , strict). Each project's tsconfig.json extends it and adds only its runtime: Runtime Adds neutral ES2023 and tools/typescript/neutral-globals.d.ts (timers and console , nothing else) node \"types\": [\"node\"] browser the DOM libs vscode \"types\": [\"node\", \"vscode\"] electron \"types\": [\"node\"] and the DOM (the preload sees the page's window); Electron's types come with its package A project's typecheck also checks the workspace sources it imports, under its own settings. esbuild doesn't typecheck, so yarn typecheck is a separate step. The build.mjs files, scripts/ , test/ and tools/ / .mjs are plain JavaScript and aren't typechecked; node --check catches syntax errors in them. Targets Target Projects Does build indexer, webview, vscode, desktop, harness node build.mjs [--production] in the project, writing its dist/ . Depends on ^build , so -c production reaches the dependencies. The webview's runs its constraint check first typecheck every project tsc -p tsconfig.json , from the target defaults watch the same as build build.mjs --watch , continuous; vscode:watch starts the indexer and webview watches and copies their builds in as they change package vscode, desktop A production build, then vsce or electron-builder. Uncached for the desktop, whose output is per platform smoke , e2e , start vscode, desktop, harness Uncached, after a build nx-release-publish indexer, vscode, desktop npm, the Marketplace and Open VSX, or GitHub release assets Each build writes nothing outside its own dist/ ; an app copies what it ships from its dependencies' dist/ . Nx caches build and typecheck in .nx/cache from each project's files, tsconfig.base.json , tools/typescript and the sources of what it depends on, but not the environment: after upgrading Node or changing a build script's environment, yarn nx reset . Nx passes a configuration along ^build but not along a dependsOn on the same project, which is why vscode:package runs nx run vscode:build:production itself, and why package.mjs refuses a dist/ still holding source maps. yarn nx show projects every project yarn nx show project vscode --json its targets yarn nx graph the project graph in a browser yarn nx run-many -t typecheck build -p webview indexer yarn nx affected -t typecheck build --base=main yarn nx reset clear the cache and stop the daemon Arguments after a root script reach the script Nx runs: yarn harness --graph g.json runs the harness on that graph after Nx built what it needs. Adding a library 1. Create packages/<name with a package.json (source exports, private: true ), a project.json (name, projectType , sourceRoot , tags, \"typecheck\": {} ), a tsconfig.json for its runtime and src/<name .ts . 2. yarn install , which links it into node modules/@orbit-code/ . 3. Add \"@orbit-code/<name \": \"workspace: \" to the dependencies of each project that imports it, then yarn install again: the lockfile records workspace dependencies, and CI installs with --immutable . 4. yarn nx show project <name , yarn typecheck , yarn boundaries . 5. Add its row to the table above and to the root README's layout table. Moving code between packages 1. git mv the file, so history follows it. 2. Imports inside the new package become relative; imports of it from elsewhere become @orbit-code/<pkg /<module . 3. Update the dependencies of the package that lost it, the one that gained it, and every importer; yarn install . 4. yarn typecheck && yarn boundaries && yarn build . 5. Grep the old path: the smoke suite names graph files, the harness names sources, and ci.yml checks that packages/webview/src/main.ts is in the indexed graph. Gotchas - node modules/@orbit-code/ are relative symlinks. A copy of the workspace whose node modules is a symlink to this one builds this workspace's sources; run yarn install in the copy. - A new project shows up in Nx from its project.json , but nothing resolves @orbit-code/<name until yarn install has linked it. A target that \"doesn't exist\" right after adding it: yarn nx reset . - Neutral means neutral: TextEncoder , URL , structuredClone and performance aren't ECMAScript, so neutral packages don't have them unless neutral-globals.d.ts declares them. - The test runners write into the workspace: the smoke creates packages/graph/src/ orbitSmoke .ts for a few seconds, and the harness writes .harness/ at the root."},{"url":"/docs/contributing/","title":"Contributing","section":"How it works","description":"The dev loop, where a change goes, the checks, CI and the commit conventions.","headings":["Setting up ","The dev loop ","Where a change goes ","The checks ","The harness ","The smoke tests ","CI ","Commits "],"text":"Setting up yarn install Yarn 4 (the version is pinned in package.json); also downloads Electron for the desktop app yarn build every project through Nx, dependencies first yarn typecheck yarn boundaries Node 22 is what CI runs. ELECTRON SKIP BINARY DOWNLOAD=1 yarn install skips the Electron binary on a machine that won't run the desktop app. The dev loop yarn self (or F5, \"Orbit Code: this repository\") Builds, then opens this repository in an Extension Development Host with apps/vscode as the extension and other extensions disabled. The panel opens by itself yarn self --reindex The same, ignoring the cached graph. If VS Code is already running, the new window may not see the variable; use Orbit Code: Reindex Workspace yarn watch Rebuilds the extension, the indexer and the webview on change and copies the builds into apps/vscode/dist/ yarn desktop --folder . Builds and opens this repository in the desktop app ( --reindex , --disable-workspace-trust ) yarn nx run desktop:watch The desktop equivalent of yarn watch yarn harness The webview in headless Chrome against a simulated host; a report and screenshots in .harness/out/ yarn index . --out graph.json The indexer alone After a rebuild with a watch running: Developer: Restart Extension Host (or a window reload) for the extension bundle; close and reopen the panel, or Developer: Reload Webviews , for the webview bundle (the host resends its snapshot); nothing for the indexer, since each index run starts a new worker, though an extractor change still needs Reindex to show. In the desktop app: restart it for the main process, View › Reload for the page. yarn watch doesn't reload a build.mjs or the indexer's shims; restart it after editing them. ORBIT CODE points yarn self at another code CLI, for Cursor or VSCodium. Where a change goes To change Edit Then also A command contributes.commands in apps/vscode/package.json ; registerCommand in extension.ts , delegating to the controller or a service The command list in apps/vscode/README.md A setting contributes.configuration ; readSettings() in apps/vscode/src/config.ts ; normalizeSettings and applySettings in packages/core/src/settings.ts (both hosts) Machine scope and restrictedConfigurations if it changes what runs; both READMEs' settings tables A host → page message HostToWebview in packages/protocol/src/protocol.ts ; post it from packages/core/src/controller.ts , and in sendSnapshot() if it is state; handle it in main.ts and wake the loop if it changes what is drawn tools/harness/src/hostSim.ts ; PROTOCOL VERSION A page → host message WebviewToHost ; a callback in main.ts ; a validated case in OrbitController.onMessage() . What only an editor can do goes through HostUi , which both apps implement receive() in hostSim.ts ; PROTOCOL VERSION The page's CSP apps/vscode/src/panel/orbitPanel.ts Identically in page() of tools/harness/harness.mjs and contentSecurityPolicy in apps/desktop/src/main/page.ts A HUD control packages/webview/src/hud/ , styles.css , wiring in main.ts The harness's selectors, if a class is renamed Claude CLI arguments or process handling packages/agent/src/claudeCli.ts ; AgentStartOptions in backend.ts sameOptions() in conversation.ts , if a change must restart the process; a real smoke turn Stream-json parsing packages/agent/src/streamJson.ts (pure; unknown shapes return nothing); onAgentEvent() in conversation.ts A real smoke turn What a tool call animates or shows packages/agent/src/tools.ts , packages/core/src/sessionProjector.ts The host simulator's scripted turn, if the page must react to something new A file operation apps/vscode/src/fileActions.ts and apps/desktop/src/main/files.ts together; shapes in packages/core/src/fileHelpers.ts Status bar, notifications, tray apps/vscode/src/statusBar.ts , apps/desktop/src/main/tray.ts and notifications.ts ; the wording in packages/agent/src/sessionSummary.ts is shared An extractor, live updates, the layout The graph pipeline Reindex to see an extractor change; a full index compared with an incremental run Anything keyed by node or cluster index in the page World and its layers An adopt for any new animation state A build, loaders, shims The project's build.mjs ; packages/indexer/shims/ Restart the watch Some rules that follow from the constraints in Architecture: page messages are checked in the controller before a service sees them; text goes into the DOM as textContent ; anything that ends up on a command line is validated; a new service is wired in extension.ts and workspace.ts rather than made a dependency of another service; and a service that needs no editor API belongs in packages/core or packages/agent . The checks There is no unit-test runner and no linter. The checks are: Change Run Anything yarn typecheck , yarn build , yarn boundaries The manifest, commands, settings, views, activation also yarn smoke The panel, CSP, HUD, frame loop, protocol, the shared controller also yarn harness (then read the report), yarn smoke and yarn desktop:smoke The session, CLI arguments, stream-json, tools also ORBIT SMOKE PROMPT=\"Read package.json and reply with its name\" ORBIT SMOKE MODEL=haiku yarn smoke , a real and paid turn A build.mjs , the shims, a dependency upgrade also yarn index . --out .harness/check.json , the harness, the smoke, and yarn package The indexer, live updates, the layout an incremental run's hash against a full index's; the harness's liveUpdate check; the smoke's live-update step apps/desktop also yarn nx run desktop:build and yarn desktop:smoke ; yarn desktop:package after touching packaging The harness yarn harness builds the webview, the indexer and the host simulator, indexes this repository (or takes --graph ), serves the page under the real CSP, and drives headless Chrome over the DevTools protocol with real mouse and keyboard events. It covers where the view opens, camera motion, zooming into a bubble, drilling in and out, a thought, a live update, the Claude bubble and transcript, the spark, a subagent, two conversations at once, the drawer and the conversation view, the composer's toggles, the file card and the editor sheet, the Flat view, the tour, the frame rate cap, and the loop parking when hidden and idle. It does not fail on a bad result : it only throws if the scene never loads. Read .harness/out/report.json and the screenshots next to it. A quick summary: node -e 'const c = require(\"./.harness/out/report.json\").checks, l = c.liveUpdate ?? {}, p = c.promptRoundTrip; console.log({ fps: c.frameLoop.measuredFps, drill: c.drill?.matchesHover, fileHover: !!c.hoverFile?.tooltip, esc: c.escapeToOverview, live: l.appliedInPlace && l.stillDrilledIn && l.newLayoutComputed === 0, thinkingPixels: c.thinking?.litPixels, prompt: p.hostReceived && p.inputCleared && p.actionWhileWorking === \"Stop\", hiddenFrames: c.hiddenStopsLoop.framesWhileHidden, idleFrames: c.idleParks.framesWhileIdle, consoleClean: c.consoleClean })' Expect a frame rate of about 30, every boolean true, zero frames while hidden and while idle, and lit pixels above zero. Chrome is expected at /Applications/Google Chrome.app/… ; pass --chrome <path otherwise. Other options: --graph , --out , --size 1440x900 , --scale , --skip-build . Compare a red check with a run on main before calling it a regression: a few checks depend on the graph under test. The smoke tests yarn smoke builds the extension, downloads VS Code into .vscode-test/ ( ORBIT VSCODE VERSION pins a version), opens a real window on this workspace for about a minute with a fresh user-data directory, and exits non-zero on failure. It waits for the graph, the saved layout, the scene and the Claude Code probe; with ORBIT SMOKE PROMPT it runs one real turn (keep the prompt read-only, since nobody answers permission requests); it reopens the panel from the activity bar, writes and deletes a file to see the live update, and drives the file requests (read, write, rename, delete) through the API the extension exports. It fails on any error line in Orbit.log , so failures that aren't Orbit bugs are logged as warnings. If it is killed mid-run, delete packages/graph/src/ orbitSmoke .ts yourself. yarn desktop:smoke starts the desktop app on a small folder it writes under .harness/desktop-smoke (a git repository of its own, since this one ignores .harness ), with its own user data, waits for the scene and the probe, checks the saved layout, writes a file and waits for the watcher's update, then starts the app again and checks the layout is reused. About 5 s. Both smokes grep specific log lines ( scene ready for graph , agent: / agent unavailable: , [webview] layout of , turn done in , graph updated , [webview] graph update applied ); don't reword them. CI .github/workflows/ci.yml runs on every push to main and pull request, on Ubuntu with Node 22: 1. check : yarn typecheck , yarn build , yarn boundaries , and an index of this repository that must hold at least 40 files and edges, dependency-cruiser results, and packages/webview/src/main.ts . Its Nx cache is saved for the jobs after it. 2. In parallel: package ( yarn package , the .vsix uploaded as an artifact), smoke under xvfb-run (no claude on the runner, so the probe logs agent unavailable ), harness with the runner's Chrome (the report and screenshots uploaded), and desktop ( yarn desktop:smoke , then yarn desktop:package --linux AppImage , uploaded). Only the desktop job downloads Electron. Releasing covers release.yml . Commits Commits read as a sequence of small, self-contained steps: - One commit per module or subtask. A change touching several areas is split, in the order a reader would want (a shared type before the code that uses it, the host before the page that talks to it). A one-line fix or a rename touching everything is one commit; the rule is about not bundling distinct work. - The subject line is the message , under 100 characters, in the repository's own shape: <Area : <description when the change sits inside one module ( Webview: , HUD: , Indexer: , Session service: , Graph service: , Extension host: , Desktop: , Harness: , README: ), or a plain sentence when it doesn't. Lowercase description, no trailing period. Add a body only when the change genuinely needs explaining. - No attribution trailers. No Co-Authored-By , no \"Generated with\" footer. - Stage files by name, so an unrelated in-progress file never rides along. git log --oneline is the reference for the convention."},{"url":"/docs/releasing/","title":"Releasing","section":"How it works","description":"Version plans, release groups, tags, and publishing to the Marketplace, Open VSX, GitHub releases and npm.","headings":["Release groups ","Cutting a release ","What the release workflow does ","The server's first release ","Packaging locally ","Before the first publish from a fork "],"text":"Releases are cut with Nx Release using version plans , because commit subjects here aren't conventional commits. A plan is a small Markdown file that says what gets which bump and why; nx release applies the plans, writes the changelogs, commits and tags; pushing the tag makes .github/workflows/release.yml build and publish. The VS Code extension and the desktop app are released together: one version, one v<version tag, one GitHub release holding the .vsix and the macOS dmgs, with that version's changelog as its notes. Release groups nx.json defines two groups, by tag: Group Projects Tag Publishes to apps apps/vscode ( publish:vscode ) and apps/desktop ( publish:desktop ), fixed: one version for both v<version A GitHub release with the .vsix and a dmg each for Apple silicon and Intel; the Visual Studio Marketplace and Open VSX npm packages/indexer , apps/server and any other publish:npm project, versioned independently <project -v<version npm, with provenance A dependent is never bumped for a dependency's release ( updateDependents: never ), and a minor bump below 1.0 is applied as a minor, not a patch ( adjustSemverBumpsForZeroMajorVersion: false ). Each project gets its own CHANGELOG.md , without author lines; there is no workspace changelog. Cutting a release 1. With the change worth releasing , write a plan and commit it in the same commit: sh yarn nx release plan patch --groups=apps -m \"Fix the file card's position after a live update\" yarn nx release plan minor --projects=indexer -m \"…\" This writes .nx/version-plans/version-plan-<time .md . The message becomes the changelog entry and the GitHub release notes, so write it for users. Name the apps group rather than one app: a plan naming only vscode still bumps both, but leaves the desktop app's changelog a bare \"version bump only\" line. Add --only-touched=false when the change touched none of the apps' files. 2. Check, then release: sh node .claude/skills/release/scripts/check-release.mjs the group, the plans, the files the packages take from git, the workflow yarn nx release --dry-run --skip-publish what would happen yarn nx release --skip-publish node .claude/skills/release/scripts/check-release.mjs again: the tag, its files and its notes nx release bumps both apps to the same version, updates yarn.lock , writes each project's CHANGELOG.md , deletes the applied plans, commits (the subject Release , with a line per release group in the body) and tags v<version . The commit must carry no attribution trailer. A bump without a plan is yarn nx release patch --groups=apps . release.git is set at the top level, so the version and changelog subcommands refuse to run on their own. 3. To publish , push the commit and the tag: sh git push origin main git push origin v<version What the release workflow does release.yml runs on tags v and -v . - v<version , in four jobs; nothing is released unless every one succeeds: 1. check : the tag must match both apps/vscode/package.json and apps/desktop/package.json ; tools/scripts/release-notes.mjs must find the version in the changelogs (a tag not cut by nx release stops here) and writes the notes to the job summary; yarn typecheck . 2. vscode (Ubuntu): yarn package , uploading the .vsix as an artifact. 3. desktop (macOS, beside vscode): yarn desktop:package --mac dmg --arm64 --x64 , uploading orbit-code-<version -mac-arm64.dmg and orbit-code-<version -mac-x64.dmg . 4. release : tools/scripts/github-release.mjs creates the GitHub release v<version with the three files. Its notes are that version's entries from both changelogs, each once, then the downloads, then GitHub's compare link. After that, apps/vscode/scripts/publish.mjs publishes the .vsix to the Marketplace with VSCE PAT and to Open VSX with OVSX PAT , skipping a registry without its secret. Re-running a failed release job uploads to the release a previous run created. The dmgs are unsigned: macOS gets an ad hoc signature so it runs on Apple silicon, and asks before the first launch. Windows and Linux installers aren't released; yarn desktop:package makes them on those platforms, and CI builds the AppImage as a check. - <project -v<version : yarn typecheck , that project's production build, then nx release publish --projects=<project to npm with provenance ( NPM TOKEN ). nx release publish doesn't run a target's dependencies, hence the explicit build; --projects and --groups can't be combined. The server's first release @orbit-code/server ( apps/server ) is released on its own, starting at 0.1.3. Its manifest holds 0.1.2, a version never published, and Nx reads the current version from the manifest, so the first plan is a patch: yarn nx release plan patch --projects=server -m \"…\" yarn nx run server:build -c production && yarn nx release publish --projects=server --dry-run the tarball yarn nx release --dry-run --skip-publish --projects=server 0.1.2 → 0.1.3, apps/server/CHANGELOG.md, tag server-v0.1.3 yarn nx release --skip-publish --projects=server git push origin main server-v0.1.3 Don't commit a server plan before the server does what its README promises: nx release without --projects applies every pending plan, so the next apps release would version and tag the server too. The published package is apps/server/dist , written by its build: server.mjs (the orbit-server bin), indexer.mjs and webview.js , with the manifest, README and LICENSE, and no dependencies. Packaging locally yarn package a production build, then vsce → dist/apps/vscode/orbit-code-<version .vsix yarn install-local package, install into VS Code with --force, then the dev build again; reload open windows yarn desktop:package --mac dmg --arm64 --x64 what the release builds: both dmgs in dist/apps/desktop yarn desktop:package the platform's default targets (dmg and zip, NSIS, AppImage) node tools/scripts/release-notes.mjs v<version the release notes node tools/scripts/github-release.mjs v<version <files … --dry-run what the release job would upload node apps/vscode/scripts/publish.mjs --dry-run - The .vsix holds only what apps/vscode/.vscodeignore whitelists: the manifest, README, LICENSE, CHANGELOG, media/ , and the three bundles. Everything the extension runs is bundled, so vsce runs with --no-dependencies . package.mjs refuses a dist/ that still holds source maps, the sign of a dev build. vsce and ovsx aren't dependencies: yarn dlx fetches the versions pinned in apps/vscode/scripts/vsce.mjs . - The desktop bundle holds app.asar (the manifest, main.js , preload.js , webview.js , the icons) and dist/indexer.mjs unpacked beside it, since a worker can't start from inside an asar. electron-builder is pinned in apps/desktop/scripts/builder.mjs . Signing and notarization are follow-ups described in apps/desktop/README.md . - A file the packages take from the repository (an icon, the README) must be committed before tagging: CI builds the tag, not your working tree. check-release.mjs checks this. - A publishable npm package publishes a dist/ whose manifest its build writes (name, version, description, license, repository, bin or exports ); the source manifest stays private: true with source exports. packages/indexer/build.mjs is the model. Provenance needs the repository to be public and repository in the published manifest. Before the first publish from a fork - Set the extension's publisher in apps/vscode/package.json to your Marketplace publisher, and create an Open VSX namespace of the same name. - Add the secrets the jobs read: VSCE PAT , OVSX PAT , NPM TOKEN . The GitHub releases use the workflow's own token. Without VSCE PAT and OVSX PAT , upload the .vsix from the GitHub release by hand. - The @orbit-code scope on npm must belong to the account or organization behind NPM TOKEN . Neither @orbit-code/indexer nor @orbit-code/server has been published yet. - Dry-run everything: yarn nx release --dry-run --skip-publish , yarn package && node apps/vscode/scripts/publish.mjs --dry-run , and yarn nx run indexer:build -c production && yarn nx release publish --projects=indexer --dry-run (the same for server )."}]}