diff --git a/apps/ui/src/components/views/github-issues-view.tsx b/apps/ui/src/components/views/github-issues-view.tsx
index a34f1225..03275b02 100644
--- a/apps/ui/src/components/views/github-issues-view.tsx
+++ b/apps/ui/src/components/views/github-issues-view.tsx
@@ -56,19 +56,23 @@ export function GitHubIssuesView() {
// Combine all issues for filtering
const allIssues = useMemo(() => [...openIssues, ...closedIssues], [openIssues, closedIssues]);
- // Apply filter to issues
+ // Apply filter to issues - now returns matched issues directly for better performance
const filterResult = useIssuesFilter(allIssues, filterState, cachedValidations);
- // Filter issues based on matched results
- const filteredOpenIssues = useMemo(
- () => openIssues.filter((issue) => filterResult.matchedIssueNumbers.has(issue.number)),
- [openIssues, filterResult.matchedIssueNumbers]
- );
-
- const filteredClosedIssues = useMemo(
- () => closedIssues.filter((issue) => filterResult.matchedIssueNumbers.has(issue.number)),
- [closedIssues, filterResult.matchedIssueNumbers]
- );
+ // Separate filtered issues by state - this is O(n) but now only done once
+ // since filterResult.matchedIssues already contains the filtered issues
+ const { filteredOpenIssues, filteredClosedIssues } = useMemo(() => {
+ const open: typeof openIssues = [];
+ const closed: typeof closedIssues = [];
+ for (const issue of filterResult.matchedIssues) {
+ if (issue.state.toLowerCase() === 'open') {
+ open.push(issue);
+ } else {
+ closed.push(issue);
+ }
+ }
+ return { filteredOpenIssues: open, filteredClosedIssues: closed };
+ }, [filterResult.matchedIssues]);
// Filter state change handlers
const handleStateFilterChange = useCallback((stateFilter: IssuesStateFilter) => {
diff --git a/apps/ui/src/components/views/github-issues-view/components/issues-filter-controls.tsx b/apps/ui/src/components/views/github-issues-view/components/issues-filter-controls.tsx
index f2378ef0..475a32d5 100644
--- a/apps/ui/src/components/views/github-issues-view/components/issues-filter-controls.tsx
+++ b/apps/ui/src/components/views/github-issues-view/components/issues-filter-controls.tsx
@@ -20,6 +20,11 @@ import { cn } from '@/lib/utils';
import type { IssuesStateFilter } from '../types';
import { ISSUES_STATE_FILTER_OPTIONS } from '../types';
+/** Maximum number of labels to display before showing "+N more" in normal layout */
+const VISIBLE_LABELS_LIMIT = 3;
+/** Maximum number of labels to display before showing "+N more" in compact layout */
+const VISIBLE_LABELS_LIMIT_COMPACT = 2;
+
interface IssuesFilterControlsProps {
/** Current state filter value */
stateFilter: IssuesStateFilter;
@@ -156,21 +161,27 @@ export function IssuesFilterControls({
{/* Selected Labels Display - shown on separate row */}
{hasSelectedLabels && (
- {selectedLabels.slice(0, compact ? 2 : 3).map((label) => (
- handleLabelToggle(label)}
- >
- {label}
-
-
- ))}
- {selectedLabels.length > (compact ? 2 : 3) && (
+ {selectedLabels
+ .slice(0, compact ? VISIBLE_LABELS_LIMIT_COMPACT : VISIBLE_LABELS_LIMIT)
+ .map((label) => (
+ handleLabelToggle(label)}
+ >
+ {label}
+
+
+ ))}
+ {selectedLabels.length >
+ (compact ? VISIBLE_LABELS_LIMIT_COMPACT : VISIBLE_LABELS_LIMIT) && (
- +{selectedLabels.length - (compact ? 2 : 3)} more
+ +
+ {selectedLabels.length -
+ (compact ? VISIBLE_LABELS_LIMIT_COMPACT : VISIBLE_LABELS_LIMIT)}{' '}
+ more
)}
diff --git a/apps/ui/src/components/views/github-issues-view/hooks/use-issues-filter.ts b/apps/ui/src/components/views/github-issues-view/hooks/use-issues-filter.ts
index 3ab5f7bb..987e890a 100644
--- a/apps/ui/src/components/views/github-issues-view/hooks/use-issues-filter.ts
+++ b/apps/ui/src/components/views/github-issues-view/hooks/use-issues-filter.ts
@@ -200,8 +200,9 @@ export function useIssuesFilter(
// Normalize search query for case-insensitive matching
const normalizedQuery = searchQuery.toLowerCase().trim();
- // Filter issues based on all criteria
- const matchedIssueNumbers = new Set();
+ // Filter issues based on all criteria - return matched issues directly
+ // This eliminates the redundant O(n) filtering operation in the consuming component
+ const matchedIssues: GitHubIssue[] = [];
for (const issue of issues) {
// All conditions must be true for a match
@@ -214,17 +215,17 @@ export function useIssuesFilter(
matchesValidationStatus(issue, validationStatusFilter, cachedValidations);
if (matchesAllFilters) {
- matchedIssueNumbers.add(issue.number);
+ matchedIssues.push(issue);
}
}
return {
- matchedIssueNumbers,
+ matchedIssues,
availableLabels,
availableAssignees,
availableMilestones,
hasActiveFilter,
- matchedCount: matchedIssueNumbers.size,
+ matchedCount: matchedIssues.length,
};
}, [
issues,
diff --git a/apps/ui/src/components/views/github-issues-view/types.ts b/apps/ui/src/components/views/github-issues-view/types.ts
index d2986c16..a66e3a96 100644
--- a/apps/ui/src/components/views/github-issues-view/types.ts
+++ b/apps/ui/src/components/views/github-issues-view/types.ts
@@ -72,8 +72,8 @@ export interface IssuesFilterState {
* Result of applying filters to the issues list
*/
export interface IssuesFilterResult {
- /** Set of issue numbers that match the current filters */
- matchedIssueNumbers: Set;
+ /** Array of GitHubIssue objects that match the current filters */
+ matchedIssues: GitHubIssue[];
/** Available labels from all issues (for filter dropdown population) */
availableLabels: string[];
/** Available assignees from all issues (for filter dropdown population) */
diff --git a/apps/ui/src/hooks/index.ts b/apps/ui/src/hooks/index.ts
index 4627f2b3..8a354b3d 100644
--- a/apps/ui/src/hooks/index.ts
+++ b/apps/ui/src/hooks/index.ts
@@ -2,7 +2,6 @@ export { useAutoMode } from './use-auto-mode';
export { useBoardBackgroundSettings } from './use-board-background-settings';
export { useElectronAgent } from './use-electron-agent';
export { useGuidedPrompts } from './use-guided-prompts';
-export { useIssuesFilter } from '@/components/views/github-issues-view/hooks/use-issues-filter';
export { useKeyboardShortcuts } from './use-keyboard-shortcuts';
export { useMessageQueue } from './use-message-queue';
export { useOSDetection, type OperatingSystem, type OSDetectionResult } from './use-os-detection';