mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
feat: git add files from code review UI (#257)
* feat: add per-file git add from code review UI (#254) Adds the ability to `git add` individual files directly from the code review interface. Users can stage approved files then switch to the new "Unstaged changes" diff view to see only remaining work. - POST /api/git-add endpoint with worktree support - gitAddFile/gitResetFile utilities in packages/server/git.ts - "Staged changes" and "Unstaged changes" added to diff type dropdown - useGitAdd hook encapsulating all staging state and API logic - "Git Add" / "Added" toggle button in FileHeader (next to Viewed) - Visual indicator (left border) for staged files in sidebar FileTree Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: use green background tint for staged files instead of left border Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: support staged/unstaged diffs in worktrees and display stage errors Adds "staged" and "unstaged" to worktree sub-type allowlists (server parser, client parser, and runGitDiff switch), displays stageError in FileHeader, and documents /api/git-add in CLAUDE.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: clean up git add feature — fix stale closure, timeout leak, dedup validation - useGitAdd: use ref for stagedFiles to stabilize stageFile callback, clear error timeout on reset, drop unnecessary useMemo - App.tsx: wrap onFileViewed in useCallback - FileTree: merge duplicate staged count conditionals - review.ts: reuse exported validateFilePath for /api/file-content Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import { isRemoteSession, getServerPort } from "./remote";
|
||||
import { type DiffType, type GitContext, runGitDiff, getFileContentsForDiff } from "./git";
|
||||
import { type DiffType, type GitContext, runGitDiff, getFileContentsForDiff, gitAddFile, gitResetFile, parseWorktreeDiffType, validateFilePath } from "./git";
|
||||
import { getRepoInfo } from "./repo";
|
||||
import { handleImage, handleUpload, handleAgents, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, type OpencodeClient } from "./shared-handlers";
|
||||
import { contentHash, deleteDraft } from "./draft";
|
||||
@@ -182,12 +182,14 @@ export async function startReviewServer(
|
||||
if (!filePath) {
|
||||
return Response.json({ error: "Missing path" }, { status: 400 });
|
||||
}
|
||||
if (filePath.includes("..") || filePath.startsWith("/")) {
|
||||
try { validateFilePath(filePath); } catch {
|
||||
return Response.json({ error: "Invalid path" }, { status: 400 });
|
||||
}
|
||||
const oldPath = url.searchParams.get("oldPath") || undefined;
|
||||
if (oldPath && (oldPath.includes("..") || oldPath.startsWith("/"))) {
|
||||
return Response.json({ error: "Invalid path" }, { status: 400 });
|
||||
if (oldPath) {
|
||||
try { validateFilePath(oldPath); } catch {
|
||||
return Response.json({ error: "Invalid path" }, { status: 400 });
|
||||
}
|
||||
}
|
||||
const defaultBranch = gitContext?.defaultBranch || "main";
|
||||
const result = await getFileContentsForDiff(
|
||||
@@ -199,6 +201,34 @@ export async function startReviewServer(
|
||||
return Response.json(result);
|
||||
}
|
||||
|
||||
// API: Git add / reset (stage / unstage) a file
|
||||
if (url.pathname === "/api/git-add" && req.method === "POST") {
|
||||
try {
|
||||
const body = (await req.json()) as { filePath: string; undo?: boolean };
|
||||
if (!body.filePath) {
|
||||
return Response.json({ error: "Missing filePath" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Determine cwd for worktree support
|
||||
let cwd: string | undefined;
|
||||
if (currentDiffType.startsWith("worktree:")) {
|
||||
const parsed = parseWorktreeDiffType(currentDiffType);
|
||||
if (parsed) cwd = parsed.path;
|
||||
}
|
||||
|
||||
if (body.undo) {
|
||||
await gitResetFile(body.filePath, cwd);
|
||||
} else {
|
||||
await gitAddFile(body.filePath, cwd);
|
||||
}
|
||||
|
||||
return Response.json({ ok: true });
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Failed to git add";
|
||||
return Response.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// API: Serve images (local paths or temp uploads)
|
||||
if (url.pathname === "/api/image") {
|
||||
return handleImage(req);
|
||||
|
||||
Reference in New Issue
Block a user