diff --git a/apps/staged/src/lib/features/projects/AddRepoModal.svelte b/apps/staged/src/lib/features/projects/AddRepoModal.svelte index 7687507d..ed0e54c5 100644 --- a/apps/staged/src/lib/features/projects/AddRepoModal.svelte +++ b/apps/staged/src/lib/features/projects/AddRepoModal.svelte @@ -66,6 +66,12 @@ const normalizedBranch = branchName.trim() || undefined; const prNumber = matchedPr?.number ?? undefined; + if (excludeRepos?.has(`${selectedRepo}\x00${normalizedSubpath ?? ''}`)) { + error = 'This repo + subpath is already in the project'; + saving = false; + return; + } + onAdded({ nameWithOwner: selectedRepo, branchName: normalizedBranch, diff --git a/apps/staged/src/lib/features/projects/ProjectHome.svelte b/apps/staged/src/lib/features/projects/ProjectHome.svelte index 75e39793..35a6c968 100644 --- a/apps/staged/src/lib/features/projects/ProjectHome.svelte +++ b/apps/staged/src/lib/features/projects/ProjectHome.svelte @@ -755,7 +755,7 @@ excludeRepos={new Set( [...reposById.values()] .filter((r) => r.projectId === project.id) - .map((r) => r.githubRepo) + .map((r) => `${r.githubRepo}\x00${r.subpath ?? ''}`) )} onRepoSelected={(selection) => handleRepoSelected(project.id, selection)} onRetryWorktree={(branchId) => setupBranchWorktree(branchId, project.id)} diff --git a/apps/staged/src/lib/features/projects/RepoConfigForm.svelte b/apps/staged/src/lib/features/projects/RepoConfigForm.svelte index 437305bf..5210ea52 100644 --- a/apps/staged/src/lib/features/projects/RepoConfigForm.svelte +++ b/apps/staged/src/lib/features/projects/RepoConfigForm.svelte @@ -175,7 +175,9 @@ }); let filteredRecentRepos = $derived( - excludeRepos ? recentRepos.filter((r) => !excludeRepos.has(r.githubRepo)) : recentRepos + excludeRepos + ? recentRepos.filter((r) => !excludeRepos.has(`${r.githubRepo}\x00${r.subpath ?? ''}`)) + : recentRepos ); const dark = $derived(darkMode.value); diff --git a/apps/staged/src/lib/features/projects/RepoSearchInput.svelte b/apps/staged/src/lib/features/projects/RepoSearchInput.svelte index a39bd029..ed02c8ec 100644 --- a/apps/staged/src/lib/features/projects/RepoSearchInput.svelte +++ b/apps/staged/src/lib/features/projects/RepoSearchInput.svelte @@ -72,13 +72,13 @@ const seen = new Set(); const result: Array<{ type: 'recent' | 'repo'; data: RecentRepo | GitHubRepo }> = []; - if (directFetchRepo && !excludeRepos.has(directFetchRepo.nameWithOwner)) { + if (directFetchRepo) { result.push({ type: 'repo', data: directFetchRepo }); seen.add(directFetchRepo.nameWithOwner); } for (const r of searchResults) { - if (!seen.has(r.nameWithOwner) && !excludeRepos.has(r.nameWithOwner)) { + if (!seen.has(r.nameWithOwner)) { result.push({ type: 'repo', data: r }); seen.add(r.nameWithOwner); } @@ -94,7 +94,7 @@ : repos; for (const r of filtered) { - if (!seen.has(r.nameWithOwner) && !excludeRepos.has(r.nameWithOwner)) { + if (!seen.has(r.nameWithOwner)) { result.push({ type: 'repo', data: r }); seen.add(r.nameWithOwner); } @@ -105,7 +105,9 @@ let filteredRecentRepos = $derived.by(() => { const q = query.toLowerCase().trim(); - const base = recentRepos.filter((r) => !excludeRepos.has(r.githubRepo)); + const base = recentRepos.filter( + (r) => !excludeRepos.has(`${r.githubRepo}\x00${r.subpath ?? ''}`) + ); return q ? base.filter((r) => r.githubRepo.toLowerCase().includes(q)) : base; }); @@ -125,7 +127,11 @@ onMount(async () => { if (autofocus) { - inputEl?.focus(); + // Defer so the click that opened our parent modal finishes bubbling + // before we open the dropdown. Otherwise the trailing window-level + // handleClickOutside (registered when this component mounted) sees a + // target outside our wrapper and closes the dropdown we just opened. + setTimeout(() => inputEl?.focus(), 0); } try { recentRepos = await commands.listRecentRepos(10); @@ -148,6 +154,7 @@ } async function handleInput() { + dropdownOpen = true; const trimmed = query.trim(); const parsed = parseGitHubUrl(trimmed);