Merge main into refactor/frontend

Merge latest features from main including:
- PR #161 (worktree-confusion): Clarified branch handling in dialogs
- PR #160 (speckits-rebase): Planning mode functionality

Resolved conflicts:
- add-feature-dialog.tsx: Combined TanStack Router navigation with branch selection state
- worktree-integration.spec.ts: Updated tests for new worktree behavior (created at execution time)
- package-lock.json: Regenerated after merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kacper
2025-12-18 12:00:45 +01:00
71 changed files with 10632 additions and 1192 deletions

View File

@@ -741,14 +741,15 @@ test.describe("Worktree Integration Tests", () => {
await waitForNetworkIdle(page);
await waitForBoardView(page);
// Create a worktree first
// Note: Worktrees are created at execution time (when feature starts),
// not when adding to backlog. We can specify a branch name without
// creating a worktree first.
const branchName = "feature/test-branch";
await apiCreateWorktree(page, testRepo.path, branchName);
// Click add feature button
await clickAddFeature(page);
// Fill in the feature details
// Fill in the feature details with a branch name
await fillAddFeatureDialog(page, "Test feature for worktree", {
branch: branchName,
category: "Testing",
@@ -773,9 +774,12 @@ test.describe("Worktree Integration Tests", () => {
expect(featureData.description).toBe("Test feature for worktree");
expect(featureData.branchName).toBe(branchName);
expect(featureData.status).toBe("backlog");
// Verify worktreePath is not set when adding to backlog
// (worktrees are created at execution time, not when adding to backlog)
expect(featureData.worktreePath).toBeUndefined();
});
test("should create worktree automatically when adding feature with new branch", async ({
test("should store branch name when adding feature with new branch (worktree created at execution)", async ({
page,
}) => {
await setupProjectWithPath(page, testRepo.path);
@@ -783,12 +787,13 @@ test.describe("Worktree Integration Tests", () => {
await waitForNetworkIdle(page);
await waitForBoardView(page);
// Use a branch name that doesn't exist yet - NO worktree is pre-created
// Use a branch name that doesn't exist yet
// Note: Worktrees are now created at execution time, not when adding to backlog
const branchName = "feature/auto-create-worktree";
const expectedWorktreePath = getWorktreePath(testRepo.path, branchName);
// Verify worktree does NOT exist before we create the feature
expect(fs.existsSync(expectedWorktreePath)).toBe(false);
// 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);
@@ -802,17 +807,14 @@ test.describe("Worktree Integration Tests", () => {
// Confirm
await confirmAddFeature(page);
// Wait for the worktree to be created
await page.waitForTimeout(2000);
// Wait for feature to be saved
await page.waitForTimeout(1000);
// Verify worktree was automatically created when feature was added
expect(fs.existsSync(expectedWorktreePath)).toBe(true);
// Verify branch was NOT created when adding feature (created at execution time)
const branchesAfter = await listBranches(testRepo.path);
expect(branchesAfter).not.toContain(branchName);
// Verify the branch was created
const branches = await listBranches(testRepo.path);
expect(branches).toContain(branchName);
// Verify feature was created with correct branch
// Verify feature was created with correct branch name stored
const featuresDir = path.join(testRepo.path, ".automaker", "features");
const featureDirs = fs.readdirSync(featuresDir);
expect(featureDirs.length).toBeGreaterThan(0);
@@ -829,9 +831,15 @@ test.describe("Worktree Integration Tests", () => {
const featureFilePath = path.join(featuresDir, featureDir!, "feature.json");
const featureData = JSON.parse(fs.readFileSync(featureFilePath, "utf-8"));
// Verify branch name is stored
expect(featureData.branchName).toBe(branchName);
// Normalize paths for comparison (server returns forward slashes, path.join returns native)
expect(path.normalize(featureData.worktreePath)).toBe(path.normalize(expectedWorktreePath));
// Verify worktreePath is NOT set (worktrees are created at execution time)
expect(featureData.worktreePath).toBeUndefined();
// Verify feature is in backlog status
expect(featureData.status).toBe("backlog");
});
test("should reset feature branch and worktree when worktree is deleted", async ({
@@ -888,9 +896,11 @@ test.describe("Worktree Integration Tests", () => {
let featureFilePath = path.join(featuresDir, featureDir!, "feature.json");
let featureData = JSON.parse(fs.readFileSync(featureFilePath, "utf-8"));
// Verify feature was created with the branch name stored
expect(featureData.branchName).toBe(branchName);
// Normalize paths for comparison (server returns forward slashes, path.join returns native)
expect(path.normalize(featureData.worktreePath)).toBe(path.normalize(worktreePath));
// Verify worktreePath is NOT set (worktrees are created at execution time, not when adding)
expect(featureData.worktreePath).toBeUndefined();
// Delete the worktree via UI
// Open the worktree actions menu
@@ -913,10 +923,11 @@ test.describe("Worktree Integration Tests", () => {
// Verify worktree is deleted
expect(fs.existsSync(worktreePath)).toBe(false);
// Verify feature's branchName and worktreePath are reset to null
// Verify feature's branchName is reset to null/undefined when worktree is deleted
// (worktreePath was never stored, so it remains undefined)
featureData = JSON.parse(fs.readFileSync(featureFilePath, "utf-8"));
expect(featureData.branchName).toBeNull();
expect(featureData.worktreePath).toBeNull();
expect(featureData.worktreePath).toBeUndefined();
// Verify the feature appears in the backlog when main is selected
const mainButton = page.getByRole("button", { name: "main" }).first();
@@ -942,8 +953,9 @@ test.describe("Worktree Integration Tests", () => {
await otherWorktreeButton.click();
await page.waitForTimeout(500);
// Unassigned features should still be visible in the backlog
await expect(featureText).toBeVisible({ timeout: 5000 });
// Unassigned features should NOT be visible on non-primary worktrees
// They should only show on the primary (main) worktree
await expect(featureText).not.toBeVisible({ timeout: 5000 });
});
test("should filter features by selected worktree", async ({ page }) => {
@@ -1064,9 +1076,11 @@ test.describe("Worktree Integration Tests", () => {
// Open add feature dialog
await clickAddFeature(page);
// Verify the branch input button shows the selected worktree's branch
const branchButton = page.locator('[data-testid="feature-branch-input"]');
await expect(branchButton).toContainText(branchName, { timeout: 5000 });
// Verify the branch selector shows the selected worktree's branch
// When a worktree is selected, "Use current selected branch" should be selected
// and the branch name should be shown in the label
const currentBranchLabel = page.locator('label[for="feature-current"]');
await expect(currentBranchLabel).toContainText(branchName, { timeout: 5000 });
// Close dialog
await page.keyboard.press("Escape");
@@ -2339,7 +2353,7 @@ test.describe("Worktree Integration Tests", () => {
// Edit Feature with Branch Change
// ==========================================================================
test("should create worktree when editing a feature and selecting a new branch", async ({
test("should update branchName when editing a feature and selecting a new branch", async ({
page,
}) => {
await setupProjectWithPath(page, testRepo.path);
@@ -2385,7 +2399,7 @@ test.describe("Worktree Integration Tests", () => {
const newBranchName = "feature/edited-branch";
const expectedWorktreePath = getWorktreePath(testRepo.path, newBranchName);
// Verify worktree does NOT exist before editing
// Verify worktree does NOT exist before editing (worktrees are created at execution time)
expect(fs.existsSync(expectedWorktreePath)).toBe(false);
// Find and click the edit button on the feature card
@@ -2400,8 +2414,12 @@ test.describe("Worktree Integration Tests", () => {
const editDialog = page.locator('[data-testid="edit-feature-dialog"]');
await expect(editDialog).toBeVisible({ timeout: 5000 });
// Select "Other branch" to enable the branch input
const otherBranchRadio = page.locator('label[for="edit-feature-other"]');
await otherBranchRadio.click();
// Find and click on the branch input to open the autocomplete
const branchInput = page.locator('[data-testid="edit-feature-branch"]');
const branchInput = page.locator('[data-testid="edit-feature-input"]');
await branchInput.click();
await page.waitForTimeout(300);
@@ -2417,21 +2435,22 @@ test.describe("Worktree Integration Tests", () => {
const saveButton = page.locator('[data-testid="confirm-edit-feature"]');
await saveButton.click();
// Wait for the dialog to close and worktree to be created
// Wait for the dialog to close
await page.waitForTimeout(2000);
// Verify worktree was automatically created
expect(fs.existsSync(expectedWorktreePath)).toBe(true);
// Verify worktree was NOT created during editing (worktrees are created at execution time)
expect(fs.existsSync(expectedWorktreePath)).toBe(false);
// Verify the branch was created
// Verify branch was NOT created (created at execution time)
const branches = await listBranches(testRepo.path);
expect(branches).toContain(newBranchName);
expect(branches).not.toContain(newBranchName);
// Verify feature was updated with correct branch and worktreePath
// Verify feature was updated with correct branchName only
// Note: worktreePath is no longer stored - worktrees are created server-side at execution time
featureData = JSON.parse(fs.readFileSync(featureFilePath, "utf-8"));
expect(featureData.branchName).toBe(newBranchName);
// Normalize paths for comparison (server returns forward slashes, path.join returns native)
expect(path.normalize(featureData.worktreePath)).toBe(path.normalize(expectedWorktreePath));
// worktreePath should not exist in the feature data
expect(featureData.worktreePath).toBeUndefined();
});
test("should not create worktree when editing a feature and selecting main branch", async ({
@@ -2494,7 +2513,7 @@ test.describe("Worktree Integration Tests", () => {
await expect(editDialog).toBeVisible({ timeout: 5000 });
// Find and click on the branch input
const branchInput = page.locator('[data-testid="edit-feature-branch"]');
const branchInput = page.locator('[data-testid="edit-feature-input"]');
await branchInput.click();
await page.waitForTimeout(300);
@@ -2553,7 +2572,7 @@ test.describe("Worktree Integration Tests", () => {
await expect(editDialog).toBeVisible({ timeout: 5000 });
// Change to the existing branch
const branchInput = page.locator('[data-testid="edit-feature-branch"]');
const branchInput = page.locator('[data-testid="edit-feature-input"]');
await branchInput.click();
await page.waitForTimeout(300);
@@ -2574,7 +2593,8 @@ test.describe("Worktree Integration Tests", () => {
);
expect(matchingWorktrees.length).toBe(1);
// Verify feature was updated with the correct worktreePath
// Verify feature was updated with the correct branchName
// Note: worktreePath is no longer stored - worktrees are created server-side at execution time
const featuresDir = path.join(testRepo.path, ".automaker", "features");
const featureDirs = fs.readdirSync(featuresDir);
const featureDir = featureDirs.find((dir) => {
@@ -2589,7 +2609,7 @@ test.describe("Worktree Integration Tests", () => {
const featureFilePath = path.join(featuresDir, featureDir!, "feature.json");
const featureData = JSON.parse(fs.readFileSync(featureFilePath, "utf-8"));
expect(featureData.branchName).toBe(existingBranch);
// Normalize paths for comparison (server returns forward slashes, path.join returns native)
expect(path.normalize(featureData.worktreePath)).toBe(path.normalize(existingWorktreePath));
// worktreePath should not exist in the feature data (worktrees are created at execution time)
expect(featureData.worktreePath).toBeUndefined();
});
});