Files
claude-nova/CLAUDE.md
T
2026-08-11 12:38:02 -03:00

137 lines
6.3 KiB
Markdown

# CLAUDE.md
Context for working on this repository. Feature-level detail lives in
[`docs/features/`](docs/features/README.md) — 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](docs/features/ide-bridge.md) 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](docs/features/session-transport.md)
for the full protocol. The three facts that cost the most to find:
1. `--permission-prompt-tool stdio` is **required** for permission prompts to
reach the host, and is absent from `claude --help`. Without it every tool
needing approval is silently auto-denied.
2. One assistant message id arrives multiple times (per completed content block,
plus streaming deltas). They must be merged, not appended.
3. A `can_use_tool` control 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](docs/features/authentication.md).
## 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 through `util.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:
```sh
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:
```js
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:
```sh
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_runtime` is declared as `10`
(`nova.crypto` is the newest API touched, and it has a fallback).
- `nova extension validate` last 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.