feat(lanes): name the files behind a lane's uncommitted counts

GET /api/lanes/:id/git now returns `files`, the raw `git status --porcelain`
lines the dirty/untracked counts were already parsed from, capped at 20 so a
lane mid-`npm install` cannot ship thousands of paths through a polled
endpoint. The lane card hovers them as the tooltip on its
"N modified · N untracked" text, appending "… +N" when the cap bit.

No new subprocess and no new endpoint — statusCounts was counting those lines
and throwing them away.
This commit is contained in:
2026-09-17 13:19:07 +07:00
parent 2fe1dcd46f
commit af2ad63a18
8 changed files with 46 additions and 8 deletions
+10 -2
View File
@@ -370,7 +370,9 @@ A missing source `.env` falls back to `.env.example` with a loud warning, never
The dashboard web UI merges lanes and runs into a single **Workspace** page accessible at `/run` (the old `/lanes` route now redirects here). Top to bottom:
- **Header** — the page title and four counters (`lanes`, `running`, `needs you`, `dead`), plus Add lane. The `needs you` and `dead` counters appear only when they are non-zero, so a quiet header means nothing is waiting on a human.
- **No page header.** The page title and the four counters (`lanes`, `running`, `needs you`, `dead`) are gone: the lane strip below already shows every lane with its own liveness dot and its ⚠ marker, so the counters restated it a row higher and cost height on a screen whose point is the terminal. `GET /api/lanes` still returns `counts` for other callers.
- **Lane strip** — the horizontally scrolling row of lane tiles. Its FIRST tile is **Add lane** (dashed border, no liveness dot), not a header button: creating a lane is the same kind of act as picking one, so it reads as the empty slot at the head of the row.
- **Lane-detail header row** — one row, and every per-lane control lives on it: liveness + status, the lane's name, time-on-stage, the pipeline picker, the Model (AI Router) select, stage badges, the feature picker, and on the right the **Tools** menu (agents install / mcp sync / sync-base check, hidden when the lane declares no runtime profile), the info toggle, the split-view toggle and Remove. Tools sits here rather than in Settings because all three of its actions run against one lane's `cwd` — in Settings it would need a lane picker just to say which.
- **Detail panel** — the selected lane's declared stage, its inferred stage when detection leads, a full-width pipeline map, and a legend naming all five node states plus the dashed-amber inferred treatment.
- **Terminal** — a real interactive terminal (xterm.js) displaying the tmux session's PTY output, with full support for interactive commands, editors, and pagers. A live run keeps its rendered history and scroll position when scrolling.
- **Split view** — a layout toggle (one pane / two side by side / a 2×2 grid, drawn as icons rather than the words, with the count kept as each button's `aria-label` and tooltip) renders that many independent terminal panes side by side (`grid-cols-2` for 2, a 2×2 grid for 4). Layout 1 is bound to the lane strip's selection, same as always; layouts 2 and 4 give each pane its own lane picker, independent of the strip. The chosen layout and each pane's lane persist to `localStorage` (`agenthub.workspace.splitView`) across reloads.
@@ -442,7 +444,8 @@ curl http://localhost:4820/api/lanes/5/git
"head": "9b3e74a",
"subject": "free-text rule mode in the form",
"dirty": 2,
"untracked": 1
"untracked": 1,
"files": [" M server/lib/alerts.js", " M client/src/lib/api.ts", "?? scratch.txt"]
}
```
@@ -451,6 +454,11 @@ curl http://localhost:4820/api/lanes/5/git
is a normal state, not a fault, and the card simply renders without a git row.
- A detached checkout reports the literal `HEAD` that git returns, rather than a
prettier invention.
- `files` holds the raw `git status --porcelain` lines the two counts came from,
**capped at 20** — the card hovers them as a tooltip on the
`N modified · N untracked` text, and a lane mid-`npm install` would otherwise
ship thousands of paths through a polled endpoint. The counts are never
capped, so the tooltip appends `… +N` when it is showing less than all of it.
- An unknown lane id is a `404`.
This is **not** folded into `GET /api/lanes` on purpose: it shells out to git