diffpane
v0.1.0
Published
Review a git diff in your browser, comment on the lines, hand the feedback back to your agent.
Maintainers
Readme

Review a git diff in your browser, comment on the lines, hand the feedback back.
diffpane opens a diff as a local web page, lets you click any line to leave a
comment, and prints the feedback to whatever called it — markdown for a human,
JSON for an agent. Nothing leaves your machine: no account, no service, no
runtime dependencies.
Install
npm i -g diffpaneOr run it without installing: npx diffpane. Either way npm fetches a
prebuilt binary; node is not needed at runtime.
Without node:
curl -LsSf https://github.com/thostetler/diffpane/releases/latest/download/diffpane-installer.sh | shFrom source:
git clone https://github.com/thostetler/diffpane
cd diffpane && cargo install --path .Needs git. Prebuilt binaries cover macOS and Linux (glibc 2.31+) on x86_64
and arm64; building from source needs Rust 1.92+.
Use
From inside a repo:
diffpane # current branch vs its base
diffpane --working # uncommitted changes
diffpane --staged # staged changes
diffpane --range main..feat # an explicit range
diffpane --commit a1b2c3d # a single commit
diffpane -- src/search # limit to a pathspecIt parses the diff, serves a page on 127.0.0.1, opens your browser, and waits.
Click a line number to comment. Each comment takes a verdict — ok, fix, or
question. Finish review prints the report and exits.
diffpane 14 files, +402/-88
review http://127.0.0.1:7777/?t=3f9c...Options
--base <ref> base ref for the branch diff
--title <text> human title for the review
--review <file> narrative JSON (chapters + descriptions) to render
--out <file> write the markdown report to a file
--json print machine-readable feedback to stdout
--port <n> preferred port (default 7777, walks forward if taken)
--no-open do not open a browser
--timeout <sec> give up waiting after N seconds (default 3600, 0 = never)Exit codes
| Code | Meaning |
|---|---|
| 0 | approved — submitted with no open fix comments, or nothing to review |
| 1 | changes requested |
| 2 | abandoned — timed out, or you quit without submitting |
| 3 | error |
diffpane --staged || { echo "review not clean"; exit 1; }Long diffs
Collapsed by default, with a one-line summary and an expand control:
- lockfiles, snapshots,
dist/, minified and generated files - added and deleted files in their entirety
- hunks over 40 lines fold their middle, keeping head and tail
- files over 400 diff lines
Anything you have commented on auto-expands. j/k move by hunk, n/p by
chapter, c comments on the focused line, ? lists the shortcuts.
Driving it from an agent
Claude Code
diffpane --install-skillWrites a skill to ~/.claude/skills/diffpane/ — --skill-dir puts it
elsewhere. Restart Claude Code, then:
/diffpaneThe agent writes the chapter narrative, serves the diff, waits for you, and works through your comments: questions answered before edits, then a report of what it fixed, answered or skipped.
Any other agent
Run with --json and read stdout:
diffpane --working --json{
"outcome": "changes-requested",
"overall": { "verdict": "fix", "body": "two blockers" },
"comments": [
{
"verdict": "fix",
"body": "Unbounded — needs a max size.",
"file": "src/search/cache.ts",
"line": 13,
"kind": "line",
"code": "+ const hit = map.get(k);"
}
]
}Each comment carries the file, the line, and the code it was pinned to.
Chapters
--review takes a narrative that regroups the hunks into chapters:
{
"title": "Search result caching",
"story": "Adds an LRU in front of the search endpoint.",
"chapters": [
{
"id": "c1",
"title": "Cache layer",
"intent": "New LRU keyed on normalised query.",
"why": "Repeat queries hit Solr on every keystroke.",
"hunks": ["f0h0", "f0h1", "f2h0"],
"size": "+61/-4",
"flags": ["No eviction test yet."]
}
]
}Hunk ids are f<file>h<hunk>, in the order the diff reports them. Unclaimed
hunks land in a trailing "Everything else". Without --review the page is a
plain file-ordered diff.
Security
Each run mints a random token: the page needs it in the URL, the API needs it in
an X-Diffpane-Token header, which cross-origin callers cannot set without a
preflight. Requests with a non-loopback Host are rejected, and mutations must
be JSON. Loopback binding alone is not access control: any page in any tab
can reach 127.0.0.1.
Development
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --checkThe browser suite runs a real Chromium against the built binary:
pnpm install
pnpm exec playwright install chromium
pnpm test:ui
pnpm typecheckLayout:
server/— CLI and HTTP server. The manifest is at the repo root because the binary embedsui/andskills/, which cargo packages only from there.ui/— vanilla HTML/CSS/JS, no build step, compiled into the binary.DIFFPANE_UI_DIR=uiserves it from disk instead.parity/—parity.sh check-goldenasserts the diff output still matchesparity/golden/.
Wire shapes are in server/src/model.rs and are frozen, snake_case.
Open the UI against fixture data without a repo:
diffpane --working --no-open # then append &fixture=1 to the URLLicense
MIT
