- 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.
230 lines
12 KiB
Markdown
230 lines
12 KiB
Markdown
# 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:
|
||
|
||
1. **Pick:** the queue shows which branches need them: new commits since they
|
||
last looked, then conflicts or failing CI, then recency.
|
||
2. **Understand:** `diff --stat` against the fork point, grouped by directory,
|
||
then the diff of any file; the commits if the story matters.
|
||
3. **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.
|
||
4. **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.
|
||
5. **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 --graph`
|
||
draws 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`/`k` move 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 <base> | 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 −r`
|
||
per 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; `esc` closes 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 what `branchgraph brief --ack` does 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 queue` prints 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. `--ack` marks 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.0` listens 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.
|