mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
Changes from worktree-select
This commit is contained in:
@@ -413,6 +413,35 @@ export function BoardView() {
|
|||||||
outputFeature,
|
outputFeature,
|
||||||
projectPath: currentProject?.path || null,
|
projectPath: currentProject?.path || null,
|
||||||
onWorktreeCreated: () => setWorktreeRefreshKey((k) => k + 1),
|
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,
|
currentWorktreeBranch,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ interface UseBoardActionsProps {
|
|||||||
outputFeature: Feature | null;
|
outputFeature: Feature | null;
|
||||||
projectPath: string | null;
|
projectPath: string | null;
|
||||||
onWorktreeCreated?: () => void;
|
onWorktreeCreated?: () => void;
|
||||||
|
onWorktreeAutoSelect?: (worktree: { path: string; branch: string }) => void;
|
||||||
currentWorktreeBranch: string | null; // Branch name of the selected worktree for filtering
|
currentWorktreeBranch: string | null; // Branch name of the selected worktree for filtering
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +69,7 @@ export function useBoardActions({
|
|||||||
outputFeature,
|
outputFeature,
|
||||||
projectPath,
|
projectPath,
|
||||||
onWorktreeCreated,
|
onWorktreeCreated,
|
||||||
|
onWorktreeAutoSelect,
|
||||||
currentWorktreeBranch,
|
currentWorktreeBranch,
|
||||||
}: UseBoardActionsProps) {
|
}: UseBoardActionsProps) {
|
||||||
const {
|
const {
|
||||||
@@ -114,15 +116,20 @@ export function useBoardActions({
|
|||||||
currentProject.path,
|
currentProject.path,
|
||||||
finalBranchName
|
finalBranchName
|
||||||
);
|
);
|
||||||
if (result.success) {
|
if (result.success && result.worktree) {
|
||||||
console.log(
|
console.log(
|
||||||
`[Board] Worktree for branch "${finalBranchName}" ${
|
`[Board] Worktree for branch "${finalBranchName}" ${
|
||||||
result.worktree?.isNew ? "created" : "already exists"
|
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
|
// Refresh worktree list in UI
|
||||||
onWorktreeCreated?.();
|
onWorktreeCreated?.();
|
||||||
} else {
|
} else if (!result.success) {
|
||||||
console.error(
|
console.error(
|
||||||
`[Board] Failed to create worktree for branch "${finalBranchName}":`,
|
`[Board] Failed to create worktree for branch "${finalBranchName}":`,
|
||||||
result.error
|
result.error
|
||||||
@@ -151,7 +158,7 @@ export function useBoardActions({
|
|||||||
await persistFeatureCreate(createdFeature);
|
await persistFeatureCreate(createdFeature);
|
||||||
saveCategory(featureData.category);
|
saveCategory(featureData.category);
|
||||||
},
|
},
|
||||||
[addFeature, persistFeatureCreate, saveCategory, useWorktrees, currentProject, onWorktreeCreated]
|
[addFeature, persistFeatureCreate, saveCategory, useWorktrees, currentProject, onWorktreeCreated, onWorktreeAutoSelect]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleUpdateFeature = useCallback(
|
const handleUpdateFeature = useCallback(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback, useRef } from "react";
|
||||||
import { useAppStore } from "@/store/app-store";
|
import { useAppStore } from "@/store/app-store";
|
||||||
import { getElectronAPI } from "@/lib/electron";
|
import { getElectronAPI } from "@/lib/electron";
|
||||||
import { pathsEqual } from "@/lib/utils";
|
import { pathsEqual } from "@/lib/utils";
|
||||||
@@ -59,14 +59,25 @@ export function useWorktrees({ projectPath, refreshTrigger = 0, onRemovedWorktre
|
|||||||
}
|
}
|
||||||
}, [refreshTrigger, fetchWorktrees, onRemovedWorktrees]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (worktrees.length > 0) {
|
if (worktrees.length > 0) {
|
||||||
const currentPath = currentWorktree?.path;
|
const current = currentWorktreeRef.current;
|
||||||
|
const currentPath = current?.path;
|
||||||
const currentWorktreeExists = currentPath === null
|
const currentWorktreeExists = currentPath === null
|
||||||
? true
|
? true
|
||||||
: worktrees.some((w) => !w.isMain && pathsEqual(w.path, currentPath));
|
: 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
|
// Find the primary worktree and get its branch name
|
||||||
// Fallback to "main" only if worktrees haven't loaded yet
|
// Fallback to "main" only if worktrees haven't loaded yet
|
||||||
const mainWorktree = worktrees.find((w) => w.isMain);
|
const mainWorktree = worktrees.find((w) => w.isMain);
|
||||||
@@ -74,7 +85,7 @@ export function useWorktrees({ projectPath, refreshTrigger = 0, onRemovedWorktre
|
|||||||
setCurrentWorktree(projectPath, null, mainBranch);
|
setCurrentWorktree(projectPath, null, mainBranch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [worktrees, currentWorktree, projectPath, setCurrentWorktree]);
|
}, [worktrees, projectPath, setCurrentWorktree]);
|
||||||
|
|
||||||
const handleSelectWorktree = useCallback(
|
const handleSelectWorktree = useCallback(
|
||||||
(worktree: WorktreeInfo) => {
|
(worktree: WorktreeInfo) => {
|
||||||
|
|||||||
@@ -843,6 +843,57 @@ test.describe("Worktree Integration Tests", () => {
|
|||||||
expect(featureData.status).toBe("backlog");
|
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 ({
|
test("should reset feature branch and worktree when worktree is deleted", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user