Merge remote-tracking branch 'upstream/v0.13.0rc' into feat/react-query

# Conflicts:
#	apps/ui/src/components/views/board-view.tsx
#	apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx
#	apps/ui/src/components/views/board-view/hooks/use-board-features.ts
#	apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx
#	apps/ui/src/hooks/use-project-settings-loader.ts
This commit is contained in:
DhanushSantosh
2026-01-20 19:19:21 +05:30
76 changed files with 5995 additions and 492 deletions

View File

@@ -132,11 +132,12 @@ export function DevServerLogsPanel({
return (
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
<DialogContent
className="w-[70vw] max-w-[900px] max-h-[85vh] flex flex-col gap-0 p-0 overflow-hidden"
className="w-full h-full max-w-full max-h-full sm:w-[70vw] sm:max-w-[900px] sm:max-h-[85vh] sm:h-auto sm:rounded-xl rounded-none flex flex-col gap-0 p-0 overflow-hidden"
data-testid="dev-server-logs-panel"
compact
>
{/* Compact Header */}
<DialogHeader className="shrink-0 px-4 py-3 border-b border-border/50">
<DialogHeader className="shrink-0 px-4 py-3 border-b border-border/50 pr-12">
<div className="flex items-center justify-between">
<DialogTitle className="flex items-center gap-2 text-base">
<Terminal className="w-4 h-4 text-primary" />

View File

@@ -25,10 +25,13 @@ import {
AlertCircle,
RefreshCw,
Copy,
Eye,
ScrollText,
Terminal,
SquarePlus,
SplitSquareHorizontal,
Zap,
Undo2,
} from 'lucide-react';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
@@ -56,12 +59,16 @@ interface WorktreeActionsDropdownProps {
gitRepoStatus: GitRepoStatus;
/** When true, renders as a standalone button (not attached to another element) */
standalone?: boolean;
/** Whether auto mode is running for this worktree */
isAutoModeRunning?: boolean;
onOpenChange: (open: boolean) => void;
onPull: (worktree: WorktreeInfo) => void;
onPush: (worktree: WorktreeInfo) => void;
onOpenInEditor: (worktree: WorktreeInfo, editorCommand?: string) => void;
onOpenInIntegratedTerminal: (worktree: WorktreeInfo, mode?: 'tab' | 'split') => void;
onOpenInExternalTerminal: (worktree: WorktreeInfo, terminalId?: string) => void;
onViewChanges: (worktree: WorktreeInfo) => void;
onDiscardChanges: (worktree: WorktreeInfo) => void;
onCommit: (worktree: WorktreeInfo) => void;
onCreatePR: (worktree: WorktreeInfo) => void;
onAddressPRComments: (worktree: WorktreeInfo, prInfo: PRInfo) => void;
@@ -73,6 +80,7 @@ interface WorktreeActionsDropdownProps {
onOpenDevServerUrl: (worktree: WorktreeInfo) => void;
onViewDevServerLogs: (worktree: WorktreeInfo) => void;
onRunInitScript: (worktree: WorktreeInfo) => void;
onToggleAutoMode?: (worktree: WorktreeInfo) => void;
hasInitScript: boolean;
}
@@ -88,12 +96,15 @@ export function WorktreeActionsDropdown({
devServerInfo,
gitRepoStatus,
standalone = false,
isAutoModeRunning = false,
onOpenChange,
onPull,
onPush,
onOpenInEditor,
onOpenInIntegratedTerminal,
onOpenInExternalTerminal,
onViewChanges,
onDiscardChanges,
onCommit,
onCreatePR,
onAddressPRComments,
@@ -105,6 +116,7 @@ export function WorktreeActionsDropdown({
onOpenDevServerUrl,
onViewDevServerLogs,
onRunInitScript,
onToggleAutoMode,
hasInitScript,
}: WorktreeActionsDropdownProps) {
// Get available editors for the "Open In" submenu
@@ -214,6 +226,26 @@ export function WorktreeActionsDropdown({
<DropdownMenuSeparator />
</>
)}
{/* Auto Mode toggle */}
{onToggleAutoMode && (
<>
{isAutoModeRunning ? (
<DropdownMenuItem onClick={() => onToggleAutoMode(worktree)} className="text-xs">
<span className="flex items-center mr-2">
<Zap className="w-3.5 h-3.5 text-yellow-500" />
<span className="ml-0.5 h-2 w-2 rounded-full bg-green-500 animate-pulse" />
</span>
Stop Auto Mode
</DropdownMenuItem>
) : (
<DropdownMenuItem onClick={() => onToggleAutoMode(worktree)} className="text-xs">
<Zap className="w-3.5 h-3.5 mr-2" />
Start Auto Mode
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
</>
)}
<TooltipWrapper showTooltip={!!gitOpsDisabledReason} tooltipContent={gitOpsDisabledReason}>
<DropdownMenuItem
onClick={() => canPerformGitOps && onPull(worktree)}
@@ -408,6 +440,13 @@ export function WorktreeActionsDropdown({
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
{worktree.hasChanges && (
<DropdownMenuItem onClick={() => onViewChanges(worktree)} className="text-xs">
<Eye className="w-3.5 h-3.5 mr-2" />
View Changes
</DropdownMenuItem>
)}
{worktree.hasChanges && (
<TooltipWrapper
showTooltip={!gitRepoStatus.isGitRepo}
@@ -483,9 +522,30 @@ export function WorktreeActionsDropdown({
</DropdownMenuItem>
</>
)}
<DropdownMenuSeparator />
{worktree.hasChanges && (
<TooltipWrapper
showTooltip={!gitRepoStatus.isGitRepo}
tooltipContent="Not a git repository"
>
<DropdownMenuItem
onClick={() => gitRepoStatus.isGitRepo && onDiscardChanges(worktree)}
disabled={!gitRepoStatus.isGitRepo}
className={cn(
'text-xs text-destructive focus:text-destructive',
!gitRepoStatus.isGitRepo && 'opacity-50 cursor-not-allowed'
)}
>
<Undo2 className="w-3.5 h-3.5 mr-2" />
Discard Changes
{!gitRepoStatus.isGitRepo && (
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
)}
</DropdownMenuItem>
</TooltipWrapper>
)}
{!worktree.isMain && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => onDeleteWorktree(worktree)}
className="text-xs text-destructive focus:text-destructive"

View File

@@ -29,6 +29,8 @@ interface WorktreeTabProps {
aheadCount: number;
behindCount: number;
gitRepoStatus: GitRepoStatus;
/** Whether auto mode is running for this worktree */
isAutoModeRunning?: boolean;
onSelectWorktree: (worktree: WorktreeInfo) => void;
onBranchDropdownOpenChange: (open: boolean) => void;
onActionsDropdownOpenChange: (open: boolean) => void;
@@ -40,6 +42,8 @@ interface WorktreeTabProps {
onOpenInEditor: (worktree: WorktreeInfo, editorCommand?: string) => void;
onOpenInIntegratedTerminal: (worktree: WorktreeInfo, mode?: 'tab' | 'split') => void;
onOpenInExternalTerminal: (worktree: WorktreeInfo, terminalId?: string) => void;
onViewChanges: (worktree: WorktreeInfo) => void;
onDiscardChanges: (worktree: WorktreeInfo) => void;
onCommit: (worktree: WorktreeInfo) => void;
onCreatePR: (worktree: WorktreeInfo) => void;
onAddressPRComments: (worktree: WorktreeInfo, prInfo: PRInfo) => void;
@@ -51,6 +55,7 @@ interface WorktreeTabProps {
onOpenDevServerUrl: (worktree: WorktreeInfo) => void;
onViewDevServerLogs: (worktree: WorktreeInfo) => void;
onRunInitScript: (worktree: WorktreeInfo) => void;
onToggleAutoMode?: (worktree: WorktreeInfo) => void;
hasInitScript: boolean;
}
@@ -75,6 +80,7 @@ export function WorktreeTab({
aheadCount,
behindCount,
gitRepoStatus,
isAutoModeRunning = false,
onSelectWorktree,
onBranchDropdownOpenChange,
onActionsDropdownOpenChange,
@@ -86,6 +92,8 @@ export function WorktreeTab({
onOpenInEditor,
onOpenInIntegratedTerminal,
onOpenInExternalTerminal,
onViewChanges,
onDiscardChanges,
onCommit,
onCreatePR,
onAddressPRComments,
@@ -97,6 +105,7 @@ export function WorktreeTab({
onOpenDevServerUrl,
onViewDevServerLogs,
onRunInitScript,
onToggleAutoMode,
hasInitScript,
}: WorktreeTabProps) {
let prBadge: JSX.Element | null = null;
@@ -332,6 +341,26 @@ export function WorktreeTab({
</TooltipProvider>
)}
{isAutoModeRunning && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span
className={cn(
'flex items-center justify-center h-7 px-1.5 rounded-none border-r-0',
isSelected ? 'bg-primary text-primary-foreground' : 'bg-secondary/50'
)}
>
<span className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
</span>
</TooltipTrigger>
<TooltipContent>
<p>Auto Mode Running</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<WorktreeActionsDropdown
worktree={worktree}
isSelected={isSelected}
@@ -343,12 +372,15 @@ export function WorktreeTab({
isDevServerRunning={isDevServerRunning}
devServerInfo={devServerInfo}
gitRepoStatus={gitRepoStatus}
isAutoModeRunning={isAutoModeRunning}
onOpenChange={onActionsDropdownOpenChange}
onPull={onPull}
onPush={onPush}
onOpenInEditor={onOpenInEditor}
onOpenInIntegratedTerminal={onOpenInIntegratedTerminal}
onOpenInExternalTerminal={onOpenInExternalTerminal}
onViewChanges={onViewChanges}
onDiscardChanges={onDiscardChanges}
onCommit={onCommit}
onCreatePR={onCreatePR}
onAddressPRComments={onAddressPRComments}
@@ -360,6 +392,7 @@ export function WorktreeTab({
onOpenDevServerUrl={onOpenDevServerUrl}
onViewDevServerLogs={onViewDevServerLogs}
onRunInitScript={onRunInitScript}
onToggleAutoMode={onToggleAutoMode}
hasInitScript={hasInitScript}
/>
</div>

View File

@@ -2,7 +2,7 @@ import { useEffect, useRef, useCallback, useState } from 'react';
import { Button } from '@/components/ui/button';
import { GitBranch, Plus, RefreshCw } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn, pathsEqual } from '@/lib/utils';
import { pathsEqual } from '@/lib/utils';
import { toast } from 'sonner';
import { getHttpApiClient } from '@/lib/http-api-client';
import { useIsMobile } from '@/hooks/use-media-query';
@@ -22,6 +22,10 @@ import {
WorktreeActionsDropdown,
BranchSwitchDropdown,
} from './components';
import { useAppStore } from '@/store/app-store';
import { ViewWorktreeChangesDialog } from '../dialogs';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { Undo2 } from 'lucide-react';
export function WorktreePanel({
projectPath,
@@ -51,7 +55,6 @@ export function WorktreePanel({
const {
isStartingDevServer,
getWorktreeKey,
isDevServerRunning,
getDevServerInfo,
handleStartDevServer,
@@ -90,10 +93,79 @@ export function WorktreePanel({
features,
});
// Auto-mode state management using the store
// Use separate selectors to avoid creating new object references on each render
const autoModeByWorktree = useAppStore((state) => state.autoModeByWorktree);
const currentProject = useAppStore((state) => state.currentProject);
// Helper to generate worktree key for auto-mode (inlined to avoid selector issues)
const getAutoModeWorktreeKey = useCallback(
(projectId: string, branchName: string | null): string => {
return `${projectId}::${branchName ?? '__main__'}`;
},
[]
);
// Helper to check if auto-mode is running for a specific worktree
const isAutoModeRunningForWorktree = useCallback(
(worktree: WorktreeInfo): boolean => {
if (!currentProject) return false;
const branchName = worktree.isMain ? null : worktree.branch;
const key = getAutoModeWorktreeKey(currentProject.id, branchName);
return autoModeByWorktree[key]?.isRunning ?? false;
},
[currentProject, autoModeByWorktree, getAutoModeWorktreeKey]
);
// Handler to toggle auto-mode for a worktree
const handleToggleAutoMode = useCallback(
async (worktree: WorktreeInfo) => {
if (!currentProject) return;
// Import the useAutoMode to get start/stop functions
// Since useAutoMode is a hook, we'll use the API client directly
const api = getHttpApiClient();
const branchName = worktree.isMain ? null : worktree.branch;
const isRunning = isAutoModeRunningForWorktree(worktree);
try {
if (isRunning) {
const result = await api.autoMode.stop(projectPath, branchName);
if (result.success) {
const desc = branchName ? `worktree ${branchName}` : 'main branch';
toast.success(`Auto Mode stopped for ${desc}`);
} else {
toast.error(result.error || 'Failed to stop Auto Mode');
}
} else {
const result = await api.autoMode.start(projectPath, branchName);
if (result.success) {
const desc = branchName ? `worktree ${branchName}` : 'main branch';
toast.success(`Auto Mode started for ${desc}`);
} else {
toast.error(result.error || 'Failed to start Auto Mode');
}
}
} catch (error) {
toast.error('Error toggling Auto Mode');
console.error('Auto mode toggle error:', error);
}
},
[currentProject, projectPath, isAutoModeRunningForWorktree]
);
// Check if init script exists for the project using React Query
const { data: initScriptData } = useWorktreeInitScript(projectPath);
const hasInitScript = initScriptData?.exists ?? false;
// View changes dialog state
const [viewChangesDialogOpen, setViewChangesDialogOpen] = useState(false);
const [viewChangesWorktree, setViewChangesWorktree] = useState<WorktreeInfo | null>(null);
// Discard changes confirmation dialog state
const [discardChangesDialogOpen, setDiscardChangesDialogOpen] = useState(false);
const [discardChangesWorktree, setDiscardChangesWorktree] = useState<WorktreeInfo | null>(null);
// Log panel state management
const [logPanelOpen, setLogPanelOpen] = useState(false);
const [logPanelWorktree, setLogPanelWorktree] = useState<WorktreeInfo | null>(null);
@@ -161,6 +233,41 @@ export function WorktreePanel({
[projectPath]
);
const handleViewChanges = useCallback((worktree: WorktreeInfo) => {
setViewChangesWorktree(worktree);
setViewChangesDialogOpen(true);
}, []);
const handleDiscardChanges = useCallback((worktree: WorktreeInfo) => {
setDiscardChangesWorktree(worktree);
setDiscardChangesDialogOpen(true);
}, []);
const handleConfirmDiscardChanges = useCallback(async () => {
if (!discardChangesWorktree) return;
try {
const api = getHttpApiClient();
const result = await api.worktree.discardChanges(discardChangesWorktree.path);
if (result.success) {
toast.success('Changes discarded', {
description: `Discarded changes in ${discardChangesWorktree.branch}`,
});
// Refresh worktrees to update the changes status
fetchWorktrees({ silent: true });
} else {
toast.error('Failed to discard changes', {
description: result.error || 'Unknown error',
});
}
} catch (error) {
toast.error('Failed to discard changes', {
description: error instanceof Error ? error.message : 'Unknown error',
});
}
}, [discardChangesWorktree, fetchWorktrees]);
// Handle opening the log panel for a specific worktree
const handleViewDevServerLogs = useCallback((worktree: WorktreeInfo) => {
setLogPanelWorktree(worktree);
@@ -224,12 +331,15 @@ export function WorktreePanel({
isDevServerRunning={isDevServerRunning(selectedWorktree)}
devServerInfo={getDevServerInfo(selectedWorktree)}
gitRepoStatus={gitRepoStatus}
isAutoModeRunning={isAutoModeRunningForWorktree(selectedWorktree)}
onOpenChange={handleActionsDropdownOpenChange(selectedWorktree)}
onPull={handlePull}
onPush={handlePush}
onOpenInEditor={handleOpenInEditor}
onOpenInIntegratedTerminal={handleOpenInIntegratedTerminal}
onOpenInExternalTerminal={handleOpenInExternalTerminal}
onViewChanges={handleViewChanges}
onDiscardChanges={handleDiscardChanges}
onCommit={onCommit}
onCreatePR={onCreatePR}
onAddressPRComments={onAddressPRComments}
@@ -241,6 +351,7 @@ export function WorktreePanel({
onOpenDevServerUrl={handleOpenDevServerUrl}
onViewDevServerLogs={handleViewDevServerLogs}
onRunInitScript={handleRunInitScript}
onToggleAutoMode={handleToggleAutoMode}
hasInitScript={hasInitScript}
/>
)}
@@ -274,6 +385,36 @@ export function WorktreePanel({
</Button>
</>
)}
{/* View Changes Dialog */}
<ViewWorktreeChangesDialog
open={viewChangesDialogOpen}
onOpenChange={setViewChangesDialogOpen}
worktree={viewChangesWorktree}
projectPath={projectPath}
/>
{/* Discard Changes Confirmation Dialog */}
<ConfirmDialog
open={discardChangesDialogOpen}
onOpenChange={setDiscardChangesDialogOpen}
onConfirm={handleConfirmDiscardChanges}
title="Discard Changes"
description={`Are you sure you want to discard all changes in "${discardChangesWorktree?.branch}"? This will reset staged changes, discard modifications to tracked files, and remove untracked files. This action cannot be undone.`}
icon={Undo2}
iconClassName="text-destructive"
confirmText="Discard Changes"
confirmVariant="destructive"
/>
{/* Dev Server Logs Panel */}
<DevServerLogsPanel
open={logPanelOpen}
onClose={handleCloseLogPanel}
worktree={logPanelWorktree}
onStopDevServer={handleStopDevServer}
onOpenDevServerUrl={handleOpenDevServerUrl}
/>
</div>
);
}
@@ -308,6 +449,7 @@ export function WorktreePanel({
aheadCount={aheadCount}
behindCount={behindCount}
gitRepoStatus={gitRepoStatus}
isAutoModeRunning={isAutoModeRunningForWorktree(mainWorktree)}
onSelectWorktree={handleSelectWorktree}
onBranchDropdownOpenChange={handleBranchDropdownOpenChange(mainWorktree)}
onActionsDropdownOpenChange={handleActionsDropdownOpenChange(mainWorktree)}
@@ -319,6 +461,8 @@ export function WorktreePanel({
onOpenInEditor={handleOpenInEditor}
onOpenInIntegratedTerminal={handleOpenInIntegratedTerminal}
onOpenInExternalTerminal={handleOpenInExternalTerminal}
onViewChanges={handleViewChanges}
onDiscardChanges={handleDiscardChanges}
onCommit={onCommit}
onCreatePR={onCreatePR}
onAddressPRComments={onAddressPRComments}
@@ -330,6 +474,7 @@ export function WorktreePanel({
onOpenDevServerUrl={handleOpenDevServerUrl}
onViewDevServerLogs={handleViewDevServerLogs}
onRunInitScript={handleRunInitScript}
onToggleAutoMode={handleToggleAutoMode}
hasInitScript={hasInitScript}
/>
)}
@@ -368,6 +513,7 @@ export function WorktreePanel({
aheadCount={aheadCount}
behindCount={behindCount}
gitRepoStatus={gitRepoStatus}
isAutoModeRunning={isAutoModeRunningForWorktree(worktree)}
onSelectWorktree={handleSelectWorktree}
onBranchDropdownOpenChange={handleBranchDropdownOpenChange(worktree)}
onActionsDropdownOpenChange={handleActionsDropdownOpenChange(worktree)}
@@ -379,6 +525,8 @@ export function WorktreePanel({
onOpenInEditor={handleOpenInEditor}
onOpenInIntegratedTerminal={handleOpenInIntegratedTerminal}
onOpenInExternalTerminal={handleOpenInExternalTerminal}
onViewChanges={handleViewChanges}
onDiscardChanges={handleDiscardChanges}
onCommit={onCommit}
onCreatePR={onCreatePR}
onAddressPRComments={onAddressPRComments}
@@ -390,6 +538,7 @@ export function WorktreePanel({
onOpenDevServerUrl={handleOpenDevServerUrl}
onViewDevServerLogs={handleViewDevServerLogs}
onRunInitScript={handleRunInitScript}
onToggleAutoMode={handleToggleAutoMode}
hasInitScript={hasInitScript}
/>
);
@@ -424,6 +573,27 @@ export function WorktreePanel({
</>
)}
{/* View Changes Dialog */}
<ViewWorktreeChangesDialog
open={viewChangesDialogOpen}
onOpenChange={setViewChangesDialogOpen}
worktree={viewChangesWorktree}
projectPath={projectPath}
/>
{/* Discard Changes Confirmation Dialog */}
<ConfirmDialog
open={discardChangesDialogOpen}
onOpenChange={setDiscardChangesDialogOpen}
onConfirm={handleConfirmDiscardChanges}
title="Discard Changes"
description={`Are you sure you want to discard all changes in "${discardChangesWorktree?.branch}"? This will reset staged changes, discard modifications to tracked files, and remove untracked files. This action cannot be undone.`}
icon={Undo2}
iconClassName="text-destructive"
confirmText="Discard Changes"
confirmVariant="destructive"
/>
{/* Dev Server Logs Panel */}
<DevServerLogsPanel
open={logPanelOpen}