feat: split worktree/diff-type into separate controls (#248)

* feat: split worktree/diff-type into separate controls

The single dropdown that mixed diff type selection (uncommitted, last-commit,
branch) with worktree switching was confusing — entering a worktree silently
replaced all dropdown options with no persistent indicator of which worktree
was active.

Split into two controls:
- Context dropdown: switches between main repo and worktrees (only shown
  when worktrees exist, highlighted when a worktree is active)
- View dropdown: always shows the same diff type options regardless of context

Server changes:
- GitContext now exposes worktrees as a separate field
- Removed getWorktreeDiffOptions() and back-to-main sentinel handling
- Simplified /api/diff/switch (no more diffOptions replacement)

Client changes:
- New activeWorktreePath state + handleWorktreeSwitch callback
- handleDiffSwitch composes worktree prefix from context automatically
- Derived activeDiffBase strips worktree prefix for dropdown display

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: derive activeWorktreePath from diffType instead of separate state

Eliminates redundant state — activeWorktreePath and activeDiffBase are
now both derived in a single useMemo from the composite diffType string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: move DiffOption, WorktreeInfo, GitContext to @plannotator/shared

Eliminates duplicate interface definitions across server and client.
Types now live in packages/shared/types.ts and are imported by both
packages/server/git.ts and packages/review-editor/.

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:
Michael Ramos
2026-03-08 16:15:14 -07:00
committed by GitHub
parent 472e17be3d
commit 933f44737e
8 changed files with 145 additions and 144 deletions
+4 -26
View File
@@ -10,7 +10,7 @@
*/
import { isRemoteSession, getServerPort } from "./remote";
import { type DiffType, type GitContext, runGitDiff, getWorktreeDiffOptions, getFileContentsForDiff } from "./git";
import { type DiffType, type GitContext, runGitDiff, getFileContentsForDiff } from "./git";
import { getRepoInfo } from "./repo";
import { handleImage, handleUpload, handleAgents, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, type OpencodeClient } from "./shared-handlers";
import { contentHash, deleteDraft } from "./draft";
@@ -19,7 +19,7 @@ import { createEditorAnnotationHandler } from "./editor-annotations";
// Re-export utilities
export { isRemoteSession, getServerPort } from "./remote";
export { openBrowser } from "./browser";
export { type DiffType, type DiffOption, type GitContext } from "./git";
export { type DiffType, type DiffOption, type GitContext, type WorktreeInfo } from "./git";
export { handleServerReady as handleReviewServerReady } from "./shared-handlers";
// --- Types ---
@@ -153,13 +153,6 @@ export async function startReviewServer(
}
const defaultBranch = gitContext?.defaultBranch || "main";
const previousDiffType = currentDiffType;
// Handle "back to main repo" sentinel
const isBackToMain = newDiffType === ("back-to-main" as DiffType);
if (isBackToMain) {
newDiffType = "uncommitted" as DiffType;
}
// Run the new diff
const result = await runGitDiff(newDiffType, defaultBranch);
@@ -170,27 +163,12 @@ export async function startReviewServer(
currentDiffType = newDiffType;
currentError = result.error;
// Build response — include diffOptions on worktree mode transitions
const response: Record<string, unknown> = {
return Response.json({
rawPatch: currentPatch,
gitRef: currentGitRef,
diffType: currentDiffType,
...(currentError && { error: currentError }),
};
if (isBackToMain) {
// Exiting worktree mode: restore original dropdown options
response.diffOptions = gitContext?.diffOptions;
} else if (
newDiffType.startsWith("worktree:") &&
!previousDiffType.startsWith("worktree:")
) {
// Entering worktree mode: send worktree-scoped dropdown options
const wtPath = newDiffType.slice("worktree:".length).replace(/:(?:uncommitted|last-commit|branch)$/, "");
response.diffOptions = getWorktreeDiffOptions(wtPath, defaultBranch);
}
return Response.json(response);
});
} catch (err) {
const message =
err instanceof Error ? err.message : "Failed to switch diff";