mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
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:
@@ -144,6 +144,7 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@pierre/diffs": "^1.1.0-beta.19",
|
||||
"@plannotator/shared": "workspace:*",
|
||||
"@plannotator/ui": "workspace:*",
|
||||
"highlight.js": "^11.11.1",
|
||||
"react": "^19.2.3",
|
||||
|
||||
@@ -18,6 +18,7 @@ import { DiffViewer } from './components/DiffViewer';
|
||||
import { ReviewPanel } from './components/ReviewPanel';
|
||||
import { FileTree } from './components/FileTree';
|
||||
import { DEMO_DIFF } from './demoData';
|
||||
import type { DiffOption, WorktreeInfo, GitContext } from '@plannotator/shared/types';
|
||||
|
||||
declare const __APP_VERSION__: string;
|
||||
|
||||
@@ -29,17 +30,6 @@ interface DiffFile {
|
||||
deletions: number;
|
||||
}
|
||||
|
||||
interface DiffOption {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface GitContext {
|
||||
currentBranch: string;
|
||||
defaultBranch: string;
|
||||
diffOptions: DiffOption[];
|
||||
}
|
||||
|
||||
interface DiffData {
|
||||
files: DiffFile[];
|
||||
rawPatch: string;
|
||||
@@ -355,16 +345,30 @@ const ReviewApp: React.FC = () => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Switch diff type (uncommitted, staged, last-commit, branch, etc.)
|
||||
const handleDiffSwitch = useCallback(async (newDiffType: string) => {
|
||||
if (newDiffType === diffType || newDiffType === 'separator') return;
|
||||
// Derive worktree path and base diff type from the composite diffType string
|
||||
const { activeWorktreePath, activeDiffBase } = useMemo(() => {
|
||||
if (diffType.startsWith('worktree:')) {
|
||||
const rest = diffType.slice('worktree:'.length);
|
||||
const lastColon = rest.lastIndexOf(':');
|
||||
if (lastColon !== -1) {
|
||||
const sub = rest.slice(lastColon + 1);
|
||||
if (['uncommitted', 'last-commit', 'branch'].includes(sub)) {
|
||||
return { activeWorktreePath: rest.slice(0, lastColon), activeDiffBase: sub };
|
||||
}
|
||||
}
|
||||
return { activeWorktreePath: rest, activeDiffBase: 'uncommitted' };
|
||||
}
|
||||
return { activeWorktreePath: null, activeDiffBase: diffType };
|
||||
}, [diffType]);
|
||||
|
||||
// Shared helper: fetch a diff switch and update state
|
||||
const fetchDiffSwitch = useCallback(async (fullDiffType: string) => {
|
||||
setIsLoadingDiff(true);
|
||||
try {
|
||||
const res = await fetch('/api/diff/switch', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ diffType: newDiffType }),
|
||||
body: JSON.stringify({ diffType: fullDiffType }),
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error('Failed to switch diff');
|
||||
@@ -373,31 +377,39 @@ const ReviewApp: React.FC = () => {
|
||||
rawPatch: string;
|
||||
gitRef: string;
|
||||
diffType: string;
|
||||
diffOptions?: { id: string; label: string }[];
|
||||
error?: string;
|
||||
};
|
||||
|
||||
const newFiles = parseDiffToFiles(data.rawPatch);
|
||||
setFiles(newFiles);
|
||||
setFiles(parseDiffToFiles(data.rawPatch));
|
||||
setDiffType(data.diffType);
|
||||
setActiveFileIndex(0);
|
||||
setPendingSelection(null);
|
||||
setDiffError(data.error || null);
|
||||
|
||||
// Update dropdown options on worktree mode transitions
|
||||
if (data.diffOptions) {
|
||||
setGitContext(prev => prev ? { ...prev, diffOptions: data.diffOptions! } : prev);
|
||||
}
|
||||
|
||||
// Note: We keep existing annotations - they may still be relevant
|
||||
// or user can clear them manually
|
||||
} catch (err) {
|
||||
console.error('Failed to switch diff:', err);
|
||||
setDiffError(err instanceof Error ? err.message : 'Failed to switch diff');
|
||||
} finally {
|
||||
setIsLoadingDiff(false);
|
||||
}
|
||||
}, [diffType]);
|
||||
}, []);
|
||||
|
||||
// Switch diff type (uncommitted, last-commit, branch) — composes worktree prefix if active
|
||||
const handleDiffSwitch = useCallback(async (baseDiffType: string) => {
|
||||
const fullDiffType = activeWorktreePath
|
||||
? `worktree:${activeWorktreePath}:${baseDiffType}`
|
||||
: baseDiffType;
|
||||
if (fullDiffType === diffType) return;
|
||||
await fetchDiffSwitch(fullDiffType);
|
||||
}, [diffType, activeWorktreePath, fetchDiffSwitch]);
|
||||
|
||||
// Switch worktree context (or back to main repo)
|
||||
const handleWorktreeSwitch = useCallback(async (worktreePath: string | null) => {
|
||||
if (worktreePath === activeWorktreePath) return;
|
||||
const fullDiffType = worktreePath
|
||||
? `worktree:${worktreePath}:uncommitted`
|
||||
: 'uncommitted';
|
||||
await fetchDiffSwitch(fullDiffType);
|
||||
}, [activeWorktreePath, fetchDiffSwitch]);
|
||||
|
||||
// Select annotation - switches file if needed and scrolls to it
|
||||
const handleSelectAnnotation = useCallback((id: string | null) => {
|
||||
@@ -788,10 +800,14 @@ const ReviewApp: React.FC = () => {
|
||||
onToggleHideViewed={() => setHideViewedFiles(prev => !prev)}
|
||||
enableKeyboardNav={!showExportModal}
|
||||
diffOptions={gitContext?.diffOptions}
|
||||
activeDiffType={diffType}
|
||||
activeDiffType={activeDiffBase}
|
||||
onSelectDiff={handleDiffSwitch}
|
||||
isLoadingDiff={isLoadingDiff}
|
||||
width={fileTreeResize.width}
|
||||
worktrees={gitContext?.worktrees}
|
||||
activeWorktreePath={activeWorktreePath}
|
||||
onSelectWorktree={handleWorktreeSwitch}
|
||||
currentBranch={gitContext?.currentBranch}
|
||||
/>
|
||||
<ResizeHandle {...fileTreeResize.handleProps} />
|
||||
</>
|
||||
@@ -850,16 +866,11 @@ const ReviewApp: React.FC = () => {
|
||||
<>
|
||||
<h3 className="text-sm font-medium text-foreground">No changes</h3>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{diffType === 'uncommitted' && "No uncommitted changes to review."}
|
||||
{diffType === 'staged' && "No staged changes. Stage some files with git add."}
|
||||
{diffType === 'unstaged' && "No unstaged changes. All changes are staged."}
|
||||
{diffType === 'last-commit' && "No changes in the last commit."}
|
||||
{diffType === 'branch' && `No changes between this branch and ${gitContext?.defaultBranch || 'main'}.`}
|
||||
{diffType?.startsWith('worktree:') && (
|
||||
diffType.endsWith(':last-commit') ? "No changes in the last commit in this worktree." :
|
||||
diffType.endsWith(':branch') ? `No changes vs ${gitContext?.defaultBranch || 'main'} in this worktree.` :
|
||||
"No uncommitted changes in this worktree."
|
||||
)}
|
||||
{activeDiffBase === 'uncommitted' && `No uncommitted changes${activeWorktreePath ? ' in this worktree' : ' to review'}.`}
|
||||
{activeDiffBase === 'staged' && "No staged changes. Stage some files with git add."}
|
||||
{activeDiffBase === 'unstaged' && "No unstaged changes. All changes are staged."}
|
||||
{activeDiffBase === 'last-commit' && `No changes in the last commit${activeWorktreePath ? ' in this worktree' : ''}.`}
|
||||
{activeDiffBase === 'branch' && `No changes vs ${gitContext?.defaultBranch || 'main'}${activeWorktreePath ? ' in this worktree' : ''}.`}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||
import { CodeAnnotation } from '@plannotator/ui/types';
|
||||
import type { DiffOption, WorktreeInfo } from '@plannotator/shared/types';
|
||||
import { buildFileTree, getAncestorPaths, getAllFolderPaths } from '../utils/buildFileTree';
|
||||
import { FileTreeNodeItem } from './FileTreeNode';
|
||||
|
||||
@@ -11,11 +12,6 @@ interface DiffFile {
|
||||
deletions: number;
|
||||
}
|
||||
|
||||
interface DiffOption {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface FileTreeProps {
|
||||
files: DiffFile[];
|
||||
activeFileIndex: number;
|
||||
@@ -31,6 +27,10 @@ interface FileTreeProps {
|
||||
onSelectDiff?: (diffType: string) => void;
|
||||
isLoadingDiff?: boolean;
|
||||
width?: number;
|
||||
worktrees?: WorktreeInfo[];
|
||||
activeWorktreePath?: string | null;
|
||||
onSelectWorktree?: (path: string | null) => void;
|
||||
currentBranch?: string;
|
||||
}
|
||||
|
||||
export const FileTree: React.FC<FileTreeProps> = ({
|
||||
@@ -48,6 +48,10 @@ export const FileTree: React.FC<FileTreeProps> = ({
|
||||
onSelectDiff,
|
||||
isLoadingDiff,
|
||||
width,
|
||||
worktrees,
|
||||
activeWorktreePath,
|
||||
onSelectWorktree,
|
||||
currentBranch,
|
||||
}) => {
|
||||
// Keyboard navigation: j/k or arrow keys
|
||||
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
@@ -163,9 +167,43 @@ export const FileTree: React.FC<FileTreeProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Diff selector dropdown - between header and file list */}
|
||||
{/* Worktree context switcher — only shown when worktrees exist */}
|
||||
{worktrees && worktrees.length > 0 && onSelectWorktree && (
|
||||
<div className="px-2 pt-2 pb-1.5 border-b border-border/30">
|
||||
<div className="text-[10px] text-muted-foreground/60 uppercase tracking-wider mb-1 px-0.5">Context</div>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={activeWorktreePath || ''}
|
||||
onChange={(e) => onSelectWorktree(e.target.value || null)}
|
||||
disabled={isLoadingDiff}
|
||||
className={`w-full px-2.5 py-1.5 rounded-md text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary/50 cursor-pointer disabled:opacity-50 disabled:cursor-wait appearance-none pr-7 ${
|
||||
activeWorktreePath
|
||||
? 'bg-primary/10 border border-primary/30'
|
||||
: 'bg-muted'
|
||||
}`}
|
||||
>
|
||||
<option value="">{currentBranch || 'Main repo'}</option>
|
||||
{worktrees.map(wt => (
|
||||
<option key={wt.path} value={wt.path}>
|
||||
{(wt.branch || wt.path.split('/').pop()) + ' (worktree)'}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none">
|
||||
<svg className="w-3.5 h-3.5 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Diff type selector — always the same base options */}
|
||||
{diffOptions && diffOptions.length > 0 && onSelectDiff && (
|
||||
<div className="px-2 py-2 border-b border-border/30">
|
||||
<div className="px-2 py-1.5 border-b border-border/30">
|
||||
{worktrees && worktrees.length > 0 && (
|
||||
<div className="text-[10px] text-muted-foreground/60 uppercase tracking-wider mb-1 px-0.5">View</div>
|
||||
)}
|
||||
<div className="relative">
|
||||
<select
|
||||
value={activeDiffType || 'uncommitted'}
|
||||
@@ -173,19 +211,12 @@ export const FileTree: React.FC<FileTreeProps> = ({
|
||||
disabled={isLoadingDiff}
|
||||
className="w-full px-2.5 py-1.5 bg-muted rounded-md text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary/50 cursor-pointer disabled:opacity-50 disabled:cursor-wait appearance-none pr-7"
|
||||
>
|
||||
{diffOptions.map((option, index) => (
|
||||
option.id === 'separator' ? (
|
||||
<option key={`sep-${index}`} disabled className="text-muted-foreground">
|
||||
────────────
|
||||
</option>
|
||||
) : (
|
||||
<option key={option.id} value={option.id}>
|
||||
{option.label}
|
||||
</option>
|
||||
)
|
||||
{diffOptions.map((option) => (
|
||||
<option key={option.id} value={option.id}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* Dropdown arrow */}
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none">
|
||||
{isLoadingDiff ? (
|
||||
<svg className="w-3.5 h-3.5 text-muted-foreground animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"./styles": "./index.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@plannotator/shared": "workspace:*",
|
||||
"@plannotator/ui": "workspace:*",
|
||||
"@pierre/diffs": "^1.1.0-beta.19",
|
||||
"highlight.js": "^11.11.1",
|
||||
|
||||
+4
-43
@@ -7,6 +7,8 @@
|
||||
|
||||
import { $ } from "bun";
|
||||
|
||||
export type { DiffOption, WorktreeInfo, GitContext } from "@plannotator/shared/types";
|
||||
|
||||
export type DiffType =
|
||||
| "uncommitted"
|
||||
| "staged"
|
||||
@@ -15,23 +17,6 @@ export type DiffType =
|
||||
| "branch"
|
||||
| `worktree:${string}`;
|
||||
|
||||
export interface DiffOption {
|
||||
id: DiffType | "separator";
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface WorktreeInfo {
|
||||
path: string;
|
||||
branch: string | null; // null = detached HEAD
|
||||
head: string;
|
||||
}
|
||||
|
||||
export interface GitContext {
|
||||
currentBranch: string;
|
||||
defaultBranch: string;
|
||||
diffOptions: DiffOption[];
|
||||
}
|
||||
|
||||
export interface DiffResult {
|
||||
patch: string;
|
||||
label: string;
|
||||
@@ -137,7 +122,7 @@ export async function getGitContext(): Promise<GitContext> {
|
||||
diffOptions.push({ id: "branch", label: `vs ${defaultBranch}` });
|
||||
}
|
||||
|
||||
// Discover worktrees and add them as diff options
|
||||
// Discover worktrees (exposed separately from diff options)
|
||||
const [worktrees, currentTreePath] = await Promise.all([
|
||||
getWorktrees(),
|
||||
$`git rev-parse --show-toplevel`.quiet().then(r => r.text().trim()).catch(() => null),
|
||||
@@ -145,17 +130,7 @@ export async function getGitContext(): Promise<GitContext> {
|
||||
|
||||
const otherWorktrees = worktrees.filter(wt => wt.path !== currentTreePath);
|
||||
|
||||
if (otherWorktrees.length > 0) {
|
||||
diffOptions.push({ id: "separator", label: "" });
|
||||
for (const wt of otherWorktrees) {
|
||||
const label = wt.branch
|
||||
? `${wt.branch} (worktree)`
|
||||
: `${wt.path.split("/").pop()} (worktree)`;
|
||||
diffOptions.push({ id: `worktree:${wt.path}`, label });
|
||||
}
|
||||
}
|
||||
|
||||
return { currentBranch, defaultBranch, diffOptions };
|
||||
return { currentBranch, defaultBranch, diffOptions, worktrees: otherWorktrees };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,20 +189,6 @@ export function parseWorktreeDiffType(diffType: string): { path: string; subType
|
||||
return { path: rest, subType: "uncommitted" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Build diff options for worktree mode: back-to-main, separator, then
|
||||
* standard diff types with the worktree path baked into each id.
|
||||
*/
|
||||
export function getWorktreeDiffOptions(worktreePath: string, defaultBranch: string): DiffOption[] {
|
||||
const prefix = `worktree:${worktreePath}:`;
|
||||
return [
|
||||
{ id: "back-to-main" as DiffType, label: "\u2190 Back to main repo" },
|
||||
{ id: "separator", label: "" },
|
||||
{ id: `${prefix}uncommitted` as DiffType, label: "Uncommitted changes" },
|
||||
{ id: `${prefix}last-commit` as DiffType, label: "Last commit" },
|
||||
{ id: `${prefix}branch` as DiffType, label: `vs ${defaultBranch}` },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run git diff with the specified type
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -8,3 +8,22 @@ export interface EditorAnnotation {
|
||||
comment?: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
// Git diff types shared between server and client
|
||||
export interface DiffOption {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface WorktreeInfo {
|
||||
path: string;
|
||||
branch: string | null;
|
||||
head: string;
|
||||
}
|
||||
|
||||
export interface GitContext {
|
||||
currentBranch: string;
|
||||
defaultBranch: string;
|
||||
diffOptions: DiffOption[];
|
||||
worktrees: WorktreeInfo[];
|
||||
}
|
||||
|
||||
@@ -14,12 +14,11 @@
|
||||
* What to test:
|
||||
*
|
||||
* WORKTREE FEATURES:
|
||||
* 1. Dropdown shows worktrees below a separator
|
||||
* 2. Selecting a worktree enters "worktree mode" — dropdown shows
|
||||
* Back to main / Uncommitted / Last commit / vs main
|
||||
* 3. "Last commit" in feature-auth shows the committed auth module
|
||||
* 4. "Uncommitted" in feature-auth shows only the rate-limit file
|
||||
* 5. "Back to main repo" restores the original dropdown
|
||||
* 1. Context dropdown appears above View dropdown, listing available worktrees
|
||||
* 2. Selecting a worktree in Context switches files; pill gets highlighted border
|
||||
* 3. View dropdown (Uncommitted/Last commit/vs main) stays the same in any context
|
||||
* 4. Can switch directly between worktrees without going "back to main" first
|
||||
* 5. Selecting main branch in Context restores the main repo view
|
||||
* 6. Empty worktree shows appropriate empty state messages
|
||||
* 7. Detached HEAD worktree uses directory name as label
|
||||
*
|
||||
@@ -1075,12 +1074,11 @@ const { patch: rawPatch, label: gitRef, error: diffError } = await runGitDiff(
|
||||
console.error("Git context discovered:");
|
||||
console.error(` Current branch: ${gitContext.currentBranch}`);
|
||||
console.error(` Default branch: ${gitContext.defaultBranch}`);
|
||||
console.error(` Diff options:`);
|
||||
for (const opt of gitContext.diffOptions) {
|
||||
if (opt.id === "separator") {
|
||||
console.error(` ────────────────`);
|
||||
} else {
|
||||
console.error(` ${opt.label} (${opt.id})`);
|
||||
console.error(` Diff options: ${gitContext.diffOptions.map(o => o.label).join(', ')}`);
|
||||
if (gitContext.worktrees.length > 0) {
|
||||
console.error(` Worktrees:`);
|
||||
for (const wt of gitContext.worktrees) {
|
||||
console.error(` ${wt.branch || wt.path.split('/').pop()} (${wt.path})`);
|
||||
}
|
||||
}
|
||||
console.error("");
|
||||
@@ -1089,13 +1087,14 @@ console.error("Starting review server...");
|
||||
console.error("Browser should open automatically.");
|
||||
console.error("");
|
||||
console.error("=== WORKTREE TESTS ===");
|
||||
console.error(" 1. Dropdown shows worktrees below a separator");
|
||||
console.error(" 2. Select 'feature-auth' → worktree mode (Back / Uncommitted / Last commit / vs main)");
|
||||
console.error(" 3. 'Uncommitted' shows rate-limit.ts");
|
||||
console.error(" 4. 'Last commit' shows committed auth module (5 files)");
|
||||
console.error(" 5. 'Back to main repo' restores original dropdown");
|
||||
console.error(" 6. 'empty-branch' → worktree mode, all options empty");
|
||||
console.error(" 7. 'wt-detached' → label uses directory name, not branch");
|
||||
console.error(" 1. 'Context' dropdown appears above 'View' dropdown listing worktrees");
|
||||
console.error(" 2. Select 'feature-auth' in Context → highlighted pill, files update");
|
||||
console.error(" 3. 'Uncommitted' in View shows rate-limit.ts");
|
||||
console.error(" 4. 'Last commit' in View shows committed auth module (5 files)");
|
||||
console.error(" 5. Switch Context back to main branch → restores main repo files");
|
||||
console.error(" 6. Switch directly between worktrees without returning to main");
|
||||
console.error(" 7. 'empty-branch' context → all View options show empty state");
|
||||
console.error(" 8. Detached HEAD worktree uses directory name as label");
|
||||
console.error("");
|
||||
console.error("=== EXPANDABLE DIFF CONTEXT TESTS ===");
|
||||
console.error(" 8. registry.ts — 4 disjoint hunks. Between each pair, you should see");
|
||||
|
||||
Reference in New Issue
Block a user