Native window · Local-first · Git at the speed of Bun
The problem · Screenshots · Scope · Supported · Incoming · Quick start · Development · Build · Layout
Day-to-day coding often splits across agents (e.g. Claude Code) and editors (e.g. Cursor). Agent UIs are great for iteration, but built-in diffs are usually not — hard to scan, easy to miss hunks, and not where you want to live when you’re reviewing changes carefully.
Patchline exists because review deserves its own surface: a small, fast, native window whose only job is to show what changed (patch by patch, line by line), let you stage and commit with confidence, and stay out of the way of your IDE.
If you want a quick, lightweight code diff tool — not another full Git GUI — this is it.
Assets live in screenshots/.
Sidebar — multiple repos (collapsible sections, Staged / Changes per repo)
Unified diff
Split diff
Commit dialog
| Multiple repos | Track several Git repositories in one window — seed with repeated --source, PATCHLINE_SOURCE=a,b, Open project / Choose folders… (multi-select where the OS allows), or Add repository (folder icon in the header). |
| No worktrees yet | Git worktrees are not supported; use a normal clone checkout. |
| Area | What works |
|---|---|
| Repositories | Multi-repo: each root has its own Staged / Changes, branch in the sidebar, and collapsible section. Seed repos at launch with repeated --source or PATCHLINE_SOURCE=/a,/b (comma-separated); Choose folders… can add multiple valid .git directories in one sheet; Add repository appends more without restart. |
| Changes | Per-repo lists from git status --porcelain — staged vs unstaged buckets |
| Diffs | Per-file diff (scoped to the correct repo) with unified or split layout (@git-diff-view); title bar shows repoFolder/path/in/repo |
| Staging | Stage / unstage one file; stage all / unstage all per repository |
| Commit | Title + description (subject + body) per repo, then refresh |
| Branch | Current branch (and upstream @{u}) per repo in each sidebar header |
| Platform | macOS-oriented Electrobun app; dev + canary build pipeline |
| Git worktrees | Open and switch worktrees in the app (today: use a normal clone checkout; see Scope) |
| Merge conflicts | Resolve conflicts here — navigate markers, pick hunks or ours/theirs, finish merge/rebase without leaving Patchline |
| Broader Git | Push to remote, sync-style flows (fetch / pull + push, publish), merge, rebase, branches UI, remote management — routine remote work without living in the terminal |
| File tree / editor | Full repo browser and in-app editing were intentionally trimmed for v1 |
Roadmap is informal — PRs welcome for the gaps you care about.
| Main process | Bun + Electrobun — native window, Git via simple-git |
| UI | React + Tailwind CSS + shadcn-style components |
| Dev UX | Vite HMR for instant UI feedback while you iterate |
Prerequisites: Bun install.
git clone <repo-url>
cd patchline
bun installVerified behavior (dev and production builds): if PATCHLINE_SOURCE is set when the process starts, each listed Git root is added (see multiple repos below). If you have no repos yet, you get Add a repository and a native Choose folders… sheet (each selection must be a directory containing .git; you can pick multiple folders in one go where the OS dialog allows). After the first repo(s), use the folder-plus control in the top bar to add more anytime.
Option A — --source flags (recommended)
Repeat --source once per repository (paths are resolved from the current working directory). With npm, pass them after -- so they reach the launcher:
cd /path/to/patchline
npm run patchline:hmr -- --source .
# two repos:
npm run patchline:hmr -- --source . --source ../other-repoSame idea with Bun directly:
bun patchline.ts --hmr --source "$PWD" --source ~/work/my-appYou can still put comma-separated paths in a single flag: --source .,../other-repo. --source=/path works too.
Option B — environment variable
PATCHLINE_SOURCE is comma-separated absolute or relative roots (the launcher also writes this when you use --source). Handy for agents and scripts:
cd /path/to/patchline
export PATCHLINE_SOURCE="$PWD,/path/to/other-repo"
bun run patchline:hmrOption C — no env / no --source
From the clone root:
bun run patchline:hmr
# or: bun patchline.ts --hmrThe launcher patchline.ts sets PATCHLINE_SOURCE from your --source arguments; if you pass none, it clears inherited PATCHLINE_SOURCE in the child process so you don’t accidentally open the wrong tree.
Point Patchline at the same working tree the agent uses, then use Patchline for diffs, staging, and commits:
cd /path/to/patchline # or whatever project the agent is editing
export PATCHLINE_SOURCE="$PWD"
bun run patchline:hmr| Command | What it does |
|---|---|
bun run patchline:hmr |
Recommended — Vite on :5173 + Electrobun; hot reload for the webview |
bun run dev |
Electrobun only (expects built assets; run vite build first if needed) |
bun run buildRuns Vite production (dist/) then Electrobun (--env=canary). The .app uses the same Bun main as dev (src/bun/index.ts). Outputs depend on your OS/arch:
| Output | Typical location |
|---|---|
| .app bundle | build/canary-macos-arm64/Patchline-canary.app (folder name may include linux / win on other platforms) |
| Installer | artifacts/canary-macos-arm64-Patchline-canary.dmg (+ .tar.zst update payload) |
Run the built macOS app
| How you open it | What happens |
|---|---|
Double-click the .app, or open Patchline-canary.app |
No env → Choose folders… / Add repository in the UI (same idea as dev with no --source). |
open --env PATCHLINE_SOURCE="…" |
One env var, one string. Split repo roots with commas (no spaces). Prefer absolute paths. |
Example (verified): two repos at launch —
open --env PATCHLINE_SOURCE="/Users/kannan/Projects/Patchline,/Users/kannan/Projects/flow" \
"/Users/kannan/Projects/Patchline/build/canary-macos-arm64/Patchline-canary.app"One repo is the same, without a comma:
open --env PATCHLINE_SOURCE="/Users/kannan/Projects/Patchline" \
"/Users/kannan/Projects/Patchline/build/canary-macos-arm64/Patchline-canary.app"open -n starts a new app instance. Use it if Patchline is already running and you need this launch to pick up PATCHLINE_SOURCE (otherwise macOS may reuse the old process and ignore the new env):
open -n --env PATCHLINE_SOURCE="/path/repo-a,/path/repo-b" "/path/to/Patchline-canary.app"How this relates to dev: the Bun main always reads PATCHLINE_SOURCE and splits on ,. Dev patchline.ts can set that for you via repeated --source flags. The .app does not take --source — for production you only use open --env (or plain open with no env).
macOS: VAR=value open … often does not inject env into a GUI .app; use open --env as above.
Tip: open -n /path/to/Patchline-canary.app with no --env opens a fresh instance with no pre-seeded repos.
Install from the .dmg in artifacts/ if you prefer; drag Patchline to Applications, then use Finder or the open / open --env patterns above.
bun run build:canaryis the same asbun run build.
src/
├── bun/index.ts # Main process: window, RPC, Git operations
├── mainview/ # React app (webview)
└── shared/types.ts # Shared RPC contracts (main ↔ webview)
electrobun.config.ts # App identity, bundle id, copy paths
vite.config.ts # Vite bundle for mainview
patchline.ts # Dev launcher (`--source` optional, `--hmr`)
screenshots/ # README gallery
| Customize | Where |
|---|---|
| Window, Git, RPC | src/bun/index.ts |
| UI & layout | src/mainview/ |
| App name & bundle id | electrobun.config.ts, package.json |
┌─────────────┐ RPC ┌──────────────┐
│ Bun main │ ◄──────────► │ React view │
│ (Git, fs) │ typed │ (Vite + TW) │
└─────────────┘ └──────────────┘
Built with Electrobun — not Electron. See Electrobun docs for the platform model.