6.3 KiB
CLAUDE.md
Context for working on this repository. Feature-level detail lives in
docs/features/ — read the relevant file there before
changing a subsystem.
What this is
A Nova extension that puts Claude Code inside Panic's Nova editor: a sidebar for the session, a live Markdown transcript, and tool approvals reviewed in the editor.
It drives the user's own claude executable as a subprocess, speaking the
CLI's newline-delimited JSON protocol over stdio. There is no bundled copy of
Claude Code, no Node sidecar, no npm dependencies, and no network access of its
own.
Hard constraints (do not re-derive these)
Nova extension API
| Constraint | Consequence |
|---|---|
| No webview / HTML UI for extensions | The chat surface is a Markdown tab, not a rendered view |
| Sidebar sections are TreeView only | No text fields, forms, or the multi-line box Nova's own Source Control sidebar has |
| No WebSocket in the JS runtime | The IDE bridge needs a Node sidecar |
| No terminal API | Cannot create a terminal or send it text |
| No diff viewer API | Diffs are rendered text; proposals are editable staged files |
| Cannot close tabs programmatically | Never promise to clean up a tab |
Can only read its own IssueCollection |
No access to other extensions' diagnostics |
| No multi-line input | Long-form input = open a scratch document |
Commands may only be placed in editor, extensions, command-palette, text |
No file-browser context menu |
| Untitled documents cannot be saved without a dialog | Stage real files when a save gesture matters |
The runtime is JavaScriptCore with fetch, Streams, TextEncoder/Decoder,
setTimeout, nova.crypto — not Node. No require of npm packages; only
relative require("./x.js") between Scripts/ files.
Type definitions worth having open: npm pack @types/nova-editor-node (it is a
single index.d.ts, and is more precise than the website).
Claude Code CLI — see session transport for the full protocol. The three facts that cost the most to find:
--permission-prompt-tool stdiois required for permission prompts to reach the host, and is absent fromclaude --help. Without it every tool needing approval is silently auto-denied.- One assistant message id arrives multiple times (per completed content block, plus streaming deltas). They must be merged, not appended.
- A
can_use_toolcontrol request must always be answered, including when the handler throws — the CLI blocks on it and the session hangs otherwise.
Policy — do not implement claude.ai OAuth in this extension. Anthropic's
Agent SDK terms bar third-party products from offering claude.ai login. Sign-in
shells out to claude auth login. See authentication.
Layout
extension.json manifest: sidebar, commands, config, entitlements
Scripts/
main.js Controller: lifecycle, commands, wiring
client.js CLI process + stream-json/control protocol
session.js conversation model (entries, tools, changed files)
permissions.js can_use_tool → approvals, plans, questions, proposals
transcript.js Markdown tab rendering
sidebar.js four TreeView providers
diff.js change reconstruction + unified diff
auth.js claude auth status/login/logout
cli.js binary resolution, environment, keychain
sessions-store.js reading ~/.claude/projects for resume
util.js conf, throttle, Signal, runCommand, processWriter
docs/features/ one file per capability; read before changing one
Conventions
- 4-space indent, CommonJS, no build step, no dependencies.
- Comments explain why, especially where a workaround encodes an API limitation. Several exist purely to stop a future reader "simplifying" a constraint back into a bug.
- User-visible strings are sentence case and plain English; no jargon from the
protocol leaks into the UI (
friendlyToolName,modeLabel). - Config keys are namespaced
claudenova.*; read them throughutil.conf(), which prefers the workspace value.
Testing
The extension has never been run inside Nova. Everything below is verified outside it; a first in-Nova run is still owed. Load it with:
nova extension validate . # manifest + bundle (makes network calls — needs connectivity)
nova extension activate . # load into Nova for development
Logs (nova.inDevMode() output) appear in Nova's Extension Console.
Node harness. The Nova API is stubbable, so the transport and model can be driven under Node against the real CLI. This caught four real bugs and is the highest-value testing move available:
global.Process = class { /* wrap child_process.spawn; onStdout/onStderr/onDidExit */ };
global.Range = class { constructor(start, end) { this.start = start; this.end = end; } };
global.nova = { environment: process.env, path, fs, workspace, extension, crypto,
inDevMode: () => true, /* … */ };
const { ClaudeClient } = require("./Scripts/client.js");
Then wire onPermission to scripted answers and send a prompt. Stub
showChoicePalette / showActionPanel / showInputPanel / notifications.add
to script UI decisions and assert on the resulting PermissionResult.
The harness scripts from the build session lived in a temporary scratchpad and are gone. Recreating them in a
Tests/folder is the obvious next improvement.
Live tests cost real money — they run turns on the signed-in account. Use
--model haiku and one-line prompts; a full verification pass is a few tens of
cents.
Cheap checks that need no CLI:
for f in Scripts/*.js; do node --check "$f"; done
python3 -c "import json; json.load(open('extension.json'))"
State
- Not yet committed. Not yet published.
min_runtimeis declared as10(nova.cryptois the newest API touched, and it has a fallback). nova extension validatelast passed cleanly on the schema; a later run failed only on network reachability from a sandbox, not on the bundle.- Icons are generated by a script (a tapered starburst rendered to PNG with a pure-stdlib encoder); regenerate rather than hand-editing if sizes change.