feat(lanes): move the lane's git section into the Changes panel

- The branch line, the uncommitted-files hover panel and the git action
  row (fetch/pull/push/branch) move from the lane card to the top of the
  Changes panel (new LaneGitHeader.tsx); the card has no git row now.
- Header and panel share one git-facts read: stage/unstage/discard/commit
  and Refresh re-read it, and a git bar action re-reads the diff, the
  history and the file tree.
- The git bar no longer commits: the panel's staged-only box is the one
  commit path. Orphaned gitBar.commit*/cancel strings removed (en, vi).
- "New lane here" is wired from Workspace to the panel.
This commit is contained in:
2026-09-24 11:44:31 +07:00
parent 7bc4594b94
commit faa56f5c75
13 changed files with 612 additions and 621 deletions
+17 -16
View File
@@ -480,8 +480,8 @@ Two concurrent calls for the same `(cwd, tool)` pair always yield one lane: the
### Reading a lane's working copy: `GET /api/lanes/:id/git`
Each lane card shows the branch its working copy is actually on, the short
HEAD, that commit's subject, and how much is uncommitted:
The top of a lane's **Changes** panel shows the branch its working copy is
actually on, the short HEAD, that commit's subject, and how much is uncommitted:
```bash
curl http://localhost:4820/api/lanes/5/git
@@ -505,7 +505,7 @@ curl http://localhost:4820/api/lanes/5/git
- A `cwd` that is missing, is not a git repository, or makes git fail returns
`{"available": false}` with HTTP **200**. A lane pointing at a plain directory
is a normal state, not a fault, and the card simply renders without a git row.
is a normal state, not a fault, and the panel simply renders without a git line.
- A detached checkout reports the literal `HEAD` that git returns, rather than a
prettier invention.
- `files` names the files the two counts came from, **capped at 20** — a lane
@@ -520,8 +520,8 @@ curl http://localhost:4820/api/lanes/5/git
read off disk, since a lane mid-build can have a 100 MB artifact), a **binary**
file, a **rename** (numstat keys those differently), or a repo with no commit
to diff against yet.
- Hovering (or focusing, or tapping) the `N modified · N untracked` text on a
lane card opens this list as a panel: one row per file, its status letter
- Hovering (or focusing, or tapping) the `N modified · N untracked` text at the
top of the Changes panel opens this list as a panel: one row per file, its status letter
colour-coded, the directory truncated before the filename ever is, and each
row's `+N −M` aligned on the right under a total.
- An unknown lane id is a `404`.
@@ -529,32 +529,31 @@ curl http://localhost:4820/api/lanes/5/git
This is **not** folded into `GET /api/lanes` on purpose: it shells out to git
five times, and the lane list is polled and re-broadcast on every hook-driven
`lane_update`. Putting it there would fire a burst of subprocesses behind every
tool call an agent makes. The browser fetches it per card instead, on mount and
every 30 seconds, and a failed fetch is silent.
tool call an agent makes. The Changes panel fetches it instead, on mount, every 30 seconds and after
every git write it or its action row makes, and a failed fetch is silent.
The same response also carries the branch's standing against its remote:
`hasRemote`, `upstream`, `ahead`, `behind`. A branch that has never been pushed
reports `upstream: null` and **null** counts — not zeros, which would claim "in
sync" about a comparison that was never possible. The card renders them as
sync" about a comparison that was never possible. The panel renders them as
`↑2 ↓1` beside the git actions below.
### Acting on a lane's working copy: the git action row
Under the git line, a lane's card carries the git **writes** the dashboard
offers (`POST /api/lanes/:id/git/:action`, see `docs/API.md`):
Under the git line, the Changes panel carries the per-repo git **writes** the
dashboard offers (`POST /api/lanes/:id/git/:action`, see `docs/API.md`):
| Button | Runs | Refuses when |
| --- | --- | --- |
| **Fetch** | `git fetch --all --prune` | the repo has no remote |
| **Pull** | `git pull --ff-only` | the branch has diverged (it reports, never merges) |
| **Push** | `git push -u origin HEAD` | the remote rejects it |
| **Commit** | `git add -A` + `git commit -m …`, or **Commit staged** (the index as it stands) | nothing is uncommitted / nothing is staged |
| **Branch** | `git switch …` | the tree is dirty, or another worktree holds that branch |
| **Create** | `git branch <name>` — never switches | the branch already exists |
| **Delete** | `git branch -d <name>` | it is protected (`main`, `master`, `development`, `develop`), current, held by another worktree, or has unmerged commits |
| **New lane here** | opens **Add lane** prefilled with this repo and the selected branch | — |
These sit on the card rather than in the ⋯ menu deliberately: that menu holds
These sit in the panel rather than in the lane card's ⋯ menu deliberately: that menu holds
the rare and dangerous (`reset worktree`), and these are the ordinary moves a
lane needs several times an hour. The boring half of git is the whole list on
purpose — there is no merge, rebase, reset or stash button, because a dashboard
@@ -565,7 +564,9 @@ deliberately does not switch: the usual reason to make a branch from a lane is
to spawn a sibling worktree lane on it, and moving this pane's HEAD under a
working agent is not. **New lane here** goes through the normal Add-lane flow
with the fields prefilled — one provisioning path for every lane, not a second
one hidden in the git bar.
one hidden in the git bar. There is no **Commit** here: committing is the
overview's staged-only box below, the one commit path, and it only ever takes
what was staged.
A failed action prints git's own `stderr` verbatim — `! [rejected] … (non-fast-forward)`
says more than any sentence the dashboard could write.
@@ -639,8 +640,8 @@ in every panel narrow enough to sit beside a console.
one file means nothing unless a commit can take the index as it stands, so the
overview grows a commit box the moment anything is staged — it sends
`only: "staged"`, never `add -A`, and it is absent with a clean index because
there is nothing it could mean. (The git bar's own **Commit staged** is the
same call from the lane card; its plain **Commit** is still stage-everything.) **Discard** is the one
there is nothing it could mean. It is the dashboard's ONLY commit: the git bar
above it has none, so no click can sweep the rest of the tree in with `add -A`. **Discard** is the one
irreversible action in the panel: it asks twice in the UI, and the server
refuses it without `confirm: true` and accepts only paths `git status` is
already reporting as changed.
@@ -1483,7 +1484,7 @@ curl -X POST http://localhost:4820/api/lanes/5/stop
### git: fetch / pull / push / stage / unstage / discard / commit / switch / branch-create / branch-delete
The git writes the lane card's action row and the Changes panel offer. See
The git writes the Changes panel and its action row offer. See
"Acting on a lane's working copy" above for what each one refuses and why.
```bash