Skip to content

fix: show dismissed PR review findings in UI instead of silently dropping them#1852

Open
AndyMik90 wants to merge 4 commits intodevelopfrom
fix/pr-review-findings-dropped
Open

fix: show dismissed PR review findings in UI instead of silently dropping them#1852
AndyMik90 wants to merge 4 commits intodevelopfrom
fix/pr-review-findings-dropped

Conversation

@AndyMik90
Copy link
Owner

@AndyMik90 AndyMik90 commented Feb 16, 2026

Summary

When PR review specialists found issues but the AI validator dismissed them all, users would see "0 findings" with no visibility into what was found or why it was dismissed. Now dismissed findings appear in a collapsible "Disputed by Validator" section so users can review them and optionally post them to GitHub.

Problem

The PR review pipeline: specialists → cross-validation → line verify → AI validation → scope filter → verdict. When the AI validator dismissed findings as false positives, they were silently dropped from the results. Users had no way to know that specialists had found issues, leading to confusion when the verdict showed "0 findings" despite specialist agents reporting concerns.

Solution

Backend: Keep dismissed findings in the pipeline instead of dropping them. Mark them with validation_status="dismissed_false_positive" and preserve the validator's explanation. Separate active findings from dismissed ones before generating the verdict — only active findings drive the merge verdict. Cross-validated findings (found by multiple specialist agents independently) are protected from dismissal.

Frontend: Split findings into active vs disputed. Active findings appear in the existing severity groups (unchanged). Disputed findings appear in a new collapsible purple "Disputed by Validator" section (collapsed by default). Each disputed finding shows:

  • A purple "Disputed" badge
  • The validator's explanation in italic text
  • A "Confirmed by N agents" badge if cross-validated
  • Individual selection checkbox (user opt-in)

"Select All" excludes disputed findings — users must explicitly choose to include them.

Changes

Backend (1 file)

  • parallel_orchestrator_reviewer.py — Keep dismissed findings with metadata in _validate_findings(); separate active vs dismissed for verdict in review()

Frontend (5 files)

  • github-api.ts — Add validationStatus, validationExplanation, sourceAgents, crossValidated to PRReviewFinding type
  • pr-handlers.ts — Map 4 new snake_case → camelCase fields in IPC handler
  • ReviewFindings.tsx — Split findings into active/disputed; add collapsible disputed section with purple styling
  • FindingItem.tsx — Add disputed badge, cross-validation badge, validation explanation display
  • FindingsSummary.tsx — Add purple disputed count badge

i18n (2 files)

  • en/common.json — 5 new translation keys (disputed, disputedByValidator, crossValidatedBy, etc.)
  • fr/common.json — French translations for all 5 keys

Testing

  • TypeScript typecheck passes
  • Biome CI passes (no new warnings)
  • Ruff check + format passes
  • All 2562 backend tests pass
  • Security review: no XSS or data leakage risks (React auto-escaping protects all new rendering)
  • i18n review: all keys present in both en and fr locales
  • Cross-platform review: no platform-specific issues

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Disputed (dismissed) findings are shown in a separate collapsible "Disputed" section with its own count; active findings drive verdicts, totals, grouping, and selection.
    • Findings display validation status, disputed and cross-validated badges, and optional explanatory text; selection preserves disputed items where appropriate.
    • UI messaging and icons updated to clarify active vs. disputed findings.
  • Localization

    • Added English and French translations for dispute and cross-validation UI text.

…ping them

Specialists would find issues but the AI validator could dismiss them all,
leaving users seeing "0 findings" with no visibility into what was found
or why it was dismissed. Now dismissed findings appear in a collapsible
"Disputed by Validator" section so users can review and optionally post them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added area/fullstack This is Frontend + Backend bug Something isn't working size/M Medium (100-499 lines) labels Feb 16, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: preventing dismissed findings from being silently dropped and instead displaying them in the UI with appropriate visual indicators.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/pr-review-findings-dropped

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @AndyMik90, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical transparency issue where AI-validated findings, if dismissed as false positives, were completely hidden from users. The changes ensure that all findings, whether active or dismissed, are presented in the user interface. This allows users to understand why certain issues were flagged by specialists but then disputed by the AI, and provides the option to manually include these disputed findings. The core impact is a significant improvement in user understanding and control over the review process, preventing confusion caused by '0 findings' when issues were indeed identified.

Highlights

  • Visibility for Dismissed Findings: Previously, AI-dismissed findings were silently dropped. Now, they are retained in the pipeline and displayed in the UI within a new collapsible 'Disputed by Validator' section, providing users with full transparency.
  • Protection for Cross-Validated Findings: Findings that are cross-validated by multiple specialist agents are now protected from being dismissed by a single AI validator, ensuring consensus-backed issues are always surfaced.
  • Frontend UI Enhancements: The UI now clearly distinguishes between active and disputed findings, with disputed findings featuring a purple badge, the validator's explanation, and an optional 'Confirmed by N agents' badge. 'Select All' functionality has been updated to exclude disputed findings by default, requiring explicit user selection.
  • Backend Logic Refinement: The backend now explicitly separates active findings (which influence the merge verdict) from dismissed findings (which are for UI display only), ensuring the verdict remains accurate while providing comprehensive information to the user.
  • Internationalization Support: New translation keys have been added for both English and French locales to support the new 'disputed' terminology and UI elements.
Changelog
  • apps/backend/runners/github/services/parallel_orchestrator_reviewer.py
    • Modified the review method to separate active findings from dismissed_false_positive findings, ensuring only active findings influence the merge verdict.
    • Updated logging and safe_print statements to reflect the count of active and disputed findings.
    • Enhanced the _validate_findings method to retain findings with dismissed_false_positive status, attaching the validator's explanation.
    • Implemented logic to prevent cross-validated findings from being dismissed, instead marking them as confirmed_valid with an explanation.
  • apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts
    • Mapped new backend fields (validation_status, validation_explanation, source_agents, cross_validated) to their camelCase equivalents in the PRReviewFinding type for frontend consumption.
  • apps/frontend/src/preload/api/modules/github-api.ts
    • Extended the PRReviewFinding interface to include optional fields for validationStatus, validationExplanation, sourceAgents, and crossValidated.
  • apps/frontend/src/renderer/components/github-prs/components/FindingItem.tsx
    • Added a disputed prop to FindingItemProps to control styling and content for disputed findings.
    • Updated the className logic to apply distinct ring and opacity styles for selected and disputed findings.
    • Introduced new Badge components to display 'Disputed' status and 'Confirmed by N agents' for cross-validated findings.
    • Added a paragraph to display the validationExplanation in italic text when a finding is disputed.
  • apps/frontend/src/renderer/components/github-prs/components/FindingsSummary.tsx
    • Added an optional disputedCount prop to FindingsSummaryProps.
    • Included a new Badge component to display the count of disputed findings with purple styling.
  • apps/frontend/src/renderer/components/github-prs/components/ReviewFindings.tsx
    • Updated the component's description to reflect the new feature of showing disputed findings.
    • Imported new Lucide icons (ChevronDown, ChevronRight, ShieldQuestion) for the disputed section.
    • Introduced disputedExpanded state to manage the visibility of the new disputed findings section.
    • Refactored finding processing to split unpostedFindings into activeFindings and disputedFindings.
    • Adjusted groupedFindings and counts calculations to only consider activeFindings for severity grouping and total counts.
    • Modified the useFindingSelection hook to operate only on activeFindings, ensuring 'Select All' excludes disputed items.
    • Updated the FindingsSummary component to pass activeFindings and the new disputedCount.
    • Implemented a new collapsible UI section for 'Disputed Findings' that displays disputedFindings using FindingItem components with specific styling and hint text.
  • apps/frontend/src/shared/i18n/locales/en/common.json
    • Added five new English translation keys: disputed, disputedByValidator, disputedExplanation, crossValidatedBy, and disputedSectionHint.
  • apps/frontend/src/shared/i18n/locales/fr/common.json
    • Added five new French translation keys corresponding to the English additions: Contesté, Contesté par le validateur, Le validateur IA est en désaccord avec cette conclusion, Confirmé par {{count}} agents, and Ces résultats ont été signalés par les spécialistes mais contestés par le validateur. Vous pouvez toujours les sélectionner et les publier.
Activity
  • The pull request includes comprehensive testing, with TypeScript typecheck, Biome CI, Ruff check/format, and all 2562 backend tests passing.
  • Security review confirmed no XSS or data leakage risks, leveraging React's auto-escaping.
  • i18n review ensured all new translation keys are present in both English and French locales.
  • Cross-platform review found no platform-specific issues.
  • The PR description indicates it was generated with 'Claude Code'.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is a well-executed pull request that significantly improves the user experience by providing visibility into dismissed findings. The changes are implemented cleanly across the backend, frontend, and i18n files.

On the backend, you've correctly modified the pipeline to retain dismissed findings without letting them affect the merge verdict. The addition of a protection mechanism for cross-validated findings is a thoughtful enhancement.

On the frontend, the new 'Disputed by Validator' section is well-integrated. The use of useMemo to split findings is good, and the new UI components are clear.

I've made two minor suggestions to optimize list processing in both the Python backend and the React frontend by using a single pass to partition the findings. These are for maintainability and minor performance gains.

Overall, this is a great change that adds valuable transparency to the review process.

Comment on lines 1293 to 1302
active_findings = [
f
for f in validated_findings
if f.validation_status != "dismissed_false_positive"
]
dismissed_findings = [
f
for f in validated_findings
if f.validation_status == "dismissed_false_positive"
]
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To improve efficiency and readability, you can partition the validated_findings into active_findings and dismissed_findings in a single loop instead of iterating over the list twice.

            active_findings = []
            dismissed_findings = []
            for f in validated_findings:
                if f.validation_status == "dismissed_false_positive":
                    dismissed_findings.append(f)
                else:
                    active_findings.append(f)

Comment on lines 62 to 71
// Split unposted findings into active vs disputed
const activeFindings = useMemo(() =>
unpostedFindings.filter(f => f.validationStatus !== 'dismissed_false_positive'),
[unpostedFindings]
);

const disputedFindings = useMemo(() =>
unpostedFindings.filter(f => f.validationStatus === 'dismissed_false_positive'),
[unpostedFindings]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To improve efficiency, you can split unpostedFindings into activeFindings and disputedFindings in a single pass using useMemo. This avoids iterating over the unpostedFindings array twice.

Suggested change
// Split unposted findings into active vs disputed
const activeFindings = useMemo(() =>
unpostedFindings.filter(f => f.validationStatus !== 'dismissed_false_positive'),
[unpostedFindings]
);
const disputedFindings = useMemo(() =>
unpostedFindings.filter(f => f.validationStatus === 'dismissed_false_positive'),
[unpostedFindings]
);
const { activeFindings, disputedFindings } = useMemo(() => {
const active: PRReviewFinding[] = [];
const disputed: PRReviewFinding[] = [];
for (const finding of unpostedFindings) {
if (finding.validationStatus === 'dismissed_false_positive') {
disputed.push(finding);
} else {
active.push(finding);
}
}
return { activeFindings: active, disputedFindings: disputed };
}, [unpostedFindings]);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/backend/runners/github/services/parallel_orchestrator_reviewer.py (4)

2003-2006: ⚠️ Potential issue | 🟡 Minor

Misleading log message after behavior change.

Before this PR, dismissed findings were excluded from validated_findings, so logging {len(validated_findings)} valid was accurate. Now that dismissed findings are retained in the list, this count includes them, making "valid" misleading. Consider using "total" or computing the actual confirmed count.

Proposed fix
+        confirmed_count = len(validated_findings) - dismissed_count - needs_human_count
         logger.info(
-            f"[PRReview] Validation complete: {len(validated_findings)} valid, "
+            f"[PRReview] Validation complete: {len(validated_findings)} total "
+            f"({confirmed_count} confirmed, "
             f"{dismissed_count} dismissed, {needs_human_count} need human review"
+            f")"
         )

2207-2212: ⚠️ Potential issue | 🟡 Minor

Missing display label for dismissed_false_positive status in summary.

The status_label dict handles confirmed_valid and needs_human_review but not dismissed_false_positive. Dismissed findings will render the raw string "dismissed_false_positive" in the GitHub markdown summary. Add a friendly label to match the frontend's "Disputed" terminology.

Proposed fix
                     status_label = {
                         "confirmed_valid": "Confirmed",
                         "needs_human_review": "Needs human review",
+                        "dismissed_false_positive": "Disputed by validator",
                     }.get(f.validation_status, f.validation_status)

2232-2243: ⚠️ Potential issue | 🟡 Minor

Summary findings count includes dismissed findings, may contradict the verdict.

The "Total" line counts all findings including dismissed ones. This can produce a confusing summary like "3 findings (1 critical)" paired with a "READY_TO_MERGE" verdict when that critical finding was actually dismissed. Consider either excluding dismissed findings from this count or adding a breakdown (e.g., "3 findings (1 disputed)").

Proposed fix — separate active and disputed counts
             # Findings count summary
-            by_severity: dict[str, int] = {}
+            active_in_summary = [f for f in findings if f.validation_status != "dismissed_false_positive"]
+            dismissed_in_summary = [f for f in findings if f.validation_status == "dismissed_false_positive"]
+            by_severity: dict[str, int] = {}
-            for f in findings:
+            for f in active_in_summary:
                 sev = f.severity.value
                 by_severity[sev] = by_severity.get(sev, 0) + 1
             summary_parts = []
             for sev in ["critical", "high", "medium", "low"]:
                 if sev in by_severity:
                     summary_parts.append(f"{by_severity[sev]} {sev}")
-            lines.append(
-                f"**Total:** {len(findings)} finding(s) ({', '.join(summary_parts)})"
-            )
+            total_str = f"**Total:** {len(active_in_summary)} active finding(s) ({', '.join(summary_parts)})"
+            if dismissed_in_summary:
+                total_str += f" + {len(dismissed_in_summary)} disputed"
+            lines.append(total_str)

1756-1770: ⚠️ Potential issue | 🟡 Minor

Outdated docstring for _validate_findings.

The return description still says "only confirmed_valid and needs_human_review", but the method now also returns findings with dismissed_false_positive status. Update to reflect the new behavior.

Proposed fix
         Returns:
-            List of validated findings (only confirmed_valid and needs_human_review)
+            List of all findings with validation metadata attached.
+            Includes confirmed_valid, dismissed_false_positive, and needs_human_review.
+            Dismissed findings are retained for UI display but excluded from verdict by the caller.
apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts (1)

1332-1348: ⚠️ Potential issue | 🔴 Critical

Synchronize PRReviewFinding in pr-handlers.ts with the preload API definition.

The PRReviewFinding interface in this file (line 260) is missing four properties that are already defined in the preload API (apps/frontend/src/preload/api/modules/github-api.ts line 368) and are being mapped in the getReviewResult() function: validationStatus, validationExplanation, sourceAgents, and crossValidated. Update the interface to match the preload version to maintain type consistency and prevent type mismatches downstream.

✅ Proposed fix
 export interface PRReviewFinding {
   id: string;
   severity: "critical" | "high" | "medium" | "low";
   category: "security" | "quality" | "style" | "test" | "docs" | "pattern" | "performance";
   title: string;
   description: string;
   file: string;
   line: number;
   endLine?: number;
   suggestedFix?: string;
   fixable: boolean;
+  validationStatus?: "confirmed_valid" | "dismissed_false_positive" | "needs_human_review" | null;
+  validationExplanation?: string;
+  sourceAgents?: string[];
+  crossValidated?: boolean;
 }
🤖 Fix all issues with AI agents
In
`@apps/frontend/src/renderer/components/github-prs/components/ReviewFindings.tsx`:
- Around line 150-155: The summary can show selected > total because
selectedIds.size includes disputed items; change the prop passed to
FindingsSummary to count only active findings that are selected (e.g., compute
selectedActiveCount by filtering activeFindings by selectedIds membership) and
pass that value instead of selectedIds.size so FindingsSummary (which uses
activeFindings.length as total) shows a correct "selected/total" ratio; update
the usage near FindingsSummary to use this computed selectedActiveCount and
leave disputedCount as disputedFindings.length.

@AndyMik90 AndyMik90 self-assigned this Feb 16, 2026
Copy link
Owner Author

@AndyMik90 AndyMik90 left a comment

Choose a reason for hiding this comment

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

🤖 Auto Claude PR Review

Merge Verdict: 🟠 NEEDS REVISION

🟠 Needs revision - 3 issue(s) require attention.

3 issue(s) must be addressed (0 required, 3 recommended), 4 suggestions

Risk Assessment

Factor Level Notes
Complexity Low Based on lines changed
Security Impact None Based on security findings
Scope Coherence Good Based on structural review

Findings Summary

  • Medium: 3 issue(s)
  • Low: 4 issue(s)

Generated by Auto Claude PR Review

Findings (7 selected of 7 total)

🟡 [6d12c7c80d1b] [MEDIUM] Summary includes dismissed findings with raw status string in GitHub comments

📁 apps/backend/runners/github/services/parallel_orchestrator_reviewer.py:2207

_generate_summary() receives unique_findings (active + dismissed) on line 1341 and iterates over ALL findings. Two problems: (1) The status_label map on lines 2208-2211 has no entry for 'dismissed_false_positive', so it falls through to the default and renders the raw string 'dismissed_false_positive' in the summary markdown (e.g. 'Validation: dismissed_false_positive'). This summary is posted directly to GitHub comments (confirmed at PRDetail.tsx lines 907, 965, 1011, 1407). (2) The findings count on line 2242 includes dismissed findings in the total, so the summary might say '5 finding(s)' while the verdict was generated from only 3 active findings — a confusing mismatch.

Suggested fix:

Either: (a) Add 'dismissed_false_positive': 'Disputed by validator' to the status_label map and visually differentiate disputed findings in the summary (e.g. a ~~strikethrough~~ or '⚪ [DISPUTED]' prefix), or (b) Pass only active_findings to _generate_summary instead of unique_findings (since dismissed findings are already shown in the frontend UI).

🟡 [671c2187d283] [MEDIUM] Selected count can exceed total in FindingsSummary when disputed findings are selected

📁 apps/frontend/src/renderer/components/github-prs/components/ReviewFindings.tsx:151

FindingsSummary receives findings=activeFindings and selectedCount=selectedIds.size. The total displayed is activeFindings.length, but selectedIds can include disputed finding IDs (users can toggle them individually in the disputed section via toggleFinding on line 273). Scenario: 3 active findings + 2 disputed findings. User clicks 'Select All' (selects 3), then manually checks both disputed findings → selectedIds.size=5 but counts.total=3 → UI shows '5 of 3 selected'. Confirmed by reading useFindingSelection.ts: toggleFinding (line 23) operates on selectedIds directly regardless of what findings array the hook was initialized with.

Suggested fix:

Compute the displayed selectedCount to only count IDs from activeFindings: `selectedCount={activeFindings.filter(f => selectedIds.has(f.id)).length}` — or alternatively count total as activeFindings.length + disputedFindings selected count and show them separately (e.g. '3 of 3 selected + 2 disputed').

🔵 [63696cad319c] [LOW] Disputed findings always dimmed even when user explicitly selects them

📁 apps/frontend/src/renderer/components/github-prs/components/FindingItem.tsx:51

The className on line 51 applies opacity-60 whenever disputed is true, regardless of the selected state. When a user deliberately expands the disputed section and checks a finding to post it, the finding remains visually dimmed (opacity-60), which signals it's disabled or inactive — contradicting the user's explicit selection. The selected ring (ring-2 ring-purple-500/50 on line 50) is applied but barely visible at 60% opacity.

Suggested fix:

Only dim unselected disputed findings: `(posted || (disputed && !selected)) && "opacity-60"` — this gives visual feedback that selecting a disputed finding 'activates' it.

🟡 [f992869c7846] [MEDIUM] selectAll and selectImportant silently drop user-selected disputed findings

📁 apps/frontend/src/renderer/components/github-prs/components/ReviewFindings.tsx:113

The useFindingSelection hook receives only activeFindings (line 114). The hook's selectAll (useFindingSelection.ts:34-36) creates a brand-new Set from only those active finding IDs: new Set(findings.map(f => f.id)). Similarly, selectImportant (useFindingSelection.ts:44-46) creates a fresh Set of only critical+high IDs. However, disputed findings can be individually selected by the user via toggleFinding in the disputed section (ReviewFindings.tsx:273). When the user clicks 'Select All' or 'Critical/High' after manually selecting disputed findings, those disputed selections are silently replaced because the new Set contains only active finding IDs.

Concrete scenario:

  1. User expands disputed section, checks 2 disputed findings via individual toggles
  2. selectedIds = {disputed1, disputed2}
  3. User clicks 'Select All' to also include all active findings
  4. selectAll creates new Set({active1, active2, active3}) — disputed1 and disputed2 are lost
  5. User's disputed selections vanish with no feedback

Note: toggleSeverityGroup does NOT have this bug because it copies the existing Set (new Set(selectedIds)) and only modifies the target group, preserving other selections.

Suggested fix:

Have selectAll and selectImportant preserve selections for IDs not in the hook's `findings` array. For example, in selectAll: create the set from active findings, then iterate selectedIds and re-add any IDs that aren't in the active findings list (i.e., disputed selections). Alternatively, wrap selectAll/selectImportant in ReviewFindings.tsx to merge the result with existing disputed selections.

🔵 [a44d514d3db6] [LOW] selectedCount can exceed total when disputed findings are selected

📁 apps/frontend/src/renderer/components/github-prs/components/ReviewFindings.tsx:153

FindingsSummary receives selectedCount={selectedIds.size} (which includes ALL selected findings, both active and disputed) but findings={activeFindings} (only active). The component displays selectedOfTotal as '{selected} of {total}'. If a user manually selects disputed findings, the selected count can exceed the total, showing e.g. '5 selected of 3 total'.

Concrete scenario:

  1. 3 active findings, 2 disputed findings
  2. User selects all 3 active via toggle, plus 2 disputed
  3. selectedIds.size = 5, activeFindings.length = 3
  4. Display shows '5 selected of 3 total'

Note: Due to the selectAll bug above (which drops disputed selections), this is only reachable via individual toggles. But it's still a valid state.

Suggested fix:

Filter selectedIds to only count IDs present in activeFindings before passing to FindingsSummary: `selectedCount={[...selectedIds].filter(id => activeFindings.some(f => f.id === id)).length}`. Or use a Set intersection.

🔵 [882fbc7ae674] [LOW] Missing human-readable label for dismissed_false_positive in summary

📁 apps/backend/runners/github/services/parallel_orchestrator_reviewer.py:2208

The status_label mapping in _generate_summary maps 'confirmed_valid' to 'Confirmed' and 'needs_human_review' to 'Needs human review', but does not map 'dismissed_false_positive'. Since dismissed findings are now included in unique_findings passed to _generate_summary (line 1341), the summary will display the raw internal string: 'Validation: dismissed_false_positive'.

Suggested fix:

Add 'dismissed_false_positive': 'Disputed' to the status_label dict:
status_label = {
    "confirmed_valid": "Confirmed",
    "needs_human_review": "Needs human review",
    "dismissed_false_positive": "Disputed",
}.get(f.validation_status, f.validation_status)

🔵 [29f0ca912fde] [LOW] Unused i18n key 'prReview.disputedExplanation' added but never referenced

📁 apps/frontend/src/shared/i18n/locales/en/common.json:407

The translation key prReview.disputedExplanation ("The AI validator disagrees with this finding") was added to both en/common.json and fr/common.json but is never used in any renderer component. Searched with Grep('disputedExplanation', 'apps/frontend/src/renderer') — zero matches. The FindingItem component renders finding.validationExplanation directly (line 95) rather than using this generic translation key. This is dead i18n code that will confuse future developers who see it in the locale files.

Suggested fix:

Remove the `disputedExplanation` key from both en/common.json and fr/common.json since it is unused. If a static label is needed alongside the dynamic explanation, reference it via `t('prReview.disputedExplanation')` in FindingItem.tsx.

This review was generated by Auto Claude.

…and review findings component

Updated the logic for separating active and dismissed findings in both the backend and frontend components. The new implementation uses a single pass to categorize findings, improving efficiency and readability. This change enhances the overall performance of the review process by reducing the number of iterations over the findings list.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/frontend/src/renderer/components/github-prs/components/ReviewFindings.tsx (1)

186-196: ⚠️ Potential issue | 🟡 Minor

Clear button may not clear disputed selections — downstream of the hook scoping concern above.

disabled={selectedIds.size === 0} considers all selections, but selectNone comes from the hook scoped to activeFindings. If a user has only disputed items checked, the button appears enabled but clicking it may do nothing. This is part of the same issue flagged on the disputed section toggle.

🤖 Fix all issues with AI agents
In `@apps/backend/runners/github/services/parallel_orchestrator_reviewer.py`:
- Around line 1957-1989: The log that reports counts is double-counting
dismissed items because validated_findings includes both confirmed and dismissed
entries; update the logger.info message that currently formats
"{len(validated_findings)} valid, {dismissed_count} dismissed" to compute
confirmed as len(validated_findings) - dismissed_count (use validated_findings
and dismissed_count variables) so it logs "{confirmed} confirmed,
{dismissed_count} dismissed". Also update the _generate_summary implementation's
status_label dictionary to include a human-friendly mapping for the
"dismissed_false_positive" status (e.g., "Dismissed (False Positive)") so the
GitHub-rendered summary shows a readable label instead of the raw status string.
- Around line 1292-1316: The variable unique_findings is being reassigned to
validated_findings and shadows an earlier deduplication result; rename
unique_findings to a clearer name such as all_findings_for_ui where it's set
from validated_findings, update any subsequent references that consume the UI
list (e.g., the later uses at the locations currently referencing
unique_findings) so they use all_findings_for_ui, and leave the earlier
deduplicated unique_findings (from the dedup step) intact to avoid
confusion—look for the variables validated_findings, unique_findings,
active_findings, and dismissed_findings to locate the code to change.

In
`@apps/frontend/src/renderer/components/github-prs/components/ReviewFindings.tsx`:
- Around line 253-267: The collapsible header button doesn't expose its expanded
state to assistive tech; update the button element (the one using onClick={() =>
setDisputedExpanded(!disputedExpanded)}) to include
aria-expanded={disputedExpanded} (and optionally aria-controls pointing to the
collapsible panel id that contains disputedFindings) so screen readers can read
the expanded/collapsed state; ensure the aria attribute value uses the
disputedExpanded boolean from the component.

Comment on lines 1957 to +1989
elif validation.validation_status == "dismissed_false_positive":
# Dismiss - do not include
dismissed_count += 1
logger.info(
f"[PRReview] Dismissed {finding.id} as false positive: "
f"{validation.explanation[:100]}"
)
# Protect cross-validated findings from dismissal —
# if multiple specialists independently found the same issue,
# a single validator should not override that consensus
if finding.cross_validated:
finding.validation_status = "confirmed_valid"
finding.validation_evidence = validation.code_evidence
finding.validation_explanation = (
f"[Auto-kept: cross-validated by {len(finding.source_agents)} agents] "
f"{validation.explanation}"
)
validated_findings.append(finding)
safe_print(
f"[FindingValidator] Kept cross-validated finding '{finding.title}' "
f"despite dismissal (agents={finding.source_agents})",
flush=True,
)
else:
# Keep finding but mark as dismissed (user can see it in UI)
finding.validation_status = "dismissed_false_positive"
finding.validation_evidence = validation.code_evidence
finding.validation_explanation = validation.explanation
validated_findings.append(finding)
dismissed_count += 1
safe_print(
f"[FindingValidator] Disputed '{finding.title}': "
f"{validation.explanation} (file={finding.file}:{finding.line})",
flush=True,
)
logger.info(
f"[PRReview] Disputed {finding.id}: "
f"{validation.explanation[:200]}"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Cross-validation protection and dismissed-finding retention look correct.

Two downstream issues to address:

  1. Misleading log at line 2001: validated_findings now includes dismissed findings, so {len(validated_findings)} valid, {dismissed_count} dismissed double-counts dismissed items. Consider {len(validated_findings) - dismissed_count} confirmed, {dismissed_count} dismissed.

  2. Raw status string in summary (line 2205-2208): _generate_summary doesn't map "dismissed_false_positive" in the status_label dict, so the GitHub-rendered summary will display the raw string "dismissed_false_positive". Add a mapping entry.

Proposed fixes

Fix 1 — Accurate log at line 2000:

         logger.info(
-            f"[PRReview] Validation complete: {len(validated_findings)} valid, "
+            f"[PRReview] Validation complete: {len(validated_findings)} total "
+            f"({len(validated_findings) - dismissed_count} confirmed, "
             f"{dismissed_count} dismissed, {needs_human_count} need human review"
         )

Fix 2 — Add label mapping at line 2205:

                     status_label = {
                         "confirmed_valid": "Confirmed",
+                        "dismissed_false_positive": "Disputed by Validator",
                         "needs_human_review": "Needs human review",
                     }.get(f.validation_status, f.validation_status)
🤖 Prompt for AI Agents
In `@apps/backend/runners/github/services/parallel_orchestrator_reviewer.py`
around lines 1957 - 1989, The log that reports counts is double-counting
dismissed items because validated_findings includes both confirmed and dismissed
entries; update the logger.info message that currently formats
"{len(validated_findings)} valid, {dismissed_count} dismissed" to compute
confirmed as len(validated_findings) - dismissed_count (use validated_findings
and dismissed_count variables) so it logs "{confirmed} confirmed,
{dismissed_count} dismissed". Also update the _generate_summary implementation's
status_label dictionary to include a human-friendly mapping for the
"dismissed_false_positive" status (e.g., "Dismissed (False Positive)") so the
GitHub-rendered summary shows a readable label instead of the raw status string.

Copy link
Owner Author

@AndyMik90 AndyMik90 left a comment

Choose a reason for hiding this comment

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

🤖 Auto Claude PR Review

🟠 Follow-up Review: Needs Revision

🟠 Needs revision - 5 unresolved finding(s) from previous review.

Resolution Status

  • Resolved: 2 previous findings addressed
  • Unresolved: 5 previous findings remain
  • 🆕 New Issues: 9 new findings in recent changes

Finding Validation

  • 🔍 Dismissed as False Positives: 0 findings were re-investigated and found to be incorrect
  • Confirmed Valid: 7 findings verified as genuine issues
  • 👤 Needs Human Review: 0 findings require manual verification

🚨 Blocking Issues

  • quality: [UNRESOLVED] Summary includes dismissed findings with raw status string in GitHub comments
  • quality: Raw 'dismissed_false_positive' string displayed in GitHub PR summary comments
  • quality: selectAll and selectImportant silently drop user-selected disputed findings

Verdict

CI Status: ✅ All 20 CI checks passing — no CI blockers.

Progress: 2 of 7 previous findings resolved (selectedCount exceeding total fixed via selectedActiveCount). Clear improvement in the core active/disputed separation architecture.

However, 2 MEDIUM severity issues remain confirmed valid, blocking merge per quality gates:

  1. [MEDIUM] _generate_summary() still passes dismissed findings through with raw 'dismissed_false_positive' string in GitHub PR comments (lines 2205-2208 lack the mapping entry). Quick fix: add 'dismissed_false_positive': 'Disputed by validator' to the status_label dict.

  2. [MEDIUM] selectAll/selectImportant silently drop user-selected disputed findings by replacing the entire selection set with only active finding IDs (useFindingSelection.ts lines 34-47). Quick fix: merge new active selections with existing disputed selections.

5 LOW severity suggestions are also confirmed but do not block merge (dimmed disputed findings, unused i18n key, interface type inconsistency, missing aria-expanded consistent with existing pattern, variable shadowing).

Review Process

Agents invoked: resolution-verifier, new-code-reviewer, comment-analyzer, finding-validator


This is an AI-generated follow-up review using parallel specialist analysis with finding validation.

Findings (13 selected of 13 total)

🟡 [6d12c7c80d1b] [MEDIUM] [UNRESOLVED] Summary includes dismissed findings with raw status string in GitHub comments

📁 apps/backend/runners/github/services/parallel_orchestrator_reviewer.py:2207

_generate_summary() receives unique_findings (active + dismissed) on line 1341 and iterates over ALL findings. Two problems: (1) The status_label map on lines 2208-2211 has no entry for 'dismissed_false_positive', so it falls through to the default and renders the raw string 'dismissed_false_positive' in the summary markdown (e.g. 'Validation: dismissed_false_positive'). This summary is posted directly to GitHub comments (confirmed at PRDetail.tsx lines 907, 965, 1011, 1407). (2) The findings count on line 2242 includes dismissed findings in the total, so the summary might say '5 finding(s)' while the verdict was generated from only 3 active findings — a confusing mismatch.

Resolution note: _generate_summary at line 1338 still receives unique_findings (= validated_findings at line 1312, includes active + dismissed). The status_label dict at lines 2205-2208 maps only 'confirmed_valid' and 'needs_human_review'. Dismissed findings with validation_status='dismissed_false_positive' fall through to the raw string via .get() default, rendering 'Validation: dismissed_false_positive' in GitHub comments.

Suggested fix:

Either: (a) Add 'dismissed_false_positive': 'Disputed by validator' to the status_label map and visually differentiate disputed findings in the summary (e.g. a ~~strikethrough~~ or '⚪ [DISPUTED]' prefix), or (b) Pass only active_findings to _generate_summary instead of unique_findings (since dismissed findings are already shown in the frontend UI).

🔵 [63696cad319c] [LOW] [UNRESOLVED] Disputed findings always dimmed even when user explicitly selects them

📁 apps/frontend/src/renderer/components/github-prs/components/FindingItem.tsx:51

The className on line 51 applies opacity-60 whenever disputed is true, regardless of the selected state. When a user deliberately expands the disputed section and checks a finding to post it, the finding remains visually dimmed (opacity-60), which signals it's disabled or inactive — contradicting the user's explicit selection. The selected ring (ring-2 ring-purple-500/50 on line 50) is applied but barely visible at 60% opacity.

Resolution note: FindingItem.tsx line 51: (posted || disputed) && "opacity-60" applies unconditionally when disputed=true regardless of selected state. A purple ring is added when selected+disputed (line 50), but the item remains dimmed at 60% opacity, creating contradictory visual signals.

Suggested fix:

Only dim unselected disputed findings: `(posted || (disputed && !selected)) && "opacity-60"` — this gives visual feedback that selecting a disputed finding 'activates' it.

🔵 [882fbc7ae674] [LOW] [UNRESOLVED] Missing human-readable label for dismissed_false_positive in summary

📁 apps/backend/runners/github/services/parallel_orchestrator_reviewer.py:2208

The status_label mapping in _generate_summary maps 'confirmed_valid' to 'Confirmed' and 'needs_human_review' to 'Needs human review', but does not map 'dismissed_false_positive'. Since dismissed findings are now included in unique_findings passed to _generate_summary (line 1341), the summary will display the raw internal string: 'Validation: dismissed_false_positive'.

Resolution note: Lines 2205-2207: status_label dict only contains 'confirmed_valid': 'Confirmed' and 'needs_human_review': 'Needs human review'. No entry for 'dismissed_false_positive'. Since unique_findings at line 1312 includes dismissed findings and they're iterated in the summary, the raw string is used as the label.

Suggested fix:

Add 'dismissed_false_positive': 'Disputed' to the status_label dict:
status_label = {
    "confirmed_valid": "Confirmed",
    "needs_human_review": "Needs human review",
    "dismissed_false_positive": "Disputed",
}.get(f.validation_status, f.validation_status)

🔵 [29f0ca912fde] [LOW] [UNRESOLVED] Unused i18n key 'prReview.disputedExplanation' added but never referenced

📁 apps/frontend/src/shared/i18n/locales/en/common.json:407

The translation key prReview.disputedExplanation ("The AI validator disagrees with this finding") was added to both en/common.json and fr/common.json but is never used in any renderer component. Searched with Grep('disputedExplanation', 'apps/frontend/src/renderer') — zero matches. The FindingItem component renders finding.validationExplanation directly (line 95) rather than using this generic translation key. This is dead i18n code that will confuse future developers who see it in the locale files.

Resolution note: Grep for 'disputedExplanation' across all .tsx/.ts/.jsx/.js files under apps/frontend/src/ returns zero matches. The key exists only in en/common.json:407 and fr/common.json:407. FindingItem.tsx uses finding.validationExplanation directly (line 96) instead of this i18n key.

Suggested fix:

Remove the `disputedExplanation` key from both en/common.json and fr/common.json since it is unused. If a static label is needed alongside the dynamic explanation, reference it via `t('prReview.disputedExplanation')` in FindingItem.tsx.

🟡 [FINDING-A] [MEDIUM] Raw 'dismissed_false_positive' string displayed in GitHub PR summary comments

📁 apps/backend/runners/github/services/parallel_orchestrator_reviewer.py:2205

_generate_summary() receives unique_findings (which includes dismissed findings via line 1312) and iterates over ALL findings. The status_label mapping at lines 2205-2208 only covers 'confirmed_valid' and 'needs_human_review'. For dismissed findings, the .get() fallback renders the raw string 'dismissed_false_positive' as 'Validation: dismissed_false_positive' in GitHub comments. Fix: add 'dismissed_false_positive': 'Disputed by validator' to the status_label dict.

Suggested fix:

Add 'dismissed_false_positive': 'Disputed by validator' to the status_label dictionary at line 2205:
status_label = {
    'confirmed_valid': 'Confirmed',
    'needs_human_review': 'Needs human review',
    'dismissed_false_positive': 'Disputed by validator',
}.get(f.validation_status, f.validation_status)

🟡 [FINDING-C] [MEDIUM] selectAll and selectImportant silently drop user-selected disputed findings

📁 apps/frontend/src/renderer/components/github-prs/hooks/useFindingSelection.ts:34

selectAll (line 35) and selectImportant (line 46) call onSelectionChange(new Set(...)) with only active finding IDs, fully replacing the selection set. Since the hook receives only activeFindings (ReviewFindings.tsx line 117), any previously-selected disputed findings are silently dropped. Fix: merge new active selections with existing disputed selections, e.g. preserve IDs in selectedIds that aren't in the active findings list.

Suggested fix:

Preserve disputed selections when using quick-select actions. In selectAll:
const selectAll = useCallback(() => {
  const newIds = new Set(findings.map(f => f.id));
  // Preserve selections not in active findings (i.e., disputed)
  for (const id of selectedIds) {
    if (!findings.some(f => f.id === id)) newIds.add(id);
  }
  onSelectionChange(newIds);
}, [findings, selectedIds, onSelectionChange]);

🔵 [FINDING-B] [LOW] Disputed findings always dimmed at 60% opacity even when user explicitly selects them

📁 apps/frontend/src/renderer/components/github-prs/components/FindingItem.tsx:51

Line 51: (posted || disputed) && 'opacity-60' applies dimming unconditionally when disputed=true, regardless of selected state. A user who selects a disputed finding sees a purple ring (line 50) but the content remains visually muted, creating contradictory UX signals. Fix: condition on selected state, e.g. (posted || (disputed && !selected)) && 'opacity-60'.

Suggested fix:

Change line 51 to: (posted || (disputed && !selected)) && 'opacity-60'

🔵 [FINDING-D] [LOW] Unused i18n key 'prReview.disputedExplanation' defined but never referenced

📁 apps/frontend/src/shared/i18n/locales/en/common.json:407

The translation key prReview.disputedExplanation exists in both en/common.json and fr/common.json but is never referenced in any component. FindingItem.tsx uses finding.validationExplanation directly (line 96). Either remove the unused key or use it as a fallback.

Suggested fix:

Remove the key from both locale files, or use as fallback in FindingItem.tsx:
{disputed && (
  <p className="text-xs text-purple-500/80 italic break-words">
    {finding.validationExplanation || t('prReview.disputedExplanation')}
  </p>
)}

🔵 [FINDING-E] [LOW] PRReviewFinding interface in IPC handler missing 4 new validation fields

📁 apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts:260

The local PRReviewFinding interface at lines 260-271 lacks validationStatus, validationExplanation, sourceAgents, and crossValidated, even though the mapping code at lines 1344-1347 adds them. The canonical type in preload/github-api.ts has these fields. Type inconsistency that may confuse maintainers.

Suggested fix:

Add the 4 missing optional fields to the local interface, or import the canonical type from preload/github-api.ts.

🔵 [FINDING-F] [LOW] Missing aria-expanded on disputed section toggle (consistent with existing pattern)

📁 apps/frontend/src/renderer/components/github-prs/components/ReviewFindings.tsx:253

The disputed section toggle button at line 253 lacks aria-expanded={disputedExpanded}. Note: SeverityGroupHeader also lacks aria-expanded, so this is a pre-existing pattern not a regression. Flagged by CodeRabbit.

Suggested fix:

Add aria-expanded={disputedExpanded} to the button element at line 253.

🔵 [FINDING-G] [LOW] Variable unique_findings reassigned with different semantics (cosmetic)

📁 apps/backend/runners/github/services/parallel_orchestrator_reviewer.py:1312

unique_findings is first assigned at line 1223 (deduplication result) then reassigned at line 1312 (validated findings including dismissed). The semantics change from 'deduplicated' to 'all validated findings'. Consider renaming to 'all_review_findings' or 'final_findings' for clarity.

Suggested fix:

Rename the line 1312 assignment to 'all_review_findings' or 'final_findings' and update downstream references at lines 1313-1315, 1338, 1383.

🔵 [CMT-001] [LOW] [FROM COMMENTS] Raw 'dismissed_false_positive' string in summary (CodeRabbit)

📁 apps/backend/runners/github/services/parallel_orchestrator_reviewer.py:2205

CodeRabbit flagged that the status_label mapping does not handle 'dismissed_false_positive', causing the raw string to appear in GitHub PR comments. This overlaps with FINDING-A and is confirmed valid.

Suggested fix:

Add 'dismissed_false_positive': 'Disputed by validator' to the status_label dict.

🔵 [CMT-002] [LOW] [FROM COMMENTS] Missing aria-expanded on disputed toggle (CodeRabbit)

📁 apps/frontend/src/renderer/components/github-prs/components/ReviewFindings.tsx:253

CodeRabbit suggested adding aria-expanded={disputedExpanded} for accessibility. Confirmed valid but consistent with existing codebase pattern (SeverityGroupHeader also lacks it). Overlaps with FINDING-F.

Suggested fix:

Add aria-expanded={disputedExpanded} to the button.

This review was generated by Auto Claude.

AndyMik90 and others added 2 commits February 16, 2026 22:13
…ling

Fix 2 MEDIUM blocking issues: add 'dismissed_false_positive' label to
summary status_label dict (preventing raw string in GitHub comments),
and preserve disputed finding selections in selectAll/selectImportant.

Also fix 5 LOW issues: conditional opacity for selected disputed findings,
remove unused i18n key, add missing validation fields to IPC interface,
add aria-expanded to disputed toggle, rename variable for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@apps/backend/runners/github/services/parallel_orchestrator_reviewer.py`:
- Line 1296: Extract the magic string "dismissed_false_positive" into a
module-level constant (e.g., VALIDATION_STATUS_DISMISSED =
"dismissed_false_positive") and replace all direct comparisons of
f.validation_status == "dismissed_false_positive" (and similar string literals
used elsewhere in this file) with that constant; update any related status
checks in this module (occurrences near f.validation_status, and other places
where the same literal appears) so they reference VALIDATION_STATUS_DISMISSED to
avoid duplication and typos, and consider adding complementary constants like
VALIDATION_STATUS_CONFIRMED and VALIDATION_STATUS_NEEDS_HUMAN if similar
literals are present.

In
`@apps/frontend/src/renderer/components/github-prs/hooks/useFindingSelection.ts`:
- Around line 33-57: The selection helpers (selectAll and selectImportant)
repeatedly call findings.some which makes membership checks O(n^2); instead
build a Set of active finding IDs once and use set.has for O(1) checks: in
selectAll create const activeIds = new Set(findings.map(f => f.id)) and then for
each id of selectedIds use if (!activeIds.has(id)) activeIds.add(id); in
selectImportant similarly build an activeIds set (or reuse the same logic) and
use activeIds.has when preserving disputed selections before calling
onSelectionChange(importantIds); leave selectNone as-is and keep the same
dependencies.

active_findings = []
dismissed_findings = []
for f in validated_findings:
if f.validation_status == "dismissed_false_positive":
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting "dismissed_false_positive" to a constant.

The string "dismissed_false_positive" appears in 5 locations across this file (lines 1296, 1976, 2174, 2210, 2240). A single constant (e.g., VALIDATION_DISMISSED = "dismissed_false_positive") would eliminate risk of typo-induced bugs and make future status changes easier.

Suggested approach
# At module level or in models.py alongside PRReviewFinding
VALIDATION_STATUS_DISMISSED = "dismissed_false_positive"
VALIDATION_STATUS_CONFIRMED = "confirmed_valid"
VALIDATION_STATUS_NEEDS_HUMAN = "needs_human_review"

Then replace all bare string comparisons with the constants.

Also applies to: 2174-2174, 2240-2240

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/runners/github/services/parallel_orchestrator_reviewer.py` at
line 1296, Extract the magic string "dismissed_false_positive" into a
module-level constant (e.g., VALIDATION_STATUS_DISMISSED =
"dismissed_false_positive") and replace all direct comparisons of
f.validation_status == "dismissed_false_positive" (and similar string literals
used elsewhere in this file) with that constant; update any related status
checks in this module (occurrences near f.validation_status, and other places
where the same literal appears) so they reference VALIDATION_STATUS_DISMISSED to
avoid duplication and typos, and consider adding complementary constants like
VALIDATION_STATUS_CONFIRMED and VALIDATION_STATUS_NEEDS_HUMAN if similar
literals are present.

Comment on lines +33 to +57
// Select all findings (preserving any disputed selections not in active findings)
const selectAll = useCallback(() => {
onSelectionChange(new Set(findings.map(f => f.id)));
}, [findings, onSelectionChange]);
const activeIds = new Set(findings.map(f => f.id));
// Preserve selections for disputed findings (IDs not in active findings list)
for (const id of selectedIds) {
if (!findings.some(f => f.id === id)) activeIds.add(id);
}
onSelectionChange(activeIds);
}, [findings, selectedIds, onSelectionChange]);

// Clear all selections
const selectNone = useCallback(() => {
onSelectionChange(new Set());
}, [onSelectionChange]);

// Select only critical and high severity findings
// Select only critical and high severity findings (preserving disputed selections)
const selectImportant = useCallback(() => {
const important = [...groupedFindings.critical, ...groupedFindings.high];
onSelectionChange(new Set(important.map(f => f.id)));
}, [groupedFindings, onSelectionChange]);
const importantIds = new Set(important.map(f => f.id));
// Preserve selections for disputed findings (IDs not in active findings list)
for (const id of selectedIds) {
if (!findings.some(f => f.id === id)) importantIds.add(id);
}
onSelectionChange(importantIds);
}, [groupedFindings, findings, selectedIds, onSelectionChange]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Avoid repeated findings.some scans in selection helpers.

You already build ID sets; reuse them for O(1) membership checks to reduce nested scans.

♻️ Suggested refactor
   const selectAll = useCallback(() => {
     const activeIds = new Set(findings.map(f => f.id));
     // Preserve selections for disputed findings (IDs not in active findings list)
     for (const id of selectedIds) {
-      if (!findings.some(f => f.id === id)) activeIds.add(id);
+      if (!activeIds.has(id)) activeIds.add(id);
     }
     onSelectionChange(activeIds);
   }, [findings, selectedIds, onSelectionChange]);
@@
   const selectImportant = useCallback(() => {
     const important = [...groupedFindings.critical, ...groupedFindings.high];
     const importantIds = new Set(important.map(f => f.id));
+    const activeIds = new Set(findings.map(f => f.id));
     // Preserve selections for disputed findings (IDs not in active findings list)
     for (const id of selectedIds) {
-      if (!findings.some(f => f.id === id)) importantIds.add(id);
+      if (!activeIds.has(id)) importantIds.add(id);
     }
     onSelectionChange(importantIds);
   }, [groupedFindings, findings, selectedIds, onSelectionChange]);
📝 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.

Suggested change
// Select all findings (preserving any disputed selections not in active findings)
const selectAll = useCallback(() => {
onSelectionChange(new Set(findings.map(f => f.id)));
}, [findings, onSelectionChange]);
const activeIds = new Set(findings.map(f => f.id));
// Preserve selections for disputed findings (IDs not in active findings list)
for (const id of selectedIds) {
if (!findings.some(f => f.id === id)) activeIds.add(id);
}
onSelectionChange(activeIds);
}, [findings, selectedIds, onSelectionChange]);
// Clear all selections
const selectNone = useCallback(() => {
onSelectionChange(new Set());
}, [onSelectionChange]);
// Select only critical and high severity findings
// Select only critical and high severity findings (preserving disputed selections)
const selectImportant = useCallback(() => {
const important = [...groupedFindings.critical, ...groupedFindings.high];
onSelectionChange(new Set(important.map(f => f.id)));
}, [groupedFindings, onSelectionChange]);
const importantIds = new Set(important.map(f => f.id));
// Preserve selections for disputed findings (IDs not in active findings list)
for (const id of selectedIds) {
if (!findings.some(f => f.id === id)) importantIds.add(id);
}
onSelectionChange(importantIds);
}, [groupedFindings, findings, selectedIds, onSelectionChange]);
// Select all findings (preserving any disputed selections not in active findings)
const selectAll = useCallback(() => {
const activeIds = new Set(findings.map(f => f.id));
// Preserve selections for disputed findings (IDs not in active findings list)
for (const id of selectedIds) {
if (!activeIds.has(id)) activeIds.add(id);
}
onSelectionChange(activeIds);
}, [findings, selectedIds, onSelectionChange]);
// Clear all selections
const selectNone = useCallback(() => {
onSelectionChange(new Set());
}, [onSelectionChange]);
// Select only critical and high severity findings (preserving disputed selections)
const selectImportant = useCallback(() => {
const important = [...groupedFindings.critical, ...groupedFindings.high];
const importantIds = new Set(important.map(f => f.id));
const activeIds = new Set(findings.map(f => f.id));
// Preserve selections for disputed findings (IDs not in active findings list)
for (const id of selectedIds) {
if (!activeIds.has(id)) importantIds.add(id);
}
onSelectionChange(importantIds);
}, [groupedFindings, findings, selectedIds, onSelectionChange]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/frontend/src/renderer/components/github-prs/hooks/useFindingSelection.ts`
around lines 33 - 57, The selection helpers (selectAll and selectImportant)
repeatedly call findings.some which makes membership checks O(n^2); instead
build a Set of active finding IDs once and use set.has for O(1) checks: in
selectAll create const activeIds = new Set(findings.map(f => f.id)) and then for
each id of selectedIds use if (!activeIds.has(id)) activeIds.add(id); in
selectImportant similarly build an activeIds set (or reuse the same logic) and
use activeIds.has when preserving disputed selections before calling
onSelectionChange(importantIds); leave selectNone as-is and keep the same
dependencies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/fullstack This is Frontend + Backend bug Something isn't working size/M Medium (100-499 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant