// @ts-nocheck import { useState, useCallback, useMemo } from 'react'; import { createLogger } from '@automaker/utils/logger'; import { CircleDot, RefreshCw, SearchX } from 'lucide-react'; import { getElectronAPI, GitHubIssue, IssueValidationResult } from '@/lib/electron'; import { useAppStore } from '@/store/app-store'; import { Button } from '@/components/ui/button'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; import { LoadingState } from '@/components/ui/loading-state'; import { ErrorState } from '@/components/ui/error-state'; import { cn, pathsEqual, generateUUID } from '@/lib/utils'; import { toast } from 'sonner'; import { useGithubIssues, useIssueValidation, useIssuesFilter } from './github-issues-view/hooks'; import { IssueRow, IssueDetailPanel, IssuesListHeader } from './github-issues-view/components'; import { ValidationDialog } from './github-issues-view/dialogs'; import { formatDate, getFeaturePriority } from './github-issues-view/utils'; import { useModelOverride } from '@/components/shared'; import type { ValidateIssueOptions, IssuesFilterState, IssuesStateFilter, } from './github-issues-view/types'; import { DEFAULT_ISSUES_FILTER_STATE } from './github-issues-view/types'; const logger = createLogger('GitHubIssuesView'); export function GitHubIssuesView() { const [selectedIssue, setSelectedIssue] = useState(null); const [validationResult, setValidationResult] = useState(null); const [showValidationDialog, setShowValidationDialog] = useState(false); const [showRevalidateConfirm, setShowRevalidateConfirm] = useState(false); const [pendingRevalidateOptions, setPendingRevalidateOptions] = useState(null); // Filter state const [filterState, setFilterState] = useState(DEFAULT_ISSUES_FILTER_STATE); const { currentProject, getCurrentWorktree, worktreesByProject } = useAppStore(); // Model override for validation const validationModelOverride = useModelOverride({ phase: 'validationModel' }); // Extract model string for API calls (backward compatibility) const validationModelString = validationModelOverride.effectiveModel; const { openIssues, closedIssues, loading, refreshing, error, refresh } = useGithubIssues(); const { validatingIssues, cachedValidations, handleValidateIssue, handleViewCachedValidation } = useIssueValidation({ selectedIssue, showValidationDialog, onValidationResultChange: setValidationResult, onShowValidationDialogChange: setShowValidationDialog, }); // Combine all issues for filtering const allIssues = useMemo(() => [...openIssues, ...closedIssues], [openIssues, closedIssues]); // Apply filter to issues - now returns matched issues directly for better performance const filterResult = useIssuesFilter(allIssues, filterState, cachedValidations); // 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) => { setFilterState((prev) => ({ ...prev, stateFilter })); }, []); const handleLabelsChange = useCallback((selectedLabels: string[]) => { setFilterState((prev) => ({ ...prev, selectedLabels })); }, []); // Clear all filters to default state const handleClearFilters = useCallback(() => { setFilterState(DEFAULT_ISSUES_FILTER_STATE); }, []); // Get current branch from selected worktree const currentBranch = useMemo(() => { if (!currentProject?.path) return ''; const currentWorktreeInfo = getCurrentWorktree(currentProject.path); const worktrees = worktreesByProject[currentProject.path] ?? []; const currentWorktreePath = currentWorktreeInfo?.path ?? null; const selectedWorktree = currentWorktreePath === null ? worktrees.find((w) => w.isMain) : worktrees.find((w) => !w.isMain && pathsEqual(w.path, currentWorktreePath)); return selectedWorktree?.branch || worktrees.find((w) => w.isMain)?.branch || ''; }, [currentProject?.path, getCurrentWorktree, worktreesByProject]); const handleOpenInGitHub = useCallback((url: string) => { const api = getElectronAPI(); api.openExternalLink(url); }, []); const handleConvertToTask = useCallback( async (issue: GitHubIssue, validation: IssueValidationResult) => { if (!currentProject?.path) { toast.error('No project selected'); return; } try { const api = getElectronAPI(); if (api.features?.create) { // Build description from issue body + validation info const description = [ `**From GitHub Issue #${issue.number}**`, '', issue.body || 'No description provided.', '', '---', '', '**AI Validation Analysis:**', validation.reasoning, validation.suggestedFix ? `\n**Suggested Approach:**\n${validation.suggestedFix}` : '', validation.relatedFiles?.length ? `\n**Related Files:**\n${validation.relatedFiles.map((f) => `- \`${f}\``).join('\n')}` : '', ] .filter(Boolean) .join('\n'); const feature = { id: `issue-${issue.number}-${generateUUID()}`, title: issue.title, description, category: 'From GitHub', status: 'backlog' as const, passes: false, priority: getFeaturePriority(validation.estimatedComplexity), model: 'opus', thinkingLevel: 'none' as const, branchName: currentBranch, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; const result = await api.features.create(currentProject.path, feature); if (result.success) { toast.success(`Created task: ${issue.title}`); } else { toast.error(result.error || 'Failed to create task'); } } } catch (err) { logger.error('Convert to task error:', err); toast.error(err instanceof Error ? err.message : 'Failed to create task'); } }, [currentProject?.path, currentBranch] ); if (loading) { return ; } if (error) { return ; } const totalIssues = filteredOpenIssues.length + filteredClosedIssues.length; const totalUnfilteredIssues = openIssues.length + closedIssues.length; const isFilteredEmpty = totalIssues === 0 && totalUnfilteredIssues > 0 && filterResult.hasActiveFilter; return (
{/* Issues List */}
{/* Header */} {/* Issues List */}
{totalIssues === 0 ? (
{isFilteredEmpty ? ( ) : ( )}

{isFilteredEmpty ? 'No Matching Issues' : 'No Issues'}

{isFilteredEmpty ? 'No issues match your current filters.' : 'This repository has no issues yet.'}

{isFilteredEmpty && ( )}
) : (
{/* Open Issues */} {filteredOpenIssues.map((issue) => ( setSelectedIssue(issue)} onOpenExternal={() => handleOpenInGitHub(issue.url)} formatDate={formatDate} cachedValidation={cachedValidations.get(issue.number)} isValidating={validatingIssues.has(issue.number)} /> ))} {/* Closed Issues Section */} {filteredClosedIssues.length > 0 && ( <>
Closed Issues ({filteredClosedIssues.length})
{filteredClosedIssues.map((issue) => ( setSelectedIssue(issue)} onOpenExternal={() => handleOpenInGitHub(issue.url)} formatDate={formatDate} cachedValidation={cachedValidations.get(issue.number)} isValidating={validatingIssues.has(issue.number)} /> ))} )}
)}
{/* Issue Detail Panel */} {selectedIssue && ( setSelectedIssue(null)} onShowRevalidateConfirm={(options) => { setPendingRevalidateOptions(options); setShowRevalidateConfirm(true); }} formatDate={formatDate} modelOverride={validationModelOverride} /> )} {/* Validation Dialog */} {/* Revalidate Confirmation Dialog */} { setShowRevalidateConfirm(open); if (!open) { setPendingRevalidateOptions(null); } }} title="Re-validate Issue" description={`Are you sure you want to re-validate issue #${selectedIssue?.number}? This will run a new AI analysis and replace the existing validation result.`} icon={RefreshCw} iconClassName="text-primary" confirmText="Re-validate" onConfirm={() => { if (selectedIssue && pendingRevalidateOptions) { logger.info('Revalidating with options:', { commentsCount: pendingRevalidateOptions.comments?.length ?? 0, linkedPRsCount: pendingRevalidateOptions.linkedPRs?.length ?? 0, }); handleValidateIssue(selectedIssue, { ...pendingRevalidateOptions, forceRevalidate: true, }); } }} />
); }