From d106b33ddda756cf7ac571f978282c91b7b249e6 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:41:31 -0700 Subject: [PATCH] fix(issues): enable new-issue button across multi-repo selection (#1051) - Removes single-repo-only constraint on new-issue button - Adds repo selector to new-issue dialog when multiple repos selected - Defaults to first selected repo when dialog opens - Handles edge case where user changes repo selection while dialog open --- src/renderer/src/components/TaskPage.tsx | 80 ++++++++++++++++-------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index 39ff8775d..67e307d98 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -23,6 +23,13 @@ import { useAppStore } from '@/store' import { useRepoMap } from '@/store/selectors' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' import { Dialog, DialogContent, @@ -574,13 +581,10 @@ export default function TaskPage(): React.JSX.Element { [eligibleRepos, repoSelection] ) - // Why: many single-repo-only affordances (new-issue dialog target, drawer - // repo path lookup, optimistic stub) need *a* repo. When exactly one is - // selected we use it; otherwise we pick the first to keep the UI - // functional, and disable the single-repo features that don't make sense - // cross-repo (new-issue button) explicitly. + // Why: many affordances (new-issue dialog default, drawer repo path lookup, + // optimistic stub) need *a* repo. First selected is used as the default; + // cross-repo dialogs still let the user override per-action. const primaryRepo = selectedRepos[0] ?? null - const isSingleRepo = selectedRepos.length === 1 // Why: seed the preset + query from the user's saved default synchronously // so the first fetch effect issues exactly one request keyed to the final @@ -671,6 +675,15 @@ export default function TaskPage(): React.JSX.Element { const [newIssueTitle, setNewIssueTitle] = useState('') const [newIssueBody, setNewIssueBody] = useState('') const [newIssueSubmitting, setNewIssueSubmitting] = useState(false) + const [newIssueRepoId, setNewIssueRepoId] = useState(null) + + // Why: resolve the target repo from the user's choice, falling back to the + // first selected repo if the chosen id drops out of the selection while the + // dialog is open — keeps submit always landing on a valid repo. + const newIssueTargetRepo = useMemo( + () => selectedRepos.find((r) => r.id === newIssueRepoId) ?? selectedRepos[0] ?? null, + [selectedRepos, newIssueRepoId] + ) const [drawerLinearIssueId, setDrawerLinearIssueId] = useState(null) const [drawerLinearIssueFallback, setDrawerLinearIssueFallback] = useState( @@ -909,7 +922,7 @@ export default function TaskPage(): React.JSX.Element { ) const handleCreateNewIssue = useCallback(async (): Promise => { - if (!primaryRepo || !isSingleRepo) { + if (!newIssueTargetRepo) { return } const title = newIssueTitle.trim() @@ -919,7 +932,7 @@ export default function TaskPage(): React.JSX.Element { setNewIssueSubmitting(true) try { const result = await window.api.gh.createIssue({ - repoPath: primaryRepo.path, + repoPath: newIssueTargetRepo.path, title, body: newIssueBody }) @@ -946,7 +959,7 @@ export default function TaskPage(): React.JSX.Element { // has immediate content, then refine with the full `workItem` fetch. const stub: GitHubWorkItem = { id: `issue:${String(result.number)}`, - repoId: primaryRepo.id, + repoId: newIssueTargetRepo.id, type: 'issue', number: result.number, title, @@ -957,9 +970,9 @@ export default function TaskPage(): React.JSX.Element { author: null } setDrawerWorkItem(stub) - const stubRepoId = primaryRepo.id + const stubRepoId = newIssueTargetRepo.id void window.api.gh - .workItem({ repoPath: primaryRepo.path, number: result.number }) + .workItem({ repoPath: newIssueTargetRepo.path, number: result.number }) .then((full) => { if (full) { // Why: `full` is `Omit` (IPC shape). @@ -974,14 +987,7 @@ export default function TaskPage(): React.JSX.Element { } finally { setNewIssueSubmitting(false) } - }, [ - isSingleRepo, - newIssueBody, - newIssueSubmitting, - newIssueTitle, - primaryRepo, - setDrawerWorkItem - ]) + }, [newIssueBody, newIssueSubmitting, newIssueTargetRepo, newIssueTitle, setDrawerWorkItem]) useEffect(() => { // Why: when a modal is open, let it own Esc dismissal. @@ -1342,9 +1348,10 @@ export default function TaskPage(): React.JSX.Element { onClick={() => { setNewIssueTitle('') setNewIssueBody('') + setNewIssueRepoId(primaryRepo?.id ?? null) setNewIssueOpen(true) }} - disabled={!primaryRepo || !isSingleRepo} + disabled={!newIssueTargetRepo} aria-label="New GitHub issue" className="border-border/50 bg-transparent hover:bg-muted/50 backdrop-blur-md supports-[backdrop-filter]:bg-transparent" > @@ -1352,9 +1359,7 @@ export default function TaskPage(): React.JSX.Element { - {isSingleRepo - ? 'New GitHub issue' - : 'Select a single repo to create an issue'} + New GitHub issue @@ -1925,10 +1930,33 @@ export default function TaskPage(): React.JSX.Element { New GitHub issue - Opens a new issue in {primaryRepo?.displayName ?? 'this repository'}. + {selectedRepos.length > 1 + ? 'Opens a new issue in the selected repository.' + : `Opens a new issue in ${newIssueTargetRepo?.displayName ?? 'this repository'}.`}
+ {selectedRepos.length > 1 ? ( +
+ + +
+ ) : null}