Conversation
Implement AI review feature (issue generalaction#562): - Add AI Review button in FileChangesPanel - Add AIReviewConfigModal for configuring review (depth, type, provider) - Add AIReviewResultsModal for displaying review results - Add aiReview.ts library with core review logic - Support review types: file-changes and agent-output - Support review depths: quick (1 agent), focused (3), comprehensive (5) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@yuzhichang is attempting to deploy a commit to the General Action Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughAdds an AI Review feature: UI modals for configuring and viewing reviews, a file-changes panel trigger, shared review types/constants, and renderer-side orchestration to launch PTY-based agents, poll conversation messages, parse/aggregate issues, and present results. Changes
Sequence DiagramssequenceDiagram
participant User
participant FilePanel as File Changes Panel
participant ConfigModal as AI Review Config Modal
participant Orchestrator as aiReview lib
participant PTY as PTY Manager / Agents
participant DB as Conversations / Messages
participant ResultsModal as AI Review Results Modal
User->>FilePanel: Click "AI Review"
FilePanel->>ConfigModal: showModal('aiReviewConfigModal', {taskId, taskPath})
User->>ConfigModal: Select depth/provider/agent, Start Review
ConfigModal->>Orchestrator: launchReviewAgents(config, taskId, taskPath, content?)
Orchestrator->>DB: create conversation(s) and metadata
Orchestrator->>PTY: startReviewAgentPty per agent
ConfigModal->>ResultsModal: open(isLoading: true)
loop Poll until timeout or enough responses
Orchestrator->>DB: pollReviewMessages(conversationId)
DB-->>Orchestrator: messages
Orchestrator->>Orchestrator: parseReviewMessages -> issues
end
Orchestrator->>Orchestrator: aggregateReviewResults(...)
Orchestrator->>ResultsModal: update results (isLoading: false)
User->>ResultsModal: View issues, open snapshots, click "Fix" or "Run Another Review"
ResultsModal->>ConfigModal: showModal to reconfigure (optional)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
src/shared/reviewPreset.ts (1)
62-118: Review prompts don't specify output format, leading to unpredictable parsing.The prompts instruct "Provide your review in a structured format" but don't specify JSON schema or expected structure. This forces
aiReview.tsto implement fragile fallback parsing (JSON → markdown). Consider adding explicit format instructions:♻️ Example: Add JSON schema instruction to prompts
comprehensive: `You are an expert code reviewer conducting a comprehensive review. Review the following file changes for: - All correctness issues including edge cases ... -Provide your review in a structured format with specific issues found, including severity and category.`, +Provide your review as a JSON array of issues with this schema: +[{ "severity": "critical|major|minor|info", "category": "string", "title": "string", "description": "string", "filePath": "string?", "lineRange": {"start": number, "end": number}?, "codeSnapshot": "string?", "fixPrompt": "string?" }]`,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/reviewPreset.ts` around lines 62 - 118, Update the REVIEW_PROMPTS constant so each prompt variant (fileChanges.quick/focused/comprehensive and agentOutput.quick/focused/comprehensive) explicitly requires a machine-readable JSON output schema; change the prompt text to append a short JSON schema specification (e.g., an array of issue objects with fields like "id", "severity", "category", "location", "description", "suggestion") and a one-line instruction "Return only valid JSON" to avoid freeform text; ensure both fileChanges and agentOutput templates include the same schema and fields so downstream parsing in aiReview.ts can rely on a stable structure.src/renderer/lib/aiReview.ts (2)
124-135: Sequential agent launches could be parallelized.Launching agents sequentially with
for...ofandawaitadds unnecessary latency when depth is "focused" (3 agents) or "comprehensive" (5 agents). Consider usingPromise.allfor parallel startup.♻️ Parallel agent launch
- for (let i = 0; i < agentCount; i++) { - const prompt = buildReviewPrompt(config.reviewType, config.depth, content); - const { conversationId, ptyId } = await launchReviewAgent({ - taskId, - taskPath, - reviewId, - agent: config.providerId, - prompt, - }); - conversationIds.push(conversationId); - ptyIds.push(ptyId); - } + const prompt = buildReviewPrompt(config.reviewType, config.depth, content); + const launches = Array.from({ length: agentCount }, () => + launchReviewAgent({ + taskId, + taskPath, + reviewId, + agent: config.providerId, + prompt, + }) + ); + const results = await Promise.all(launches); + for (const { conversationId, ptyId } of results) { + conversationIds.push(conversationId); + ptyIds.push(ptyId); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/lib/aiReview.ts` around lines 124 - 135, The loop that calls buildReviewPrompt and await launchReviewAgent (using agentCount and config) runs agents sequentially; change it to start all agent launches in parallel by creating an array of launch promises (e.g., map over indices to call buildReviewPrompt and launchReviewAgent) and await Promise.all on that array, then extract conversationId and ptyId from the resolved results to populate conversationIds and ptyIds in the same order; ensure any thrown errors are handled or propagated appropriately (refer to buildReviewPrompt, launchReviewAgent, conversationIds, ptyIds, agentCount, and config).
208-284: Add test coverage for the markdown parsing function, particularly for edge cases with flexible regex patterns.The
parseMarkdownIssuesfunction parses variable markdown formats using multiple regex patterns with optional groups (e.g.,/\[?(\w+)\]?/,/[:\-]?/). No test coverage currently exists for this critical parser. Consider adding tests for edge cases such as malformed headers, boundary conditions in code block handling, and variations in file path and category formatting.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/lib/aiReview.ts` around lines 208 - 284, Add unit tests for parseMarkdownIssues covering edge cases of its flexible regexes and code-block handling: write tests that feed malformed headers (e.g., missing brackets, extra punctuation), headers with different casing/spacing that exercise headerMatch and bulletMatch, and ensure normalizeSeverity and generateId behavior is controlled (mock or stub generateId for deterministic assertions). Include tests for code-fence boundaries (adjacent fences, empty code blocks, indented code vs fenced code), variations of File:/Path:/Location: and Category:/Type: lines parsing into filePath and category, and multi-issue inputs to assert currentCodeBlock and description are correctly accumulated and pushed as AIReviewIssue objects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/renderer/components/AIReviewConfigModal.tsx`:
- Around line 96-109: The results modal is opened with
showModal('aiReviewResultsModal') but no onFixIssue handler is provided, so
AIReviewResultsModal's "Fix" button is a no-op; update the showModal call (and
the similar showResultsModal helper) to pass an onFixIssue callback that either
reopens the aiReviewConfigModal with the selected issue context or dispatches
the existing fix flow: implement onFixIssue = (issue) =>
showModal('aiReviewConfigModal', { taskId, taskPath, availableAgents,
installedAgents, issue, onSuccess: () => {} }) (or call the shared fix routine
used elsewhere) so the button becomes functional and receives the issue payload.
- Around line 143-149: The current heuristic in the loop that sets hasResponses
(iterating conversationIds, calling pollReviewMessages, then checking
messages.some(m => m.sender === 'agent' && m.content.length > 50)) is brittle;
replace the length check with a structural/completion check: for each message
from sender 'agent' validate the content by either attempting to parse JSON and
confirm expected fields (e.g., checking parsed.content.response, parsed.result,
or a "status":"completed" marker) or by looking for explicit completion
markers/phrases (e.g., "Response:", "### End", or absence of error indicators
like "error"/"traceback"); update the condition used in the messages.some(...)
call to use this validator so hasResponses is true only for messages that match
the structured/completion criteria rather than a raw length check.
- Around line 283-294: In AIReviewConfigModal, the AgentDropdown can be empty
because the current fallback (availableAgents.length > 0 ? availableAgents :
installedAgents) doesn’t handle both arrays being empty; update the render for
the 'agent-output' branch to detect when both availableAgents and
installedAgents are empty and show a user-facing message (e.g., "No agents
available — install or enable an agent") instead of the dropdown; if you prefer
keeping the dropdown, pass an empty list and disable it and ensure
selectedAgentId is cleared or set to null to avoid invalid selection. Target the
JSX around AgentDropdown, the availableAgents/installedAgents check, and any
state setting for selectedAgentId to implement this behavior.
- Around line 120-164: pollForResults currently schedules recursive setTimeout
calls and calls showResultsModal even after the component unmounts; change
polling to be cancellable by tracking mounted/cancel state (e.g., an isMounted
or isCancelled ref or an AbortController-like flag) and check it before any
state/modal calls and before scheduling the next setTimeout, also store and
clear the timeout handle (from setTimeout) on unmount so pending timers are
cleared; update functions pollForResults, the setTimeout callback, and any
useEffect that starts polling to respect the cancel flag and avoid calling
showResultsModal or collectResults when cancelled.
In `@src/renderer/components/AIReviewResultsModal.tsx`:
- Around line 272-276: The "Run Another Review" Button in AIReviewResultsModal
calls the optional prop onRunAnotherReview directly which can throw if
undefined; update the Button's onClick to guard the call (use optional chaining
or a no-op fallback) so it only invokes onRunAnotherReview when defined (e.g.,
onClick={() => onRunAnotherReview?.()}), and ensure any TypeScript/JSX
references to onRunAnotherReview are adjusted accordingly to avoid runtime
errors.
- Around line 180-186: sortedIssues is not being filtered by the current
activeTab, so the issue list always shows all issues; update the logic that
computes or supplies sortedIssues (used in the map inside AIReviewResultsModal
where sortedIssues.map(...) and activeTab/expandedIssues are referenced) to
apply a filter: when activeTab is the "All Issues" value keep all issues,
otherwise restrict to issues whose agent identifier matches activeTab (e.g.,
issue.agentId or the property your issue objects use for agent ownership).
Ensure the filtered array is then sorted the same way as before and used in the
existing map so selecting an agent tab shows only that agent's issues.
In `@src/renderer/components/FileChangesPanel.tsx`:
- Around line 265-276: The modal is being opened with installedAgents: [] which
causes AgentDropdown to render no options; update handleOpenAIReview so it
supplies the real installed agents instead of an empty array — obtain them
either by adding an installedAgents prop to FileChangesPanel (and passing it
from the parent RightSidebar) or derive them inside FileChangesPanel via the app
settings hook/context (e.g., useAppSettings or the existing app context) and
then call showModal('aiReviewConfigModal', { taskId: resolvedTaskId, taskPath:
safeTaskPath, installedAgents: installedAgents, onSuccess: ... }). Ensure you
reference the handleOpenAIReview function and the showModal call when modifying
the code so AgentDropdown receives the correct installedAgents set.
In `@src/renderer/lib/aiReview.ts`:
- Line 14: The renderer code imports the Message type from main process code
(Message in aiReview.ts), creating an unwanted cross-boundary dependency; fix by
extracting or re-exporting the Message interface into a shared types module and
update the import in aiReview.ts to import Message from that shared module (or
alternatively declare the IPC/renderer typing for Message in your renderer
declaration file such as electron-api.d.ts) so the renderer no longer imports
from main process sources.
---
Nitpick comments:
In `@src/renderer/lib/aiReview.ts`:
- Around line 124-135: The loop that calls buildReviewPrompt and await
launchReviewAgent (using agentCount and config) runs agents sequentially; change
it to start all agent launches in parallel by creating an array of launch
promises (e.g., map over indices to call buildReviewPrompt and
launchReviewAgent) and await Promise.all on that array, then extract
conversationId and ptyId from the resolved results to populate conversationIds
and ptyIds in the same order; ensure any thrown errors are handled or propagated
appropriately (refer to buildReviewPrompt, launchReviewAgent, conversationIds,
ptyIds, agentCount, and config).
- Around line 208-284: Add unit tests for parseMarkdownIssues covering edge
cases of its flexible regexes and code-block handling: write tests that feed
malformed headers (e.g., missing brackets, extra punctuation), headers with
different casing/spacing that exercise headerMatch and bulletMatch, and ensure
normalizeSeverity and generateId behavior is controlled (mock or stub generateId
for deterministic assertions). Include tests for code-fence boundaries (adjacent
fences, empty code blocks, indented code vs fenced code), variations of
File:/Path:/Location: and Category:/Type: lines parsing into filePath and
category, and multi-issue inputs to assert currentCodeBlock and description are
correctly accumulated and pushed as AIReviewIssue objects.
In `@src/shared/reviewPreset.ts`:
- Around line 62-118: Update the REVIEW_PROMPTS constant so each prompt variant
(fileChanges.quick/focused/comprehensive and
agentOutput.quick/focused/comprehensive) explicitly requires a machine-readable
JSON output schema; change the prompt text to append a short JSON schema
specification (e.g., an array of issue objects with fields like "id",
"severity", "category", "location", "description", "suggestion") and a one-line
instruction "Return only valid JSON" to avoid freeform text; ensure both
fileChanges and agentOutput templates include the same schema and fields so
downstream parsing in aiReview.ts can rely on a stable structure.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 26ef21c0-39e3-4447-86b4-9f979f8f5395
📒 Files selected for processing (6)
src/renderer/components/AIReviewConfigModal.tsxsrc/renderer/components/AIReviewResultsModal.tsxsrc/renderer/components/FileChangesPanel.tsxsrc/renderer/contexts/ModalProvider.tsxsrc/renderer/lib/aiReview.tssrc/shared/reviewPreset.ts
| showModal('aiReviewResultsModal', { | ||
| results: [], | ||
| isLoading: true, | ||
| onRunAnotherReview: () => { | ||
| // Reopen config modal | ||
| showModal('aiReviewConfigModal', { | ||
| taskId, | ||
| taskPath, | ||
| availableAgents, | ||
| installedAgents, | ||
| onSuccess: () => {}, | ||
| }); | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Results modal shows loading state but onFixIssue handler is not wired.
When opening the results modal (here and in showResultsModal), no onFixIssue handler is passed. The "Fix" button in AIReviewResultsModal will be non-functional since onFixIssue?.() will be a no-op. Consider implementing or documenting this as a TODO.
Would you like me to help draft an onFixIssue implementation, or should I open an issue to track this as follow-up work?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AIReviewConfigModal.tsx` around lines 96 - 109, The
results modal is opened with showModal('aiReviewResultsModal') but no onFixIssue
handler is provided, so AIReviewResultsModal's "Fix" button is a no-op; update
the showModal call (and the similar showResultsModal helper) to pass an
onFixIssue callback that either reopens the aiReviewConfigModal with the
selected issue context or dispatches the existing fix flow: implement onFixIssue
= (issue) => showModal('aiReviewConfigModal', { taskId, taskPath,
availableAgents, installedAgents, issue, onSuccess: () => {} }) (or call the
shared fix routine used elsewhere) so the button becomes functional and receives
the issue payload.
| async function pollForResults( | ||
| reviewId: string, | ||
| conversationIds: string[], | ||
| config: AIReviewConfig, | ||
| pollCount: number | ||
| ) { | ||
| const maxPolls = 120; // 2 minutes with 1s interval | ||
| const pollInterval = 1000; | ||
|
|
||
| if (pollCount >= maxPolls) { | ||
| // Timeout - show partial results | ||
| try { | ||
| const results = await collectResults(conversationIds, config, reviewId); | ||
| showResultsModal(results); | ||
| } catch { | ||
| // Ignore errors on timeout | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| // Check if we have any responses from review agents | ||
| let hasResponses = false; | ||
| for (const convId of conversationIds) { | ||
| const { messages } = await pollReviewMessages(convId); | ||
| if (messages.some((m) => m.sender === 'agent' && m.content.length > 50)) { | ||
| hasResponses = true; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (hasResponses) { | ||
| const results = await collectResults(conversationIds, config, reviewId); | ||
| showResultsModal(results); | ||
| return; | ||
| } | ||
| } catch { | ||
| // Continue polling on error | ||
| } | ||
|
|
||
| // Schedule next poll | ||
| setTimeout(() => { | ||
| pollForResults(reviewId, conversationIds, config, pollCount + 1); | ||
| }, pollInterval); | ||
| } |
There was a problem hiding this comment.
Polling continues after component unmount, risking state updates on unmounted component.
pollForResults uses recursive setTimeout without cleanup. If the user closes the modal during polling, subsequent showModal calls may error or cause unexpected behavior. Consider using useEffect with cleanup or an AbortController-like pattern.
🛠️ Suggested approach using a ref to track mount state
+ const isMountedRef = useRef(true);
+
+ useEffect(() => {
+ return () => {
+ isMountedRef.current = false;
+ };
+ }, []);
+
async function pollForResults(
reviewId: string,
conversationIds: string[],
config: AIReviewConfig,
pollCount: number
) {
+ if (!isMountedRef.current) return;
+
const maxPolls = 120;
// ... rest of function
// Before showResultsModal calls:
+ if (!isMountedRef.current) return;
showResultsModal(results);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AIReviewConfigModal.tsx` around lines 120 - 164,
pollForResults currently schedules recursive setTimeout calls and calls
showResultsModal even after the component unmounts; change polling to be
cancellable by tracking mounted/cancel state (e.g., an isMounted or isCancelled
ref or an AbortController-like flag) and check it before any state/modal calls
and before scheduling the next setTimeout, also store and clear the timeout
handle (from setTimeout) on unmount so pending timers are cleared; update
functions pollForResults, the setTimeout callback, and any useEffect that starts
polling to respect the cancel flag and avoid calling showResultsModal or
collectResults when cancelled.
| for (const convId of conversationIds) { | ||
| const { messages } = await pollReviewMessages(convId); | ||
| if (messages.some((m) => m.sender === 'agent' && m.content.length > 50)) { | ||
| hasResponses = true; | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
Fragile heuristic for detecting agent responses.
Checking m.content.length > 50 to determine if an agent has meaningfully responded is brittle—short valid responses would be missed, and long error messages could be misinterpreted as success.
Consider a more robust approach, such as checking for specific response structure or completion markers.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AIReviewConfigModal.tsx` around lines 143 - 149, The
current heuristic in the loop that sets hasResponses (iterating conversationIds,
calling pollReviewMessages, then checking messages.some(m => m.sender ===
'agent' && m.content.length > 50)) is brittle; replace the length check with a
structural/completion check: for each message from sender 'agent' validate the
content by either attempting to parse JSON and confirm expected fields (e.g.,
checking parsed.content.response, parsed.result, or a "status":"completed"
marker) or by looking for explicit completion markers/phrases (e.g.,
"Response:", "### End", or absence of error indicators like
"error"/"traceback"); update the condition used in the messages.some(...) call
to use this validator so hasResponses is true only for messages that match the
structured/completion criteria rather than a raw length check.
| {reviewType === 'agent-output' && ( | ||
| <div className="space-y-2"> | ||
| <Label className="text-sm font-medium">Agent to Review</Label> | ||
| <AgentDropdown | ||
| value={selectedAgentId} | ||
| onChange={(v) => setSelectedAgentId(v as Agent)} | ||
| installedAgents={availableAgents.length > 0 ? availableAgents : installedAgents} | ||
| /> | ||
| <p className="text-xs text-muted-foreground"> | ||
| Select which agent's output to review | ||
| </p> | ||
| </div> |
There was a problem hiding this comment.
Agent selection dropdown may be empty when availableAgents and installedAgents are both empty.
The fallback logic availableAgents.length > 0 ? availableAgents : installedAgents doesn't help if both are empty (as happens with the current FileChangesPanel invocation). Consider adding a user-facing message when no agents are available.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AIReviewConfigModal.tsx` around lines 283 - 294, In
AIReviewConfigModal, the AgentDropdown can be empty because the current fallback
(availableAgents.length > 0 ? availableAgents : installedAgents) doesn’t handle
both arrays being empty; update the render for the 'agent-output' branch to
detect when both availableAgents and installedAgents are empty and show a
user-facing message (e.g., "No agents available — install or enable an agent")
instead of the dropdown; if you prefer keeping the dropdown, pass an empty list
and disable it and ensure selectedAgentId is cleared or set to null to avoid
invalid selection. Target the JSX around AgentDropdown, the
availableAgents/installedAgents check, and any state setting for selectedAgentId
to implement this behavior.
| {/* Issues list */} | ||
| <ScrollArea className="max-h-[60vh] flex-1 overflow-y-auto"> | ||
| <div className="space-y-3 p-6"> | ||
| {sortedIssues.map((issue) => { | ||
| const config = severityConfig[issue.severity]; | ||
| const SeverityIcon = config.icon; | ||
| const isExpanded = expandedIssues.has(issue.id); |
There was a problem hiding this comment.
Tab selection doesn't filter the displayed issues.
The tab bar shows "All Issues" and individual agent tabs, but sortedIssues always shows all issues regardless of activeTab. When a user selects a specific agent tab, they expect to see only that agent's issues.
🐛 Proposed fix to filter issues by active tab
+ const displayedIssues = React.useMemo(() => {
+ if (activeTab === 'all') {
+ return sortedIssues;
+ }
+ const result = initialResults[activeTab as number];
+ if (!result) return [];
+ const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
+ return [...result.issues].sort(
+ (a, b) => severityOrder[a.severity] - severityOrder[b.severity]
+ );
+ }, [activeTab, sortedIssues, initialResults]);
{/* Issues list */}
<ScrollArea className="max-h-[60vh] flex-1 overflow-y-auto">
<div className="space-y-3 p-6">
- {sortedIssues.map((issue) => {
+ {displayedIssues.map((issue) => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {/* Issues list */} | |
| <ScrollArea className="max-h-[60vh] flex-1 overflow-y-auto"> | |
| <div className="space-y-3 p-6"> | |
| {sortedIssues.map((issue) => { | |
| const config = severityConfig[issue.severity]; | |
| const SeverityIcon = config.icon; | |
| const isExpanded = expandedIssues.has(issue.id); | |
| const displayedIssues = React.useMemo(() => { | |
| if (activeTab === 'all') { | |
| return sortedIssues; | |
| } | |
| const result = initialResults[activeTab as number]; | |
| if (!result) return []; | |
| const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 }; | |
| return [...result.issues].sort( | |
| (a, b) => severityOrder[a.severity] - severityOrder[b.severity] | |
| ); | |
| }, [activeTab, sortedIssues, initialResults]); | |
| {/* Issues list */} | |
| <ScrollArea className="max-h-[60vh] flex-1 overflow-y-auto"> | |
| <div className="space-y-3 p-6"> | |
| {displayedIssues.map((issue) => { | |
| const config = severityConfig[issue.severity]; | |
| const SeverityIcon = config.icon; | |
| const isExpanded = expandedIssues.has(issue.id); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AIReviewResultsModal.tsx` around lines 180 - 186,
sortedIssues is not being filtered by the current activeTab, so the issue list
always shows all issues; update the logic that computes or supplies sortedIssues
(used in the map inside AIReviewResultsModal where sortedIssues.map(...) and
activeTab/expandedIssues are referenced) to apply a filter: when activeTab is
the "All Issues" value keep all issues, otherwise restrict to issues whose agent
identifier matches activeTab (e.g., issue.agentId or the property your issue
objects use for agent ownership). Ensure the filtered array is then sorted the
same way as before and used in the existing map so selecting an agent tab shows
only that agent's issues.
| <DialogFooter className="border-t border-border px-6 py-4"> | ||
| <Button variant="outline" onClick={onRunAnotherReview}> | ||
| Run Another Review | ||
| </Button> | ||
| <Button onClick={onClose}>Close</Button> |
There was a problem hiding this comment.
"Run Another Review" button may crash if onRunAnotherReview is undefined.
The button calls onRunAnotherReview directly without optional chaining, but the prop is typed as optional (onRunAnotherReview?: () => void). This could throw if the handler isn't provided.
🐛 Proposed fix
- <Button variant="outline" onClick={onRunAnotherReview}>
+ <Button variant="outline" onClick={onRunAnotherReview} disabled={!onRunAnotherReview}>
Run Another Review
</Button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <DialogFooter className="border-t border-border px-6 py-4"> | |
| <Button variant="outline" onClick={onRunAnotherReview}> | |
| Run Another Review | |
| </Button> | |
| <Button onClick={onClose}>Close</Button> | |
| <DialogFooter className="border-t border-border px-6 py-4"> | |
| <Button variant="outline" onClick={onRunAnotherReview} disabled={!onRunAnotherReview}> | |
| Run Another Review | |
| </Button> | |
| <Button onClick={onClose}>Close</Button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AIReviewResultsModal.tsx` around lines 272 - 276, The
"Run Another Review" Button in AIReviewResultsModal calls the optional prop
onRunAnotherReview directly which can throw if undefined; update the Button's
onClick to guard the call (use optional chaining or a no-op fallback) so it only
invokes onRunAnotherReview when defined (e.g., onClick={() =>
onRunAnotherReview?.()}), and ensure any TypeScript/JSX references to
onRunAnotherReview are adjusted accordingly to avoid runtime errors.
| const handleOpenAIReview = useCallback(() => { | ||
| if (!resolvedTaskId || !safeTaskPath) return; | ||
| showModal('aiReviewConfigModal', { | ||
| taskId: resolvedTaskId, | ||
| taskPath: safeTaskPath, | ||
| installedAgents: [], | ||
| onSuccess: (config) => { | ||
| // Review configuration selected - the results modal will be shown by the caller | ||
| console.log('AI Review config:', config); | ||
| }, | ||
| }); | ||
| }, [resolvedTaskId, safeTaskPath, showModal]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify how installedAgents is used in other modal invocations to understand the expected pattern
rg -n "installedAgents" --type=ts -C 3Repository: generalaction/emdash
Length of output: 11167
🏁 Script executed:
# Check FileChangesPanel.tsx structure, imports, props, and context access
head -100 src/renderer/components/FileChangesPanel.tsx | cat -nRepository: generalaction/emdash
Length of output: 4161
🏁 Script executed:
# Search for how FileChangesPanel is used/instantiated to understand what props it receives
rg -n "FileChangesPanel" --type=ts -B 2 -A 2Repository: generalaction/emdash
Length of output: 3253
🏁 Script executed:
# Check if FileChangesPanel has agentStatuses in props or receives it through context
rg -n "agentStatuses\|useAppSettings\|useModalContext" src/renderer/components/FileChangesPanel.tsx -A 2 -B 2Repository: generalaction/emdash
Length of output: 46
🏁 Script executed:
# Check RightSidebar to see if it has access to agentStatuses
head -50 src/renderer/components/RightSidebar.tsx | cat -nRepository: generalaction/emdash
Length of output: 2185
🏁 Script executed:
# Check if there's a context provider pattern that FileChangesPanel could use
rg -n "useAppSettings\|agentStatuses" src/renderer/components/RightSidebar.tsx -B 2 -A 2Repository: generalaction/emdash
Length of output: 46
🏁 Script executed:
# Search for where FileChangesPanel's handleOpenAIReview callback is and check for missing context
sed -n '265,280p' src/renderer/components/FileChangesPanel.tsx | cat -nRepository: generalaction/emdash
Length of output: 739
🏁 Script executed:
# Check where RightSidebar is used and if agentStatuses is available there
rg -n "<RightSidebar" --type=tsx -B 5 -A 5Repository: generalaction/emdash
Length of output: 91
🏁 Script executed:
# Look at ChatInterface to see the full context of how it manages agentStatuses
sed -n '265,280p' src/renderer/components/ChatInterface.tsx | cat -nRepository: generalaction/emdash
Length of output: 682
🏁 Script executed:
# Check if ChatInterface passes agentStatuses down to RightSidebar or related components
rg -n "agentStatuses" src/renderer/components/ChatInterface.tsx -B 3 -A 3 | head -50Repository: generalaction/emdash
Length of output: 955
🏁 Script executed:
# Check where RightSidebar is instantiated/used in ChatInterface
rg -n "RightSidebar" src/renderer/components/ChatInterface.ts -B 3 -A 3Repository: generalaction/emdash
Length of output: 144
🏁 Script executed:
# Check the full RightSidebarProps to see if installedAgents should be part of it
rg -n "interface RightSidebarProps\|RightSidebarProps" src/renderer/components/RightSidebar.ts -A 10Repository: generalaction/emdash
Length of output: 143
🏁 Script executed:
# Check if there's a pattern of passing agentStatuses through context or props to descendants
rg -n "installedAgents" src/renderer/components/ChatInterface.ts -B 2 -A 2Repository: generalaction/emdash
Length of output: 144
🏁 Script executed:
# Check where RightSidebar is instantiated/used in ChatInterface
rg -n "RightSidebar" src/renderer/components/ChatInterface.tsx -B 3 -A 3Repository: generalaction/emdash
Length of output: 46
🏁 Script executed:
# Check the full RightSidebarProps to see if installedAgents should be part of it
rg -n "interface RightSidebarProps" src/renderer/components/RightSidebar.tsx -A 12Repository: generalaction/emdash
Length of output: 530
🏁 Script executed:
# Check if installedAgents is passed to RightSidebar in ChatInterface
rg -n "installedAgents=" src/renderer/components/ChatInterface.tsxRepository: generalaction/emdash
Length of output: 112
🏁 Script executed:
# Search where RightSidebar is used to understand the component hierarchy
rg -n "<RightSidebar" src/renderer/components --type=ts -B 2 -A 2Repository: generalaction/emdash
Length of output: 1977
🏁 Script executed:
# Check the complete usage of RightSidebar in ChatInterface to see if it's a child component
rg -n "RightSidebar\|FileChangesPanel" src/renderer/components/ChatInterface.tsx -B 1 -A 1 | head -40Repository: generalaction/emdash
Length of output: 46
🏁 Script executed:
# Check if FileChangesPanel is ever passed agentStatuses or installedAgents as a prop anywhere
rg -n "FileChangesPanel.*installedAgents\|<FileChangesPanel" src/renderer/components -B 2 -A 2Repository: generalaction/emdash
Length of output: 46
🏁 Script executed:
# Check the full structure around where RightSidebar is rendered in ChatInterface
rg -n "RightSidebar\|FileChangesPanel" src/renderer/components/ChatInterface.tsx --type=ts -B 3 -A 3 | grep -A 8 -B 8 "RightSidebar"Repository: generalaction/emdash
Length of output: 46
🏁 Script executed:
# Look at the actual lines in ChatInterface around component rendering
sed -n '1100,1180p' src/renderer/components/ChatInterface.tsx | cat -nRepository: generalaction/emdash
Length of output: 3441
🏁 Script executed:
# Verify that FileChangesPanel cannot access agentStatuses or installed agents through any means
grep -n "useAppSettings\|useContext\|useMemo.*agent\|agentStatuses" src/renderer/components/FileChangesPanel.tsx | head -20Repository: generalaction/emdash
Length of output: 46
🏁 Script executed:
# Look for where installedAgents is being passed in ChatInterface around line 1159
sed -n '1145,1175p' src/renderer/components/ChatInterface.tsx | cat -nRepository: generalaction/emdash
Length of output: 1491
🏁 Script executed:
# Check if there's a context that provides agent information that FileChangesPanel could use
rg -n "export.*Context\|createContext" src/renderer/contexts/ --type=tsRepository: generalaction/emdash
Length of output: 46
🏁 Script executed:
# Check all the props that FileChangesPanel receives and how it's instantiated
sed -n '133,150p' src/renderer/components/FileChangesPanel.tsx | cat -nRepository: generalaction/emdash
Length of output: 787
🏁 Script executed:
# Double-check the AgentDropdown filtering logic to confirm empty array means no options
sed -n '10,35p' src/renderer/components/AgentDropdown.tsx | cat -nRepository: generalaction/emdash
Length of output: 1084
Empty installedAgents array prevents agent selection in the modal.
Passing installedAgents: [] will cause the AgentDropdown component to render zero options, since it filters agents using installedSet.has(key). This blocks users from selecting a provider or agent-to-review, making the review feature non-functional.
FileChangesPanel has no access to installed agents—it's not passed as a prop and no context provides it. You need to either:
- Pass
installedAgentsas a prop from the parent component (RightSidebar → FileChangesPanel) - Derive it within FileChangesPanel using a hook (e.g.,
useAppSettings) - Use a context provider that supplies installed agents
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/FileChangesPanel.tsx` around lines 265 - 276, The
modal is being opened with installedAgents: [] which causes AgentDropdown to
render no options; update handleOpenAIReview so it supplies the real installed
agents instead of an empty array — obtain them either by adding an
installedAgents prop to FileChangesPanel (and passing it from the parent
RightSidebar) or derive them inside FileChangesPanel via the app settings
hook/context (e.g., useAppSettings or the existing app context) and then call
showModal('aiReviewConfigModal', { taskId: resolvedTaskId, taskPath:
safeTaskPath, installedAgents: installedAgents, onSuccess: ... }). Ensure you
reference the handleOpenAIReview function and the showModal call when modifying
the code so AgentDropdown receives the correct installedAgents set.
| import { makePtyId, type PtyIdKind } from '@shared/ptyId'; | ||
| import type { ProviderId } from '@shared/providers/registry'; | ||
| import { buildReviewConversationMetadata } from '@shared/reviewPreset'; | ||
| import type { Message } from '../../main/services/DatabaseService'; |
There was a problem hiding this comment.
Cross-boundary import from main process code in renderer.
Importing Message type from ../../main/services/DatabaseService creates a coupling between renderer and main process code. This may cause bundling issues and violates the process separation pattern.
♻️ Suggested fix: Use or create shared type
Move the Message interface to a shared location (e.g., src/shared/types.ts) or re-export from an existing shared module, then import from there:
-import type { Message } from '../../main/services/DatabaseService';
+import type { Message } from '@shared/types';Based on learnings: "Add renderer typings in src/renderer/types/electron-api.d.ts for any new IPC method"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/lib/aiReview.ts` at line 14, The renderer code imports the
Message type from main process code (Message in aiReview.ts), creating an
unwanted cross-boundary dependency; fix by extracting or re-exporting the
Message interface into a shared types module and update the import in
aiReview.ts to import Message from that shared module (or alternatively declare
the IPC/renderer typing for Message in your renderer declaration file such as
electron-api.d.ts) so the renderer no longer imports from main process sources.
Summary
git diffdirectlyFiles Changed
src/shared/reviewPreset.ts- Types and prompt templatessrc/renderer/lib/aiReview.ts- Core review logicsrc/renderer/components/AIReviewConfigModal.tsx- Configuration modalsrc/renderer/components/AIReviewResultsModal.tsx- Results display modalsrc/renderer/components/FileChangesPanel.tsx- AI Review buttonsrc/renderer/contexts/ModalProvider.tsx- Modal registryTest plan
Closes #562
🤖 Generated with Claude Code
Summary by CodeRabbit