Skip to content

feat(detections): quick review (Correct/Incorrect) in action menu#2809

Merged
tphakala merged 14 commits intomainfrom
feat/actionmenu-quick-review
Apr 18, 2026
Merged

feat(detections): quick review (Correct/Incorrect) in action menu#2809
tphakala merged 14 commits intomainfrom
feat/actionmenu-quick-review

Conversation

@tphakala
Copy link
Copy Markdown
Owner

@tphakala tphakala commented Apr 18, 2026

Summary

  • Adds Correct / Incorrect quick-review items to the detection action menu so users can mark verification status without leaving the feed
  • Merges the duplicated ActionMenu (detections row) and CardActionMenu (dashboard card) components into a single reusable component with a variant: 'default' | 'overlay' prop
  • Replaces leftover DaisyUI class names across the merged component with native Tailwind 4.2 utilities, matching the project's CSS-only styling approach

Closes #2712

Behavior

  • Correct / Incorrect items sit at the top of the menu when the user can edit and the detection is not locked; clicking either fires the existing /api/v2/detections/{id}/review endpoint immediately with no confirmation modal, then updates the local card state and shows a success toast
  • Badges ( / ) appear next to the matching item when the detection is already verified
  • Items are hidden when the detection is locked (matches ReviewCard behavior)
  • The menu trigger and Download item remain available to unauthenticated users; only mutating actions are gated by canEdit
  • Overlay variant uses theme-aware CSS variables with backdrop blur, adapting to any theme

Test plan

  • npm run check:all clean (typecheck, ESLint, Stylelint, ast-grep)
  • npm test green across 1895 tests / 98 files
  • 32 ActionMenu tests including quick-review render, fire, lock-gating, badges, variant, and optional Download
  • Manual: open menu on dashboard cards and detections list in both light and dark themes, mark Correct/Incorrect, verify badge + toast
  • Manual: lock a detection and confirm Correct/Incorrect disappear
  • Manual: switch to Finnish and German locales and confirm labels do not wrap at the menu width
  • Manual: sign out / sign in and confirm Download is accessible while unauthenticated

Files

  • frontend/src/lib/desktop/components/ui/ActionMenu.svelte - merged component, new props, de-DaisyUI
  • frontend/src/lib/desktop/components/ui/ActionMenu.test.ts - fixture fix + 8 new tests
  • frontend/src/lib/desktop/features/dashboard/components/CardActionMenu.svelte - deleted
  • frontend/src/lib/desktop/features/dashboard/components/DetectionCard.svelte - switched to merged ActionMenu with variant="overlay"
  • frontend/src/lib/desktop/features/dashboard/components/DetectionCardGrid.svelte - wires new handlers
  • frontend/src/lib/desktop/features/detections/components/DetectionRow.svelte - wires new handlers
  • frontend/src/lib/desktop/features/detections/components/DetectionsCardView.svelte - wires new handlers
  • frontend/src/lib/desktop/features/detections/composables/useDetectionActions.svelte.ts - shared postReview helper and handlers
  • frontend/static/messages/{en,de,es,fi,fr,nl,pl,pt}.json - markCorrect / markFalsePositive labels (short, wrap-safe)
  • frontend/src/lib/desktop/components/ui/README.md + frontend/src/lib/desktop/components/README.md - inventory updates
  • frontend/src/test/setup.ts - adds the two new keys to the shared i18n mock

Summary by CodeRabbit

  • New Features

    • Quick-review shortcuts to mark detections as Correct or Incorrect from action menus
    • Optional Download action in detection menus
    • New overlay styling variant for improved menu presentation
  • Refactor

    • Consolidated card menus into a unified ActionMenu component (legacy card menu removed)
  • Utilities

    • Added detection verification API helper with success/error toasts
  • Documentation

    • Added comprehensive ActionMenu docs with usage examples
  • Tests & i18n

    • Added tests for new menu behaviors and translations for multiple locales

tphakala added 12 commits April 18, 2026 13:14
Replace snake_case fields and the `as Detection` cast with a fully
typed object matching the camelCase Detection interface, adding the
required beginTime, endTime, speciesCode, and verified fields.
Adds variant='default'|'overlay' prop to ActionMenu. Default keeps
light in-row styling with marker class am-trigger-default; overlay
renders a semi-transparent dark rounded button for spectrogram use
with marker class am-trigger-overlay. TDD: tests written first.
Adds onMarkCorrect and onMarkFalsePositive props with corresponding menu
items at the top of the action menu. Items are hidden when detection is
locked. Active state badges (tick/cross) appear next to the relevant item
when the detection is already verified. Updates the i18n mock in test
setup with the two new translation keys.
Add handleMarkCorrect and handleMarkFalsePositive to the shared
useDetectionActions composable and pass them through DetectionCardGrid
and DetectionsCardView. The handlers fire the review API immediately
without a confirmation modal, matching DetectionRow fast-triage UX.
- Restructure canEdit gate so trigger and Download are available to
  unauthenticated users (matches old CardActionMenu behavior)
- Add hover/padding to menu items (previously relied on dead DaisyUI
  classes)
- Restore focus to trigger button after an action is selected, matching
  the Escape handler
- Use role="separator" on the quick-review divider
- Drop leftover dropdown class from the root div
- Switch overlay variant to theme-aware CSS vars with backdrop blur
  instead of hardcoded slate
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 50699e5c-e356-4dd7-9c4a-fb92b5ebeaee

📥 Commits

Reviewing files that changed from the base of the PR and between e2cd999 and 0c80beb.

📒 Files selected for processing (5)
  • frontend/src/lib/desktop/components/ui/ActionMenu.svelte
  • frontend/src/lib/desktop/components/ui/ActionMenu.test.ts
  • frontend/src/lib/desktop/features/detections/components/DetectionRow.svelte
  • frontend/src/lib/desktop/features/detections/composables/useDetectionActions.svelte.ts
  • frontend/src/lib/utils/reviewDetection.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/lib/desktop/components/ui/ActionMenu.test.ts
  • frontend/src/lib/desktop/features/detections/composables/useDetectionActions.svelte.ts
  • frontend/src/lib/desktop/components/ui/ActionMenu.svelte

Walkthrough

Consolidates card menus by removing CardActionMenu and adopting a shared ActionMenu; adds quick-review actions ("Correct" / "False Positive") with API-backed verification, UI/tests/docs/i18n updates, and wires new handlers through detection rows, cards, and composables.

Changes

Cohort / File(s) Summary
Core ActionMenu
frontend/src/lib/desktop/components/ui/ActionMenu.svelte
Added variant prop, optional callbacks onMarkCorrect, onMarkFalsePositive, onDownload; forward HTML attributes; reworked trigger logic, menu items (quick-review, download), focus/close behavior, and styling to support overlay variant.
ActionMenu tests & docs
frontend/src/lib/desktop/components/ui/ActionMenu.test.ts, frontend/src/lib/desktop/components/ui/README.md
Updated tests to new Detection shape and added coverage for variant styling, download item, quick-review items and lock gating; added README documenting props and usage.
Deprecated component removal & inventory
frontend/src/lib/desktop/features/dashboard/components/CardActionMenu.svelte, frontend/src/lib/desktop/components/README.md
Removed CardActionMenu.svelte and deleted its README inventory entry; functionality migrated into ActionMenu.
Dashboard card integration
frontend/src/lib/desktop/features/dashboard/components/DetectionCard.svelte, frontend/src/lib/desktop/features/dashboard/components/DetectionCardGrid.svelte
Switched import to ActionMenu, set variant="overlay" for dashboard cards, and pass new onMarkCorrect / onMarkFalsePositive callbacks from grid to cards.
Detections list wiring
frontend/src/lib/desktop/features/detections/components/DetectionRow.svelte, frontend/src/lib/desktop/features/detections/components/DetectionsCardView.svelte
Added async handlers handleMarkCorrect / handleMarkFalsePositive that call API util and refresh UI on success; wired them into ActionMenu props.
Action handlers composable
frontend/src/lib/desktop/features/detections/composables/useDetectionActions.svelte.ts
Added handleMarkCorrect and handleMarkFalsePositive to call verification API, update detection.verified, and trigger optional refresh.
API util for review
frontend/src/lib/utils/reviewDetection.ts
Added exported setDetectionVerification(detectionId, verified) to POST /api/v2/detections/{id}/review, show success/error toasts, and return boolean success.
Tests / i18n setup
frontend/src/test/setup.ts
Added mocked i18n keys used by tests for quick-review labels.
Translations
frontend/static/messages/{de,en,es,fi,fr,nl,pl,pt}.json
Added dashboard.recentDetections.actions.markCorrect and ...markFalsePositive keys across 8 locales.

Sequence Diagram

sequenceDiagram
    actor User
    participant ActionMenu
    participant DetectionRow
    participant Composable as useDetectionActions
    participant API as Detection API
    participant Toast

    User->>ActionMenu: Click "Correct" / "False Positive"
    ActionMenu->>DetectionRow: call onMarkCorrect/onMarkFalsePositive()
    DetectionRow->>Composable: invoke handleMarkCorrect/handleMarkFalsePositive(detection)
    Composable->>API: POST /api/v2/detections/{id}/review {verified: "correct"|"false_positive"}
    API-->>Composable: 200 OK
    Composable->>DetectionRow: return success
    DetectionRow->>DetectionRow: set detection.verified
    DetectionRow->>Toast: show success message
    DetectionRow->>ActionMenu: request menu close
    ActionMenu-->>User: menu closed, UI updated inline
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 primary change: adding quick review (Correct/Incorrect) items to the detection action menu.
Linked Issues check ✅ Passed All objectives from #2712 are met: quick-review items added to action menu, same API endpoint called, inline state updates without navigation, existing Review item preserved, icons reused, and lock-gating implemented.
Out of Scope Changes check ✅ Passed All changes are in-scope: ActionMenu consolidation serves the quick-review feature, i18n additions support new UI labels, test updates validate new behavior, and README documents the refactored component.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/actionmenu-quick-review

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.

Copy link
Copy Markdown
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 pull request merges the CardActionMenu into a unified ActionMenu component, introducing a new overlay variant for spectrogram cards and quick-review actions ('Correct' and 'Incorrect'). It also adds a download action and updates the relevant features and tests to support these changes. Feedback focuses on ensuring quick-review items are only displayed when their callbacks are provided, addressing a regression where verification status is hidden for locked detections, and centralizing duplicated API logic for posting reviews.

</div>
</button>
</li>
{#if canEdit && !detection.locked}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The quick-review items (Correct/Incorrect) are currently displayed whenever canEdit is true and the detection is not locked. However, since onMarkCorrect and onMarkFalsePositive are optional props, the menu will show non-functional items if these handlers are not provided. These items should also be gated by the presence of their respective callbacks to ensure a consistent user experience.

        {#if canEdit && !detection.locked && onMarkCorrect && onMarkFalsePositive}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in e2cd999 — added onMarkCorrect && onMarkFalsePositive to the guard so the quick-review items only render when their handlers are provided, matching the onDownload pattern.

Comment on lines +287 to +301
<li>
<button
onclick={() => handleAction(onReview)}
class={cn(
'text-sm w-full text-left px-3 py-2 rounded-md transition-colors',
itemHoverClass
)}
role="menuitem"
>
<div class="flex items-center gap-2">
<SquarePen class="size-4" />
<span>{t('dashboard.recentDetections.actions.review')}</span>
</div>
</button>
</li>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The verification status badges (✓/✗) have been removed from the 'Review' menu item and moved to the new quick-review items. However, since the quick-review items are hidden when a detection is locked (line 240), the verification status is no longer visible anywhere in the action menu for locked detections. This is a regression in information visibility compared to the previous implementation. Consider retaining the status badge in the 'Review' item or ensuring it remains visible when the detection is locked.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Good catch. Fixed in e2cd999 — restored the ✓/✗ badge on the "Review detection" item so the verified status is visible in the menu even when the detection is locked and the quick-review items are hidden. When not locked the badge now appears twice (on Review and on Correct/Incorrect), which I decided is acceptable: the Review item acts as a persistent status indicator independent of lock state.

Comment on lines +189 to +207
async function postReview(verified: 'correct' | 'false_positive') {
try {
await fetchWithCSRF(`/api/v2/detections/${detection.id}/review`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ verified }),
});
detection.verified = verified;
toastActions.success(
verified === 'correct'
? t('search.review.markedCorrect')
: t('search.review.markedFalsePositive')
);
onRefresh?.();
} catch (error) {
toastActions.error(t('search.review.failed'));
logger.error('Error setting verification status:', error);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The postReview logic is duplicated here and in useDetectionActions.svelte.ts. To improve maintainability and ensure consistency across the application, this logic should be centralized. Since this component is part of the detections feature, it should ideally leverage the useDetectionActions composable or a shared utility function for these API calls rather than re-implementing the fetch logic locally.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in e2cd999 — extracted the review POST into a new shared utility $lib/utils/reviewDetection.ts (setDetectionVerification). Both DetectionRow and the useDetectionActions composable now call it, so the fetch + toast + logger logic lives in one place.

Comment on lines +263 to +273
<button
onclick={() => handleAction(onMarkFalsePositive)}
class={cn(
'text-sm w-full text-left px-3 py-2 rounded-md transition-colors',
itemHoverClass
)}
role="menuitem"
>
<div class="flex items-center gap-2">
<CircleX class="size-4 text-[var(--color-error)]" />
<span>{t('dashboard.recentDetections.actions.markFalsePositive')}</span>

This comment was marked as outdated.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

False positive. The keys dashboard.recentDetections.actions.markCorrect and dashboard.recentDetections.actions.markFalsePositive were added to all 8 locale files in commit d6938c3 (the first commit on this branch), placed right after the existing menuLabel key. They render correctly in tests and in the app; the static analysis must be looking at a stale index. Verify with: grep -c '"markCorrect"' frontend/static/messages/*.json — every file prints 1.

Copy link
Copy Markdown
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

🧹 Nitpick comments (5)
frontend/static/messages/fr.json (1)

660-661: Consider aligning translations with existing keys for consistency.

The translations for markCorrect and markFalsePositive in this section differ from identical keys in the search.review namespace (lines 521-522):

  • Here (dashboard actions): "Correct" / "Incorrect"
  • search.review: "Marquer comme correcte" / "Marquer comme faux positif"

While both are valid French, using consistent translations across namespaces improves UX and reduces maintenance burden. If the shorter forms are intentional for space-constrained UI elements, consider documenting this decision. Otherwise, aligning with the existing verb phrases would be more consistent.

♻️ Suggested alignment with existing translations
-        "markCorrect": "Correct",
-        "markFalsePositive": "Incorrect",
+        "markCorrect": "Marquer comme correcte",
+        "markFalsePositive": "Marquer comme faux positif",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/static/messages/fr.json` around lines 660 - 661, The dashboard
translations for the keys markCorrect and markFalsePositive are inconsistent
with the existing phrases used in the search.review namespace; update the
fr.json entries for markCorrect and markFalsePositive to match the existing
translations ("Marquer comme correcte" and "Marquer comme faux positif") unless
the shorter "Correct"/"Incorrect" forms are intentionally used for UI
constraints—in that case, add a short comment in the JSON or documentation
indicating the intentional short form; locate and change the values for the keys
markCorrect and markFalsePositive (or add the documentation note) so the two
namespaces remain consistent.
frontend/src/lib/desktop/features/detections/composables/useDetectionActions.svelte.ts (1)

134-160: Centralize the review status literals.

'correct' and 'false_positive' are backend API contract values and are repeated across the new handlers. Use named constants to avoid typos and make future reuse safer. As per coding guidelines, Do not use magic numbers or strings - use named constants with descriptive names.

♻️ Proposed refactor
+const REVIEW_STATUS = {
+  CORRECT: 'correct',
+  FALSE_POSITIVE: 'false_positive',
+} as const;
+
+type ReviewStatus = (typeof REVIEW_STATUS)[keyof typeof REVIEW_STATUS];
+
-  async function postReview(detection: Detection, verified: 'correct' | 'false_positive') {
+  async function postReview(detection: Detection, verified: ReviewStatus) {
     try {
       await fetchWithCSRF(`/api/v2/detections/${detection.id}/review`, {
         method: 'POST',
         headers: { 'Content-Type': 'application/json' },
         body: JSON.stringify({ verified }),
       });
       detection.verified = verified;
       toastActions.success(
-        verified === 'correct'
+        verified === REVIEW_STATUS.CORRECT
           ? t('search.review.markedCorrect')
           : t('search.review.markedFalsePositive')
       );
       options.onRefresh?.();
     } catch (err) {
       toastActions.error(t('search.review.failed'));
       logger.error('Error setting verification status:', err);
     }
   }
 
   function handleMarkCorrect(detection: Detection) {
-    postReview(detection, 'correct');
+    postReview(detection, REVIEW_STATUS.CORRECT);
   }
 
   function handleMarkFalsePositive(detection: Detection) {
-    postReview(detection, 'false_positive');
+    postReview(detection, REVIEW_STATUS.FALSE_POSITIVE);
   }

Also applies to: 186-187

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

In
`@frontend/src/lib/desktop/features/detections/composables/useDetectionActions.svelte.ts`
around lines 134 - 160, The code repeats the backend status literals 'correct'
and 'false_positive' in postReview and the handlers (postReview,
handleMarkCorrect, handleMarkFalsePositive); introduce named constants (e.g.,
REVIEW_VERIFIED_CORRECT and REVIEW_VERIFIED_FALSE_POSITIVE) at the top of the
module and replace all literal occurrences in postReview calls and any other
handlers so the API contract values are centralized and less error-prone.
frontend/src/lib/desktop/features/detections/components/DetectionRow.svelte (1)

189-215: Avoid duplicating the quick-review POST flow here.

useDetectionActions.svelte.ts now contains the same postReview logic, so this row can drift from the card/grid behavior. Consider routing the row through the shared composable or extracting a shared reviewDetection(...) helper used by both paths. Based on learnings, Prefer existing components and extend them rather than creating duplicate functionality.

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

In `@frontend/src/lib/desktop/features/detections/components/DetectionRow.svelte`
around lines 189 - 215, The DetectionRow.svelte duplicate postReview flow should
be replaced by the shared action from useDetectionActions.svelte.ts: remove the
local async postReview and instead import and call the shared reviewDetection
(or the exported function that posts reviews) when handling verification; update
handleMarkCorrect and handleMarkFalsePositive to invoke
reviewDetection(detection.id, 'correct') and reviewDetection(detection.id,
'false_positive') (or adapt to that helper's signature), keep the toast and
onRefresh logic where the shared helper doesn't already handle them, and ensure
any error logging uses the centralized logger; this makes postReview logic
consistent across DetectionRow.svelte and the card/grid components.
frontend/src/lib/desktop/components/ui/ActionMenu.svelte (1)

250-282: Reflect verified state in the menuitem's accessible name/state.

The ✓ / ✗ badges are bare text inside the <button role="menuitem">, so the button's accessible name becomes e.g. "Correct ✓" (announced by screen readers as "Correct, check mark"). Two minor improvements worth considering:

  1. Convey the verified status semantically rather than via a glyph — e.g., add aria-current="true" (or aria-pressed/disabled) on the active item, and aria-hidden="true" on the badge span (or replace with an sr-only label like "currently marked correct").
  2. Consider visually distinguishing the active item (e.g., disabled/pressed styling) since clicking an already-verified action still fires onMarkCorrect / onMarkFalsePositive and re-hits the API.
♻️ Sketch
-              <button
-                onclick={() => handleAction(onMarkCorrect)}
-                class={cn(
-                  'text-sm w-full text-left px-3 py-2 rounded-md transition-colors',
-                  itemHoverClass
-                )}
-                role="menuitem"
-              >
+              {`@const` isCorrect = detection.verified === 'correct'}
+              <button
+                onclick={() => handleAction(onMarkCorrect)}
+                class={cn(
+                  'text-sm w-full text-left px-3 py-2 rounded-md transition-colors',
+                  itemHoverClass
+                )}
+                role="menuitem"
+                aria-current={isCorrect ? 'true' : undefined}
+              >
                 <div class="flex items-center gap-2">
                   <CircleCheck class="size-4 text-[var(--color-success)]" />
                   <span>{t('dashboard.recentDetections.actions.markCorrect')}</span>
-                  {`#if` detection.verified === 'correct'}
+                  {`#if` isCorrect}
                     <span
+                      aria-hidden="true"
                       class="ml-auto inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-[var(--color-success)]/15 text-[var(--color-success)]"
                       >✓</span
                     >
                   {/if}
                 </div>
               </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/lib/desktop/components/ui/ActionMenu.svelte` around lines 250 -
282, The menu items for markCorrect/markFalsePositive render glyph badges that
become part of the button accessible name; update the menu item buttons (the
elements using onclick={() => handleAction(onMarkCorrect)} and onclick={() =>
handleAction(onMarkFalsePositive)}) to expose semantic state and hide decorative
glyphs: add aria-current="true" (or aria-pressed="true") on the active item when
detection.verified matches the action, add aria-hidden="true" to the badge
<span> (or replace the glyph with an sr-only label like "currently marked
correct"), and prevent or short-circuit the click handler (in handleAction or
before invoking onMarkCorrect/onMarkFalsePositive) when the item is already
active so the API is not re-hit; also apply a distinct disabled/active class to
visually indicate the current state.
frontend/src/lib/desktop/features/dashboard/components/DetectionCard.svelte (1)

7-16: Optional: update top-of-file doc comment to list new callbacks.

The header docblock enumerates the supported callback props but doesn't mention the newly added onMarkCorrect / onMarkFalsePositive. Minor docs drift.

📝 Suggested doc update
   - onReview?: () => void - Callback for review action
+  - onMarkCorrect?: () => void - Callback for marking detection as correct
+  - onMarkFalsePositive?: () => void - Callback for marking detection as false positive
   - onToggleSpecies?: () => void - Callback for toggle species action
   - onToggleLock?: () => void - Callback for toggle lock action
   - onDelete?: () => void - Callback for delete action
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/lib/desktop/features/dashboard/components/DetectionCard.svelte`
around lines 7 - 16, Update the top-of-file docblock in DetectionCard.svelte to
include the two new callback props: onMarkCorrect?: () => void - Callback
invoked when a detection is marked as correct, and onMarkFalsePositive?: () =>
void - Callback invoked when a detection is marked as a false positive; edit the
header that currently lists detection, isNew, onFreezeStart/onFreezeEnd,
onReview, onToggleSpecies, onToggleLock, onDelete to also mention these two new
handlers so the props list stays accurate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/static/messages/nl.json`:
- Around line 660-661: The label pair uses mixed languages: the "markCorrect"
key is English while "markFalsePositive" is Dutch; update the value of
markCorrect to "Juist" so both quick-review labels are consistent in Dutch
(adjust the string value for the markCorrect key in the NL localization to
"Juist").

---

Nitpick comments:
In `@frontend/src/lib/desktop/components/ui/ActionMenu.svelte`:
- Around line 250-282: The menu items for markCorrect/markFalsePositive render
glyph badges that become part of the button accessible name; update the menu
item buttons (the elements using onclick={() => handleAction(onMarkCorrect)} and
onclick={() => handleAction(onMarkFalsePositive)}) to expose semantic state and
hide decorative glyphs: add aria-current="true" (or aria-pressed="true") on the
active item when detection.verified matches the action, add aria-hidden="true"
to the badge <span> (or replace the glyph with an sr-only label like "currently
marked correct"), and prevent or short-circuit the click handler (in
handleAction or before invoking onMarkCorrect/onMarkFalsePositive) when the item
is already active so the API is not re-hit; also apply a distinct
disabled/active class to visually indicate the current state.

In `@frontend/src/lib/desktop/features/dashboard/components/DetectionCard.svelte`:
- Around line 7-16: Update the top-of-file docblock in DetectionCard.svelte to
include the two new callback props: onMarkCorrect?: () => void - Callback
invoked when a detection is marked as correct, and onMarkFalsePositive?: () =>
void - Callback invoked when a detection is marked as a false positive; edit the
header that currently lists detection, isNew, onFreezeStart/onFreezeEnd,
onReview, onToggleSpecies, onToggleLock, onDelete to also mention these two new
handlers so the props list stays accurate.

In `@frontend/src/lib/desktop/features/detections/components/DetectionRow.svelte`:
- Around line 189-215: The DetectionRow.svelte duplicate postReview flow should
be replaced by the shared action from useDetectionActions.svelte.ts: remove the
local async postReview and instead import and call the shared reviewDetection
(or the exported function that posts reviews) when handling verification; update
handleMarkCorrect and handleMarkFalsePositive to invoke
reviewDetection(detection.id, 'correct') and reviewDetection(detection.id,
'false_positive') (or adapt to that helper's signature), keep the toast and
onRefresh logic where the shared helper doesn't already handle them, and ensure
any error logging uses the centralized logger; this makes postReview logic
consistent across DetectionRow.svelte and the card/grid components.

In
`@frontend/src/lib/desktop/features/detections/composables/useDetectionActions.svelte.ts`:
- Around line 134-160: The code repeats the backend status literals 'correct'
and 'false_positive' in postReview and the handlers (postReview,
handleMarkCorrect, handleMarkFalsePositive); introduce named constants (e.g.,
REVIEW_VERIFIED_CORRECT and REVIEW_VERIFIED_FALSE_POSITIVE) at the top of the
module and replace all literal occurrences in postReview calls and any other
handlers so the API contract values are centralized and less error-prone.

In `@frontend/static/messages/fr.json`:
- Around line 660-661: The dashboard translations for the keys markCorrect and
markFalsePositive are inconsistent with the existing phrases used in the
search.review namespace; update the fr.json entries for markCorrect and
markFalsePositive to match the existing translations ("Marquer comme correcte"
and "Marquer comme faux positif") unless the shorter "Correct"/"Incorrect" forms
are intentionally used for UI constraints—in that case, add a short comment in
the JSON or documentation indicating the intentional short form; locate and
change the values for the keys markCorrect and markFalsePositive (or add the
documentation note) so the two namespaces remain consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 01321b2c-c1e1-46c2-b7e8-bb822e0552ab

📥 Commits

Reviewing files that changed from the base of the PR and between 38898a7 and 1deddda.

📒 Files selected for processing (19)
  • frontend/src/lib/desktop/components/README.md
  • frontend/src/lib/desktop/components/ui/ActionMenu.svelte
  • frontend/src/lib/desktop/components/ui/ActionMenu.test.ts
  • frontend/src/lib/desktop/components/ui/README.md
  • frontend/src/lib/desktop/features/dashboard/components/CardActionMenu.svelte
  • frontend/src/lib/desktop/features/dashboard/components/DetectionCard.svelte
  • frontend/src/lib/desktop/features/dashboard/components/DetectionCardGrid.svelte
  • frontend/src/lib/desktop/features/detections/components/DetectionRow.svelte
  • frontend/src/lib/desktop/features/detections/components/DetectionsCardView.svelte
  • frontend/src/lib/desktop/features/detections/composables/useDetectionActions.svelte.ts
  • frontend/src/test/setup.ts
  • frontend/static/messages/de.json
  • frontend/static/messages/en.json
  • frontend/static/messages/es.json
  • frontend/static/messages/fi.json
  • frontend/static/messages/fr.json
  • frontend/static/messages/nl.json
  • frontend/static/messages/pl.json
  • frontend/static/messages/pt.json
💤 Files with no reviewable changes (2)
  • frontend/src/lib/desktop/components/README.md
  • frontend/src/lib/desktop/features/dashboard/components/CardActionMenu.svelte

Comment thread frontend/static/messages/nl.json Outdated
- Gate quick-review items by handler presence (not just canEdit/locked)
  (Gemini: ActionMenu.svelte:240)
- Restore verified badge on Review item so status stays visible when the
  detection is locked and quick-review items are hidden
  (Gemini: ActionMenu.svelte:301)
- Extract setDetectionVerification into $lib/utils/reviewDetection and
  reuse from DetectionRow and useDetectionActions composable
  (Gemini: DetectionRow.svelte:207)
- Use consistent Dutch wording Juist/Onjuist in nl.json
  (CodeRabbit: nl.json:660)
- Update ActionMenu tests to supply both callbacks when expecting
  quick-review items to be rendered
@tphakala
Copy link
Copy Markdown
Owner Author

/gemini review

Copy link
Copy Markdown
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 pull request unifies the detection action menus by merging CardActionMenu into a single ActionMenu component that supports both standard and overlay variants. It introduces "Quick Review" functionality (Mark Correct/Incorrect) and a download option, supported by a new setDetectionVerification utility and updated action composables across the dashboard and detection views. The review feedback highlights that the conditional rendering for the quick-review section is currently too restrictive and suggests avoiding direct object mutation within the utility function to ensure more predictable state management.

</div>
</button>
</li>
{#if canEdit && !detection.locked && onMarkCorrect && onMarkFalsePositive}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The condition onMarkCorrect && onMarkFalsePositive is overly restrictive. If a consumer of this component only provides one of these callbacks (e.g., in a specialized view), the entire quick-review section will be hidden. It is better to use an OR condition here and then conditionally render each item based on the presence of its respective callback to ensure the component is flexible for different use cases.

{#if canEdit && !detection.locked && (onMarkCorrect || onMarkFalsePositive)}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 0c80beb — used OR at the outer quick-review guard and added per-item {#if onMarkCorrect} / {#if onMarkFalsePositive} gates so consumers can wire just one callback if they want.

headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ verified }),
});
detection.verified = verified;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Directly mutating the detection object (detection.verified = verified) inside a utility function is a side effect that can make state management harder to reason about. While using object property access is the preferred way to preserve reactivity in Svelte 5 (as destructuring would break it), it is generally recommended to treat props and shared objects as read-only and use callbacks to update state in the owner component. Since onRefresh is typically called immediately after this utility, ensure this local mutation doesn't conflict with the subsequent data reload or lead to inconsistent UI states if the refresh fails.

References
  1. In Svelte 5, use object property access instead of destructuring to preserve reactivity when accessing properties of reactive objects.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 0c80beb — made setDetectionVerification pure: it takes detectionId: number, posts to the API, shows the toast, and returns a boolean. The callers in DetectionRow and useDetectionActions now own the detection.verified update and onRefresh?.() call, so the utility no longer has a hidden mutation side effect.

Copy link
Copy Markdown
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.

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/lib/desktop/components/ui/ActionMenu.svelte (1)

286-399: ⚠️ Potential issue | 🟡 Minor

Gate optional menu item handlers individually, not just by canEdit.

The onReview, onToggleSpecies, onToggleLock, and onDelete callbacks are optional in the component's type definition, but menu items render whenever canEdit is true. A future consumer that omits one or more of these callbacks would display clickable no-op actions that close the menu without executing any handler.

While the current handleAction function safely handles undefined callbacks via if (action), gating each item by its corresponding handler prevents UI clutter and confusion when callbacks are intentionally omitted.

Proposed callback gating
-        {`#if` canEdit}
+        {`#if` canEdit && onReview}
           <li>
             <button
               onclick={() => handleAction(onReview)}
@@
             </button>
           </li>
+        {/if}
 
+        {`#if` canEdit && onToggleSpecies}
           <li>
             <button
               onclick={() => handleAction(onToggleSpecies)}
@@
             </button>
           </li>
+        {/if}
 
+        {`#if` canEdit && onToggleLock}
           <li>
             <button
               onclick={() => handleAction(onToggleLock)}
@@
             </button>
           </li>
         {/if}
@@
-        {`#if` canEdit && !detection.locked}
+        {`#if` canEdit && !detection.locked && onDelete}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/lib/desktop/components/ui/ActionMenu.svelte` around lines 286 -
399, Menu items for onReview, onToggleSpecies, onToggleLock, and onDelete are
rendered whenever canEdit is true even if their callback props are undefined;
update the Svelte conditionals to gate each menu item by its specific handler as
well (e.g., use {`#if` canEdit && onReview} for the review item, {`#if` canEdit &&
onToggleSpecies} for species toggle, {`#if` canEdit && onToggleLock} for lock
toggle, and {`#if` canEdit && !detection.locked && onDelete} for delete) so that
handleAction is only reachable when a callback exists.
🧹 Nitpick comments (1)
frontend/src/lib/desktop/components/ui/ActionMenu.svelte (1)

17-65: Forward rest HTML attributes on the root element.

ActionMenu is now a shared UI primitive, but it drops valid root attributes such as id, data-*, and test hooks. Capture ...rest from $props() and spread it onto the wrapper.

♻️ Proposed rest-prop forwarding
 <script lang="ts">
   import { cn } from '$lib/utils/cn';
   import type { Detection } from '$lib/types/detection.types';
+  import type { HTMLAttributes } from 'svelte/elements';
@@
-  interface Props {
+  interface Props extends HTMLAttributes<HTMLDivElement> {
@@
     onMenuClose?: () => void;
   }
@@
     onMenuOpen,
     onMenuClose,
+    class: classProp = '',
+    ...rest
   }: Props = $props();
@@
-{`#if` canEdit || onDownload}
-  <div class={cn('relative', className)}>
+{`#if` canEdit || onDownload}
+  <div {...rest} class={cn('relative', classProp, className)}>

As per coding guidelines, “All components must accept additional HTML attributes via rest parameters for extensibility.”

Also applies to: 207-208

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

In `@frontend/src/lib/desktop/components/ui/ActionMenu.svelte` around lines 17 -
65, The ActionMenu component is not forwarding arbitrary HTML attributes (id,
data-*, test hooks); capture the component's rest props by calling Svelte's
$props() or using the ...$$restProps pattern and spread them onto the root
wrapper element (the element that renders the menu UI) so external attributes
are preserved; update the component around the root wrapper (referencing
ActionMenu, $props(), or $$restProps) to include {...rest} (or {...$$restProps})
and ensure any existing className/variant handling merges with the forwarded
props.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@frontend/src/lib/desktop/components/ui/ActionMenu.svelte`:
- Around line 286-399: Menu items for onReview, onToggleSpecies, onToggleLock,
and onDelete are rendered whenever canEdit is true even if their callback props
are undefined; update the Svelte conditionals to gate each menu item by its
specific handler as well (e.g., use {`#if` canEdit && onReview} for the review
item, {`#if` canEdit && onToggleSpecies} for species toggle, {`#if` canEdit &&
onToggleLock} for lock toggle, and {`#if` canEdit && !detection.locked &&
onDelete} for delete) so that handleAction is only reachable when a callback
exists.

---

Nitpick comments:
In `@frontend/src/lib/desktop/components/ui/ActionMenu.svelte`:
- Around line 17-65: The ActionMenu component is not forwarding arbitrary HTML
attributes (id, data-*, test hooks); capture the component's rest props by
calling Svelte's $props() or using the ...$$restProps pattern and spread them
onto the root wrapper element (the element that renders the menu UI) so external
attributes are preserved; update the component around the root wrapper
(referencing ActionMenu, $props(), or $$restProps) to include {...rest} (or
{...$$restProps}) and ensure any existing className/variant handling merges with
the forwarded props.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: caa86835-8c08-4082-8268-013607776eaf

📥 Commits

Reviewing files that changed from the base of the PR and between 1deddda and e2cd999.

📒 Files selected for processing (6)
  • frontend/src/lib/desktop/components/ui/ActionMenu.svelte
  • frontend/src/lib/desktop/components/ui/ActionMenu.test.ts
  • frontend/src/lib/desktop/features/detections/components/DetectionRow.svelte
  • frontend/src/lib/desktop/features/detections/composables/useDetectionActions.svelte.ts
  • frontend/src/lib/utils/reviewDetection.ts
  • frontend/static/messages/nl.json
✅ Files skipped from review due to trivial changes (2)
  • frontend/static/messages/nl.json
  • frontend/src/lib/desktop/components/ui/ActionMenu.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/lib/desktop/features/detections/composables/useDetectionActions.svelte.ts

- Gate every menu item by its own callback presence, so a consumer that
  omits a handler does not render a no-op item
  (Gemini + CodeRabbit)
- Make setDetectionVerification pure: accept detectionId, return boolean,
  let callers update detection.verified and trigger refresh themselves
  (Gemini)
- Forward rest HTML attributes on the root div so callers can pass id,
  data-*, aria-* and other standard attributes, per the ui components
  convention
  (CodeRabbit)
@tphakala
Copy link
Copy Markdown
Owner Author

@coderabbitai Fixed both of your outside-diff findings in 0c80beb:

  • Per-handler gating for existing items — every menu item (Review, ToggleSpecies, ToggleLock, Download, Delete) now has its own {#if onXxx} guard, consistent with the new quick-review items.
  • Rest HTML attribute forwardingActionMenu's Props now extends HTMLAttributes<HTMLDivElement>; rest attributes are spread onto the root <div> so callers can pass id, data-*, aria-*, etc.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

Tip

For best results, initiate chat on the files or code changes.

@tphakala Thanks for the update! Both fixes look exactly right:

  • Per-handler gating — having a dedicated {#if onXxx} guard on every item keeps the menu surface clean when callers omit optional callbacks, and it's nicely consistent with the new quick-review items.
  • HTMLAttributes<HTMLDivElement> extension + rest spread — this is the idiomatic Svelte pattern for wrapper components; callers can now pass id, data-testid, aria-*, etc. without any changes to ActionMenu's explicit props.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@tphakala tphakala merged commit 35314ef into main Apr 18, 2026
17 checks passed
@tphakala tphakala deleted the feat/actionmenu-quick-review branch April 18, 2026 11:39
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.

[Feature] Quick review shortcut (Correct / False Positive) in detection context menu

1 participant