feat: edit in the Changes panel, git graph history, plan quota (v2.3.0)

- Changes panel: a working file opens in a CodeMirror editor with its diff
  inline (DiffEditor.tsx, @codemirror/merge, lazy-loaded); Changes folds the
  unchanged lines, Whole file shows them; side by side at 50/50 and full
  width; a Revert per block; original from HEAD or the branch's merge base
  (`mergeBase` on GET /git/changes?range=branch)
- Every diff: green = line added whole, red = removed whole, blue = edited
  in place (--diff-mod)
- History drawn as a commit graph (#58: `parents` on GET /git/log,
  --date-order; lib/commitGraph.ts)
- Per-workspace never-stage list, drawn orange in the Files tree (--kept);
  closed Terminal rows past three deleted
- Status bar plan quota for Claude (per account) and Codex
  (GET /api/pricing/quota, server/lib/quota.js); compact hover panel that
  stays open, Refresh button with a spinner
- Settings → Appearance holds language, theme and pet; Ctrl+Shift+C copies;
  compact sidebar navigation and short session times
This commit is contained in:
2026-10-06 15:38:25 +07:00
parent f0500dc592
commit daf5b7be92
65 changed files with 2414 additions and 587 deletions
+37 -19
View File
@@ -492,8 +492,9 @@ curl http://localhost:4820/api/lanes/5/git
read off disk, since a workspace 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 at the
top of the Changes panel opens this list as a panel: one row per file, its status letter
- Hovering (or focusing, or tapping) the vertical `N modified · N untracked` chip
on the CLOSED Changes rail opens this list as a panel (the open panel shows no
counts — its own list below says the same): 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.
- A repo with no commit yet is **not** unavailable: it reports `unborn: true`,
@@ -600,10 +601,9 @@ persists per viewer in `localStorage`). Its header holds **one** control, which
steps **Narrow** (30%) → **Medium** (50/50) → **Full screen** (the console beside it is hidden, never unmounted; the page is
height-locked so the panel fills the viewport even with no live run; the whole
Files view (finder, expand-all, tree) becomes a left sidebar and the Files tab
is dropped, and every working-copy file opened there, from the sidebar, Overview or Review, IS
the editor (Whole file toggles back to its diff; a commit still opens as a diff) — no
Edit click, **Save**/**Cancel** stay and wake up once the text differs, Cancel
restores the saved text) → closed (`agenthub.workspace.changesSize`
is dropped; a file opens on its **Changes** like at any width, and
**Whole file** is the editor — **Save**/**Cancel** wake up once the text
differs, Cancel restores the saved text) → closed (`agenthub.workspace.changesSize`
alongside `agenthub.workspace.changesOpen`); the collapsed rail reopens it
narrow. Between the console and the open (not full-screen) panel sits a **splitter**: drag it, or move it with the arrow keys, to any width between 15% and 85%; that fraction is stored as a NUMBER in the same `agenthub.workspace.changesSize` key, and the header button still steps through the presets. A stored size from an older build that no longer exists (`wide`) falls back to narrow. Reading a diff and watching a console want opposite amounts of room and
which one is "work" changes several times an hour, so the width is a step, not
@@ -627,19 +627,36 @@ reads the workspace's git surface in five modes:
one green with `U` (`A` added, `D` deleted and struck through, `R`
renamed), and a folder holding any of them gets an amber dot. The colours
come with the tree read, so **Refresh** re-reads them.
Opening a file entry shows the whole file (`/git/file`) for reading, at every
panel width; an image file (png, jpg, gif, webp, avif, bmp, ico, svg) is shown
as the picture (`/git/raw`), with no editor. **Clicking its text** (or Enter on it; any range but a commit)
turns that view into a text editor with the caret on the clicked line and
the scroll position kept — a click that only ends a drag-selection does not,
so copying never drops you into the editor. **Cancel** returns to reading.
From a diff, which has no whole text to click, an **Edit** button does the
same once the file has loaded. **Save** (or
A changed file opens on its **Changes**, also when picked in Files; an
unchanged file picked in Files opens as the whole file. Any working file
(not a commit's, a binary, a deleted or a conflicted one) is the **editor**
in both views (`client/src/components/lanes/DiffEditor.tsx`, CodeMirror 6
with `@codemirror/merge`, lazy-loaded), editable, with its diff against
HEAD (`work`) or the branch's merge base (`branch`, `mergeBase` on
`/git/changes`): **Changes** folds the unchanged stretches away (a click on
a fold opens it), **Whole file** shows them, and the toggle keeps the draft.
Unified puts the removed lines read-only above their replacement; side by
side puts the original, read-only, left of the file. Every changed block
carries a **Revert** (`→` between split columns) that puts the original back
in the editor — nothing is written until Save. The panel opens side by side
at 50/50 and full width (Workspace's `wide`) and unified when narrower; the
layout button overrides it until the next resize. The colours follow every
keystroke: **green** is a line added whole, **red** a line removed whole,
**blue** a line edited in place (it keeps a letter or digit of its old
text), its edit in a stronger blue; the read-only diffs (Review, a commit, a
file the editor cannot take) use the same scheme, and only they carry the
per-block Stage/Undo review bars and the line-comment buttons — Review
keeps both for every file. In a read-only side-by-side diff, every row is at
least one line tall (`MIN_ROW`), so an empty filler row keeps the two
columns aligned. An image file (png, jpg, gif, webp, avif, bmp, ico, svg) is
shown as the picture (`/git/raw`), and a conflicted file opens in the
conflict view. **Cancel** reverts unsaved edits. **Save** (or
Ctrl/Cmd+S) is `PUT /api/lanes/:id/git/file`, which writes only over the
exact version that was opened — a file the agent changed meanwhile answers
`409 ESTALE` and the draft stays for a retry — and keeps the file's CRLF
line endings and final newline. A file the editor cannot take (binary, or
over the 1 MB view cap) says so in the file's header.
line endings and final newline; the editor stays open on what was saved. A
file the editor cannot take (binary, or over the 1 MB view cap) says so in
the file's header.
- **Overview** — every changed file with its `+N −M`. Polls every 15s, because
it is the cheap shape and the one you leave open while an agent works. Each
row carries its own **stage** / **unstage** / **discard** buttons.
@@ -673,8 +690,9 @@ reads the workspace's git surface in five modes:
**All files** returns to the list it was opened from — the same mode, at the
same scroll offset — because reading a change-set is a walk down a long list
and landing back at its top is the whole cost of opening one file.
- **History** — the workspace's recent commits (`GET /:id/git/log`); opening one
shows that commit's diff (`?range=commit&sha=`).
- **History** — the workspace's recent commits (`GET /:id/git/log`) drawn as a
commit graph, one coloured lane per branch line (`client/src/lib/commitGraph.ts`);
opening one shows that commit's diff (`?range=commit&sha=`).
A range toggle switches the comparison between **Uncommitted** (HEAD vs the
working tree, untracked files included as an all-added patch) and **vs base**
@@ -710,7 +728,7 @@ overview grows a commit box the moment anything is staged — above the list and
pinned while it scrolls, on ONE row with a **Stage all** that stages every listed file
the index lacks in one call and an **Unstage all** that unstages every staged one — it sends
`only: "staged"`, never `add -A`, and it is absent with a clean index because
there is nothing it could mean. It is the dashboard's ONLY commit: the git bar
there is nothing it could mean. A file you never want committed (a local note, a scratch folder) can be marked **Never stage** (its row's eye-off button, or right-click): **Stage all** and a folder's stage button then skip it, the row is struck through, and the file is drawn orange in the Files tree (`--kept`, apart from the amber of a modified file). The list is kept in this browser per workspace (`agenthub.changes.neverStage.<lane id>`) and git is never told — nothing is written to the repo, and the file's own Stage still works. 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