Feature: worktree view customization and stability fixes (#805)

* Changes from feature/worktree-view-customization

* Feature: Git sync, set-tracking, and push divergence handling (#796)

* Add quick-add feature with improved workflows (#802)

* Changes from feature/quick-add

* feat: Clarify system prompt and improve error handling across services. Address PR Feedback

* feat: Improve PR description parsing and refactor event handling

* feat: Add context options to pipeline orchestrator initialization

* fix: Deduplicate React and handle CJS interop for use-sync-external-store

Resolve "Cannot read properties of null (reading 'useState')" errors by
deduplicating React/react-dom and ensuring use-sync-external-store is
bundled together with React to prevent CJS packages from resolving to
different React instances.

* Changes from feature/worktree-view-customization

* refactor: Remove unused worktree swap and highlight props

* refactor: Consolidate feature completion logic and improve thinking level defaults

* feat: Increase max turn limit to 10000

- Update DEFAULT_MAX_TURNS from 1000 to 10000 in settings-helpers.ts and agent-executor.ts
- Update MAX_ALLOWED_TURNS from 2000 to 10000 in settings-helpers.ts
- Update UI clamping logic from 2000 to 10000 in app-store.ts
- Update fallback values from 1000 to 10000 in use-settings-sync.ts
- Update default value from 1000 to 10000 in DEFAULT_GLOBAL_SETTINGS
- Update documentation to reflect new range: 1-10000

Allows agents to perform up to 10000 turns for complex feature execution.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: Add model resolution, improve session handling, and enhance UI stability

* refactor: Remove unused sync and tracking branch props from worktree components

* feat: Add PR number update functionality to worktrees. Address pr feedback

* feat: Optimize Gemini CLI startup and add tool result tracking

* refactor: Improve error handling and simplify worktree task cleanup

---------

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-02-23 20:31:25 -08:00
committed by GitHub
parent e7504b247f
commit 0330c70261
72 changed files with 3667 additions and 1173 deletions

View File

@@ -28,7 +28,11 @@ import { cn } from '@/lib/utils';
import { modelSupportsThinking } from '@/lib/utils';
import { useAppStore, ThinkingLevel, FeatureImage, PlanningMode, Feature } from '@/store/app-store';
import type { ReasoningEffort, PhaseModelEntry, AgentModel } from '@automaker/types';
import { supportsReasoningEffort, isAdaptiveThinkingModel } from '@automaker/types';
import {
supportsReasoningEffort,
isAdaptiveThinkingModel,
getThinkingLevelsForModel,
} from '@automaker/types';
import {
PrioritySelector,
WorkModeSelector,
@@ -211,6 +215,7 @@ export function AddFeatureDialog({
defaultRequirePlanApproval,
useWorktrees,
defaultFeatureModel,
defaultThinkingLevel,
currentProject,
} = useAppStore();
@@ -240,7 +245,22 @@ export function AddFeatureDialog({
);
setPlanningMode(defaultPlanningMode);
setRequirePlanApproval(defaultRequirePlanApproval);
setModelEntry(effectiveDefaultFeatureModel);
// Apply defaultThinkingLevel from settings to the model entry.
// This ensures the "Quick-Select Defaults" thinking level setting is respected
// even when the user doesn't change the model in the dropdown.
const modelId =
typeof effectiveDefaultFeatureModel.model === 'string'
? effectiveDefaultFeatureModel.model
: '';
const availableLevels = getThinkingLevelsForModel(modelId);
const effectiveThinkingLevel = availableLevels.includes(defaultThinkingLevel)
? defaultThinkingLevel
: availableLevels[0];
setModelEntry({
...effectiveDefaultFeatureModel,
thinkingLevel: effectiveThinkingLevel,
});
// Initialize description history (empty for new feature)
setDescriptionHistory([]);
@@ -269,6 +289,7 @@ export function AddFeatureDialog({
defaultPlanningMode,
defaultRequirePlanApproval,
effectiveDefaultFeatureModel,
defaultThinkingLevel,
useWorktrees,
selectedNonMainWorktreeBranch,
forceCurrentBranchMode,
@@ -394,7 +415,19 @@ export function AddFeatureDialog({
// When a non-main worktree is selected, use its branch name for custom mode
setBranchName(selectedNonMainWorktreeBranch || '');
setPriority(2);
setModelEntry(effectiveDefaultFeatureModel);
// Apply defaultThinkingLevel to the model entry (same logic as dialog open)
const resetModelId =
typeof effectiveDefaultFeatureModel.model === 'string'
? effectiveDefaultFeatureModel.model
: '';
const resetAvailableLevels = getThinkingLevelsForModel(resetModelId);
const resetThinkingLevel = resetAvailableLevels.includes(defaultThinkingLevel)
? defaultThinkingLevel
: resetAvailableLevels[0];
setModelEntry({
...effectiveDefaultFeatureModel,
thinkingLevel: resetThinkingLevel,
});
setWorkMode(
getDefaultWorkMode(useWorktrees, selectedNonMainWorktreeBranch, forceCurrentBranchMode)
);

View File

@@ -0,0 +1,197 @@
import { useState, useEffect, useCallback } from 'react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { GitPullRequest } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner';
interface WorktreeInfo {
path: string;
branch: string;
isMain: boolean;
pr?: {
number: number;
url: string;
title: string;
state: string;
createdAt: string;
};
}
interface ChangePRNumberDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
worktree: WorktreeInfo | null;
projectPath: string | null;
onChanged: () => void;
}
export function ChangePRNumberDialog({
open,
onOpenChange,
worktree,
projectPath,
onChanged,
}: ChangePRNumberDialogProps) {
const [prNumberInput, setPrNumberInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Initialize with current PR number when dialog opens
useEffect(() => {
if (open && worktree?.pr?.number) {
setPrNumberInput(String(worktree.pr.number));
} else if (open) {
setPrNumberInput('');
}
setError(null);
}, [open, worktree]);
const handleSubmit = useCallback(async () => {
if (!worktree) return;
const trimmed = prNumberInput.trim();
if (!/^\d+$/.test(trimmed)) {
setError('Please enter a valid positive PR number');
return;
}
const prNumber = Number(trimmed);
if (prNumber <= 0) {
setError('Please enter a valid positive PR number');
return;
}
setIsLoading(true);
setError(null);
try {
const api = getElectronAPI();
if (!api?.worktree?.updatePRNumber) {
setError('Worktree API not available');
return;
}
const result = await api.worktree.updatePRNumber(
worktree.path,
prNumber,
projectPath || undefined
);
if (result.success) {
const prInfo = result.result?.prInfo;
toast.success('PR tracking updated', {
description: prInfo?.title
? `Now tracking PR #${prNumber}: ${prInfo.title}`
: `Now tracking PR #${prNumber}`,
});
onOpenChange(false);
onChanged();
} else {
setError(result.error || 'Failed to update PR number');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to update PR number');
} finally {
setIsLoading(false);
}
}, [worktree, prNumberInput, projectPath, onOpenChange, onChanged]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !isLoading) {
e.preventDefault();
handleSubmit();
}
},
[isLoading, handleSubmit]
);
if (!worktree) return null;
return (
<Dialog
open={open}
onOpenChange={(isOpen) => {
if (!isLoading) {
onOpenChange(isOpen);
}
}}
>
<DialogContent className="sm:max-w-[400px]" onKeyDown={handleKeyDown}>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<GitPullRequest className="w-5 h-5" />
Change Tracked PR Number
</DialogTitle>
<DialogDescription>
Update which pull request number is tracked for{' '}
<code className="font-mono bg-muted px-1 rounded">{worktree.branch}</code>.
{worktree.pr && (
<span className="block mt-1 text-xs">
Currently tracking PR #{worktree.pr.number}
</span>
)}
</DialogDescription>
</DialogHeader>
<div className="py-2 space-y-3">
<div className="space-y-2">
<Label htmlFor="pr-number">Pull Request Number</Label>
<div className="flex items-center gap-2">
<span className="text-muted-foreground text-sm">#</span>
<Input
id="pr-number"
type="text"
inputMode="numeric"
placeholder="e.g. 42"
value={prNumberInput}
onChange={(e) => {
setPrNumberInput(e.target.value);
setError(null);
}}
disabled={isLoading}
autoFocus
className="flex-1"
/>
</div>
<p className="text-xs text-muted-foreground">
Enter the GitHub PR number to associate with this worktree. The PR info will be
fetched from GitHub if available.
</p>
</div>
{error && <p className="text-sm text-destructive">{error}</p>}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isLoading}>
Cancel
</Button>
<Button onClick={handleSubmit} disabled={isLoading || !prNumberInput.trim()}>
{isLoading ? (
<>
<Spinner size="xs" className="mr-2" />
Updating...
</>
) : (
<>
<GitPullRequest className="w-4 h-4 mr-2" />
Update PR
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -27,6 +27,7 @@ import { getHttpApiClient } from '@/lib/http-api-client';
import { toast } from 'sonner';
import { useWorktreeBranches } from '@/hooks/queries';
import { ModelOverrideTrigger, useModelOverride } from '@/components/shared';
import { resolveModelString } from '@automaker/model-resolver';
interface RemoteInfo {
name: string;
@@ -313,7 +314,7 @@ export function CreatePRDialog({
const result = await api.worktree.generatePRDescription(
worktree.path,
branchNameForApi,
prDescriptionModelOverride.effectiveModel,
resolveModelString(prDescriptionModelOverride.effectiveModel),
prDescriptionModelOverride.effectiveModelEntry.thinkingLevel,
prDescriptionModelOverride.effectiveModelEntry.providerId
);
@@ -501,7 +502,7 @@ export function CreatePRDialog({
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[550px] flex flex-col">
<DialogContent className="sm:max-w-[550px] max-h-[85vh] flex flex-col">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<GitPullRequest className="w-5 h-5" />

View File

@@ -25,6 +25,7 @@ export { ViewStashesDialog } from './view-stashes-dialog';
export { StashApplyConflictDialog } from './stash-apply-conflict-dialog';
export { CherryPickDialog } from './cherry-pick-dialog';
export { GitPullDialog } from './git-pull-dialog';
export { ChangePRNumberDialog } from './change-pr-number-dialog';
export {
BranchConflictDialog,
type BranchConflictData,