Patchline

March 22, 2026 · View on GitHub

Patchline

Patches, line diffs, and commits — a lightweight window built for reading source changes.

Bun React Electrobun TypeScript Tailwind CSS


Native window · Local-first · Git at the speed of Bun

The problem · Screenshots · Scope · Supported · Incoming · Quick start · Development · Build · Layout



The problem

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.


Screenshots

Assets live in screenshots/.

Sidebar — multiple repos (collapsible sections, Staged / Changes per repo)

Sidebar with multiple repositories

Unified diff

Unified diff

Split diff

Split diff

Commit dialog

Commit dialog


Scope

Multiple reposTrack 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 yetGit worktrees are not supported; use a normal clone checkout.

Supported today

AreaWhat works
RepositoriesMulti-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.
ChangesPer-repo lists from git status --porcelain — staged vs unstaged buckets
DiffsPer-file diff (scoped to the correct repo) with unified or split layout (@git-diff-view); title bar shows repoFolder/path/in/repo
StagingStage / unstage one file; stage all / unstage all per repository
CommitTitle + description (subject + body) per repo, then refresh
BranchCurrent branch (and upstream @{u}) per repo in each sidebar header
PlatformmacOS-oriented Electrobun app; dev + canary build pipeline

Incoming / not yet

Git worktreesOpen and switch worktrees in the app (today: use a normal clone checkout; see Scope)
Merge conflictsResolve conflicts here — navigate markers, pick hunks or ours/theirs, finish merge/rebase without leaving Patchline
Broader GitPush 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 / editorFull repo browser and in-app editing were intentionally trimmed for v1

Roadmap is informal — PRs welcome for the gaps you care about.


Why Patchline? (technical)

Main processBun + Electrobun — native window, Git via simple-git
UIReact + Tailwind CSS + shadcn-style components
Dev UXVite HMR for instant UI feedback while you iterate

Quick start

Prerequisites: Bun install.

git clone <repo-url>
cd patchline
bun install

Opening a repository

Verified 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-repo

Same idea with Bun directly:

bun patchline.ts --hmr --source "$PWD" --source ~/work/my-app

You 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:hmr

Option C — no env / no --source
From the clone root:

bun run patchline:hmr
# or: bun patchline.ts --hmr

The 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.

Claude Code (and other agents)

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

Development

CommandWhat it does
bun run patchline:hmrRecommended — Vite on :5173 + Electrobun; hot reload for the webview
bun run devElectrobun only (expects built assets; run vite build first if needed)

Production build

bun run build

Runs 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:

OutputTypical location
.app bundlebuild/canary-macos-arm64/Patchline-canary.app (folder name may include linux / win on other platforms)
Installerartifacts/canary-macos-arm64-Patchline-canary.dmg (+ .tar.zst update payload)

Run the built macOS app

How you open itWhat happens
Double-click the .app, or open Patchline-canary.appNo 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:canary is the same as bun run build.


Repository layout

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
CustomizeWhere
Window, Git, RPCsrc/bun/index.ts
UI & layoutsrc/mainview/
App name & bundle idelectrobun.config.ts, package.json

Tech stack

┌─────────────┐     RPC      ┌──────────────┐
│  Bun main   │ ◄──────────► │ React view   │
│  (Git, fs)  │   typed      │ (Vite + TW)  │
└─────────────┘              └──────────────┘

Built with Electrobun — not Electron. See Electrobun docs for the platform model.