Merge v0.11.0rc into feat/mobile-improvements-contributor

Resolves merge conflicts by keeping both features:
- enableAiCommitMessages (from our branch)
- defaultFeatureModel (from v0.11.0rc)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Shirone
2026-01-13 21:31:44 +01:00
100 changed files with 4042 additions and 1903 deletions

View File

@@ -21,7 +21,8 @@ import {
FeatureTextFilePath as DescriptionTextFilePath,
ImagePreviewMap,
} from '@/components/ui/description-image-dropzone';
import { Play, Cpu, FolderKanban } from 'lucide-react';
import { Play, Cpu, FolderKanban, Settings2 } from 'lucide-react';
import { useNavigate } from '@tanstack/react-router';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
import { modelSupportsThinking } from '@/lib/utils';
@@ -33,7 +34,7 @@ import {
PlanningMode,
Feature,
} from '@/store/app-store';
import type { ReasoningEffort, PhaseModelEntry } from '@automaker/types';
import type { ReasoningEffort, PhaseModelEntry, AgentModel } from '@automaker/types';
import { supportsReasoningEffort, isClaudeModel } from '@automaker/types';
import {
TestingTabContent,
@@ -122,7 +123,7 @@ interface AddFeatureDialogProps {
selectedNonMainWorktreeBranch?: string;
/**
* When true, forces the dialog to default to 'current' work mode (work on current branch).
* This is used when the "Use selected worktree branch" setting is disabled.
* This is used when the "Default to worktree mode" setting is disabled.
*/
forceCurrentBranchMode?: boolean;
}
@@ -152,6 +153,7 @@ export function AddFeatureDialog({
forceCurrentBranchMode,
}: AddFeatureDialogProps) {
const isSpawnMode = !!parentFeature;
const navigate = useNavigate();
const [workMode, setWorkMode] = useState<WorkMode>('current');
// Form state
@@ -187,7 +189,8 @@ export function AddFeatureDialog({
const [selectedAncestorIds, setSelectedAncestorIds] = useState<Set<string>>(new Set());
// Get defaults from store
const { defaultPlanningMode, defaultRequirePlanApproval, useWorktrees } = useAppStore();
const { defaultPlanningMode, defaultRequirePlanApproval, useWorktrees, defaultFeatureModel } =
useAppStore();
// Track previous open state to detect when dialog opens
const wasOpenRef = useRef(false);
@@ -207,7 +210,7 @@ export function AddFeatureDialog({
);
setPlanningMode(defaultPlanningMode);
setRequirePlanApproval(defaultRequirePlanApproval);
setModelEntry({ model: 'opus' });
setModelEntry(defaultFeatureModel);
// Initialize description history (empty for new feature)
setDescriptionHistory([]);
@@ -228,6 +231,7 @@ export function AddFeatureDialog({
defaultBranch,
defaultPlanningMode,
defaultRequirePlanApproval,
defaultFeatureModel,
useWorktrees,
selectedNonMainWorktreeBranch,
forceCurrentBranchMode,
@@ -318,7 +322,7 @@ export function AddFeatureDialog({
// When a non-main worktree is selected, use its branch name for custom mode
setBranchName(selectedNonMainWorktreeBranch || '');
setPriority(2);
setModelEntry({ model: 'opus' });
setModelEntry(defaultFeatureModel);
setWorkMode(
getDefaultWorkMode(useWorktrees, selectedNonMainWorktreeBranch, forceCurrentBranchMode)
);
@@ -473,9 +477,31 @@ export function AddFeatureDialog({
{/* AI & Execution Section */}
<div className={cardClass}>
<div className={sectionHeaderClass}>
<Cpu className="w-4 h-4 text-muted-foreground" />
<span>AI & Execution</span>
<div className="flex items-center justify-between">
<div className={sectionHeaderClass}>
<Cpu className="w-4 h-4 text-muted-foreground" />
<span>AI & Execution</span>
</div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={() => {
onOpenChange(false);
navigate({ to: '/settings', search: { view: 'defaults' } });
}}
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
<Settings2 className="w-3.5 h-3.5" />
<span>Edit Defaults</span>
</button>
</TooltipTrigger>
<TooltipContent>
<p>Change default model and planning settings for new features</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="space-y-1.5">

View File

@@ -419,7 +419,7 @@ export function BacklogPlanDialog({
</DialogDescription>
</DialogHeader>
<div className="py-4">{renderContent()}</div>
<div className="py-4 overflow-y-auto">{renderContent()}</div>
<DialogFooter>
{mode === 'input' && (

View File

@@ -117,7 +117,7 @@ export function CreatePRDialog({
description: `PR already exists for ${result.result.branch}`,
action: {
label: 'View PR',
onClick: () => window.open(result.result!.prUrl!, '_blank'),
onClick: () => window.open(result.result!.prUrl!, '_blank', 'noopener,noreferrer'),
},
});
} else {
@@ -125,7 +125,7 @@ export function CreatePRDialog({
description: `PR created from ${result.result.branch}`,
action: {
label: 'View PR',
onClick: () => window.open(result.result!.prUrl!, '_blank'),
onClick: () => window.open(result.result!.prUrl!, '_blank', 'noopener,noreferrer'),
},
});
}
@@ -251,7 +251,10 @@ export function CreatePRDialog({
<p className="text-sm text-muted-foreground mt-1">Your PR is ready for review</p>
</div>
<div className="flex gap-2 justify-center">
<Button onClick={() => window.open(prUrl, '_blank')} className="gap-2">
<Button
onClick={() => window.open(prUrl, '_blank', 'noopener,noreferrer')}
className="gap-2"
>
<ExternalLink className="w-4 h-4" />
View Pull Request
</Button>
@@ -277,7 +280,7 @@ export function CreatePRDialog({
<Button
onClick={() => {
if (browserUrl) {
window.open(browserUrl, '_blank');
window.open(browserUrl, '_blank', 'noopener,noreferrer');
}
}}
className="gap-2 w-full"

View File

@@ -21,7 +21,8 @@ import {
FeatureTextFilePath as DescriptionTextFilePath,
ImagePreviewMap,
} from '@/components/ui/description-image-dropzone';
import { GitBranch, Cpu, FolderKanban } from 'lucide-react';
import { GitBranch, Cpu, FolderKanban, Settings2 } from 'lucide-react';
import { useNavigate } from '@tanstack/react-router';
import { toast } from 'sonner';
import { cn, modelSupportsThinking } from '@/lib/utils';
import { Feature, ModelAlias, ThinkingLevel, useAppStore, PlanningMode } from '@/store/app-store';
@@ -86,6 +87,7 @@ export function EditFeatureDialog({
isMaximized,
allFeatures,
}: EditFeatureDialogProps) {
const navigate = useNavigate();
const [editingFeature, setEditingFeature] = useState<Feature | null>(feature);
// Derive initial workMode from feature's branchName
const [workMode, setWorkMode] = useState<WorkMode>(() => {
@@ -363,9 +365,31 @@ export function EditFeatureDialog({
{/* AI & Execution Section */}
<div className={cardClass}>
<div className={sectionHeaderClass}>
<Cpu className="w-4 h-4 text-muted-foreground" />
<span>AI & Execution</span>
<div className="flex items-center justify-between">
<div className={sectionHeaderClass}>
<Cpu className="w-4 h-4 text-muted-foreground" />
<span>AI & Execution</span>
</div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={() => {
onClose();
navigate({ to: '/settings', search: { view: 'defaults' } });
}}
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
<Settings2 className="w-3.5 h-3.5" />
<span>Edit Defaults</span>
</button>
</TooltipTrigger>
<TooltipContent>
<p>Change default model and planning settings for new features</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="space-y-1.5">

View File

@@ -13,7 +13,8 @@ import { Label } from '@/components/ui/label';
import { AlertCircle } from 'lucide-react';
import { modelSupportsThinking } from '@/lib/utils';
import { Feature, ModelAlias, ThinkingLevel, PlanningMode } from '@/store/app-store';
import { TestingTabContent, PrioritySelect, PlanningModeSelect } from '../shared';
import { TestingTabContent, PrioritySelect, PlanningModeSelect, WorkModeSelector } from '../shared';
import type { WorkMode } from '../shared';
import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector';
import { isCursorModel, isClaudeModel, type PhaseModelEntry } from '@automaker/types';
import { cn } from '@/lib/utils';
@@ -23,7 +24,10 @@ interface MassEditDialogProps {
open: boolean;
onClose: () => void;
selectedFeatures: Feature[];
onApply: (updates: Partial<Feature>) => Promise<void>;
onApply: (updates: Partial<Feature>, workMode: WorkMode) => Promise<void>;
branchSuggestions: string[];
branchCardCounts?: Record<string, number>;
currentBranch?: string;
}
interface ApplyState {
@@ -33,6 +37,7 @@ interface ApplyState {
requirePlanApproval: boolean;
priority: boolean;
skipTests: boolean;
branchName: boolean;
}
function getMixedValues(features: Feature[]): Record<string, boolean> {
@@ -47,6 +52,7 @@ function getMixedValues(features: Feature[]): Record<string, boolean> {
),
priority: !features.every((f) => f.priority === first.priority),
skipTests: !features.every((f) => f.skipTests === first.skipTests),
branchName: !features.every((f) => f.branchName === first.branchName),
};
}
@@ -97,7 +103,15 @@ function FieldWrapper({ label, isMixed, willApply, onApplyChange, children }: Fi
);
}
export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: MassEditDialogProps) {
export function MassEditDialog({
open,
onClose,
selectedFeatures,
onApply,
branchSuggestions,
branchCardCounts,
currentBranch,
}: MassEditDialogProps) {
const [isApplying, setIsApplying] = useState(false);
// Track which fields to apply
@@ -108,6 +122,7 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas
requirePlanApproval: false,
priority: false,
skipTests: false,
branchName: false,
});
// Field values
@@ -118,6 +133,18 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas
const [priority, setPriority] = useState(2);
const [skipTests, setSkipTests] = useState(false);
// Work mode and branch name state
const [workMode, setWorkMode] = useState<WorkMode>(() => {
// Derive initial work mode from first selected feature's branchName
if (selectedFeatures.length > 0 && selectedFeatures[0].branchName) {
return 'custom';
}
return 'current';
});
const [branchName, setBranchName] = useState(() => {
return getInitialValue(selectedFeatures, 'branchName', '') as string;
});
// Calculate mixed values
const mixedValues = useMemo(() => getMixedValues(selectedFeatures), [selectedFeatures]);
@@ -131,6 +158,7 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas
requirePlanApproval: false,
priority: false,
skipTests: false,
branchName: false,
});
setModel(getInitialValue(selectedFeatures, 'model', 'sonnet') as ModelAlias);
setThinkingLevel(getInitialValue(selectedFeatures, 'thinkingLevel', 'none') as ThinkingLevel);
@@ -138,6 +166,10 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas
setRequirePlanApproval(getInitialValue(selectedFeatures, 'requirePlanApproval', false));
setPriority(getInitialValue(selectedFeatures, 'priority', 2));
setSkipTests(getInitialValue(selectedFeatures, 'skipTests', false));
// Reset work mode and branch name
const initialBranchName = getInitialValue(selectedFeatures, 'branchName', '') as string;
setBranchName(initialBranchName);
setWorkMode(initialBranchName ? 'custom' : 'current');
}
}, [open, selectedFeatures]);
@@ -150,6 +182,12 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas
if (applyState.requirePlanApproval) updates.requirePlanApproval = requirePlanApproval;
if (applyState.priority) updates.priority = priority;
if (applyState.skipTests) updates.skipTests = skipTests;
if (applyState.branchName) {
// For 'current' mode, use empty string (work on current branch)
// For 'auto' mode, use empty string (will be auto-generated)
// For 'custom' mode, use the specified branch name
updates.branchName = workMode === 'custom' ? branchName : '';
}
if (Object.keys(updates).length === 0) {
onClose();
@@ -158,7 +196,7 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas
setIsApplying(true);
try {
await onApply(updates);
await onApply(updates, workMode);
onClose();
} finally {
setIsApplying(false);
@@ -293,6 +331,25 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas
testIdPrefix="mass-edit"
/>
</FieldWrapper>
{/* Branch / Work Mode */}
<FieldWrapper
label="Branch / Work Mode"
isMixed={mixedValues.branchName}
willApply={applyState.branchName}
onApplyChange={(apply) => setApplyState((prev) => ({ ...prev, branchName: apply }))}
>
<WorkModeSelector
workMode={workMode}
onWorkModeChange={setWorkMode}
branchName={branchName}
onBranchNameChange={setBranchName}
branchSuggestions={branchSuggestions}
branchCardCounts={branchCardCounts}
currentBranch={currentBranch}
testIdPrefix="mass-edit-work-mode"
/>
</FieldWrapper>
</div>
<DialogFooter>

View File

@@ -45,7 +45,7 @@ export function PlanSettingsDialog({
className="text-sm font-medium cursor-pointer flex items-center gap-2"
>
<GitBranch className="w-4 h-4 text-brand-500" />
Use selected worktree branch
Default to worktree mode
</Label>
<Switch
id="plan-worktree-branch-toggle"
@@ -55,8 +55,8 @@ export function PlanSettingsDialog({
/>
</div>
<p className="text-xs text-muted-foreground leading-relaxed">
When enabled, features created via the Plan dialog will be assigned to the currently
selected worktree branch. When disabled, features will be added to the main branch.
Planned features will automatically use isolated worktrees, keeping changes separate
from your main branch until you're ready to merge.
</p>
</div>
</div>

View File

@@ -45,7 +45,7 @@ export function WorktreeSettingsDialog({
className="text-sm font-medium cursor-pointer flex items-center gap-2"
>
<GitBranch className="w-4 h-4 text-brand-500" />
Use selected worktree branch
Default to worktree mode
</Label>
<Switch
id="worktree-branch-toggle"
@@ -55,8 +55,8 @@ export function WorktreeSettingsDialog({
/>
</div>
<p className="text-xs text-muted-foreground leading-relaxed">
When enabled, the Add Feature dialog will default to custom branch mode with the
currently selected worktree branch pre-filled.
New features will automatically use isolated worktrees, keeping changes separate
from your main branch until you're ready to merge.
</p>
</div>
</div>