Skip to content

Add folder filters and update UI filter actions#146

Merged
isasmendiagus merged 21 commits intomainfrom
feat/SP-2883/add-folder-Actions
Jan 26, 2026
Merged

Add folder filters and update UI filter actions#146
isasmendiagus merged 21 commits intomainfrom
feat/SP-2883/add-folder-Actions

Conversation

@isasmendiagus
Copy link
Collaborator

@isasmendiagus isasmendiagus commented Jan 14, 2026

Changes:

  • Added support for applying filters to specific folders.
  • Replaced the dropdown menu with a modal for executing filter actions, improving usability and user experience.

Summary by CodeRabbit

  • New Features

    • Apply filters to entire folders via modal workflows (replacing dropdowns).
    • New skip targets: file, folder, extension.
    • Searchable component selector with add-online-search and replace flows.
    • Path breadcrumb, tabbed UI primitives, and a styled menubar.
    • Editor-focus aware shortcuts and consolidated keyboard shortcut actions.
    • Folder-scoped filtering and improved "next result" navigation with skip predicate.
  • Tests

    • Comprehensive unit tests for filter matching, priority, and folder behaviors.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

Warning

Rate limit exceeded

@isasmendiagus has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 17 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Adds folder-scoped filters and priority-based backend matching; replaces dropdown actions with a menubar and modal-driven Include/Dismiss/Replace/Skip flows; introduces PathBreadcrumb, ComponentSelector, ReplaceComponentSection, tabs/menubar UI primitives, editor-focus-aware shortcuts, and tests for folder/priority behavior.

Changes

Cohort / File(s) Summary
Backend Filter Logic
backend/entities/result.go, backend/entities/scanoss_settings.go, backend/entities/scanoss_settings_test.go
Added ByFolder filter type; implemented Priority(), Compare(), MatchesPath(), MatchesAnyPurl(), AppliesTo(); refactored IsResultInList to pick highest-priority filter; extensive unit tests for folder and priority behavior.
Backend Entities
backend/entities/component.go
Made Purl in ComponentFilterDTO optional for JSON/validation (json:"purl,omitempty").
Frontend Modals & Breadcrumb
frontend/src/components/FilterActionModal.tsx, frontend/src/components/SkipActionModal.tsx, frontend/src/components/PathBreadcrumb.tsx
Added modal UIs (FilterActionModal, SkipActionModal) with Path/Component tabs and PathBreadcrumb for segment selection and folder/file/extension targeting.
Frontend Replace Helpers
frontend/src/components/ReplaceComponentSection.tsx, frontend/src/components/ComponentSelector.tsx
Added ReplaceComponentSection (declared components, license matching, dialogs) and ComponentSelector (searchable popover, online search) to support replacements.
Frontend Integration & Menubar/Tabs
frontend/src/components/FilterComponentActions.tsx, frontend/src/components/ui/menubar.tsx, frontend/src/components/ui/tabs.tsx
Replaced dropdown flow with Radix Menubar/Tabs wrappers; modal-driven action handling; added modal state/initial-selection logic and keyboard shortcut bindings.
Removed Frontend Components
frontend/src/components/FilterActionButton.tsx, frontend/src/components/ReplaceComponentDialog.tsx
Removed legacy dropdown-based FilterActionButton and monolithic ReplaceComponentDialog; functionality migrated into new modals and sections.
Frontend Store Changes
frontend/src/modules/components/stores/useComponentFilterStore.ts
OnFilterComponentArgs.filterBy now includes by_folder; added folderPath? and purl?; implemented folder-path handling that aggregates purls and emits per-purl folder-scoped filter DTOs.
Keyboard Shortcuts & Models
backend/entities/keyboard.go, frontend/src/lib/shortcuts.ts, frontend/wailsjs/go/models.ts
Consolidated many per-variant actions into unified actions (Include*/Dismiss*/Replace*/Skip*); updated keyboard mappings and generated Action enum; made generated ComponentFilterDTO.purl optional.
App Menu
app.go
Replaced dynamic Actions menu construction with a static Actions submenu hierarchy (Include, Dismiss, Replace, Skip) wired to new action events.
Utilities & Packaging
frontend/src/lib/utils.ts, frontend/package.json, frontend/package.json.md5, CHANGELOG.md
Added getPathSegments utility; added @radix-ui/react-menubar and @radix-ui/react-tabs deps; updated checksum and changelog notes.
Editor Focus & Shortcuts
frontend/src/stores/useEditorFocusStore.ts, frontend/src/components/CodeViewer.tsx, frontend/src/hooks/useKeyboardShortcut.tsx
New editor-focus store; CodeViewer registers editor focus/blur to the store; keyboard shortcut hook honors editor focus to enable form-tag shortcuts.
Results Navigation
frontend/src/modules/results/stores/useResultsStore.ts, frontend/src/components/Sidebar.tsx
moveToNextResult now accepts optional skip predicate; Sidebar keyboard handler adjusted to inline wrapper.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend as FilterActionModal
    participant Store as ComponentFilterStore
    participant Backend as FilterComponents API

    User->>Frontend: Open modal & select folder via PathBreadcrumb
    User->>Frontend: Confirm action (Include/Dismiss/Replace/Skip)
    Frontend->>Frontend: Build payload (action, filterBy="by_folder", folderPath, purl?, comment, license, replaceWith)
    Frontend->>Store: onFilterComponent(payload)
    Store->>Store: Detect filterBy == "by_folder" and aggregate purls
    Store->>Backend: FilterComponents(list of per-purl DTOs)
    Backend->>Backend: Match using MatchesPath/MatchesAnyPurl & select best by Priority/Compare
    Backend-->>Store: Success
    Store->>Store: Advance to next result / update undo state
    Store-->>Frontend: Acknowledge
    Frontend-->>User: Close modal / show toast
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • eeisegn

Poem

🐰 I hopped through folders, paths aglow,
Modals bloom where menus used to go,
Breadcrumbs point the way, small segments bright,
Priority hums and chooses what’s right,
Thump-thump — filters set; the rabbit’s light!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main changes: adding folder-level filter support and replacing dropdown menus with modals for filter actions, which are the primary objectives.

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


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.

@github-actions
Copy link

🔍 SCANOSS Code Similarity Detected

📄 1 snippet matches found

🔗 View detailed findings on commit 3927a66

Files with similarities:

  • frontend/src/components/ui/tabs.tsx

💡 Click the commit link above to see detailed annotations for each match.

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 4
  • Undeclared components: 1
  • Declared components: 3
  • Detected files: 185
  • Detected files undeclared: 1
  • Detected files declared: 184
  • Licenses detected: 2
  • Licenses detected with copyleft: 1
  • Policies: ❌ 1 fail (1 total)

View more details on SCANOSS Action Summary

@isasmendiagus isasmendiagus force-pushed the feat/SP-2883/add-folder-Actions branch from 3927a66 to 189c9cf Compare January 14, 2026 17:25
@github-actions
Copy link

🔍 SCANOSS Code Similarity Detected

📄 1 snippet matches found

🔗 View detailed findings on commit 189c9cf

Files with similarities:

  • frontend/src/components/ui/tabs.tsx

💡 Click the commit link above to see detailed annotations for each match.

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 4
  • Undeclared components: 1
  • Declared components: 3
  • Detected files: 185
  • Detected files undeclared: 1
  • Detected files declared: 184
  • Licenses detected: 2
  • Licenses detected with copyleft: 1
  • Policies: ❌ 1 fail (1 total)

View more details on SCANOSS Action Summary

Copy link

@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

🤖 Fix all issues with AI agents
In `@frontend/src/components/FilterComponentActions.tsx`:
- Around line 55-87: The shortcut lookups use camelCase object properties (e.g.,
KEYBOARD_SHORTCUTS.includeFileWithComments) but KEYBOARD_SHORTCUTS is keyed by
entities.Action enum values; update all shortcut props passed to
FilterActionButton to index KEYBOARD_SHORTCUTS with the appropriate
entities.Action enum keys (for Include: entities.Action.IncludeFileWithComments,
entities.Action.IncludeFileWithoutComments,
entities.Action.IncludeComponentWithComments,
entities.Action.IncludeComponentWithoutComments; for Remove/Dismiss: the
corresponding Dismiss* enum keys; for Replace: the corresponding Replace* enum
keys) so the .keys values come from the enum-keyed entries rather than undefined
camelCase properties.

In `@frontend/src/components/ReplaceComponentSection.tsx`:
- Around line 63-67: The current useEffect replaces the entire
declaredComponents state whenever componentsData changes, which discards
locally-added items; change the effect in ReplaceComponentSection so that when
componentsData is present it merges incoming components with existing
declaredComponents (using unique ids or keys to avoid duplicates) instead of
calling setDeclaredComponents(componentsData) outright, or alternatively update
the add flow in handleComponentSelected to use react-query’s
queryClient.setQueryData to optimistically insert the new component into the
componentsData cache so refetches won’t wipe locally-added items.
🧹 Nitpick comments (5)
backend/entities/result.go (1)

45-48: Documentation note acknowledged.

The TODO highlights a valid concern about the pointer-to-slice pattern adding complexity. Converting *[]string to []string with omitempty would simplify nil checks while preserving JSON behavior.

Would you like me to open an issue to track this refactor, or generate a diff for the change?

frontend/src/components/ComponentSelector.tsx (1)

3-3: Minor: Copyright year inconsistency.

This file uses 2024 while other new files in this PR (e.g., PathBreadcrumb.tsx, ReplaceComponentSection.tsx) use 2026.

frontend/src/components/ReplaceComponentSection.tsx (1)

79-96: Consider adding error feedback for license fetch failures.

The catch block silently clears matchedLicenses. While the fallback is reasonable, users might benefit from knowing why no licenses appear for a component lookup failure.

💡 Optional: Add user feedback
    } catch {
      setMatchedLicenses([]);
+     toast({
+       title: 'Notice',
+       description: 'Could not fetch licenses for this component',
+       variant: 'default',
+     });
    }
backend/entities/scanoss_settings_test.go (1)

319-392: Consider adding a test case for path-only filter without trailing slash.

The GetResultFilterType tests don't cover the case where a filter has only a Path without a trailing slash and no Purl. This maps to ByPurl in the current implementation, which may be intentional but should be explicitly tested to document the expected behavior.

{
    name:     "path only without trailing slash (no purl)",
    filter:   ComponentFilter{Path: "src/file.js"},
    expected: ByPurl, // or ByFile if the implementation is updated
},
frontend/src/components/FilterActionButton.tsx (1)

61-69: handleOpenModal should be memoized with useCallback.

handleOpenModal is included in the useMemo dependency array for eventHandlerMap, but it's recreated on every render. This causes eventHandlerMap to be recreated unnecessarily, potentially triggering re-registrations in useMenuEvents.

Proposed fix
+import { useCallback, useMemo, useState } from 'react';
-import { useMemo, useState } from 'react';

...

-  const handleOpenModal = () => {
-    if (!isCompletedResult) {
-      setModalOpen(true);
-    }
-  };
+  const handleOpenModal = useCallback(() => {
+    if (!isCompletedResult) {
+      setModalOpen(true);
+    }
+  }, [isCompletedResult]);

-  const handleConfirm = async (args: OnFilterComponentArgs) => {
-    await onAdd(args);
-  };
+  const handleConfirm = useCallback(async (args: OnFilterComponentArgs) => {
+    await onAdd(args);
+  }, [onAdd]);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3927a66 and 189c9cf.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (16)
  • CHANGELOG.md
  • backend/entities/component.go
  • backend/entities/result.go
  • backend/entities/scanoss_settings.go
  • backend/entities/scanoss_settings_test.go
  • frontend/package.json
  • frontend/src/components/ComponentSelector.tsx
  • frontend/src/components/FilterActionButton.tsx
  • frontend/src/components/FilterActionModal.tsx
  • frontend/src/components/FilterComponentActions.tsx
  • frontend/src/components/PathBreadcrumb.tsx
  • frontend/src/components/ReplaceComponentDialog.tsx
  • frontend/src/components/ReplaceComponentSection.tsx
  • frontend/src/components/ui/tabs.tsx
  • frontend/src/lib/utils.ts
  • frontend/src/modules/components/stores/useComponentFilterStore.ts
💤 Files with no reviewable changes (1)
  • frontend/src/components/ReplaceComponentDialog.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • CHANGELOG.md
  • frontend/src/lib/utils.ts
  • frontend/package.json
  • frontend/src/components/FilterActionModal.tsx
  • backend/entities/component.go
🧰 Additional context used
🧬 Code graph analysis (7)
frontend/src/components/FilterComponentActions.tsx (3)
frontend/src/components/FilterActionButton.tsx (1)
  • FilterActionButton (47-136)
backend/entities/component.go (2)
  • FilterAction (86-86)
  • Replace (91-91)
frontend/src/lib/shortcuts.ts (1)
  • KEYBOARD_SHORTCUTS (32-164)
frontend/src/components/ComponentSelector.tsx (8)
frontend/src/hooks/useEnvironment.tsx (1)
  • useEnvironment (37-66)
frontend/src/hooks/useKeyboardShortcut.tsx (1)
  • useKeyboardShortcut (31-52)
frontend/src/lib/shortcuts.ts (2)
  • KEYBOARD_SHORTCUTS (32-164)
  • getShortcutDisplay (166-180)
frontend/src/components/ui/popover.tsx (3)
  • Popover (31-31)
  • PopoverTrigger (31-31)
  • PopoverContent (31-31)
frontend/src/components/ui/button.tsx (1)
  • Button (57-57)
frontend/src/lib/utils.ts (1)
  • cn (27-29)
frontend/src/components/ShortcutBadge.tsx (1)
  • ShortcutBadge (1-3)
frontend/src/components/ui/scroll-area.tsx (1)
  • ScrollArea (46-46)
frontend/src/components/ReplaceComponentSection.tsx (8)
frontend/src/components/ui/use-toast.ts (2)
  • useToast (192-192)
  • toast (192-192)
frontend/wailsjs/go/service/ComponentServiceImpl.js (1)
  • GetDeclaredComponents (25-27)
frontend/wailsjs/go/service/LicenseServiceImpl.js (1)
  • GetLicensesByPurl (9-11)
frontend/src/components/ui/label.tsx (1)
  • Label (24-24)
frontend/src/components/ComponentSelector.tsx (1)
  • ComponentSelector (47-132)
frontend/src/components/SelectLicenseList.tsx (1)
  • SelectLicenseList (53-113)
frontend/src/components/NewComponentDialog.tsx (1)
  • NewComponentDialog (46-107)
frontend/src/components/OnlineComponentSearchDialog.tsx (1)
  • OnlineComponentSearchDialog (91-234)
frontend/src/components/PathBreadcrumb.tsx (1)
frontend/src/lib/utils.ts (2)
  • getPathSegments (31-33)
  • cn (27-29)
frontend/src/components/ui/tabs.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (27-29)
backend/entities/scanoss_settings_test.go (4)
backend/entities/scanoss_settings.go (3)
  • ComponentFilter (79-86)
  • SettingsFile (43-46)
  • Bom (73-77)
backend/entities/result.go (5)
  • Result (42-51)
  • FilterType (127-127)
  • ByFolder (132-132)
  • ByFile (130-130)
  • ByPurl (131-131)
backend/entities/component.go (3)
  • Include (89-89)
  • Remove (90-90)
  • Replace (91-91)
backend/entities/keyboard.go (1)
  • Action (31-31)
backend/entities/scanoss_settings.go (2)
frontend/wailsjs/go/models.ts (1)
  • ComponentFilter (29-50)
backend/entities/result.go (3)
  • Result (42-51)
  • ByFolder (132-132)
  • ByFile (130-130)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (17)
backend/entities/result.go (1)

130-133: LGTM!

The new ByFolder filter type follows the established pattern and integrates cleanly with the existing ByFile and ByPurl constants to support folder-scoped filtering.

frontend/src/components/PathBreadcrumb.tsx (1)

36-57: LGTM!

Clean, focused component with appropriate memoization. The use of index as key is acceptable here since segments are derived deterministically from filePath and won't reorder independently.

frontend/src/components/ui/tabs.tsx (1)

1-53: LGTM!

Standard shadcn/ui Tabs implementation following Radix UI best practices. The forwardRef pattern, displayName assignments, and cn utility usage are all consistent with the existing UI component patterns.

frontend/src/components/ComponentSelector.tsx (1)

47-131: LGTM!

Well-structured component with good UX touches: keyboard shortcut integration with visual badge, conditional online search option, and clean separation between local components and actions.

frontend/src/components/ReplaceComponentSection.tsx (1)

98-109: LGTM!

The component selection flow is well-implemented: properly handles new components, resets license state, and uses a composite key pattern to force SelectLicenseList re-renders. The dialog coordination is clean.

Also applies to: 117-154

backend/entities/scanoss_settings.go (4)

94-113: LGTM! Priority scoring logic is clear and well-documented.

The priority hierarchy (path+purl=4 > purl=2 > path=1 > empty=0) correctly prioritizes more specific rules. The implementation aligns with the documented behavior.


115-143: LGTM! Comparison logic correctly implements priority-based sorting.

The two-tier comparison (priority score first, then path length) provides sensible tie-breaking that naturally favors more specific paths.


145-178: LGTM! Path and purl matching logic handles constraints correctly.

The folder detection via trailing slash and the wildcard behavior for empty constraints are implemented correctly. The nil-safe handling in AppliesTo properly converts nil Purl to an empty slice.


220-236: LGTM! Priority-based selection correctly finds the best matching filter.

The loop iterates all candidates and uses Compare to select the highest-priority match rather than returning the first match. This ensures folder rules don't override more specific file+purl rules.

backend/entities/scanoss_settings_test.go (4)

34-73: LGTM! Priority tests cover all score combinations.

The table-driven tests verify all priority tiers including the distinction between folder paths and file paths at the same score level.


75-140: LGTM! AppliesTo tests comprehensively cover constraint combinations.

Good coverage of edge cases including nil Purl handling and the requirement that both path and purl must match when both are specified.


142-186: LGTM! Compare tests validate the priority ordering.

Tests correctly verify that higher-priority filters sort before lower-priority ones, and that longer paths win at the same score.


188-317: LGTM! IsResultInList tests verify folder matching and priority selection.

Good coverage of folder prefix matching, nested paths, non-matching paths, and the priority system ensuring the highest-priority match is selected regardless of list order.

frontend/src/modules/components/stores/useComponentFilterStore.ts (2)

38-47: LGTM! Extended interface supports folder-level filtering.

The new folderPath and purl fields along with the 'by_folder' filter type provide the necessary structure for folder-based filter operations.


72-87: Ensure folderPath includes trailing slash for folder rule detection.

The backend's getResultFilterType identifies folder rules by checking if Path ends with /. If the modal/UI doesn't guarantee a trailing slash on folderPath, folder filters may be misclassified as purl-only rules.

Consider normalizing the path:

       if (filterBy === 'by_folder' && folderPath) {
+        // Ensure folder path ends with / for backend folder rule detection
+        const normalizedPath = folderPath.endsWith('/') ? folderPath : `${folderPath}/`;
         const dto: entities.ComponentFilterDTO[] = [{
           action,
           comment,
           license,
           purl: purl ?? '',
-          path: folderPath,
+          path: normalizedPath,
           ...(replaceWith && { replace_with: replaceWith }),
         }];

Please verify how folderPath is constructed in the UI to confirm whether normalization is needed.

frontend/src/components/FilterActionButton.tsx (2)

71-83: Keyboard shortcuts consistently open the modal regardless of original intent.

Previously, shortcuts like shortcutKeysByFileWithoutComments vs shortcutKeysByFileWithComments triggered different behaviors. Now all four shortcuts open the same modal. This is intentional per the PR objectives (modal-driven workflow), but users relying on direct action shortcuts will experience a workflow change.


108-135: LGTM! Button and modal integration is clean.

The conditional rendering of FilterActionModal only when selectedResult exists prevents errors, and the modal receives all necessary props for the new workflow.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 259 to 270
func getResultFilterType(cf ComponentFilter) FilterType {
// Folder rule: path ends with /
if strings.HasSuffix(cf.Path, "/") {
return ByFolder
}
// File rule: has both path and purl
if cf.Path != "" && cf.Purl != "" {
return ByFile
}
// Component rule: purl only
return ByPurl
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Path-only filter without trailing slash returns ByPurl unexpectedly.

When a filter has only Path set (no trailing slash, no Purl), this function returns ByPurl, which seems semantically incorrect. Consider whether this represents a valid use case or if it should return a different type.

For example, ComponentFilter{Path: "src/file.js"} would return ByPurl even though it has no purl constraint.

Potential fix if path-only without trailing slash should be treated as file rule
 func getResultFilterType(cf ComponentFilter) FilterType {
 	// Folder rule: path ends with /
 	if strings.HasSuffix(cf.Path, "/") {
 		return ByFolder
 	}
 	// File rule: has both path and purl
 	if cf.Path != "" && cf.Purl != "" {
 		return ByFile
 	}
+	// File rule: has path only (no trailing slash)
+	if cf.Path != "" {
+		return ByFile
+	}
 	// Component rule: purl only
 	return ByPurl
 }
📝 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
func getResultFilterType(cf ComponentFilter) FilterType {
// Folder rule: path ends with /
if strings.HasSuffix(cf.Path, "/") {
return ByFolder
}
// File rule: has both path and purl
if cf.Path != "" && cf.Purl != "" {
return ByFile
}
// Component rule: purl only
return ByPurl
}
func getResultFilterType(cf ComponentFilter) FilterType {
// Folder rule: path ends with /
if strings.HasSuffix(cf.Path, "/") {
return ByFolder
}
// File rule: has both path and purl
if cf.Path != "" && cf.Purl != "" {
return ByFile
}
// File rule: has path only (no trailing slash)
if cf.Path != "" {
return ByFile
}
// Component rule: purl only
return ByPurl
}

Comment on lines +63 to +67
useEffect(() => {
if (componentsData) {
setDeclaredComponents(componentsData);
}
}, [componentsData]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential data loss: Query refetch overwrites locally-added components.

When componentsData updates (e.g., on refetch, window focus), setDeclaredComponents(componentsData) replaces the entire list, losing any components added locally via handleComponentSelected that haven't been persisted.

Consider merging instead of replacing:

🔧 Proposed fix
  useEffect(() => {
    if (componentsData) {
-     setDeclaredComponents(componentsData);
+     setDeclaredComponents((prev) => {
+       const existingPurls = new Set(componentsData.map((c) => c.purl));
+       const localOnly = prev.filter((c) => !existingPurls.has(c.purl));
+       return [...componentsData, ...localOnly];
+     });
    }
  }, [componentsData]);

Alternatively, consider using react-query's queryClient.setQueryData to optimistically update the cache when adding new components, keeping the query as the single source of truth.

📝 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
useEffect(() => {
if (componentsData) {
setDeclaredComponents(componentsData);
}
}, [componentsData]);
useEffect(() => {
if (componentsData) {
setDeclaredComponents((prev) => {
const existingPurls = new Set(componentsData.map((c) => c.purl));
const localOnly = prev.filter((c) => !existingPurls.has(c.purl));
return [...componentsData, ...localOnly];
});
}
}, [componentsData]);
🤖 Prompt for AI Agents
In `@frontend/src/components/ReplaceComponentSection.tsx` around lines 63 - 67,
The current useEffect replaces the entire declaredComponents state whenever
componentsData changes, which discards locally-added items; change the effect in
ReplaceComponentSection so that when componentsData is present it merges
incoming components with existing declaredComponents (using unique ids or keys
to avoid duplicates) instead of calling setDeclaredComponents(componentsData)
outright, or alternatively update the add flow in handleComponentSelected to use
react-query’s queryClient.setQueryData to optimistically insert the new
component into the componentsData cache so refetches won’t wipe locally-added
items.

@github-actions
Copy link

🔍 SCANOSS Code Similarity Detected

📄 1 snippet matches found

🔗 View detailed findings on commit 7f9f3d0

Files with similarities:

  • frontend/src/components/ui/tabs.tsx

💡 Click the commit link above to see detailed annotations for each match.

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 4
  • Undeclared components: 1
  • Declared components: 3
  • Detected files: 185
  • Detected files undeclared: 1
  • Detected files declared: 184
  • Licenses detected: 2
  • Licenses detected with copyleft: 1
  • Policies: ❌ 1 fail (1 total)

View more details on SCANOSS Action Summary

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 3
  • Undeclared components: 0
  • Declared components: 3
  • Detected files: 188
  • Detected files undeclared: 0
  • Detected files declared: 188
  • Licenses detected: 2
  • Licenses detected with copyleft: 1
  • Policies: ✅ 1 pass (1 total)

View more details on SCANOSS Action Summary

Copy link

@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
In `@frontend/src/components/SkipActionButton.tsx`:
- Around line 46-47: handleOpenModal currently sets modalOpen(true)
unconditionally which allows menu events (from useMenuEvents) to open the modal
when no selectedResult exists; update handleOpenModal to first check
selectedResult and return early if falsy, and add a useEffect that watches
selectedResult and sets modalOpen(false) when selectedResult becomes
null/undefined to ensure stale modal state is cleared; reference the
handleOpenModal function, modalOpen state, selectedResult variable, and the
useMenuEvents integration to locate the changes.

In `@frontend/src/components/SkipActionModal.tsx`:
- Around line 44-54: getFileExtension currently uses filename.indexOf('.') which
returns the first dot and misparses names like "foo.bar.js"; change it to use
filename.lastIndexOf('.') and keep the existing guard for dot at position 0 and
-1 so it returns null for hidden files or no-extension names; update
getFileExtension to compute dotIndex = filename.lastIndexOf('.') and return
substring(dotIndex + 1). No changes required to buildPath other than referencing
it remains correct.
♻️ Duplicate comments (1)
frontend/src/components/FilterComponentActions.tsx (1)

60-82: Verify KEYBOARD_SHORTCUTS keys match the enum values.

These lookups use dot properties (e.g., KEYBOARD_SHORTCUTS.include). If entities.Action values are PascalCase (e.g., Include), these will be undefined and .keys will throw at render time. Please confirm the enum values and, if needed, index via the enum keys instead (e.g., KEYBOARD_SHORTCUTS[entities.Action.Include]).

You can confirm quickly with:

#!/bin/bash
# Locate the Action enum values (if defined in repo) and the shortcuts map.
rg -n "enum Action" frontend/src -g '*.ts' -C2
rg -n "export const KEYBOARD_SHORTCUTS" frontend/src/lib/shortcuts.ts -n -A80
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f9f3d0 and fdef6bf.

📒 Files selected for processing (7)
  • backend/entities/keyboard.go
  • frontend/package.json.md5
  • frontend/src/components/FilterComponentActions.tsx
  • frontend/src/components/SkipActionButton.tsx
  • frontend/src/components/SkipActionModal.tsx
  • frontend/src/lib/shortcuts.ts
  • frontend/wailsjs/go/models.ts
✅ Files skipped from review due to trivial changes (1)
  • frontend/package.json.md5
🧰 Additional context used
🧬 Code graph analysis (3)
frontend/src/components/SkipActionModal.tsx (7)
frontend/src/hooks/useEnvironment.tsx (1)
  • useEnvironment (37-66)
frontend/src/lib/utils.ts (1)
  • getPathSegments (31-33)
frontend/wailsjs/go/service/ScanossSettingsServiceImp.js (2)
  • AddStagedScanningSkipPattern (5-7)
  • CommitStagedScanningSkipPatterns (9-11)
frontend/src/hooks/useKeyboardShortcut.tsx (1)
  • useKeyboardShortcut (31-52)
frontend/src/lib/shortcuts.ts (2)
  • KEYBOARD_SHORTCUTS (32-139)
  • getShortcutDisplay (141-155)
frontend/src/components/PathBreadcrumb.tsx (1)
  • PathBreadcrumb (36-58)
frontend/src/components/ShortcutBadge.tsx (1)
  • ShortcutBadge (1-3)
frontend/src/components/FilterComponentActions.tsx (4)
frontend/src/components/FilterActionButton.tsx (1)
  • FilterActionButton (45-131)
frontend/src/lib/shortcuts.ts (1)
  • KEYBOARD_SHORTCUTS (32-139)
frontend/src/components/ui/separator.tsx (1)
  • Separator (29-29)
frontend/src/components/SkipActionButton.tsx (1)
  • SkipActionButton (41-105)
backend/entities/keyboard.go (1)
frontend/wailsjs/go/models.ts (1)
  • Accelerator (830-843)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build for Linux (webkit40)
  • GitHub Check: Build for Windows
  • GitHub Check: Build for Linux (webkit41)
  • GitHub Check: Build for MacOS
🔇 Additional comments (11)
frontend/src/components/FilterComponentActions.tsx (2)

32-35: Imports align with the new UI composition.

Nice and clean additions for the skip action and separator.


84-88: Separation of BOM vs. skip actions is clear and readable.

The vertical separator + dedicated skip button is a nice UX touch.

backend/entities/keyboard.go (2)

33-53: Action constants and binding list are consistent.

The new generic/skip actions are declared and exposed via AllShortcutActions coherently.

Also applies to: 95-101


179-242: Shortcut defaults are aligned with the new action set.

Names, descriptions, and key bindings for include/dismiss/replace/skip look consistent.

frontend/src/components/SkipActionButton.tsx (2)

48-65: Direct skip flow and toast feedback look solid.


80-103: Modal rendering gated by selection is clean and safe.

frontend/src/components/SkipActionModal.tsx (2)

116-137: Pattern staging + commit flow is clear and user-friendly.


145-193: Modal layout and warning UX look good.

frontend/wailsjs/go/models.ts (2)

12-18: Action enum updates match the consolidated action set.


153-170: Verify consumers handle optional ComponentFilterDTO.purl.

Line 155 makes purl optional; ensure downstream usage doesn’t assume a value before using/serializing it.

frontend/src/lib/shortcuts.ts (1)

78-114: Shortcut mapping reflects the new generic + skip actions.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +46 to +47
const handleOpenModal = useCallback(() => setModalOpen(true), []);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n frontend/src/components/SkipActionButton.tsx

Repository: scanoss/scanoss.cc

Length of output: 4451


Prevent stale modal-open state when no selection exists.

Line 46: handleOpenModal sets modalOpen unconditionally. The keyboard shortcut is guarded with enabled: !!selectedResult (line 72), but useMenuEvents at lines 75-78 has no such guard. If a menu event fires without a selection, modalOpen becomes true, but the modal won't render (line 96 checks selectedResult). When a result is later selected, the modal unexpectedly opens. Add a guard in handleOpenModal to return early if !selectedResult, and a useEffect to close the modal when selection clears.

🛠️ Proposed fix
-import { useCallback, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';

-const handleOpenModal = useCallback(() => setModalOpen(true), []);
+const handleOpenModal = useCallback(() => {
+  if (!selectedResult) return;
+  setModalOpen(true);
+}, [selectedResult]);
+
+useEffect(() => {
+  if (!selectedResult) setModalOpen(false);
+}, [selectedResult]);
🤖 Prompt for AI Agents
In `@frontend/src/components/SkipActionButton.tsx` around lines 46 - 47,
handleOpenModal currently sets modalOpen(true) unconditionally which allows menu
events (from useMenuEvents) to open the modal when no selectedResult exists;
update handleOpenModal to first check selectedResult and return early if falsy,
and add a useEffect that watches selectedResult and sets modalOpen(false) when
selectedResult becomes null/undefined to ensure stale modal state is cleared;
reference the handleOpenModal function, modalOpen state, selectedResult
variable, and the useMenuEvents integration to locate the changes.

Comment on lines +44 to +54
function getFileExtension(filename: string): string | null {
const dotIndex = filename.indexOf('.');
if (dotIndex === -1 || dotIndex === 0) return null;
return filename.substring(dotIndex + 1);
}

function buildPath(segments: string[], index: number): string {
const path = segments.slice(0, index + 1).join('/');
const isFile = index === segments.length - 1;
return isFile ? path : path + '/';
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use the last dot for extension parsing.

Line 45: indexOf('.') makes foo.bar.js produce bar.js. lastIndexOf('.') yields the expected js extension for skip patterns.

🛠️ Proposed fix
-  const dotIndex = filename.indexOf('.');
+  const dotIndex = filename.lastIndexOf('.');
📝 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
function getFileExtension(filename: string): string | null {
const dotIndex = filename.indexOf('.');
if (dotIndex === -1 || dotIndex === 0) return null;
return filename.substring(dotIndex + 1);
}
function buildPath(segments: string[], index: number): string {
const path = segments.slice(0, index + 1).join('/');
const isFile = index === segments.length - 1;
return isFile ? path : path + '/';
}
function getFileExtension(filename: string): string | null {
const dotIndex = filename.lastIndexOf('.');
if (dotIndex === -1 || dotIndex === 0) return null;
return filename.substring(dotIndex + 1);
}
function buildPath(segments: string[], index: number): string {
const path = segments.slice(0, index + 1).join('/');
const isFile = index === segments.length - 1;
return isFile ? path : path + '/';
}
🤖 Prompt for AI Agents
In `@frontend/src/components/SkipActionModal.tsx` around lines 44 - 54,
getFileExtension currently uses filename.indexOf('.') which returns the first
dot and misparses names like "foo.bar.js"; change it to use
filename.lastIndexOf('.') and keep the existing guard for dot at position 0 and
-1 so it returns null for hidden files or no-extension names; update
getFileExtension to compute dotIndex = filename.lastIndexOf('.') and return
substring(dotIndex + 1). No changes required to buildPath other than referencing
it remains correct.

@github-actions
Copy link

🔍 SCANOSS Code Similarity Detected

📋 8 full file matches found

🔗 View detailed findings on commit 43ba389

8 files contain code similarities

💡 Click the commit link above to see detailed annotations for each match.

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 5
  • Undeclared components: 3
  • Declared components: 2
  • Detected files: 191
  • Detected files undeclared: 8
  • Detected files declared: 183
  • Licenses detected: 3
  • Licenses detected with copyleft: 1
  • Policies: ❌ 1 fail (1 total)

View more details on SCANOSS Action Summary

Copy link

@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 (1)
frontend/wailsjs/go/models.ts (1)

27-48: Make ComponentFilter.purl optional to match ComponentFilterDTO.purl.

ComponentFilter.purl at line 29 is declared as required (purl: string), while the equivalent ComponentFilterDTO.purl at line 158 is optional (purl?: string). Since folder-level filter actions (IncludeFolder, DismissFolder, ReplaceFolder, SkipFolder) don't require a specific package URL and are actively used in the UI, ComponentFilter.purl should also be marked optional to properly support these scenarios and maintain type consistency between the two classes.

🤖 Fix all issues with AI agents
In `@frontend/src/components/FilterActionModal.tsx`:
- Around line 82-83: The initial selectedPathIndex may become -1 when filePath
is empty; update the useState initializer for selectedPathIndex to use
Math.max(0, segments.length - 1) so it never goes negative (symbols: segments
from getPathSegments, selectedPathIndex, useState). Also update the related
useEffect assignments that compute folder/file initial indices (the assignments
around setSelectedPathIndex in the useEffect) to guard with Math.max(0, ...) the
same way (symbols: setSelectedPathIndex, useEffect) so both initial state and
subsequent effect updates handle empty filePath defensively.

Comment on lines +82 to +83
const segments = useMemo(() => getPathSegments(filePath), [filePath]);
const [selectedPathIndex, setSelectedPathIndex] = useState(segments.length - 1);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine FilterActionModal.tsx
fd -t f "FilterActionModal.tsx" --exec wc -l {}

Repository: scanoss/scanoss.cc

Length of output: 113


🏁 Script executed:

# Get the full content of FilterActionModal.tsx to understand the context
fd -t f "FilterActionModal.tsx" --exec cat -n {}

Repository: scanoss/scanoss.cc

Length of output: 10889


🏁 Script executed:

# Find getPathSegments implementation to understand its behavior with empty string
rg "getPathSegments" -A 5 -B 2

Repository: scanoss/scanoss.cc

Length of output: 4514


🏁 Script executed:

# Check buildPath function to see how selectedPathIndex is used
rg "buildPath" -A 10 -B 2

Repository: scanoss/scanoss.cc

Length of output: 4045


🏁 Script executed:

# Find where FilterActionModal is used/imported
rg "FilterActionModal" -B 2 -A 5

Repository: scanoss/scanoss.cc

Length of output: 2959


🏁 Script executed:

# Check PathBreadcrumb to see how it handles empty segments
fd -t f "PathBreadcrumb.tsx" --exec cat -n {}

Repository: scanoss/scanoss.cc

Length of output: 2665


🏁 Script executed:

# Search for where the modal is actually opened with props
rg "filePath" frontend/src/components/FilterActionModal.tsx -B 2 -A 2

Repository: scanoss/scanoss.cc

Length of output: 768


Consider handling the empty filePath edge case for defensive consistency.

While the modal is guarded by selectedResult at the call site (FilterActionButton.tsx), the initialization at line 83 doesn't protect against segments.length - 1 becoming -1 if filePath is empty. This is inconsistent with line 113, which already uses Math.max(0, segments.length - 2) for the folder case. For defensive consistency, apply the same protection to the initial state and the file/component initializations:

Suggested fix
  const segments = useMemo(() => getPathSegments(filePath), [filePath]);
- const [selectedPathIndex, setSelectedPathIndex] = useState(segments.length - 1);
+ const [selectedPathIndex, setSelectedPathIndex] = useState(Math.max(0, segments.length - 1));

Also apply the same to lines 109 and 116 in the useEffect.

🤖 Prompt for AI Agents
In `@frontend/src/components/FilterActionModal.tsx` around lines 82 - 83, The
initial selectedPathIndex may become -1 when filePath is empty; update the
useState initializer for selectedPathIndex to use Math.max(0, segments.length -
1) so it never goes negative (symbols: segments from getPathSegments,
selectedPathIndex, useState). Also update the related useEffect assignments that
compute folder/file initial indices (the assignments around setSelectedPathIndex
in the useEffect) to guard with Math.max(0, ...) the same way (symbols:
setSelectedPathIndex, useEffect) so both initial state and subsequent effect
updates handle empty filePath defensively.

@github-actions
Copy link

🔍 SCANOSS Code Similarity Detected

📋 8 full file matches found

🔗 View detailed findings on commit e743adb

8 files contain code similarities

💡 Click the commit link above to see detailed annotations for each match.

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 5
  • Undeclared components: 3
  • Declared components: 2
  • Detected files: 191
  • Detected files undeclared: 8
  • Detected files declared: 183
  • Licenses detected: 3
  • Licenses detected with copyleft: 1
  • Policies: ❌ 1 fail (1 total)

View more details on SCANOSS Action Summary

@github-actions
Copy link

🔍 SCANOSS Code Similarity Detected

📋 9 full file matches found

🔗 View detailed findings on commit 9fbd0ed

9 files contain code similarities

💡 Click the commit link above to see detailed annotations for each match.

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 6
  • Undeclared components: 4
  • Declared components: 2
  • Detected files: 191
  • Detected files undeclared: 9
  • Detected files declared: 182
  • Licenses detected: 3
  • Licenses detected with copyleft: 1
  • Policies: ❌ 1 fail (1 total)

View more details on SCANOSS Action Summary

@isasmendiagus isasmendiagus force-pushed the feat/SP-2883/add-folder-Actions branch from 9fbd0ed to 7f6dc17 Compare January 22, 2026 14:38
@github-actions
Copy link

🔍 SCANOSS Code Similarity Detected

📋 1 full file matches found

🔗 View detailed findings on commit 7f6dc17

Files with similarities:

  • frontend/src/components/ui/menubar.tsx

💡 Click the commit link above to see detailed annotations for each match.

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 4
  • Undeclared components: 1
  • Declared components: 3
  • Detected files: 190
  • Detected files undeclared: 1
  • Detected files declared: 189
  • Licenses detected: 2
  • Licenses detected with copyleft: 1
  • Policies: ❌ 1 fail (1 total)

View more details on SCANOSS Action Summary

Copy link

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/FilterActionButton.tsx (1)

79-169: Avoid direct “Replace file” action without replacement selection.

For FilterAction.Replace, handleDirectAction fires with filterBy: 'by_file' but no replaceWith, which likely fails or yields an incomplete filter. This diverges from the new modal flow (e.g., in FilterComponentActions). Make the “File” item open the modal for Replace.

🐛 Suggested fix
+  const isReplace = action === FilterAction.Replace;
+  const handleFileSelection = isReplace ? handleOpenModalFile : handleDirectAction;
@@
-          <DropdownMenuItem onClick={handleDirectAction}>
+          <DropdownMenuItem onClick={handleFileSelection}>
             File
             <DropdownMenuShortcut>{shortcuts.file}</DropdownMenuShortcut>
           </DropdownMenuItem>
🤖 Fix all issues with AI agents
In `@app.go`:
- Around line 135-184: The Actions submenu items currently pass nil for
accelerators (e.g., IncludeMenu.AddText(..., nil, ...), ReplaceMenu.AddText(...,
nil, ...), SkipMenu.AddText(..., nil, ...)), so shortcut hints and OS-level
bindings are missing; obtain the accelerators from
keyboardService.GetGroupedShortcuts()[entities.GroupActions] (like File/View
menus do) and pass the corresponding map entry for each action key
(entities.ActionInclude, entities.ActionIncludeFolder,
entities.ActionReplaceComponent, entities.ActionSkipExtension, etc.) instead of
nil so items show shortcuts and work when the webview is unfocused.

In `@backend/entities/keyboard.go`:
- Around line 235-267: DefaultShortcuts is missing entries for the exported
actions ReplaceComponent and SkipExtension so they don't appear in the UI; add
two shortcut objects to DefaultShortcuts mirroring the pattern used for
Replace/Skip (include Name, Description, Accelerator, AlternativeAccelerator,
Keys, Group: GroupActions, and Action set to ReplaceComponent and SkipExtension
respectively) so the frontend shortcuts map and backend lists include those
actions.

In `@frontend/src/components/SkipActionModal.tsx`:
- Around line 88-128: When initializing/resetting in the useEffect that checks
initialSelection, guard the 'extension' branch so that if initialSelection ===
'extension' but extension is falsy or empty you instead setActiveTab('path') and
setSelectedPathIndex to the appropriate value (e.g., segments.length - 1 or
parent folder for 'folder') to avoid selecting a non-rendered tab; also update
getSkipPattern (and optionally getTargetType/getWarningMessage) to treat a
missing extension by returning the path-based pattern (via buildPath(segments,
selectedPathIndex)) so you never generate patterns like '*.null' and UI text
stays consistent.

In `@frontend/src/components/ui/menubar.tsx`:
- Around line 221-235: The component uses a misspelled property name
"displayname" so React DevTools won't show the correct name; update the
component's static property to MenubarShortcut.displayName = "MenubarShortcut"
(capital D) replacing the existing MenubarShortcut.displayname assignment to
ensure the component name appears properly in debugging tools.
♻️ Duplicate comments (1)
frontend/src/components/SkipActionModal.tsx (1)

44-47: Use the last dot for extension parsing.

indexOf('.') misparses names like foo.bar.jsbar.js. lastIndexOf('.') yields the expected js.

🛠️ Suggested fix
-  const dotIndex = filename.indexOf('.');
+  const dotIndex = filename.lastIndexOf('.');
🧹 Nitpick comments (2)
frontend/src/components/FilterComponentActions.tsx (2)

83-161: Consolidate modal-open logic to reduce duplication.

The same setFilterModalAction / setFilterModalInitialSelection / setFilterModalOpen sequence is repeated across include/dismiss/replace handlers. A small helper keeps behavior consistent and easier to change.

♻️ Suggested refactor
+  const openFilterModal = useCallback(
+    (action: FilterAction, initialSelection: FilterInitialSelection) => {
+      if (!isCompletedResult && selectedResult) {
+        setFilterModalAction(action);
+        setFilterModalInitialSelection(initialSelection);
+        setFilterModalOpen(true);
+      }
+    },
+    [isCompletedResult, selectedResult]
+  );
+
-  const handleIncludeFolder = useCallback(() => {
-    if (!isCompletedResult && selectedResult) {
-      setFilterModalAction(FilterAction.Include);
-      setFilterModalInitialSelection('folder');
-      setFilterModalOpen(true);
-    }
-  }, [isCompletedResult, selectedResult]);
+  const handleIncludeFolder = useCallback(
+    () => openFilterModal(FilterAction.Include, 'folder'),
+    [openFilterModal]
+  );

287-377: Keep Menubar shortcut labels in sync with KEYBOARD_SHORTCUTS.

The displayed shortcuts are hardcoded (I, Shift+I, etc.) and can drift from the real bindings (e.g., F1/F2 variants). Consider deriving labels from KEYBOARD_SHORTCUTS so the menu stays accurate.

💡 Example approach
-import { KEYBOARD_SHORTCUTS } from '@/lib/shortcuts';
+import { getShortcutDisplay, KEYBOARD_SHORTCUTS } from '@/lib/shortcuts';
+import useEnvironment from '@/hooks/useEnvironment';
@@
+  const { modifierKey } = useEnvironment();
+  const shortcutLabel = useCallback(
+    (keys: string) => getShortcutDisplay(keys, modifierKey.label)[0],
+    [modifierKey.label]
+  );
@@
-            <MenubarItem onSelect={handleIncludeFile}>
+            <MenubarItem onSelect={handleIncludeFile}>
               File
-              <MenubarShortcut>I</MenubarShortcut>
+              <MenubarShortcut>{shortcutLabel(KEYBOARD_SHORTCUTS.include.keys)}</MenubarShortcut>
             </MenubarItem>

Comment on lines +135 to +184
// Actions menu with submenus
ActionsMenu := AppMenu.AddSubmenu("Actions")
for _, shortcut := range actionShortcuts {
sc := shortcut
ActionsMenu.AddText(sc.Name, sc.Accelerator, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(sc.Action))
})
}

// Include submenu
IncludeMenu := ActionsMenu.AddSubmenu("Include")
IncludeMenu.AddText("Include file", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionInclude))
})
IncludeMenu.AddText("Include folder", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionIncludeFolder))
})
IncludeMenu.AddText("Include component", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionIncludeWithModal))
})

// Dismiss submenu
DismissMenu := ActionsMenu.AddSubmenu("Dismiss")
DismissMenu.AddText("Dismiss file", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionDismiss))
})
DismissMenu.AddText("Dismiss folder", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionDismissFolder))
})
DismissMenu.AddText("Dismiss component", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionDismissWithModal))
})

// Replace submenu
ReplaceMenu := ActionsMenu.AddSubmenu("Replace")
ReplaceMenu.AddText("Replace file", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionReplace))
})
ReplaceMenu.AddText("Replace folder", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionReplaceFolder))
})
ReplaceMenu.AddText("Replace component", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionReplaceComponent))
})

// Skip submenu
SkipMenu := ActionsMenu.AddSubmenu("Skip")
SkipMenu.AddText("Skip file", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionSkip))
})
SkipMenu.AddText("Skip folder", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionSkipFolder))
})
SkipMenu.AddText("Skip extension", nil, func(cd *menu.CallbackData) {
runtime.EventsEmit(a.ctx, string(entities.ActionSkipExtension))
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, locate app.go and examine the lines in question
find . -name "app.go" -type f | head -5

Repository: scanoss/scanoss.cc

Length of output: 180


🏁 Script executed:

#!/bin/bash
# Search for the file containing the menu setup code
rg -l "ActionsMenu.*AddSubmenu.*Actions" --type go

Repository: scanoss/scanoss.cc

Length of output: 180


🏁 Script executed:

#!/bin/bash
# Look for keyboardService and GetGroupedShortcuts references
rg "GetGroupedShortcuts|keyboardService" --type go | head -20

Repository: scanoss/scanoss.cc

Length of output: 2158


🏁 Script executed:

#!/bin/bash
# Search for GroupActions and Action definitions in entities
rg "GroupActions|type Action" --type go | head -30

Repository: scanoss/scanoss.cc

Length of output: 819


🏁 Script executed:

#!/bin/bash
# Get the actual app.go file and examine lines around 135-184
wc -l app.go

Repository: scanoss/scanoss.cc

Length of output: 72


🏁 Script executed:

#!/bin/bash
# Read the app.go file to see the BuildMenu function
cat -n app.go | sed -n '120,200p'

Repository: scanoss/scanoss.cc

Length of output: 3579


🏁 Script executed:

#!/bin/bash
# Check the keyboard entities to understand the structure
cat -n backend/entities/keyboard.go | head -100

Repository: scanoss/scanoss.cc

Length of output: 4335


🏁 Script executed:

#!/bin/bash
# Look for Shortcut and Accelerator definitions
rg "type Shortcut|type Accelerator|Action.*string" backend/entities/keyboard.go -A 2 | head -40

Repository: scanoss/scanoss.cc

Length of output: 234


🏁 Script executed:

#!/bin/bash
# Check the BuildMenu function signature and beginning
cat -n app.go | sed -n '105,140p'

Repository: scanoss/scanoss.cc

Length of output: 1338


🏁 Script executed:

#!/bin/bash
# Get more context - look for where groupedShortcuts is used or defined
rg "groupedShortcuts" app.go -A 2 -B 2

Repository: scanoss/scanoss.cc

Length of output: 284


🏁 Script executed:

#!/bin/bash
# Check if there are any action accelerators being used elsewhere
rg "ActionInclude|ActionDismiss|ActionReplace|ActionSkip" backend/entities/keyboard.go -B 1 -A 1 | head -60

Repository: scanoss/scanoss.cc

Length of output: 1851


🏁 Script executed:

#!/bin/bash
# Check the keyboard_service_in_memory_impl.go to see actual shortcuts defined
cat -n backend/service/keyboard_service_in_memory_impl.go | grep -A 5 "ActionInclude\|ActionDismiss\|ActionReplace\|ActionSkip" | head -80

Repository: scanoss/scanoss.cc

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Get the full keyboard service implementation to see what shortcuts are defined
wc -l backend/service/keyboard_service_in_memory_impl.go

Repository: scanoss/scanoss.cc

Length of output: 115


🏁 Script executed:

#!/bin/bash
# Get the full keyboard_service_in_memory_impl.go file
cat -n backend/service/keyboard_service_in_memory_impl.go

Repository: scanoss/scanoss.cc

Length of output: 2198


🏁 Script executed:

#!/bin/bash
# Find where DefaultShortcuts is defined
rg "DefaultShortcuts" --type go | head -10

Repository: scanoss/scanoss.cc

Length of output: 315


🏁 Script executed:

#!/bin/bash
# Search for keyboard.go or shortcuts definitions
fd keyboard.go --type f

Repository: scanoss/scanoss.cc

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Get the full DefaultShortcuts definition
cat -n backend/entities/keyboard.go | sed -n '100,250p'

Repository: scanoss/scanoss.cc

Length of output: 5857


🏁 Script executed:

#!/bin/bash
# Get more of the DefaultShortcuts to see all Action group shortcuts
cat -n backend/entities/keyboard.go | sed -n '250,300p'

Repository: scanoss/scanoss.cc

Length of output: 1691


🏁 Script executed:

#!/bin/bash
# Check if ActionReplaceComponent and ActionSkipExtension have shortcuts defined
rg "ActionReplaceComponent|ActionSkipExtension" backend/entities/keyboard.go -B 2 -A 8

Repository: scanoss/scanoss.cc

Length of output: 1138


Use keyboard shortcuts from the service for Actions menu items.

The Actions menu is hardcoded with nil accelerators, preventing shortcut hints from appearing in the menu and disabling OS-level menu bindings when the webview isn't focused. Extract accelerators from keyboardService.GetGroupedShortcuts()[entities.GroupActions] as done in the File and View menus. Actions without defined shortcuts (e.g., ActionReplaceComponent, ActionSkipExtension) will naturally receive nil accelerators via the map lookup.

💡 Suggested pattern
@@
-  // Actions menu with submenus
-  ActionsMenu := AppMenu.AddSubmenu("Actions")
+  // Actions menu with submenus
+  ActionsMenu := AppMenu.AddSubmenu("Actions")
+  actionAccelerators := map[entities.Action]*keys.Accelerator{}
+  for _, sc := range groupedShortcuts[entities.GroupActions] {
+    actionAccelerators[sc.Action] = sc.Accelerator
+  }
@@
-  IncludeMenu.AddText("Include file", nil, func(cd *menu.CallbackData) {
+  IncludeMenu.AddText("Include file", actionAccelerators[entities.ActionInclude], func(cd *menu.CallbackData) {
     runtime.EventsEmit(a.ctx, string(entities.ActionInclude))
   })
🤖 Prompt for AI Agents
In `@app.go` around lines 135 - 184, The Actions submenu items currently pass nil
for accelerators (e.g., IncludeMenu.AddText(..., nil, ...),
ReplaceMenu.AddText(..., nil, ...), SkipMenu.AddText(..., nil, ...)), so
shortcut hints and OS-level bindings are missing; obtain the accelerators from
keyboardService.GetGroupedShortcuts()[entities.GroupActions] (like File/View
menus do) and pass the corresponding map entry for each action key
(entities.ActionInclude, entities.ActionIncludeFolder,
entities.ActionReplaceComponent, entities.ActionSkipExtension, etc.) instead of
nil so items show shortcuts and work when the webview is unfocused.

Comment on lines +88 to +128
// Reset state when modal opens
useEffect(() => {
if (open) {
if (initialSelection === 'extension') {
setActiveTab('extension');
setSelectedPathIndex(segments.length - 1);
} else {
setActiveTab('path');
if (initialSelection === 'folder') {
// Select parent folder (one level up from file)
setSelectedPathIndex(Math.max(0, segments.length - 2));
} else {
setSelectedPathIndex(segments.length - 1);
}
}
}
}, [open, segments.length, initialSelection]);

// Determine target type for dynamic text
const getTargetType = () => {
if (activeTab === 'extension') return 'extension';
return selectedPathIndex < segments.length - 1 ? 'folder' : 'file';
};
const targetType = getTargetType();

const getSkipPattern = (): string => {
if (activeTab === 'extension') {
return `*.${extension}`;
}
return buildPath(segments, selectedPathIndex);
};

const getWarningMessage = (): string => {
if (activeTab === 'extension') {
return `All .${extension} files will be skipped from future scans in this project.`;
}
if (selectedPathIndex < segments.length - 1) {
return 'This folder and all its contents will be skipped from future scans.';
}
return 'This file will be skipped from future scans.';
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard “extension” selection when no extension exists.

If initialSelection === 'extension' but the file has no extension, activeTab still becomes extension, the tab isn’t rendered, and getSkipPattern() produces *.null. Add a guard to fall back to the path tab and avoid invalid patterns.

🐛 Suggested fix
@@
-  const extension = getFileExtension(filename);
+  const extension = getFileExtension(filename);
+  const hasExtension = !!extension;
@@
-  useEffect(() => {
-    if (open) {
-      if (initialSelection === 'extension') {
-        setActiveTab('extension');
-        setSelectedPathIndex(segments.length - 1);
-      } else {
-        setActiveTab('path');
-        if (initialSelection === 'folder') {
-          // Select parent folder (one level up from file)
-          setSelectedPathIndex(Math.max(0, segments.length - 2));
-        } else {
-          setSelectedPathIndex(segments.length - 1);
-        }
-      }
-    }
-  }, [open, segments.length, initialSelection]);
+  useEffect(() => {
+    if (open) {
+      if (initialSelection === 'extension' && hasExtension) {
+        setActiveTab('extension');
+        setSelectedPathIndex(segments.length - 1);
+      } else {
+        setActiveTab('path');
+        if (initialSelection === 'folder') {
+          // Select parent folder (one level up from file)
+          setSelectedPathIndex(Math.max(0, segments.length - 2));
+        } else {
+          setSelectedPathIndex(segments.length - 1);
+        }
+      }
+    }
+  }, [open, segments.length, initialSelection, hasExtension]);
@@
-  const getSkipPattern = (): string => {
-    if (activeTab === 'extension') {
-      return `*.${extension}`;
-    }
-    return buildPath(segments, selectedPathIndex);
-  };
+  const getSkipPattern = (): string => {
+    if (activeTab === 'extension' && hasExtension) {
+      return `*.${extension}`;
+    }
+    return buildPath(segments, selectedPathIndex);
+  };
@@
-  const getWarningMessage = (): string => {
-    if (activeTab === 'extension') {
-      return `All .${extension} files will be skipped from future scans in this project.`;
-    }
+  const getWarningMessage = (): string => {
+    if (activeTab === 'extension' && hasExtension) {
+      return `All .${extension} files will be skipped from future scans in this project.`;
+    }
     if (selectedPathIndex < segments.length - 1) {
       return 'This folder and all its contents will be skipped from future scans.';
     }
     return 'This file will be skipped from future scans.';
   };
🤖 Prompt for AI Agents
In `@frontend/src/components/SkipActionModal.tsx` around lines 88 - 128, When
initializing/resetting in the useEffect that checks initialSelection, guard the
'extension' branch so that if initialSelection === 'extension' but extension is
falsy or empty you instead setActiveTab('path') and setSelectedPathIndex to the
appropriate value (e.g., segments.length - 1 or parent folder for 'folder') to
avoid selecting a non-rendered tab; also update getSkipPattern (and optionally
getTargetType/getWarningMessage) to treat a missing extension by returning the
path-based pattern (via buildPath(segments, selectedPathIndex)) so you never
generate patterns like '*.null' and UI text stays consistent.

Comment on lines +221 to +235
const MenubarShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
MenubarShortcut.displayname = "MenubarShortcut"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix displayName casing for MenubarShortcut.

displayname is a typo, so the component name won’t show correctly in React DevTools.

🔧 Fix
-MenubarShortcut.displayname = "MenubarShortcut"
+MenubarShortcut.displayName = "MenubarShortcut"
📝 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
const MenubarShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
MenubarShortcut.displayname = "MenubarShortcut"
const MenubarShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
MenubarShortcut.displayName = "MenubarShortcut"
🤖 Prompt for AI Agents
In `@frontend/src/components/ui/menubar.tsx` around lines 221 - 235, The component
uses a misspelled property name "displayname" so React DevTools won't show the
correct name; update the component's static property to
MenubarShortcut.displayName = "MenubarShortcut" (capital D) replacing the
existing MenubarShortcut.displayname assignment to ensure the component name
appears properly in debugging tools.

Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/FilterActionButton.tsx (1)

103-178: Component shortcut labels aren’t wired to handlers.

The menu shows component shortcuts (e.g., Shift+R), but this component doesn’t register a component-specific shortcut handler. Either bind the component shortcut (e.g., KEYBOARD_SHORTCUTS.replaceComponent) or remove the label to avoid misleading hints.

✅ Optional wiring for Replace component
+  const componentShortcutKeys = useMemo(() => {
+    if (action === FilterAction.Replace) return KEYBOARD_SHORTCUTS.replaceComponent?.keys;
+    return undefined;
+  }, [action]);
+
+  useKeyboardShortcut(componentShortcutKeys ?? '', handleOpenModalComponent, {
+    enabled: !isCompletedResult && !!selectedResult && !!componentShortcutKeys,
+  });
🤖 Fix all issues with AI agents
In `@frontend/src/components/FilterActionButton.tsx`:
- Around line 79-87: handleDirectAction currently calls onAdd for all actions
(including "Replace") without ensuring a replacement component (replaceWith)
exists; update handleDirectAction to guard against Replace by either routing it
through the modal or returning early: check the action value inside
handleDirectAction (referencing action, selectedResult, isCompletedResult) and
if action === 'replace' do not call onAdd (instead trigger the modal flow or
perform the replaceWith validation), otherwise proceed to call onAdd with the
existing filterBy and purl fields.

In `@frontend/src/components/FilterComponentActions.tsx`:
- Around line 288-324: The displayed shortcuts for "Component" (Shift+I /
Shift+D) are inconsistent with the actual behavior; either wire the keyboard
shortcuts to the component handlers or change the labels. Update the keyboard
binding logic so Shift+I triggers handleIncludeComponent and Shift+D triggers
handleDismissComponent (instead of opening the modal with file preselected), or
if you want the current file-first behavior, change the MenubarShortcut text for
the "Component" items to the actual keys that invoke the file-first action;
reference the MenubarItem onSelect handlers handleIncludeFile,
handleIncludeFolder, handleIncludeComponent and handleDismissFile,
handleDismissFolder, handleDismissComponent when making the change.
♻️ Duplicate comments (3)
app.go (1)

138-184: Missing keyboard accelerators for Actions menu items.

All submenu items pass nil as the accelerator parameter, which is inconsistent with the File and View menus that correctly extract accelerators from groupedShortcuts. This prevents shortcut hints from appearing in menu items and disables OS-level menu bindings when the webview isn't focused.

Since groupedShortcuts is already available (line 117), extract accelerators for the Actions group:

Suggested approach
 	// Actions menu with submenus
 	ActionsMenu := AppMenu.AddSubmenu("Actions")
+	actionAccelerators := map[entities.Action]*keys.Accelerator{}
+	for _, sc := range groupedShortcuts[entities.GroupActions] {
+		actionAccelerators[sc.Action] = sc.Accelerator
+	}

 	// Include submenu
 	IncludeMenu := ActionsMenu.AddSubmenu("Include")
-	IncludeMenu.AddText("Include file", nil, func(cd *menu.CallbackData) {
+	IncludeMenu.AddText("Include file", actionAccelerators[entities.ActionInclude], func(cd *menu.CallbackData) {
 		runtime.EventsEmit(a.ctx, string(entities.ActionInclude))
 	})
-	IncludeMenu.AddText("Include folder", nil, func(cd *menu.CallbackData) {
+	IncludeMenu.AddText("Include folder", actionAccelerators[entities.ActionIncludeFolder], func(cd *menu.CallbackData) {
 		runtime.EventsEmit(a.ctx, string(entities.ActionIncludeFolder))
 	})
 	// ... apply same pattern to remaining items

Actions without defined shortcuts will naturally receive nil accelerators via the map lookup.

backend/entities/keyboard.go (1)

100-111: AllShortcutActions includes ReplaceComponent and SkipExtension, but they have no corresponding entries in DefaultShortcuts.

These actions are registered for frontend binding but won't appear in the keyboard shortcuts UI or have associated keybindings. If these actions are intentionally UI-only (triggered via buttons/menus), consider adding a comment to clarify. Otherwise, add the missing shortcut entries.

frontend/src/components/ui/menubar.tsx (1)

221-235: Fix MenubarShortcut.displayName casing.

React DevTools ignores displayname, so the component name won’t show correctly.

🔧 Proposed fix
-MenubarShortcut.displayname = "MenubarShortcut"
+MenubarShortcut.displayName = "MenubarShortcut"

Comment on lines +79 to +87
const handleDirectAction = useCallback(() => {
if (!isCompletedResult && selectedResult) {
onAdd({
action,
filterBy: 'by_file',
purl: selectedResult.detected_purl ?? '',
});

if (!confirmed) return;
}
}, [isCompletedResult, selectedResult, action, onAdd]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent Replace direct action without a replacement component.

handleDirectAction invokes onAdd for Replace without a replaceWith, which can yield an invalid request. Route Replace through the modal (or guard it) to ensure a replacement component is provided.

🐛 Suggested fix
-  const handleDirectAction = useCallback(() => {
-    if (!isCompletedResult && selectedResult) {
-      onAdd({
-        action,
-        filterBy: 'by_file',
-        purl: selectedResult.detected_purl ?? '',
-      });
-    }
-  }, [isCompletedResult, selectedResult, action, onAdd]);
+  const handleDirectAction = useCallback(() => {
+    if (!isCompletedResult && selectedResult) {
+      if (action === FilterAction.Replace) {
+        handleOpenModal('file');
+        return;
+      }
+      onAdd({
+        action,
+        filterBy: 'by_file',
+        purl: selectedResult.detected_purl ?? '',
+      });
+    }
+  }, [isCompletedResult, selectedResult, action, onAdd, handleOpenModal]);
🤖 Prompt for AI Agents
In `@frontend/src/components/FilterActionButton.tsx` around lines 79 - 87,
handleDirectAction currently calls onAdd for all actions (including "Replace")
without ensuring a replacement component (replaceWith) exists; update
handleDirectAction to guard against Replace by either routing it through the
modal or returning early: check the action value inside handleDirectAction
(referencing action, selectedResult, isCompletedResult) and if action ===
'replace' do not call onAdd (instead trigger the modal flow or perform the
replaceWith validation), otherwise proceed to call onAdd with the existing
filterBy and purl fields.

@github-actions
Copy link

🔍 SCANOSS Code Similarity Detected

📋 1 full file matches found

🔗 View detailed findings on commit 617c505

Files with similarities:

  • frontend/src/components/ui/menubar.tsx

💡 Click the commit link above to see detailed annotations for each match.

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 4
  • Undeclared components: 1
  • Declared components: 3
  • Detected files: 190
  • Detected files undeclared: 1
  • Detected files declared: 189
  • Licenses detected: 2
  • Licenses detected with copyleft: 1
  • Policies: ❌ 1 fail (1 total)

View more details on SCANOSS Action Summary

@github-actions
Copy link

🔍 SCANOSS Code Similarity Detected

📋 1 full file matches found

🔗 View detailed findings on commit c6ec070

Files with similarities:

  • frontend/src/components/ui/menubar.tsx

💡 Click the commit link above to see detailed annotations for each match.

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 4
  • Undeclared components: 1
  • Declared components: 3
  • Detected files: 188
  • Detected files undeclared: 1
  • Detected files declared: 187
  • Licenses detected: 2
  • Licenses detected with copyleft: 1
  • Policies: ❌ 1 fail (1 total)

View more details on SCANOSS Action Summary

Copy link

@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

🤖 Fix all issues with AI agents
In `@frontend/src/components/FilterComponentActions.tsx`:
- Around line 107-115: The Skip action is currently allowed for completed
results; update the createModalSkipHandler to early-return when
isCompletedResult is true (same style as other handlers) so it won't open the
modal for completed items, add the !isCompletedResult condition into the
skipEnabled computation so the keyboard shortcut respects immutability, and
change the Skip menu trigger to use the existing isDisabled boolean (instead of
its current enablement expression) so the UI button is disabled consistently;
reference the createModalSkipHandler function, the skipEnabled variable, the
isCompletedResult flag, and the isDisabled variable when making these changes.
♻️ Duplicate comments (1)
frontend/src/components/FilterComponentActions.tsx (1)

150-157: Align “Component” shortcut hints with actual bindings.

Line 150–157 binds Shift+I/Shift+D to include/dismiss with modal (file-initial selection), while Line 212–240 labels those keys on the Component items. Either wire Shift+I/Shift+D to handlers.includeComponent / handlers.dismissComponent or update the labels to reflect the real shortcut target.

Also applies to: 212-240

Comment on lines +107 to +115
// Creates handler that opens skip modal with given selection
const createModalSkipHandler = useCallback(
(selection: SkipInitialSelection) => () => {
if (!selectedResult) return;
setSkipModalInitialSelection(selection);
setSkipModalOpen(true);
},
[selectedResult]
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd FilterComponentActions.tsx

Repository: scanoss/scanoss.cc

Length of output: 112


🏁 Script executed:

wc -l frontend/src/components/FilterComponentActions.tsx

Repository: scanoss/scanoss.cc

Length of output: 116


🏁 Script executed:

cat -n frontend/src/components/FilterComponentActions.tsx | head -150

Repository: scanoss/scanoss.cc

Length of output: 7401


🏁 Script executed:

cat -n frontend/src/components/FilterComponentActions.tsx | sed -n '145,280p'

Repository: scanoss/scanoss.cc

Length of output: 7014


Skip action should be blocked for completed results to match filter action immutability.

The Skip handler and keyboard shortcuts are currently enabled for completed results, while Include, Dismiss, and Replace are properly blocked. This creates an inconsistency—if completed results should be immutable, the Skip action should follow the same pattern.

The fix requires three changes:

  1. Add isCompletedResult check to createModalSkipHandler (line 110)
  2. Update skipEnabled (line 147) to include the !isCompletedResult check
  3. Update the Skip menu trigger (line 275) to use the existing isDisabled variable
Proposed adjustment
   const createModalSkipHandler = useCallback(
     (selection: SkipInitialSelection) => () => {
-      if (!selectedResult) return;
+      if (!selectedResult || isCompletedResult) return;
       setSkipModalInitialSelection(selection);
       setSkipModalOpen(true);
     },
-    [selectedResult]
+    [selectedResult, isCompletedResult]
   );

-  const skipEnabled = !!selectedResult;
+  const skipEnabled = !isCompletedResult && !!selectedResult;

       <MenubarTrigger
-        disabled={!selectedResult}
+        disabled={isDisabled}
📝 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
// Creates handler that opens skip modal with given selection
const createModalSkipHandler = useCallback(
(selection: SkipInitialSelection) => () => {
if (!selectedResult) return;
setSkipModalInitialSelection(selection);
setSkipModalOpen(true);
},
[selectedResult]
);
// Creates handler that opens skip modal with given selection
const createModalSkipHandler = useCallback(
(selection: SkipInitialSelection) => () => {
if (!selectedResult || isCompletedResult) return;
setSkipModalInitialSelection(selection);
setSkipModalOpen(true);
},
[selectedResult, isCompletedResult]
);
🤖 Prompt for AI Agents
In `@frontend/src/components/FilterComponentActions.tsx` around lines 107 - 115,
The Skip action is currently allowed for completed results; update the
createModalSkipHandler to early-return when isCompletedResult is true (same
style as other handlers) so it won't open the modal for completed items, add the
!isCompletedResult condition into the skipEnabled computation so the keyboard
shortcut respects immutability, and change the Skip menu trigger to use the
existing isDisabled boolean (instead of its current enablement expression) so
the UI button is disabled consistently; reference the createModalSkipHandler
function, the skipEnabled variable, the isCompletedResult flag, and the
isDisabled variable when making these changes.

@github-actions
Copy link

🔍 SCANOSS Code Similarity Detected

📋 1 full file matches found

🔗 View detailed findings on commit 93fd741

Files with similarities:

  • frontend/src/components/ui/menubar.tsx

💡 Click the commit link above to see detailed annotations for each match.

@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 4
  • Undeclared components: 1
  • Declared components: 3
  • Detected files: 188
  • Detected files undeclared: 1
  • Detected files declared: 187
  • Licenses detected: 2
  • Licenses detected with copyleft: 1
  • Policies: ❌ 1 fail (1 total)

View more details on SCANOSS Action Summary

@isasmendiagus isasmendiagus force-pushed the feat/SP-2883/add-folder-Actions branch from e35bb53 to 43df69a Compare January 26, 2026 16:36
@github-actions
Copy link

SCANOSS SCAN Completed 🚀

  • Detected components: 3
  • Undeclared components: 0
  • Declared components: 3
  • Detected files: 188
  • Detected files undeclared: 0
  • Detected files declared: 188
  • Licenses detected: 2
  • Licenses detected with copyleft: 1
  • Policies: ✅ 1 pass (1 total)

View more details on SCANOSS Action Summary

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant