Skip to content
Open
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
22 changes: 22 additions & 0 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { type Provider } from './types';
import type { Project, Task } from './types/app';
import type { TaskMetadata } from './types/chat';
import { type GitHubIssueSummary } from './types/github';
import { type GitHubIssueLink } from './types/chat';
import { type JiraIssueSummary } from './types/jira';
import { type LinearIssueSummary } from './types/linear';

Expand All @@ -68,6 +69,21 @@ const TERMINAL_PROVIDER_IDS = [
'rovo',
] as const;

const buildLinkedGithubIssueMap = (tasks?: Task[] | null): Map<number, GitHubIssueLink> => {
const linked = new Map<number, GitHubIssueLink>();
if (!tasks?.length) return linked;
for (const task of tasks) {
const issueNumber = task.metadata?.githubIssue?.number;
if (typeof issueNumber !== 'number' || linked.has(issueNumber)) continue;
linked.set(issueNumber, {
number: issueNumber,
taskId: task.id,
taskName: task.name,
});
}
return linked;
};

const RightSidebarBridge: React.FC<{
onCollapsedChange: (collapsed: boolean) => void;
setCollapsedRef: React.MutableRefObject<((next: boolean) => void) | null>;
Expand Down Expand Up @@ -1946,6 +1962,11 @@ const AppContent: React.FC = () => {
};
}, [handleDeviceFlowSuccess, handleDeviceFlowError, checkStatus]);

const linkedGithubIssueMap = useMemo(
() => buildLinkedGithubIssueMap(selectedProject?.tasks),
[selectedProject?.tasks]
);

const renderMainContent = () => {
if (selectedProject && showKanban) {
return (
Expand Down Expand Up @@ -2244,6 +2265,7 @@ const AppContent: React.FC = () => {
projectName={selectedProject?.name || ''}
defaultBranch={projectDefaultBranch}
existingNames={(selectedProject?.tasks || []).map((w) => w.name)}
linkedGithubIssueMap={linkedGithubIssueMap}
projectPath={selectedProject?.path}
branchOptions={projectBranchOptions}
isLoadingBranches={isLoadingBranches}
Expand Down
70 changes: 49 additions & 21 deletions src/renderer/components/GitHubIssueSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,30 @@ import { Separator } from './ui/separator';
import { Badge } from './ui/badge';
import { Spinner } from './ui/spinner';
import { type GitHubIssueSummary } from '../types/github';
import { type GitHubIssueLink } from '../types/chat';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
import { GitHubIssuePreviewTooltip } from './GitHubIssuePreviewTooltip';

interface GitHubIssueSelectorProps {
projectPath: string;
selectedIssue: GitHubIssueSummary | null;
onIssueChange: (issue: GitHubIssueSummary | null) => void;
linkedIssueMap?: ReadonlyMap<number, GitHubIssueLink>;
linkedIssueMode?: 'disable' | 'hide';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows us to either disable them (so users can see) or hide them completely, so they don't show up at all

isOpen?: boolean;
className?: string;
disabled?: boolean;
placeholder?: string;
}

const EMPTY_LINKED_ISSUE_MAP = new Map<number, GitHubIssueLink>();

export const GitHubIssueSelector: React.FC<GitHubIssueSelectorProps> = ({
projectPath,
selectedIssue,
onIssueChange,
linkedIssueMap,
linkedIssueMode = 'disable',
isOpen = false,
className = '',
disabled = false,
Expand Down Expand Up @@ -125,22 +132,29 @@ export const GitHubIssueSelector: React.FC<GitHubIssueSelectorProps> = ({
return availableIssues;
}, [searchResults, availableIssues, searchTerm]);

const linkedIssueLookup = linkedIssueMap ?? EMPTY_LINKED_ISSUE_MAP;

const filteredIssues = useMemo(() => {
if (linkedIssueMode !== 'hide' || linkedIssueLookup.size === 0) return displayIssues;
return displayIssues.filter((issue) => !linkedIssueLookup.has(issue.number));
}, [displayIssues, linkedIssueLookup, linkedIssueMode]);

useEffect(() => setVisibleCount(10), [searchTerm]);

const showIssues = useMemo(
() => displayIssues.slice(0, Math.max(10, visibleCount)),
[displayIssues, visibleCount]
() => filteredIssues.slice(0, Math.max(10, visibleCount)),
[filteredIssues, visibleCount]
);

const handleScroll = useCallback(
(e: React.UIEvent<HTMLDivElement>) => {
const el = e.currentTarget;
const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 16;
if (nearBottom && showIssues.length < displayIssues.length) {
setVisibleCount((prev) => Math.min(prev + 10, displayIssues.length));
if (nearBottom && showIssues.length < filteredIssues.length) {
setVisibleCount((prev) => Math.min(prev + 10, filteredIssues.length));
}
},
[displayIssues.length, showIssues.length]
[filteredIssues.length, showIssues.length]
);

const handleIssueSelect = (value: string) => {
Expand All @@ -149,7 +163,7 @@ export const GitHubIssueSelector: React.FC<GitHubIssueSelectorProps> = ({
return;
}
const num = Number(String(value).replace(/^#/, ''));
const issue = displayIssues.find((i) => i.number === num) ?? null;
const issue = filteredIssues.find((i) => i.number === num) ?? null;
onIssueChange(issue);
};

Expand Down Expand Up @@ -228,23 +242,37 @@ export const GitHubIssueSelector: React.FC<GitHubIssueSelectorProps> = ({
</SelectItem>
<Separator className="my-1" />
{showIssues.length > 0 ? (
showIssues.map((issue) => (
<GitHubIssuePreviewTooltip key={issue.number} issue={issue} side="left">
<SelectItem value={`#${issue.number}`}>
<span className="flex min-w-0 items-center gap-2">
<span className="inline-flex shrink-0 items-center gap-1.5 rounded border border-border bg-muted px-1.5 py-0.5 dark:border-border dark:bg-card">
<img src={githubLogo} alt="GitHub" className="h-3.5 w-3.5" />
<span className="text-[11px] font-medium text-foreground">
#{issue.number}
showIssues.map((issue) => {
const linkedIssue = linkedIssueLookup.get(issue.number);
const isLinked = Boolean(linkedIssue);
const isDisabled = linkedIssueMode === 'disable' && isLinked;
return (
<GitHubIssuePreviewTooltip key={issue.number} issue={issue} side="left">
<SelectItem value={`#${issue.number}`} disabled={isDisabled}>
<span className="flex min-w-0 flex-1 items-center gap-2">
<span className="inline-flex shrink-0 items-center gap-1.5 rounded border border-border bg-muted px-1.5 py-0.5 dark:border-border dark:bg-card">
<img src={githubLogo} alt="GitHub" className="h-3.5 w-3.5" />
<span className="text-[11px] font-medium text-foreground">
#{issue.number}
</span>
</span>
{issue.title ? (
<span className="truncate text-muted-foreground">{issue.title}</span>
) : null}
{linkedIssueMode === 'disable' && isLinked ? (
<Badge
variant="outline"
className="ml-auto shrink-0 text-[10px]"
title={`Linked to task "${linkedIssue?.taskName ?? 'another task'}"`}
>
In use
</Badge>
) : null}
</span>
{issue.title ? (
<span className="ml-2 truncate text-muted-foreground">{issue.title}</span>
) : null}
</span>
</SelectItem>
</GitHubIssuePreviewTooltip>
))
</SelectItem>
</GitHubIssuePreviewTooltip>
);
})
) : searchTerm.trim() ? (
<div className="px-3 py-2 text-sm text-muted-foreground">
{isSearching ? (
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/components/TaskAdvancedSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import LinearSetupForm from './integrations/LinearSetupForm';
import JiraSetupForm from './integrations/JiraSetupForm';
import { type LinearIssueSummary } from '../types/linear';
import { type GitHubIssueSummary } from '../types/github';
import { type GitHubIssueLink } from '../types/chat';
import { type JiraIssueSummary } from '../types/jira';

interface TaskAdvancedSettingsProps {
Expand Down Expand Up @@ -43,6 +44,7 @@ interface TaskAdvancedSettingsProps {
// GitHub
selectedGithubIssue: GitHubIssueSummary | null;
onGithubIssueChange: (issue: GitHubIssueSummary | null) => void;
linkedGithubIssueMap?: ReadonlyMap<number, GitHubIssueLink>;
isGithubConnected: boolean;
onGithubConnect: () => Promise<void>;
githubLoading: boolean;
Expand Down Expand Up @@ -72,6 +74,7 @@ export const TaskAdvancedSettings: React.FC<TaskAdvancedSettingsProps> = ({
onLinearConnect,
selectedGithubIssue,
onGithubIssueChange,
linkedGithubIssueMap,
isGithubConnected,
onGithubConnect,
githubLoading,
Expand Down Expand Up @@ -324,6 +327,7 @@ export const TaskAdvancedSettings: React.FC<TaskAdvancedSettingsProps> = ({
projectPath={projectPath || ''}
selectedIssue={selectedGithubIssue}
onIssueChange={handleGithubIssueChange}
linkedIssueMap={linkedGithubIssueMap}
isOpen={isOpen}
disabled={
!hasInitialPromptSupport ||
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/components/TaskModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { providerMeta } from '../providers/meta';
import { isValidProviderId } from '@shared/providers/registry';
import { type LinearIssueSummary } from '../types/linear';
import { type GitHubIssueSummary } from '../types/github';
import { type GitHubIssueLink } from '../types/chat';
import { type JiraIssueSummary } from '../types/jira';
import {
generateFriendlyTaskName,
Expand Down Expand Up @@ -41,6 +42,7 @@ interface TaskModalProps {
projectName: string;
defaultBranch: string;
existingNames?: string[];
linkedGithubIssueMap?: ReadonlyMap<number, GitHubIssueLink>;
projectPath?: string;
branchOptions?: BranchOption[];
isLoadingBranches?: boolean;
Expand All @@ -53,6 +55,7 @@ const TaskModal: React.FC<TaskModalProps> = ({
projectName,
defaultBranch,
existingNames = [],
linkedGithubIssueMap,
projectPath,
branchOptions = [],
isLoadingBranches = false,
Expand Down Expand Up @@ -326,6 +329,7 @@ const TaskModal: React.FC<TaskModalProps> = ({
onLinearConnect={integrations.handleLinearConnect}
selectedGithubIssue={selectedGithubIssue}
onGithubIssueChange={setSelectedGithubIssue}
linkedGithubIssueMap={linkedGithubIssueMap}
isGithubConnected={integrations.isGithubConnected}
onGithubConnect={integrations.handleGithubConnect}
githubLoading={integrations.githubLoading}
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/types/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ export interface ProviderRun {
runs: number;
}

export interface GitHubIssueLink {
number: number;
taskId: string;
taskName: string;
}

export interface TaskMetadata {
linearIssue?: LinearIssueSummary | null;
githubIssue?: GitHubIssueSummary | null;
Expand Down