Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 70 additions & 6 deletions staged/src/lib/features/branches/BranchCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@
interface Props {
branch: Branch;
deleting?: boolean;
worktreeError?: string;
onDelete?: () => void;
onRetryWorktree?: () => void;
}

let { branch, deleting = false, onDelete }: Props = $props();
let { branch, deleting = false, worktreeError, onDelete, onRetryWorktree }: Props = $props();

// =========================================================================
// PR button state
Expand Down Expand Up @@ -860,7 +862,10 @@
<div
class="branch-card"
class:deleting
class:creating-worktree={branch.branchType === 'local' && !branch.worktreePath && !deleting}
class:creating-worktree={branch.branchType === 'local' &&
!branch.worktreePath &&
!worktreeError &&
!deleting}
>
{#if deleting}
<div class="deleting-overlay">
Expand All @@ -875,12 +880,29 @@
<span class="branch-separator">›</span>
<span class="base-branch-name">{formatBaseBranch(branch.baseBranch)}</span>
</div>
{#if worktreeError}
<div class="header-actions">
<button class="more-button" onclick={() => onDelete?.()} title="Delete branch">
<Trash2 size={16} />
</button>
</div>
{/if}
</div>
<div class="card-content">
<div class="loading">
<Spinner size={14} />
<span>Creating worktree…</span>
</div>
{#if worktreeError}
<div class="worktree-error">
<div class="worktree-error-message">
<AlertCircle size={14} />
<span>Failed to create worktree: {worktreeError}</span>
</div>
<button class="worktree-retry-btn" onclick={() => onRetryWorktree?.()}> Retry </button>
</div>
{:else}
<div class="loading">
<Spinner size={14} />
<span>Creating worktree…</span>
</div>
{/if}
</div>
{:else}
<div class="card-header">
Expand Down Expand Up @@ -1598,6 +1620,48 @@
font-size: var(--size-sm);
}

/* Worktree error state */
.worktree-error {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}

.worktree-error-message {
display: flex;
align-items: flex-start;
gap: 8px;
color: var(--ui-danger);
font-size: var(--size-sm);
line-height: 1.4;
min-width: 0;
}

.worktree-error-message :global(svg) {
flex-shrink: 0;
margin-top: 1px;
}

.worktree-retry-btn {
flex-shrink: 0;
padding: 5px 14px;
background: none;
border: 1px solid var(--border-muted);
border-radius: 6px;
color: var(--text-primary);
font-size: var(--size-xs);
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}

.worktree-retry-btn:hover {
border-color: var(--ui-accent);
color: var(--ui-accent);
background-color: var(--bg-hover);
}

/* Footer */
.card-footer {
display: flex;
Expand Down
60 changes: 37 additions & 23 deletions staged/src/lib/features/projects/ProjectHome.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
let branchToDelete = $state<{ branch: Branch; project: Project } | null>(null);
let deletingBranches = $state<Set<string>>(new Set());

// Worktree setup errors — maps branch ID → error message
let worktreeErrors = $state<Map<string, string>>(new Map());

// Action detection state
let detectingProjectIds = $state<Set<string>>(new Set());

Expand Down Expand Up @@ -168,32 +171,41 @@
// For local branches without a worktree, set up the git worktree in the
// background. The card will show "Creating worktree…" while this runs.
if (branch.branchType === 'local' && !branch.worktreePath) {
const branchId = branch.id;
const projectId = branch.projectId;

commands
.setupWorktree(branchId)
.then((updated) => {
// Replace the branch record so the card picks up worktreePath
const branches = branchesByProject.get(projectId) || [];
branchesByProject = new Map(branchesByProject).set(
projectId,
branches.map((b) => (b.id === updated.id ? updated : b))
);

// Now that the worktree exists, run prerun actions
setTimeout(() => {
runPrerunActions(branchId, projectId).catch((e) => {
console.error('[ProjectHome] Failed to run prerun actions:', e);
});
}, 150);
})
.catch((e) => {
console.error('[ProjectHome] Failed to setup worktree:', e);
});
setupBranchWorktree(branch.id, branch.projectId);
}
}

/** Set up a git worktree for a branch, updating the UI on success or error. */
function setupBranchWorktree(branchId: string, projectId: string) {
// Clear any previous error for this branch
const nextErrors = new Map(worktreeErrors);
nextErrors.delete(branchId);
worktreeErrors = nextErrors;

commands
.setupWorktree(branchId)
.then((updated) => {
// Replace the branch record so the card picks up worktreePath
const branches = branchesByProject.get(projectId) || [];
branchesByProject = new Map(branchesByProject).set(
projectId,
branches.map((b) => (b.id === updated.id ? updated : b))
);

// Now that the worktree exists, run prerun actions
setTimeout(() => {
runPrerunActions(branchId, projectId).catch((e) => {
console.error('[ProjectHome] Failed to run prerun actions:', e);
});
}, 150);
})
.catch((e) => {
console.error('[ProjectHome] Failed to setup worktree:', e);
const errMsg = e instanceof Error ? e.message : typeof e === 'string' ? e : String(e);
worktreeErrors = new Map(worktreeErrors).set(branchId, errMsg);
});
}

function handleDeleteBranchRequest(branchId: string, project: Project) {
const branches = branchesByProject.get(project.id) || [];
const branch = branches.find((b) => b.id === branchId);
Expand Down Expand Up @@ -323,10 +335,12 @@
{project}
branches={branchesByProject.get(project.id) || []}
{deletingBranches}
{worktreeErrors}
detecting={detectingProjectIds.has(project.id)}
onDeleteProject={() => handleDeleteProjectRequest(project)}
onDeleteBranch={(branchId) => handleDeleteBranchRequest(branchId, project)}
onNewBranch={() => handleNewBranch(project)}
onRetryWorktree={(branchId) => setupBranchWorktree(branchId, project.id)}
/>
{/each}
</div>
Expand Down
6 changes: 6 additions & 0 deletions staged/src/lib/features/projects/ProjectSection.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,24 @@
project: Project;
branches: Branch[];
deletingBranches?: Set<string>;
worktreeErrors?: Map<string, string>;
detecting?: boolean;
onDeleteProject?: () => void;
onDeleteBranch?: (branchId: string) => void;
onNewBranch?: () => void;
onRetryWorktree?: (branchId: string) => void;
}

let {
project,
branches,
deletingBranches = new Set(),
worktreeErrors = new Map(),
detecting = false,
onDeleteProject,
onDeleteBranch,
onNewBranch,
onRetryWorktree,
}: Props = $props();

let showProjectSettings = $state(false);
Expand Down Expand Up @@ -75,7 +79,9 @@
<BranchCard
{branch}
deleting={deletingBranches.has(branch.id)}
worktreeError={worktreeErrors.get(branch.id)}
onDelete={() => onDeleteBranch?.(branch.id)}
onRetryWorktree={() => onRetryWorktree?.(branch.id)}
/>
{/if}
{/each}
Expand Down