mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-20 23:13:07 +00:00
Feature: Add PR review comments and resolution, improve AI prompt handling (#790)
* feat: Add PR review comments and resolution endpoints, improve prompt handling * Feature: File Editor (#789) * feat: Add file management feature * feat: Add auto-save functionality to file editor * fix: Replace HardDriveDownload icon with Save icon for consistency * fix: Prevent recursive copy/move and improve shell injection prevention * refactor: Extract editor settings form into separate component * ``` fix: Improve error handling and stabilize async operations - Add error event handlers to GraphQL process spawns to prevent unhandled rejections - Replace execAsync with execFile for safer command execution and better control - Fix timeout cleanup in withTimeout generator to prevent memory leaks - Improve outdated comment detection logic by removing redundant condition - Use resolveModelString for consistent model string handling - Replace || with ?? for proper falsy value handling in dialog initialization - Add comments clarifying branch name resolution logic for local branches with slashes - Add catch handler for project selection to handle async errors gracefully ``` * refactor: Extract PR review comments logic to dedicated service * fix: Improve robustness and UX for PR review and file operations * fix: Consolidate exec utilities and improve type safety * refactor: Replace ScrollArea with div and improve file tree layout
This commit is contained in:
@@ -62,6 +62,7 @@ export {
|
||||
useValidateIssue,
|
||||
useMarkValidationViewed,
|
||||
useGetValidationStatus,
|
||||
useResolveReviewThread,
|
||||
} from './use-github-mutations';
|
||||
|
||||
// Ideation mutations
|
||||
|
||||
@@ -135,6 +135,55 @@ export function useMarkValidationViewed(projectPath: string) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve or unresolve a PR review thread
|
||||
*
|
||||
* @param projectPath - Path to the project
|
||||
* @param prNumber - PR number (for cache invalidation)
|
||||
* @returns Mutation for resolving/unresolving a review thread
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const resolveThread = useResolveReviewThread(projectPath, prNumber);
|
||||
* resolveThread.mutate({ threadId: comment.threadId, resolve: true });
|
||||
* ```
|
||||
*/
|
||||
export function useResolveReviewThread(projectPath: string, prNumber: number) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ threadId, resolve }: { threadId: string; resolve: boolean }) => {
|
||||
const api = getElectronAPI();
|
||||
if (!api.github?.resolveReviewThread) {
|
||||
throw new Error('Resolve review thread API not available');
|
||||
}
|
||||
|
||||
const result = await api.github.resolveReviewThread(projectPath, threadId, resolve);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to resolve review thread');
|
||||
}
|
||||
|
||||
return { isResolved: result.isResolved ?? resolve };
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
const action = variables.resolve ? 'resolved' : 'unresolved';
|
||||
toast.success(`Comment ${action}`, {
|
||||
description: `The review thread has been ${action} on GitHub`,
|
||||
});
|
||||
// Invalidate the PR review comments cache to reflect updated resolved status
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.github.prReviewComments(projectPath, prNumber),
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error('Failed to update comment', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get running validation status
|
||||
*
|
||||
|
||||
@@ -20,6 +20,7 @@ export {
|
||||
useGitHubValidations,
|
||||
useGitHubRemote,
|
||||
useGitHubIssueComments,
|
||||
useGitHubPRReviewComments,
|
||||
} from './use-github';
|
||||
|
||||
// Usage
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* automatic caching, deduplication, and background refetching.
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo, useEffect, useRef } from 'react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { queryKeys } from '@/lib/query-keys';
|
||||
import { STALE_TIMES } from '@/lib/query-client';
|
||||
@@ -151,6 +151,34 @@ export function useFeatures(projectPath: string | undefined) {
|
||||
[projectPath]
|
||||
);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Subscribe to React Query cache changes for features and sync to localStorage.
|
||||
// This ensures optimistic updates (e.g., status changes to 'verified') are
|
||||
// persisted to localStorage immediately, not just when queryFn runs.
|
||||
// Without this, a page refresh after an optimistic update could show stale
|
||||
// localStorage data where features appear in the wrong column (e.g., verified
|
||||
// features showing up in backlog).
|
||||
const projectPathRef = useRef(projectPath);
|
||||
projectPathRef.current = projectPath;
|
||||
useEffect(() => {
|
||||
if (!projectPath) return;
|
||||
const targetQueryHash = JSON.stringify(queryKeys.features.all(projectPath));
|
||||
const unsubscribe = queryClient.getQueryCache().subscribe((event) => {
|
||||
if (
|
||||
event.type === 'updated' &&
|
||||
event.action.type === 'success' &&
|
||||
event.query.queryHash === targetQueryHash
|
||||
) {
|
||||
const features = event.query.state.data as Feature[] | undefined;
|
||||
if (features && projectPathRef.current) {
|
||||
writePersistedFeatures(projectPathRef.current, features);
|
||||
}
|
||||
}
|
||||
});
|
||||
return unsubscribe;
|
||||
}, [projectPath, queryClient]);
|
||||
|
||||
return useQuery({
|
||||
queryKey: queryKeys.features.all(projectPath ?? ''),
|
||||
queryFn: async (): Promise<Feature[]> => {
|
||||
@@ -166,7 +194,11 @@ export function useFeatures(projectPath: string | undefined) {
|
||||
},
|
||||
enabled: !!projectPath,
|
||||
initialData: () => persisted?.features,
|
||||
initialDataUpdatedAt: () => persisted?.timestamp,
|
||||
// Always treat localStorage cache as stale so React Query immediately
|
||||
// fetches fresh data from the server on page load. This prevents stale
|
||||
// feature statuses (e.g., 'verified' features appearing in backlog)
|
||||
// while still showing cached data instantly for a fast initial render.
|
||||
initialDataUpdatedAt: 0,
|
||||
staleTime: STALE_TIMES.FEATURES,
|
||||
refetchInterval: createSmartPollingInterval(FEATURES_POLLING_INTERVAL),
|
||||
refetchOnWindowFocus: FEATURES_REFETCH_ON_FOCUS,
|
||||
|
||||
@@ -8,7 +8,13 @@ import { useQuery, useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { queryKeys } from '@/lib/query-keys';
|
||||
import { STALE_TIMES } from '@/lib/query-client';
|
||||
import type { GitHubIssue, GitHubPR, GitHubComment, StoredValidation } from '@/lib/electron';
|
||||
import type {
|
||||
GitHubIssue,
|
||||
GitHubPR,
|
||||
GitHubComment,
|
||||
PRReviewComment,
|
||||
StoredValidation,
|
||||
} from '@/lib/electron';
|
||||
|
||||
interface GitHubIssuesResult {
|
||||
openIssues: GitHubIssue[];
|
||||
@@ -197,3 +203,45 @@ export function useGitHubIssueComments(
|
||||
staleTime: STALE_TIMES.GITHUB,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch review comments for a GitHub PR
|
||||
*
|
||||
* Fetches both regular PR comments and inline code review comments
|
||||
* with file path and line context for each.
|
||||
*
|
||||
* @param projectPath - Path to the project
|
||||
* @param prNumber - PR number
|
||||
* @returns Query result with review comments
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { data, isLoading } = useGitHubPRReviewComments(projectPath, prNumber);
|
||||
* const comments = data?.comments ?? [];
|
||||
* ```
|
||||
*/
|
||||
export function useGitHubPRReviewComments(
|
||||
projectPath: string | undefined,
|
||||
prNumber: number | undefined
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.github.prReviewComments(projectPath ?? '', prNumber ?? 0),
|
||||
queryFn: async (): Promise<{ comments: PRReviewComment[]; totalCount: number }> => {
|
||||
if (!projectPath || !prNumber) throw new Error('Missing project path or PR number');
|
||||
const api = getElectronAPI();
|
||||
if (!api.github) {
|
||||
throw new Error('GitHub API not available');
|
||||
}
|
||||
const result = await api.github.getPRReviewComments(projectPath, prNumber);
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to fetch PR review comments');
|
||||
}
|
||||
return {
|
||||
comments: (result.comments ?? []) as PRReviewComment[],
|
||||
totalCount: result.totalCount ?? 0,
|
||||
};
|
||||
},
|
||||
enabled: !!projectPath && !!prNumber,
|
||||
staleTime: STALE_TIMES.GITHUB,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ const SETTINGS_FIELDS_TO_SYNC = [
|
||||
'projectHistory',
|
||||
'projectHistoryIndex',
|
||||
'lastSelectedSessionByProject',
|
||||
'currentWorktreeByProject',
|
||||
// Codex CLI Settings
|
||||
'codexAutoLoadAgents',
|
||||
'codexSandboxMode',
|
||||
@@ -768,6 +769,8 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
|
||||
projectHistory: serverSettings.projectHistory,
|
||||
projectHistoryIndex: serverSettings.projectHistoryIndex,
|
||||
lastSelectedSessionByProject: serverSettings.lastSelectedSessionByProject,
|
||||
currentWorktreeByProject:
|
||||
serverSettings.currentWorktreeByProject ?? currentAppState.currentWorktreeByProject,
|
||||
// UI State (previously in localStorage)
|
||||
worktreePanelCollapsed: serverSettings.worktreePanelCollapsed ?? false,
|
||||
lastProjectDir: serverSettings.lastProjectDir ?? '',
|
||||
|
||||
Reference in New Issue
Block a user