Changes from worktree-select

This commit is contained in:
Cody Seibert
2025-12-19 21:34:13 -05:00
parent 46c3dd252f
commit 89c53acdcf
4 changed files with 105 additions and 7 deletions

View File

@@ -413,6 +413,35 @@ export function BoardView() {
outputFeature,
projectPath: currentProject?.path || null,
onWorktreeCreated: () => setWorktreeRefreshKey((k) => k + 1),
onWorktreeAutoSelect: (newWorktree) => {
if (!currentProject) return;
// Check if worktree already exists in the store (by branch name)
const currentWorktrees = getWorktrees(currentProject.path);
const existingWorktree = currentWorktrees.find(
(w) => w.branch === newWorktree.branch
);
// Only add if it doesn't already exist (to avoid duplicates)
if (!existingWorktree) {
const newWorktreeInfo = {
path: newWorktree.path,
branch: newWorktree.branch,
isMain: false,
isCurrent: false,
hasWorktree: true,
};
setWorktrees(currentProject.path, [
...currentWorktrees,
newWorktreeInfo,
]);
}
// Select the worktree (whether it existed or was just added)
setCurrentWorktree(
currentProject.path,
newWorktree.path,
newWorktree.branch
);
},
currentWorktreeBranch,
});

View File

@@ -41,6 +41,7 @@ interface UseBoardActionsProps {
outputFeature: Feature | null;
projectPath: string | null;
onWorktreeCreated?: () => void;
onWorktreeAutoSelect?: (worktree: { path: string; branch: string }) => void;
currentWorktreeBranch: string | null; // Branch name of the selected worktree for filtering
}
@@ -68,6 +69,7 @@ export function useBoardActions({
outputFeature,
projectPath,
onWorktreeCreated,
onWorktreeAutoSelect,
currentWorktreeBranch,
}: UseBoardActionsProps) {
const {
@@ -114,15 +116,20 @@ export function useBoardActions({
currentProject.path,
finalBranchName
);
if (result.success) {
if (result.success && result.worktree) {
console.log(
`[Board] Worktree for branch "${finalBranchName}" ${
result.worktree?.isNew ? "created" : "already exists"
}`
);
// Auto-select the worktree when creating a feature for it
onWorktreeAutoSelect?.({
path: result.worktree.path,
branch: result.worktree.branch,
});
// Refresh worktree list in UI
onWorktreeCreated?.();
} else {
} else if (!result.success) {
console.error(
`[Board] Failed to create worktree for branch "${finalBranchName}":`,
result.error
@@ -151,7 +158,7 @@ export function useBoardActions({
await persistFeatureCreate(createdFeature);
saveCategory(featureData.category);
},
[addFeature, persistFeatureCreate, saveCategory, useWorktrees, currentProject, onWorktreeCreated]
[addFeature, persistFeatureCreate, saveCategory, useWorktrees, currentProject, onWorktreeCreated, onWorktreeAutoSelect]
);
const handleUpdateFeature = useCallback(

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, useRef } from "react";
import { useAppStore } from "@/store/app-store";
import { getElectronAPI } from "@/lib/electron";
import { pathsEqual } from "@/lib/utils";
@@ -59,14 +59,25 @@ export function useWorktrees({ projectPath, refreshTrigger = 0, onRemovedWorktre
}
}, [refreshTrigger, fetchWorktrees, onRemovedWorktrees]);
// Use a ref to track the current worktree to avoid running validation
// when selection changes (which could cause a race condition with stale worktrees list)
const currentWorktreeRef = useRef(currentWorktree);
useEffect(() => {
currentWorktreeRef.current = currentWorktree;
}, [currentWorktree]);
// Validation effect: only runs when worktrees list changes (not on selection change)
// This prevents a race condition where the selection is reset because the
// local worktrees state hasn't been updated yet from the async fetch
useEffect(() => {
if (worktrees.length > 0) {
const currentPath = currentWorktree?.path;
const current = currentWorktreeRef.current;
const currentPath = current?.path;
const currentWorktreeExists = currentPath === null
? true
: worktrees.some((w) => !w.isMain && pathsEqual(w.path, currentPath));
if (currentWorktree == null || (currentPath !== null && !currentWorktreeExists)) {
if (current == null || (currentPath !== null && !currentWorktreeExists)) {
// Find the primary worktree and get its branch name
// Fallback to "main" only if worktrees haven't loaded yet
const mainWorktree = worktrees.find((w) => w.isMain);
@@ -74,7 +85,7 @@ export function useWorktrees({ projectPath, refreshTrigger = 0, onRemovedWorktre
setCurrentWorktree(projectPath, null, mainBranch);
}
}
}, [worktrees, currentWorktree, projectPath, setCurrentWorktree]);
}, [worktrees, projectPath, setCurrentWorktree]);
const handleSelectWorktree = useCallback(
(worktree: WorktreeInfo) => {

View File

@@ -843,6 +843,57 @@ test.describe("Worktree Integration Tests", () => {
expect(featureData.status).toBe("backlog");
});
test("should auto-select worktree after creating feature with new branch", async ({
page,
}) => {
await setupProjectWithPath(page, testRepo.path);
await page.goto("/");
await waitForNetworkIdle(page);
await waitForBoardView(page);
// Use a branch name that doesn't exist yet
const branchName = "feature/auto-select-worktree";
// Verify branch does NOT exist before we create the feature
const branchesBefore = await listBranches(testRepo.path);
expect(branchesBefore).not.toContain(branchName);
// Click add feature button
await clickAddFeature(page);
// Fill in the feature details with the new branch
await fillAddFeatureDialog(page, "Feature with auto-select worktree", {
branch: branchName,
category: "Testing",
});
// Confirm
await confirmAddFeature(page);
// Wait for feature to be saved and worktree to be created
await page.waitForTimeout(2000);
// Verify the new worktree is auto-selected (highlighted/active in the worktree panel)
// The worktree button should now be in a selected state (indicated by data-selected or similar class)
const worktreeButton = page.getByRole("button", {
name: new RegExp(branchName.replace("/", "\\/"), "i"),
});
await expect(worktreeButton).toBeVisible({ timeout: 5000 });
// Check that the worktree button has the selected state (using the aria-pressed attribute or data-state)
// The selected worktree should have a different visual state
await expect(worktreeButton).toHaveAttribute("data-state", "active", { timeout: 5000 }).catch(async () => {
// Fallback: check if the button has a specific class that indicates selection
// or verify the feature is visible, which would only happen if the worktree is selected
const featureText = page.getByText("Feature with auto-select worktree");
await expect(featureText).toBeVisible({ timeout: 5000 });
});
// Verify the feature is visible in the backlog (which means the worktree is selected)
const featureText = page.getByText("Feature with auto-select worktree");
await expect(featureText).toBeVisible({ timeout: 5000 });
});
test("should reset feature branch and worktree when worktree is deleted", async ({
page,
}) => {