feat(ui): sidebar project tree, status bar and a tabbed workbench for workspaces
The Workspace page's lane strip moves into the sidebar as a project tree: workspaces grouped by git repository (new GET /api/projects, cached in memory, POST /api/projects/refresh), untracked worktrees with Adopt, inline rename, drag/Alt+arrow ordering and a right-click row menu. The selected workspace is the URL's ?lane=, one shared lane list feeds the tree, the new bottom status bar and the page. The Changes panel gains an activity bar (Files / Changes / Sessions, with resume), and split panes get a header.
This commit is contained in:
+28
@@ -1120,6 +1120,34 @@ POST /api/locks/:name/release
|
||||
- **409** `{ "error": { "code": "ENOTHOLDER", "message": "...", "currentHolder": "lane1" } }` — `holder` doesn't match the current owner; a release never trusts its caller, same rule every other destructive path in this project follows.
|
||||
- **404** `{ "error": { "code": "ENOLOCK", "message": "no such lock: build" } }` — nothing by that name is held.
|
||||
|
||||
#### Projects (workspaces grouped by repository)
|
||||
|
||||
```http
|
||||
GET /api/projects
|
||||
POST /api/projects/refresh
|
||||
```
|
||||
|
||||
Every workspace grouped under the git repository it belongs to, for the sidebar's project tree. A project's `root` is the repo's MAIN worktree top level (`git rev-parse --git-common-dir`, realpath); workspaces outside any repo form the `root: null` pseudo-project, listed last. `worktrees` is `git worktree list` for that repo (a worktree whose directory is gone is left out); `workspace_id` is the lane sitting exactly on that path, else `null` — an **untracked worktree** the UI offers to adopt. A lane on a subdirectory of a worktree is in `workspaces` but marks no worktree. `source` is `"claude"` under `<root>/.claude/worktrees/`, `"agenthub"` under `LANES_ROOT`, else absent.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"projects": [
|
||||
{
|
||||
"root": "/home/u/agenthub",
|
||||
"name": "agenthub",
|
||||
"workspaces": [12, 15],
|
||||
"worktrees": [
|
||||
{ "path": "/home/u/agenthub", "branch": "main", "main": true, "workspace_id": 12 },
|
||||
{ "path": "/home/u/agenthub/.claude/worktrees/wf-1", "branch": "wf-1", "main": false, "workspace_id": null, "source": "claude" }
|
||||
]
|
||||
},
|
||||
{ "root": null, "name": "Folders", "workspaces": [3], "worktrees": [] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Both git reads are cached in memory — a directory's root for the life of the server, a repo's worktree list for 30 s — and the lane ↔ worktree match is recomputed on every call, so a new or removed workspace needs no invalidation. This route is read on demand (the client re-reads it when the set of workspaces changes), never on the polled `GET /api/lanes`. `POST /api/projects/refresh` drops both caches first and answers the same shape; it is same-origin only (`403 EBADORIGIN`).
|
||||
|
||||
### Sessions
|
||||
|
||||
#### List Sessions
|
||||
|
||||
Reference in New Issue
Block a user