- Drop fields and functions nothing reads: job and proc tips, fetch time, PR url/author (and their GraphQL fields), spine summaries, diff::commit, unread data attributes, unused CSS and tokens. - One home each for state wording (naming main as the repo does), lane lookup, unseen, outdated, main's tip, the stop wait and the token header. - Narrow visibility; rename render::View to Filter; queue ranking moves to the CLI, its only user. - Token test covers every route; shared checkout snapshot in the fixture. - Client: errors outside Run/Send are toasts, history only after a good load, a drag stays in one file, the agent marker is a ring. - design.md, the skill and AGENTS.md describe what is built.
12 KiB
Branch Graph — product design
2026-10-09, revision 5. Revision 3 (the live graph, now built) is in git history.
Value
A lead has ten branches open, by people and by agents. Each one has to end merged, fixed, or sent back with clear instructions. Today that takes a GitHub review, a copied link, a paragraph to the agent explaining what to do with it, a checkout and a run, for every branch.
Branch Graph shortens that loop to one screen per branch: see what the branch
changes (diff --stat first, then the diff), run it in its own worktree with
one key, write comments on lines, files or commits, and send them to a coding
agent working in that worktree. The graph of branches stays, as navigation.
Revision 3 answered "where are the branches". It drew topology well and
stopped at "copy this command". The owner's review found the gaps: no
branch-level diff, no diff --stat, no way to run a branch, no path from a
comment to an agent.
User and loop
The user is a git CLI power user who runs coding agents (opencode, Claude Code) in parallel. The loop, per branch:
- Pick: the queue shows which branches need them: new commits since they last looked, then conflicts or failing CI, then recency.
- Understand:
diff --statagainst the fork point, grouped by directory, then the diff of any file; the commits if the story matters. - Try: a worktree for the branch, created on demand, never touching the user's own checkout; the project's run command started there, output on the page.
- Act: comment on a line range, a file, a commit or the branch. The comments form the branch's brief. Send the brief to the agent, which works in the same worktree. Or check the merge preview and merge from the label's menu.
- Follow: the agent's commits appear live in the branch; the brief marks which comments were sent.
Screen
One screen, review-first. Layout at 1440 × 900:
- Top bar, 48 px: repository, Base selector, filter field (name or
@author), live dot, fetch. Base is the branch "All" compares with: main, or any open branch, then the stat and diff show what the branch adds since it split from that one, as a pull request into it would. It stays while the user moves between branches. Off main it turns amber, and a line above the stat says "Compared with feat/x, not main · use main": a review against the wrong base is costly. - Rail, 400 px, left: the commit graph, as gitk or
git log --graphdraws it, so the user sees how every branch ties back to main. One row per commit, newest first; main in the first column, each branch in its own column, forks and merges drawn. Main between fork points and merges folds into a count (+37); a branch shows its newest 30 commits. Branch tips carry a label: state on its border (red conflicts, amber CI failing, green ready), an accent dot for commits not seen yet. Lines stay neutral; the selected branch's rows and nodes take the accent. A label opens its menu, as gitk's branch heads do: Review, Check out, Merge into (the branch checked out in the user's checkout), Preview merge, Compare the open branch with this, Copy name; a modified click opens the branch in a tab; a commit opens it narrowed to that commit;j/kmove between branch labels. The graph waits while the pointer is on it. Hollow nodes are not pushed. "3 merged or stale · show" at the bottom; the filter field (name or@author) redraws the graph for the matching branches. - Stat, 300 px:
- Header: branch name; one muted line: author · PR #182 draft (or no PR) · worktree path when there is one. Two commands on the name line: Run and Send (n), n the number of draft comments.
- Toggle "All vs | Since last review". "Since last review" is the default when the branch moved since the user last reviewed it, typically after an agent pushed.
- The changed files against the merge base, grouped by directory:
+a −rper file, a thin bar on directory rows, churn order inside a directory, lock files in a muted group at the end. - A commit picked in the graph narrows stat and diff to it, with "a11c09e · clear" above the files.
- Diff, rest of the width: every file, one scroll, in the stat's order,
each under a sticky header (path, counts, comment on file). A file clicked
in the stat scrolls to the top of the pane; the stat marks the file being
read as the pane scrolls;
[/]step between files. The pane holds the first 200 KB of the diff; a file past that shows "Load diff", which loads it alone (up to 2 MB). Drag down the line numbers to select lines; the comment box opens on release. Drafts sit inline under their lines.⌘↵saves a draft;esccloses the box and keeps the text. - Drawer, under stat and diff: closed, one 28 px status line (the run
command, "running", "port 20431" as a link to the app, "agent idle"), hidden
until something ran. Open (
o), 280 px: Run and Agent tabs on the left, the output on the right. Stop is the Run button in the stat header, which turns into Stop while the run lives. - Send dialog, 760 px, centred: the review as it will go out, compact
like a submitted GitHub review. Header: branch, "5 comments on 3 files",
base and the tip reviewed. Tabs: Review, Markdown (the exact brief).
Review: the instruction, then the comments grouped by file in line order,
each with its spot (
L40–52, file, commit), a short excerpt and its text; branch and commit comments last. Every text is editable in place and saves as you type; each comment can be deleted. Actions: Copy brief (paste it into a running agent session), then Mark as sent so it does not go out twice (the page's way to do whatbranchgraph brief --ackdoes for an agent); Send to agent (⌘↵) runs the agent command in the worktree. The footer says where the agent runs. It refuses while a comment box in the diff is still open, or an agent job runs in that worktree. - Comments written on an older tip show "outdated".
Keys
j/k branch, [/] file, r run, s send, o drawer, m merge
preview, p pause the live graph, / filter, ? keys; ⌘↵ saves a comment
or sends, esc closes a box, dialog or menu.
Actions
| Action | What it does |
|---|---|
| Worktree (implicit) | Run and Send need one: reuse the worktree where the branch is checked out, else create <clone>.worktrees/<slug> with gix (a remote-only branch gets a local tracking branch). On creation, copy the untracked files named by branchgraph.copy (for example .env*) from the main worktree, then run branchgraph.setup once. |
| Run | branchgraph.run in the worktree, one per branch, BG_PORT stable per branch. Output in the drawer, last 2 000 lines. Stop sends SIGTERM to the main process only, SIGKILL 10 s later; the command (a dev.sh) decides what else stops. |
| Comment | On a line range, a file, a commit or the branch; a draft until sent. |
| Send | Writes the brief, runs branchgraph.agent in the worktree, marks the drafts sent. One agent job per worktree at a time. |
| Merge preview | m: the merge preview, with copy-commands. |
| Check out | From a label's menu: git switch in the user's checkout (a remote-only branch gets a tracking branch). Refused over modified tracked files, from a detached HEAD (its commits would be left unnamed) and for a branch checked out in another worktree. Says so when the local branch is not at the tip shown. |
| Merge into | From a label's menu: git merge of the tip the page shows into the branch checked out in the user's checkout. Refused over modified tracked files or on a detached HEAD; a conflict is aborted at once and its files listed, so the checkout is never left mid-merge. Never pushed. |
Commands: the branch's .branchgraph
Settings travel with the code, in .branchgraph at the repository root,
git-config syntax:
[branchgraph]
run = cd recordum && bash localdev.sh
setup = cargo fetch
copy = .env*
agent = opencode run -c -f "$BG_BRIEF" "Apply the review in the attached brief."
verify = cargo test
Read for the branch being run: the file in its worktree (an edit counts before it is committed), else as committed on the branch, else main's. No git-config source, so a setting is never one person's machine only.
Settings (the gear in the branch header) opens the file in an editor and saves it into the branch's worktree (made if needed). It is the only file branchgraph edits; the user commits and pushes it.
A branch can change what Run executes. When its commands differ from main's,
Run (or Send) shows them in the drawer and asks once; "Run it" approves what
was shown for that branch, so a command that arrived since is asked about
again. Copying files into a new worktree (copy) counts as a command.
Commands the user saved in the editor count as approved.
Each command runs with sh -c in the worktree. Values reach it only as
environment variables: BG_BRANCH, BG_SLUG, BG_WORKTREE, BG_BRIEF,
BG_PORT. The default agent command continues the worktree's last opencode
session, which is the agent that wrote the branch, not a stranger.
The server prints every command it runs, its directory, its output (one line per output line, prefixed with the branch) and its exit status to the terminal it was started from. A refused Run or Send shows its reason in the drawer.
Brief
A Markdown file an agent acts on without a follow-up question:
- branch, base, merge-base sha and the tip sha reviewed, so it can detect drift;
- the overall instruction, what done means, the verify command
(
branchgraph.verify; without it, "run the project's tests"), and the push rule (default: do not push); - each comment: anchor (
src/fleet/vehicle.rs:40-52 @ a11c09e2), code excerpt, text; - rules: one commit per comment citing its anchor; skip an ambiguous comment and say why rather than ask.
As an agent skill
skill/branchgraph/SKILL.md lets opencode or Claude Code use the tool. The
CLI reads the repository and the state directory directly; open and
run-log need the server.
branchgraph open [branch]starts the server for the repository if none runs (detached, port file in the state directory) and prints the URL, so an agent can end with "review here: URL".branchgraph queueprints the queue with each branch's stat.branchgraph brief [branch]prints the unsent brief; the branch defaults to the one checked out in the current directory.--ackmarks it sent once the agent has acted.branchgraph run-log [branch]prints the run output, so the agent reads the error without the user copying it.
Trust boundaries
- Commands come from the branch's
.branchgraph; one that main does not run is asked about once before it runs, so a teammate's branch cannot run new code on a click without the user seeing it. - Run executes the branch's code; it starts only on the user's click or key.
- Every route needs a per-launch token: the printed URL carries it once and
the browser keeps it as an HttpOnly, SameSite=Strict cookie named per port.
The server binds 127.0.0.1, where it also checks the Host name;
--bind 0.0.0.0listens on the network over plain HTTP, accepts any Host, still requires the token and a same-origin Origin, and warns at start. - Writes to the clone: fetch, worktree creation and the local tracking branch it needs, and Check out or Merge in the user's checkout from a branch's menu, on that click only. Never a rebase, push or branch delete. Agents write in their worktree; that is their job.
- Briefs, review state and logs live in
$XDG_STATE_HOME/branchgraph/<repo id>/(default~/.local/state), written atomically.
Data
| Data | Source | Bound |
|---|---|---|
| Branch stat | Tree diff of merge base and tip, line counts per file | 1 000 files; blobs over 1 MB counted as changed, not diffed |
| File diff | Blob diff, every file loads with the page | 200 KB for the pane; 2 MB for one file loaded alone |
| Queue rank | Seen commits (state dir), conflicts, PR state | — |
| Run output | Child process | 2 000 lines per run, one run per branch |
| Agent job | Child process | Last job per branch, 2 000 lines |
Scope of this milestone
In: the one-screen review (rail, stat, diff, drawer, send sheet), comments and brief, implicit worktrees with copy and setup, Run, Send through the command template, the commit graph in the rail, the CLI subcommands and the skill file.
Not yet (owner, 2026-10-08): posting reviews to GitHub. Later: replies from
the agent inline, several agent targets per branch, n/p hunk keys.