feat(lanes,ui): per-lane git surface — Changes panel, VS Code-style diff, branch-aware Add lane, redesigned tooltips (#34, #35)
- Changes card beside lane detail with five modes: Files tree, overview, Review (whole diff, per-file collapse), single file, commit history. - LaneDiffView: unified / side-by-side with independent scroll on both axes and mirrored scrollLeft, expand/collapse context, intra-line emphasis. - Staged-only commit box; git actions (fetch/pull/push/stage/unstage/ discard/commit/switch/branch-create/branch-delete) guarded in git-ops.js. - Server: git-diff.js + git-ops.js behind GET /api/lanes/:id/git/* and POST /api/lanes/:id/git/:action; gitFacts gains upstream/ahead/behind. - Add lane: new/existing branch, current branch, worktree list, prefill from the git bar's "New lane here"; /api/run/cwds gains a repo kind. - GlobalTooltip upgrades every native title into a themed anchored bubble; chart/info popovers use theme tokens.
This commit is contained in:
+249
-2
@@ -3,7 +3,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Agent Dashboard for Claude Code API
|
||||
version: 1.7.0
|
||||
version: 1.8.1
|
||||
description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence.
|
||||
contact:
|
||||
name: Nguyễn Ngọc Trí Vĩ
|
||||
@@ -10977,9 +10977,256 @@ paths:
|
||||
description: Absolute path to an existing git repository.
|
||||
responses:
|
||||
'200':
|
||||
description: '{branches: string[], current: string | null}.'
|
||||
description: '{branches: string[], current: string | null, worktrees: {path, branch?, locked?}[]}. The worktrees ride along so the picker can grey out branches git would refuse to check out twice.'
|
||||
'400':
|
||||
description: repo is missing, relative, does not exist, or is not a git repo.
|
||||
/api/lanes/{id}/git/changes:
|
||||
get:
|
||||
tags:
|
||||
- Lanes
|
||||
summary: A lane's code changes, at one of three depths
|
||||
description: Backs the Workspace Changes panel. No extra params = a summary of every changed file; full=1 adds every file's hunks (review mode); path=<file> returns that one file's hunks. range=work (default) is HEAD vs the working tree with untracked files included; range=branch is <base_branch>...HEAD; range=commit needs sha= and shows that one commit. context= widens each hunk (default 3, clamped to 5000) — the viewer's expand button. Lines arrive with both gutters resolved. Capped at 300 files, 200 KB of patch per file and 1 MB per full read; a cap that bit sets `truncated`. Read-only, no same-origin guard.
|
||||
operationId: getLaneChanges
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- name: range
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- work
|
||||
- branch
|
||||
- commit
|
||||
default: work
|
||||
- name: sha
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: 'range=commit only: the commit to show.'
|
||||
- name: context
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 3
|
||||
maximum: 5000
|
||||
description: Unchanged lines around each hunk.
|
||||
- name: path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: Return only this file, with its hunks.
|
||||
- name: full
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- '1'
|
||||
- 'true'
|
||||
description: Include every file's hunks (review mode).
|
||||
responses:
|
||||
'200':
|
||||
description: '{available, range, base, baseRef, sha, context, files, truncated}, or {available:false} for a cwd git cannot read.'
|
||||
'400':
|
||||
description: ENOBASE (range=branch on a lane with no base branch), ENOFILECHANGE (path has no changes in this range), EBADRANGE or EBADREV.
|
||||
'404':
|
||||
description: Lane not found.
|
||||
/api/lanes/{id}/git/log:
|
||||
get:
|
||||
tags:
|
||||
- Lanes
|
||||
summary: The lane's recent commits
|
||||
description: Newest first, up to limit (default 30, max 200). file= narrows it to the commits touching one path. Reading a commit's diff is GET /api/lanes/{id}/git/changes?range=commit&sha=. Read-only, no same-origin guard.
|
||||
operationId: listLaneCommits
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 30
|
||||
maximum: 200
|
||||
- name: file
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: '{available, commits:[{sha, short, subject, author, date, refs}]}.'
|
||||
'404':
|
||||
description: Lane not found.
|
||||
/api/lanes/{id}/git/file:
|
||||
get:
|
||||
tags:
|
||||
- Lanes
|
||||
summary: One file's whole content at a revision
|
||||
description: rev=work (the file on disk, untracked included), HEAD, or a commit-ish. The `work` revision is the only one that reads the filesystem and it is realpath-confined to the lane directory. Binary content is reported, never returned. Capped at 1 MB. Read-only, no same-origin guard.
|
||||
operationId: readLaneFile
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- name: path
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: rev
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
default: work
|
||||
responses:
|
||||
'200':
|
||||
description: '{available, path, rev, binary, truncated, lines}.'
|
||||
'400':
|
||||
description: EBADPATH, EBADREV or ENOFILE.
|
||||
'404':
|
||||
description: Lane not found.
|
||||
/api/lanes/{id}/git/ls:
|
||||
get:
|
||||
tags:
|
||||
- Lanes
|
||||
summary: Tracked paths matching a substring (quick-open)
|
||||
description: Reads `git ls-files`, so ignored files never appear and no skip-list is maintained. Basename hits rank first. Capped at 500 paths. Read-only, no same-origin guard.
|
||||
operationId: listLanePaths
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- name: q
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: '{available, paths, truncated, total}.'
|
||||
'404':
|
||||
description: Lane not found.
|
||||
/api/lanes/{id}/git/tree:
|
||||
get:
|
||||
tags:
|
||||
- Lanes
|
||||
summary: One directory level of tracked files
|
||||
description: 'The file explorer''s expand-a-folder read: tracked entries under path=, folded into directories at that level. Read-only, no same-origin guard.'
|
||||
operationId: listLaneTree
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- name: path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
default: ''
|
||||
responses:
|
||||
'200':
|
||||
description: '{available, path, entries:[{name, path, dir}], truncated}.'
|
||||
'400':
|
||||
description: EBADPATH (the path escapes the lane).
|
||||
'404':
|
||||
description: Lane not found.
|
||||
/api/lanes/{id}/git/branches:
|
||||
get:
|
||||
tags:
|
||||
- Lanes
|
||||
summary: The lane repo's own local branches
|
||||
description: Feeds the lane's branch switcher. The repository comes from the lane row, never from the query — that is what GET /api/lanes/branches is for. Read-only, no same-origin guard.
|
||||
operationId: listLaneOwnBranches
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: '{available, branches, current, worktrees}.'
|
||||
'404':
|
||||
description: Lane not found.
|
||||
/api/lanes/{id}/git/{action}:
|
||||
post:
|
||||
tags:
|
||||
- Lanes
|
||||
summary: One git write on the lane's working copy
|
||||
description: 'fetch (--all --prune), pull (ALWAYS --ff-only — a diverged branch is reported, never merged), push (-u origin HEAD, so only the branch the lane is on), stage/unstage (per file), discard (per file; irreversible, so it REQUIRES confirm:true), commit (git add -A then commit -m <message>, or only:"staged" to take the index as it stands), switch (git switch, -c to create; refuses a dirty tree so an agent''s half-finished work is never carried onto another branch), and branch-create / branch-delete (create never switches; delete is `git branch -d`, refuses main/master/development/develop, the current branch and one held by another worktree). A path in the body must be one `git status` is reporting as changed, so no path outside the lane is reachable. Guarded and serialized per lane under the lane lock. Deliberately no merge, rebase, reset or stash: reset stays behind the destroy guard on POST /api/lanes/{id}/reset. Network actions are killed after 60s.'
|
||||
operationId: runLaneGitAction
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- name: action
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- fetch
|
||||
- pull
|
||||
- push
|
||||
- stage
|
||||
- unstage
|
||||
- discard
|
||||
- commit
|
||||
- switch
|
||||
- branch-create
|
||||
- branch-delete
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
description: commit only; required, max 4000 chars.
|
||||
only:
|
||||
type: string
|
||||
enum:
|
||||
- staged
|
||||
description: commit only; commit the index as it stands, with no add -A.
|
||||
branch:
|
||||
type: string
|
||||
description: switch, branch-create and branch-delete.
|
||||
base:
|
||||
type: string
|
||||
description: branch-create only; defaults to HEAD.
|
||||
create:
|
||||
type: boolean
|
||||
description: switch only; create the branch.
|
||||
paths:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: stage, unstage, discard; each must be a path git reports changed.
|
||||
confirm:
|
||||
type: boolean
|
||||
description: discard only; the call is refused without it.
|
||||
responses:
|
||||
'200':
|
||||
description: '{action, ok:true, output} — git''s own stdout+stderr.'
|
||||
'400':
|
||||
description: EBADACTION, EBADMESSAGE, EBADBRANCH, EBADPATH, ECONFIRM, EPROTECTED, ENOBRANCH, EBRANCHEXISTS, EBRANCHBUSY, EDIRTY or ENOCHANGES.
|
||||
'404':
|
||||
description: Lane not found.
|
||||
'500':
|
||||
description: git failed; its stderr is appended to error.message.
|
||||
/api/lanes/{id}/{action}:
|
||||
post:
|
||||
tags:
|
||||
|
||||
Reference in New Issue
Block a user