feat: enhance worktree management and feature filtering

- Added logic to show all local branches as suggestions in the branch autocomplete, allowing users to type new branch names.
- Implemented current worktree information retrieval for filtering features based on the selected worktree's branch.
- Updated feature handling to filter backlog features by the currently selected worktree branch, ensuring only relevant features are displayed.
- Enhanced the WorktreeSelector component to utilize branch names for determining the appropriate worktree for features.
- Introduced integration tests for worktree creation, deletion, and feature management to ensure robust functionality.
This commit is contained in:
Cody Seibert
2025-12-16 16:36:47 -05:00
parent 04d263b1ed
commit 30db67f89c
4 changed files with 693 additions and 24 deletions

View File

@@ -62,6 +62,7 @@ interface DevServerInfo {
interface FeatureInfo {
id: string;
worktreePath?: string;
branchName?: string; // Used as fallback to determine which worktree the spinner should show on
}
interface WorktreeSelectorProps {
@@ -302,14 +303,25 @@ export function WorktreeSelector({
const feature = features.find((f) => f.id === featureId);
if (!feature) return false;
// For main worktree, check features with no worktreePath or matching projectPath
// First, check if worktreePath is set and matches
// Use pathsEqual for cross-platform compatibility (Windows uses backslashes)
if (worktree.isMain) {
return !feature.worktreePath || pathsEqual(feature.worktreePath, projectPath);
if (feature.worktreePath) {
if (worktree.isMain) {
// Feature has worktreePath - show on main only if it matches projectPath
return pathsEqual(feature.worktreePath, projectPath);
}
// For non-main worktrees, check if worktreePath matches
return pathsEqual(feature.worktreePath, worktreeKey);
}
// For other worktrees, check if worktreePath matches
return pathsEqual(feature.worktreePath, worktreeKey);
// If worktreePath is not set, use branchName as fallback
if (feature.branchName) {
// Feature has a branchName - show spinner on the worktree with matching branch
return worktree.branch === feature.branchName;
}
// No worktreePath and no branchName - default to main
return worktree.isMain;
});
};

View File

@@ -39,6 +39,7 @@ interface UseBoardActionsProps {
outputFeature: Feature | null;
projectPath: string | null;
onWorktreeCreated?: () => void;
currentWorktreeBranch: string | null; // Branch name of the selected worktree for filtering
}
export function useBoardActions({
@@ -65,6 +66,7 @@ export function useBoardActions({
outputFeature,
projectPath,
onWorktreeCreated,
currentWorktreeBranch,
}: UseBoardActionsProps) {
const {
addFeature,
@@ -720,7 +722,24 @@ export function useBoardActions({
);
const handleStartNextFeatures = useCallback(async () => {
const backlogFeatures = features.filter((f) => f.status === "backlog");
// Filter backlog features by the currently selected worktree branch
// This ensures "G" only starts features from the filtered list
const backlogFeatures = features.filter((f) => {
if (f.status !== "backlog") return false;
// Determine the feature's branch (default to "main" if not set)
const featureBranch = f.branchName || "main";
// If no worktree is selected (currentWorktreeBranch is null or main-like),
// show features with no branch or "main"/"master" branch
if (!currentWorktreeBranch || currentWorktreeBranch === "main" || currentWorktreeBranch === "master") {
return !f.branchName || featureBranch === "main" || featureBranch === "master";
}
// Otherwise, only show features matching the selected worktree branch
return featureBranch === currentWorktreeBranch;
});
const availableSlots =
useAppStore.getState().maxConcurrency - runningAutoTasks.length;
@@ -734,7 +753,9 @@ export function useBoardActions({
if (backlogFeatures.length === 0) {
toast.info("Backlog empty", {
description: "No features in backlog to start.",
description: currentWorktreeBranch && currentWorktreeBranch !== "main" && currentWorktreeBranch !== "master"
? `No features in backlog for branch "${currentWorktreeBranch}".`
: "No features in backlog to start.",
});
return;
}
@@ -764,6 +785,7 @@ export function useBoardActions({
getOrCreateWorktreeForFeature,
persistFeatureUpdate,
onWorktreeCreated,
currentWorktreeBranch,
]);
const handleDeleteAllVerified = useCallback(async () => {