Skip to content

[SSP] Trigger new sort run from similarity popover in the grid/sample modal + bugfix + remove feature flag#7339

Merged
lanzhenw merged 17 commits into
developfrom
feat/wires-up-grid-action
Apr 9, 2026
Merged

[SSP] Trigger new sort run from similarity popover in the grid/sample modal + bugfix + remove feature flag#7339
lanzhenw merged 17 commits into
developfrom
feat/wires-up-grid-action

Conversation

@lanzhenw
Copy link
Copy Markdown
Member

@lanzhenw lanzhenw commented Apr 8, 2026

🔗 Related Issues

closes #FOEPD-3370

📋 What changes are proposed in this pull request?

Summary
Replaces the old direct POST /sort similarity flow with an operator-based flow that triggers @voxel51/panels/similarity_search and opens the similarity search panel.

Popover changes:

  • Simplified the grid/modal action popover — always shows search UI (text input or "Show similar samples/patches"), no more Reset button or inline advanced settings
  • Text search: type query + Enter/click Search → runs operator → opens panel
  • Image search (selected samples): click "Show similar samples" → runs operator → opens panel
  • Patches search (selected label in modal): click "Show similar patches" → runs operator → closes modal → switches to patches view → opens panel
  • Settings icon: closes modal (if open) → opens panel
  • Warns when labels from multiple fields are selected, or when no similarity index exists for the selected label's field
  • Persists lastUsedBrainKeys per-dataset from both popover and panel

Panel changes:

  • Added SSE subscription in useRuns for auto-refresh when execution store changes
  • useSearchSubmission now persists lastUsedBrainKeys per-dataset
  • apply_run clears selected samples and labels before setting the view

Operator changes:

  • Patches-based searches now convert to patches view before sort_by_similarity when not already on a patches view
  • Fixed result ID collection to use patch IDs (not sample IDs) for patches searches

Modal:

  • Restored Similarity action button to the modal action bar (conditional on EXPLORE mode)

🧪 How is this patch tested? If it is not, please explain why.

Compute similarity index on dataset and on predictions patch view:

import fiftyone.brain as fob

fob.compute_similarity(
    dataset,
    model="clip-vit-base32-torch",
    brain_key="clip_similarity",
    backend="sklearn",
    metric="cosine",
)

fob.compute_similarity(
    dataset,
    patches_field="predictions",
    model="clip-vit-base32-torch",
    brain_key="predictions_similarity",
    backend="sklearn",
    metric="cosine",
)
  • click the similarity popover, the setting icon should open the similarity panel
  • select an image from the sample grid, click the similarity popover, and click "show similar images". This should open the similarity panel and kick off a new similarity run using the last used similarity index.
  • click the similarity popover (no selection on the grid), type in "dog" and hit enter. This should open the similarity panel and kick off a new similarity run using the last used similarity index.
  • open a sample modal, select a "predictions" label, and click similarity popover, click "show similar patches". This should go to the toPatches view first, then open the similarity panel and kick off a new similarity sort run. Click show results, it should load the correct patch search results.
  • open a sample modal, select labels from different fields, similarity popover would display warning texts.

select label -> one click patch search:

select_label_search_similar_patch.mov

select sample -> one click sample search:

select-sample-one-click-search.mov

text -> one click text search:

text-search.mov

📝 Release Notes

Is this a user-facing change that should be mentioned in the release notes?

  • No. You can skip the rest of this section.
  • Yes. Give a description of this change to be included in the release
    notes for FiftyOne users.

What areas of FiftyOne does this PR affect?

  • App: FiftyOne application changes
  • Build: Build and test infrastructure changes
  • Core: Core fiftyone Python library changes
  • Documentation: FiftyOne documentation changes
  • Other

Summary by CodeRabbit

  • New Features

    • Streamlined similarity popover with simple text input and single-action buttons.
    • Per-dataset “last used” similarity key is persisted and restored automatically.
    • Search runs can auto-refresh in real time via background updates.
  • Improvements

    • Advanced similarity controls removed for a cleaner UX; settings preserved via a single settings button.
    • Submissions block when query/input is empty or no image IDs are available.
    • Applying a run now clears prior selections for a consistent view.
  • Bug Fixes

    • Patch-mode searches now return patch-level results when applicable.

@lanzhenw lanzhenw self-assigned this Apr 8, 2026
@lanzhenw lanzhenw requested a review from a team as a code owner April 8, 2026 22:04
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The similarity UI was refactored from a Recoil-driven "sort by similarity" flow into a simplified operator-driven similarity popover (component renamed to SimilarityPopover). Recoil hooks/selectors for similarity were removed and query construction moved to submit-time; the component now uses local textQuery and derives available brain keys and a per-dataset last-used brain key from browser storage. On submit it validates input (image IDs or non-empty text), optionally resolves patches_field, persists the chosen brain key, closes the popover, and invokes SEARCH_OPERATOR_URI (with INIT_RUN_OPERATOR_URI used for delegated runs). Backend/operator changes ensure patches views are created when needed and runs clear selected samples/labels before applying views. Runs can auto-refresh via SSE; new panel/operator constants were added.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant UI as UI (SimilarityPopover)
participant Storage as BrowserStorage
participant Operator as @voxel51/panels/similarity_search
participant InitOp as @voxel51/panels/init_similarity_run
participant Exec as ExecutionStore / SSE
participant Panel as similarity_search_panel

UI->>Storage: read lastUsedBrainKeys[datasetId]
UI->>UI: resolveBrainKey, validate query (image ids or non-empty text)
UI->>Operator: invoke SEARCH_OPERATOR_URI with params (brain_key, query_type, query, reverse=false, k=DEFAULT_K, patches_field?)
Operator-->>UI: response (immediate results OR delegated run id)
alt delegated run
UI->>InitOp: invoke INIT_RUN_OPERATOR_URI with run id
Exec->>UI: SSE notify run updates -> UI refreshes runs
UI->>Panel: open similarity panel (similarity_search_panel)
else immediate results
UI->>Panel: open similarity panel (similarity_search_panel)
end
UI->>Storage: persist lastUsedBrainKeys[datasetId] = brain_key

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • tom-vx51
  • imanjra
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 The PR description addresses all required template sections with substantive detail: Related Issues (closes #FOEPD-3370), comprehensive proposal explanation (popover/panel/operator/modal changes with test steps), testing methodology documented, and release notes correctly marked as non-user-facing.
Title check ✅ Passed The title clearly describes the main change: triggering similarity search runs from the popover in grid/modal, with a bugfix and feature flag removal.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wires-up-grid-action

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

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: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/packages/core/src/components/Actions/Similarity/constants.ts`:
- Around line 1-3: The three duplicate constants SEARCH_OPERATOR_URI,
INIT_RUN_OPERATOR_URI, and PANEL_NAME are defined here and also in the
similarity-search package; either consolidate them to a single source of truth
by removing these local definitions and importing the constants from
app/packages/similarity-search/src/constants.ts (update any references in
Actions/Similarity to use the imported symbols), or if intentional to avoid
cross-package imports add a concise comment above these exports explaining the
reason for duplication and referencing the original file so future maintainers
know it's deliberate.

In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx`:
- Around line 80-86: Extract the literal "open_panel" into a named constant
(e.g., OPEN_PANEL_OPERATOR_URI) and use that constant inside the useCallback in
Similar.tsx; update the executeOperator call in openPanel to pass
OPEN_PANEL_OPERATOR_URI instead of the raw string, keeping the rest of the
payload (PANEL_NAME, isActive, layout) unchanged; align the new constant
alongside existing SEARCH_OPERATOR_URI and INIT_RUN_OPERATOR_URI for
consistency.
- Around line 55-66: The code in the useMemo for resolvedBrainKey is
double-parsing JSON because lastUsedBrainKeys (from useBrowserStorage) already
returns a parsed object; remove the manual JSON.parse and treat
lastUsedBrainKeys as an object/map instead of a string. Update the resolution
logic in resolvedBrainKey to read lastUsedBrainKeys[datasetId] (guarding for
null/undefined) and keep the fallback to keys[0]; also consider switching the
useBrowserStorage invocation that provides lastUsedBrainKeys to a
typed/initial-value form so it returns a typed Record<string,string> (or null)
instead of a raw string, and adjust any related code that currently uses
JSON.stringify/JSON.parse for storage/retrieval.
- Around line 119-135: The call to executeOperator(SEARCH_OPERATOR_URI, params,
...) lacks error handling so failures are silently ignored; update the call to
pass an error callback (or use the existing handleError function pattern from
useSearchSubmission.ts) that logs or surfaces the error and prevents downstream
calls; ensure the callback for delegated results still checks result?.delegated
and only calls executeOperator(INIT_RUN_OPERATOR_URI, {..., operator_run_id:
operatorRunId}, { callback: () => openPanel() }) when no error occurred, and
call openPanel() or an error handler appropriately when errors are received.

In `@app/packages/similarity-search/src/hooks/useSearchSubmission.ts`:
- Around line 37-44: The code in handleOptionSelected double-serializes
lastUsedBrainKeys because it calls JSON.parse on lastUsedBrainKeys and
JSON.stringify when setting it even though useBrowserStorage already serializes
values; change handleOptionSelected to treat lastUsedBrainKeys as an object
(avoid JSON.parse) and call setLastUsedBrainKeys with the merged object directly
(avoid JSON.stringify). Update usage of lastUsedBrainKeys in that function so
you do const current = lastUsedBrainKeys || {} and then setLastUsedBrainKeys({
...current, [datasetId]: input.brainKey }) to let useBrowserStorage handle
serialization.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1af8a575-1320-42d5-8b12-d427eeb2c163

📥 Commits

Reviewing files that changed from the base of the PR and between 4f16c55 and a92b85f4fa2ff41a1e0ac846e528598d96ee312e.

📒 Files selected for processing (6)
  • app/packages/core/src/components/Actions/Similarity/Similar.tsx
  • app/packages/core/src/components/Actions/Similarity/constants.ts
  • app/packages/core/src/components/Actions/Similarity/index.tsx
  • app/packages/core/src/components/Actions/Similarity/utils.ts
  • app/packages/similarity-search/src/constants.ts
  • app/packages/similarity-search/src/hooks/useSearchSubmission.ts

Comment thread app/packages/core/src/components/Actions/Similarity/constants.ts
Comment thread app/packages/core/src/components/Actions/Similarity/Similar.tsx Outdated
Comment thread app/packages/core/src/components/Actions/Similarity/Similar.tsx Outdated
Comment thread app/packages/similarity-search/src/hooks/useSearchSubmission.ts
@coderabbitai coderabbitai Bot requested review from imanjra and tom-vx51 April 9, 2026 01:02
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

♻️ Duplicate comments (2)
app/packages/core/src/components/Actions/Similarity/Similar.tsx (2)

54-68: ⚠️ Potential issue | 🟠 Major

Remove the extra JSON layer around lastUsedBrainKeys.

useBrowserStorage already deserializes on read and serializes on write. Re-parsing in resolvedBrainKey and pre-stringifying before setLastUsedBrainKeys creates a second storage format, which makes this shared key incompatible with code that stores the map directly and can throw at Line 115 once that happens.

Suggested fix
-  const [lastUsedBrainKeys, setLastUsedBrainKeys] =
-    useBrowserStorage("lastUsedBrainKeys");
+  const [lastUsedBrainKeys, setLastUsedBrainKeys] =
+    useBrowserStorage<Record<string, string>>("lastUsedBrainKeys", {});
@@
   const resolvedBrainKey = useMemo(() => {
     if (keys.length === 0) return undefined;
-    try {
-      const stored = lastUsedBrainKeys
-        ? JSON.parse(lastUsedBrainKeys)[datasetId]
-        : null;
-      if (stored && keys.includes(stored)) return stored;
-    } catch {
-      // parse may fail
-    }
+    const stored = lastUsedBrainKeys?.[datasetId];
+    if (stored && keys.includes(stored)) return stored;
     return keys[0];
   }, [keys, lastUsedBrainKeys, datasetId]);
@@
-        const current = lastUsedBrainKeys ? JSON.parse(lastUsedBrainKeys) : {};
-        setLastUsedBrainKeys(
-          JSON.stringify({ ...current, [datasetId]: resolvedBrainKey })
-        );
+        setLastUsedBrainKeys((current = {}) => ({
+          ...current,
+          [datasetId]: resolvedBrainKey,
+        }));

Also applies to: 115-118

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

In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx` around lines
54 - 68, The code double-serializes lastUsedBrainKeys: stop JSON.parse/stringify
and treat the value from useBrowserStorage as an already-deserialized object. In
resolvedBrainKey (and the setter usage around setLastUsedBrainKeys), replace
JSON.parse(lastUsedBrainKeys)[datasetId] with a safe access like
lastUsedBrainKeys?.[datasetId] (guarding for null/undefined) and when updating
use setLastUsedBrainKeys(updatedMap) with the updated object (not
JSON.stringify(updatedMap)). Update references in resolvedBrainKey, and any code
that writes to setLastUsedBrainKeys (lines around setLastUsedBrainKeys usage) to
remove manual serialization and handle null initial state by creating a new
object before setting.

120-138: ⚠️ Potential issue | 🟠 Major

Add failure handling for both operator executions.

The popover closes at Line 120, but neither executeOperator(SEARCH_OPERATOR_URI, ...) nor the delegated INIT_RUN_OPERATOR_URI call handles errors. If either request fails, the user loses the popover and gets a silent no-op.

Suggested fix
+        const handleOperatorError = (error: unknown) => {
+          console.error("Similarity search failed:", error);
+        };
+
         close();
 
         executeOperator(SEARCH_OPERATOR_URI, params, {
           callback: (result) => {
             if (result?.delegated) {
@@
               executeOperator(
                 INIT_RUN_OPERATOR_URI,
                 { ...params, operator_run_id: operatorRunId },
-                { callback: () => openPanel() }
+                {
+                  callback: () => openPanel(),
+                  errorCallback: handleOperatorError,
+                }
               );
             } else {
               openPanel();
             }
           },
+          errorCallback: handleOperatorError,
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx` around lines
120 - 138, The code currently calls close() then
executeOperator(SEARCH_OPERATOR_URI, ...) and a nested
executeOperator(INIT_RUN_OPERATOR_URI, ...) without any failure handling; add
error callbacks (or promise rejection handlers depending on executeOperator API)
to both executeOperator calls so that on error you reopen the popover (or call
openPanel/restore UI) and surface an error message via the same logger/UI
notification used elsewhere; specifically update the executeOperator call for
SEARCH_OPERATOR_URI (and the nested INIT_RUN_OPERATOR_URI call) to provide an
onError/err callback that calls openPanel() (or reverses close()) and reports
the error with context including operator name/params and operator_run_id.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx`:
- Around line 32-36: The anchorRef prop in SimilarityPopoverProps is too narrow
(MutableRefObject) and should be changed to accept RefObject to match callers
and the Popout component; update the SimilarityPopoverProps definition (the
anchorRef?: ...) to use React.RefObject<HTMLElement> (or
React.RefObject<HTMLElement> | null) instead of MutableRefObject<HTMLElement>,
and update any usages/interfaces that reference SimilarityPopoverProps or pass
anchorRef to Popout to ensure types align with Popout's expected
RefObject<HTMLElement> | null.

---

Duplicate comments:
In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx`:
- Around line 54-68: The code double-serializes lastUsedBrainKeys: stop
JSON.parse/stringify and treat the value from useBrowserStorage as an
already-deserialized object. In resolvedBrainKey (and the setter usage around
setLastUsedBrainKeys), replace JSON.parse(lastUsedBrainKeys)[datasetId] with a
safe access like lastUsedBrainKeys?.[datasetId] (guarding for null/undefined)
and when updating use setLastUsedBrainKeys(updatedMap) with the updated object
(not JSON.stringify(updatedMap)). Update references in resolvedBrainKey, and any
code that writes to setLastUsedBrainKeys (lines around setLastUsedBrainKeys
usage) to remove manual serialization and handle null initial state by creating
a new object before setting.
- Around line 120-138: The code currently calls close() then
executeOperator(SEARCH_OPERATOR_URI, ...) and a nested
executeOperator(INIT_RUN_OPERATOR_URI, ...) without any failure handling; add
error callbacks (or promise rejection handlers depending on executeOperator API)
to both executeOperator calls so that on error you reopen the popover (or call
openPanel/restore UI) and surface an error message via the same logger/UI
notification used elsewhere; specifically update the executeOperator call for
SEARCH_OPERATOR_URI (and the nested INIT_RUN_OPERATOR_URI call) to provide an
onError/err callback that calls openPanel() (or reverses close()) and reports
the error with context including operator name/params and operator_run_id.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 080894b9-a979-4802-81a0-941b8b73fb0f

📥 Commits

Reviewing files that changed from the base of the PR and between a92b85f4fa2ff41a1e0ac846e528598d96ee312e and 792bf358d0b4a0600bea45014ab1c79d04cf36cb.

📒 Files selected for processing (3)
  • app/packages/core/src/components/Actions/Similarity/Similar.tsx
  • app/packages/similarity-search/src/constants.ts
  • app/packages/similarity-search/src/hooks/useRuns.ts

Comment thread app/packages/core/src/components/Actions/Similarity/Similar.tsx
@lanzhenw lanzhenw requested a review from a team as a code owner April 9, 2026 01:50
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: 3

♻️ Duplicate comments (2)
app/packages/core/src/components/Actions/Similarity/Similar.tsx (2)

134-159: ⚠️ Potential issue | 🟠 Major

Handle operator failures before discarding the popover state.

The popover is closed first, and this request path has no failure callback. If the operator call errors, the user loses the query input, gets no feedback, and the panel never opens.

#!/bin/bash
set -euo pipefail

echo "Current SimilarityPopover submission flow:"
sed -n '134,170p' app/packages/core/src/components/Actions/Similarity/Similar.tsx

echo
echo "executeOperator definition and option types:"
rg -n -C3 'export .*executeOperator|function executeOperator|const executeOperator' app/packages

echo
echo "Existing failure-handled operator submission patterns:"
rg -n -C3 'errorCallback|handleError|onError' app/packages/core/src/components app/packages/similarity-search/src/hooks
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx` around lines
134 - 159, The popover is closed immediately before calling
executeOperator(SEARCH_OPERATOR_URI, params, ...) which means on failures users
lose input and the panel never opens; modify the flow so you only call close()
and clear fos.modalSelector after a successful operator run: move
close()/set(fos.modalSelector, null) into the success path (onSearchComplete)
and add an error/failure handler to both executeOperator calls (the
SEARCH_OPERATOR_URI call and the delegated INIT_RUN_OPERATOR_URI call) to
restore the popover state (do not call openPanel) and surface an error (e.g.,
show a toast or set error state) when either operator fails; keep the existing
onSearchComplete/openPanel behavior for success and ensure you reference
executeOperator, SEARCH_OPERATOR_URI, INIT_RUN_OPERATOR_URI, params,
onSearchComplete, close, set(fos.modalSelector, ...), and openPanel when making
these changes.

6-9: ⚠️ Potential issue | 🟠 Major

Widen anchorRef to the ref type the callers actually pass.

MutableRefObject<HTMLElement> is narrower than the usual useRef<HTMLDivElement>(null) result and narrower than the Popout anchor contract, so this either forces casts or fails type-checking.

Proposed fix
 import React, {
-  type MutableRefObject,
+  type RefObject,
   useCallback,
   useMemo,
   useState,
 } from "react";
@@
 interface SimilarityPopoverProps {
   modal: boolean;
   isImageSearch: boolean;
   close: () => void;
-  anchorRef?: MutableRefObject<HTMLElement>;
+  anchorRef?: RefObject<HTMLElement | null>;
 }
#!/bin/bash
set -euo pipefail

echo "SimilarityPopover prop type:"
rg -n -C2 'anchorRef\?:' app/packages/core/src/components/Actions/Similarity/Similar.tsx

echo
echo "Caller-side ref creation and prop passing:"
rg -n -C2 'useRef<|anchorRef=' app/packages/core/src/components/Actions/Similarity/index.tsx

echo
echo "Popout anchorRef prop type:"
rg -n -C2 'anchorRef\?:|anchorRef=' app/packages/core/src/components/Actions/Popout.tsx

Also applies to: 33-38

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

In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx` around lines
6 - 9, The prop type for anchorRef in Similar.tsx is too narrow
(MutableRefObject<HTMLElement>) and conflicts with callers that pass
useRef<HTMLDivElement>(null) and the Popout anchor contract; update the
anchorRef prop type on the Similar component to accept the broader ref shapes
used by callers (e.g., React.RefObject<HTMLElement | null> or
MutableRefObject<HTMLElement | null> and allow undefined), mirror that wider
type where Similar passes anchorRef into Popout, and remove any forced casts at
call sites so the ref flows without type errors (look for the anchorRef prop in
Similar.tsx, the caller in index.tsx, and Popout's anchorRef type to align
them).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx`:
- Around line 71-82: The submit path currently does an unguarded JSON.parse of
lastUsedBrainKeys which can throw on malformed/stale storage and abort the
search; make the write side defensive just like the read side by wrapping
JSON.parse(...) in a try/catch (or use a safe parse helper) before
merging/updating the object for datasetId, falling back to an empty object when
parse fails, then safely stringify and write back; update the code in the submit
handler (the block referencing lastUsedBrainKeys around lines ~129-132) and keep
behavior consistent with the useMemo resolvedBrainKey logic so malformed stored
values never throw.
- Around line 205-208: The warning text is hard-coded and incorrectly references
a selected label/patch flow even when the modal is doing sample-based search;
update the UI to render context-aware messages instead of the fixed string.
Replace the PopoutSectionTitle usage that currently shows "No similarity index
found for the selected label field" (rendered when showNoIndexWarning is true)
with a conditional that checks whether a label field is actually selected (e.g.,
selectedLabelField or the modal's label selection state) and render one message
for label-based searches and a different message for sample-based fallback
(e.g., mention "sample-based search using sample ID" or similar). Apply the same
conditional replacement for the other similar block around lines 226-230 so both
warnings/CTAs reflect the active search mode.
- Around line 116-127: The operator payload currently built in the block around
resolvePatchesField(...) omits the active view stages so searches fall back to
the full dataset; add a source_view property to the params object containing the
current view's stages (e.g., JSON-serialised view stages from your component's
active view — something like currentView?.stages or via getActiveViewStages()),
ensuring you set params.source_view only when those stages exist; keep the rest
of the payload (brain_key, query_type, query, reverse, k) unchanged so
plugins/panels/similarity_search/operators.py will use the intended
filtered/sliced view.

---

Duplicate comments:
In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx`:
- Around line 134-159: The popover is closed immediately before calling
executeOperator(SEARCH_OPERATOR_URI, params, ...) which means on failures users
lose input and the panel never opens; modify the flow so you only call close()
and clear fos.modalSelector after a successful operator run: move
close()/set(fos.modalSelector, null) into the success path (onSearchComplete)
and add an error/failure handler to both executeOperator calls (the
SEARCH_OPERATOR_URI call and the delegated INIT_RUN_OPERATOR_URI call) to
restore the popover state (do not call openPanel) and surface an error (e.g.,
show a toast or set error state) when either operator fails; keep the existing
onSearchComplete/openPanel behavior for success and ensure you reference
executeOperator, SEARCH_OPERATOR_URI, INIT_RUN_OPERATOR_URI, params,
onSearchComplete, close, set(fos.modalSelector, ...), and openPanel when making
these changes.
- Around line 6-9: The prop type for anchorRef in Similar.tsx is too narrow
(MutableRefObject<HTMLElement>) and conflicts with callers that pass
useRef<HTMLDivElement>(null) and the Popout anchor contract; update the
anchorRef prop type on the Similar component to accept the broader ref shapes
used by callers (e.g., React.RefObject<HTMLElement | null> or
MutableRefObject<HTMLElement | null> and allow undefined), mirror that wider
type where Similar passes anchorRef into Popout, and remove any forced casts at
call sites so the ref flows without type errors (look for the anchorRef prop in
Similar.tsx, the caller in index.tsx, and Popout's anchorRef type to align
them).
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8d46eb29-f914-4e88-9045-4814982add02

📥 Commits

Reviewing files that changed from the base of the PR and between 792bf358d0b4a0600bea45014ab1c79d04cf36cb and 8605bff6c6ada98816331f53773aa47171887297.

📒 Files selected for processing (5)
  • app/packages/core/src/components/Actions/Similarity/Similar.tsx
  • app/packages/core/src/components/Actions/Similarity/utils.ts
  • app/packages/core/src/components/Modal/Actions/index.tsx
  • plugins/panels/similarity_search/__init__.py
  • plugins/panels/similarity_search/operators.py

Comment thread app/packages/core/src/components/Actions/Similarity/Similar.tsx Outdated
Comment thread app/packages/core/src/components/Actions/Similarity/Similar.tsx Outdated
Comment thread app/packages/core/src/components/Actions/Similarity/Similar.tsx
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.

♻️ Duplicate comments (4)
app/packages/core/src/components/Actions/Similarity/Similar.tsx (4)

29-33: ⚠️ Potential issue | 🟠 Major

Broaden anchorRef to RefObject.

Similarity passes useRef<HTMLDivElement>(null), but this prop only accepts MutableRefObject<HTMLElement>. That is narrower than the caller and the Popout boundary, so this can fail type-checking under the current React typings.

Suggested fix
 import React, {
-  type MutableRefObject,
+  type RefObject,
   useCallback,
   useMemo,
   useState,
 } from "react";
@@
 interface SimilarityPopoverProps {
   modal: boolean;
   isImageSearch: boolean;
   close: () => void;
-  anchorRef?: MutableRefObject<HTMLElement>;
+  anchorRef?: RefObject<HTMLElement | null>;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx` around lines
29 - 33, The prop type for anchorRef in SimilarityPopoverProps is too narrow
(MutableRefObject<HTMLElement>) and can reject callers using RefObject like
useRef<HTMLDivElement>(null); update the type of anchorRef to accept
React.RefObject<HTMLElement> or the broader React.RefObject<HTMLElement> |
React.MutableRefObject<HTMLElement> (or simply React.RefObject<HTMLElement> |
null) so the Similarity component and the Popout boundary accept
useRef<HTMLDivElement>(null) without type errors; adjust the interface
SimilarityPopoverProps and any consumers (e.g., Similarity, Popout) to use the
broader ref type.

125-127: ⚠️ Potential issue | 🟡 Minor

Guard the write-path storage parse.

resolvedBrainKey already treats malformed storage defensively, but this submit path does an unguarded JSON.parse. A stale browser value will throw here and abort the search before the operator runs.

Suggested fix
-        const current = lastUsedBrainKeys ? JSON.parse(lastUsedBrainKeys) : {};
+        let current = {};
+        try {
+          current = lastUsedBrainKeys ? JSON.parse(lastUsedBrainKeys) : {};
+        } catch {
+          current = {};
+        }
         setLastUsedBrainKeys(
           JSON.stringify({ ...current, [datasetId]: resolvedBrainKey })
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx` around lines
125 - 127, Guard the JSON.parse of lastUsedBrainKeys before writing: in the
submit path that calls setLastUsedBrainKeys, wrap parsing of lastUsedBrainKeys
in a safe try/catch (or use a safeParse helper) and fall back to an empty object
on error, then merge { [datasetId]: resolvedBrainKey } into that safe current
object and stringify for setLastUsedBrainKeys; update the code around
lastUsedBrainKeys / setLastUsedBrainKeys in Similar.tsx to use this guarded
parse so a stale/malformed stored value doesn't throw and abort the operator.

213-216: ⚠️ Potential issue | 🟡 Minor

Make the no-index warning match the actual search mode.

When modal image search falls back to the sample ID because no label is selected, this still says “selected label field”. That misdescribes the path the user is on.

Suggested fix
       {showNoIndexWarning && (
         <PopoutSectionTitle style={{ fontSize: 12 }}>
-          No similarity index found for the selected label field
+          {hasSelectedLabels
+            ? "No similarity index found for the selected label field"
+            : "No similarity index found for sample-based similarity search"}
         </PopoutSectionTitle>
       )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx` around lines
213 - 216, The warning text "No similarity index found for the selected label
field" is inaccurate when modal image search falls back to using sample ID;
update the message in Similar.tsx where showNoIndexWarning is rendered (the
PopoutSectionTitle) to reflect the actual search mode: detect whether a label
field is selected (or whether modal image search is falling back to sample ID)
and render a conditional string such as "No similarity index found for the
selected label field" when a label is chosen, otherwise "No similarity index
found for the current search mode (searching by sample ID)" — adjust the
conditional logic around showNoIndexWarning (or use the existing selectedLabel/
searchMode state) so the displayed text matches the active search path.

112-123: ⚠️ Potential issue | 🟠 Major

Include the active view in the operator payload.

This payload never passes the current grid view. Searches started from a filtered or sliced grid can therefore run against the full dataset instead of the active view.

Suggested fix
       async () => {
         if (!resolvedBrainKey) return;
+        const sourceView = await snapshot.getPromise(fos.view);

         const queryIds = isImageSearch
           ? await getQueryIds(snapshot, resolvedBrainKey)
           : undefined;
@@
         const params: Record<string, unknown> = {
           brain_key: resolvedBrainKey,
           query_type: isImageSearch ? "image" : "text",
           query: isImageSearch ? queryIds : textQuery.trim(),
           reverse: false,
           k: DEFAULT_K,
+          source_view: sourceView,
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx` around lines
112 - 123, The operator payload built in params (in Similar.tsx near
resolvePatchesField) is missing the current active view, so searches ignore grid
filters; add the active view identifier to the params object (e.g.,
params.active_view = activeView or params.view_id = activeGridView depending on
the component prop/name you have) before sending the request so the search uses
the current grid slice/filter; ensure you reference the actual component
variable that holds the active view (for example activeView, currentView, or
activeGridView) and include it alongside existing keys like brain_key,
query_type, query, reverse, and k.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx`:
- Around line 29-33: The prop type for anchorRef in SimilarityPopoverProps is
too narrow (MutableRefObject<HTMLElement>) and can reject callers using
RefObject like useRef<HTMLDivElement>(null); update the type of anchorRef to
accept React.RefObject<HTMLElement> or the broader React.RefObject<HTMLElement>
| React.MutableRefObject<HTMLElement> (or simply React.RefObject<HTMLElement> |
null) so the Similarity component and the Popout boundary accept
useRef<HTMLDivElement>(null) without type errors; adjust the interface
SimilarityPopoverProps and any consumers (e.g., Similarity, Popout) to use the
broader ref type.
- Around line 125-127: Guard the JSON.parse of lastUsedBrainKeys before writing:
in the submit path that calls setLastUsedBrainKeys, wrap parsing of
lastUsedBrainKeys in a safe try/catch (or use a safeParse helper) and fall back
to an empty object on error, then merge { [datasetId]: resolvedBrainKey } into
that safe current object and stringify for setLastUsedBrainKeys; update the code
around lastUsedBrainKeys / setLastUsedBrainKeys in Similar.tsx to use this
guarded parse so a stale/malformed stored value doesn't throw and abort the
operator.
- Around line 213-216: The warning text "No similarity index found for the
selected label field" is inaccurate when modal image search falls back to using
sample ID; update the message in Similar.tsx where showNoIndexWarning is
rendered (the PopoutSectionTitle) to reflect the actual search mode: detect
whether a label field is selected (or whether modal image search is falling back
to sample ID) and render a conditional string such as "No similarity index found
for the selected label field" when a label is chosen, otherwise "No similarity
index found for the current search mode (searching by sample ID)" — adjust the
conditional logic around showNoIndexWarning (or use the existing selectedLabel/
searchMode state) so the displayed text matches the active search path.
- Around line 112-123: The operator payload built in params (in Similar.tsx near
resolvePatchesField) is missing the current active view, so searches ignore grid
filters; add the active view identifier to the params object (e.g.,
params.active_view = activeView or params.view_id = activeGridView depending on
the component prop/name you have) before sending the request so the search uses
the current grid slice/filter; ensure you reference the actual component
variable that holds the active view (for example activeView, currentView, or
activeGridView) and include it alongside existing keys like brain_key,
query_type, query, reverse, and k.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a0b11daa-2640-4a34-9ee1-f549443c2f18

📥 Commits

Reviewing files that changed from the base of the PR and between 8605bff6c6ada98816331f53773aa47171887297 and 1f6bc62dcf88654ec99e65fa13c7d3d2030f9a34.

📒 Files selected for processing (1)
  • app/packages/core/src/components/Actions/Similarity/Similar.tsx

@lanzhenw lanzhenw force-pushed the feat/wires-up-grid-action branch from 1f6bc62 to d65b067 Compare April 9, 2026 03:29
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/packages/core/src/components/Actions/Similarity/Similar.tsx`:
- Around line 144-155: The ToPatches stage kwargs currently only include the
field; update the executeOperator call for "set_view" so the kwargs array
matches the canonical pattern used in useToPatches.ts by adding the ["_state",
null] entry alongside ["field", patchesField] (i.e., include the _state
parameter set to null in the kwargs for the ToPatches stage passed to
executeOperator) so the stage is constructed correctly when openPanel() is
called.

In `@app/packages/core/src/components/Actions/Similarity/utils.ts`:
- Around line 18-26: The current logic in the block using
snapshot.getPromise(fos.similarityMethods) and computing labels_field can return
an empty array when selectedLabelIds exist but labels_field is undefined or no
labels match that field; change the branch in the function that uses
selectedLabelIds/selectedLabelMap (the block referencing labels_field) to detect
when labels_field is falsy or when the filtered result is empty and, in that
case, return a fallback containing modalSampleId instead of an empty array;
specifically update the code that computes labels_field and the subsequent
return so that if !labels_field or [...selectedLabelIds].filter(...).length ===
0 you return [modalSampleId], otherwise return the filtered ids.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: febe50d7-b8b3-43b6-a511-97ecab3cf50e

📥 Commits

Reviewing files that changed from the base of the PR and between 1f6bc62dcf88654ec99e65fa13c7d3d2030f9a34 and d65b0672c414ca5b020eb82244d80faf58fd52cf.

📒 Files selected for processing (5)
  • app/packages/core/src/components/Actions/Similarity/Similar.tsx
  • app/packages/core/src/components/Actions/Similarity/utils.ts
  • app/packages/core/src/components/Modal/Actions/index.tsx
  • plugins/panels/similarity_search/__init__.py
  • plugins/panels/similarity_search/operators.py

Comment thread app/packages/core/src/components/Actions/Similarity/Similar.tsx Outdated
Comment thread app/packages/core/src/components/Actions/Similarity/utils.ts
@lanzhenw lanzhenw changed the title [SSP] Trigger new sort run from sort popover in the grid [SSP] Trigger new sort run from similarity popover in the grid/sample modal Apr 9, 2026
Comment thread app/packages/similarity-search/src/hooks/useRuns.ts
Copy link
Copy Markdown
Contributor

@imanjra imanjra left a comment

Choose a reason for hiding this comment

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

can we please add a loading state. There is decent delay before panel opens and it looks like the action did not work to the user

@imanjra
Copy link
Copy Markdown
Contributor

imanjra commented Apr 9, 2026

Couple of thoughts from local testing:

  • When there are no keys, should there be a way to take you to the panel?
image
  • Should we just launch the panel when clicking the search icon? (avoid the need to implement loading state or addresing item below)

@lanzhenw lanzhenw force-pushed the feat/wires-up-grid-action branch from bbce196 to aea065a Compare April 9, 2026 20:48
@lanzhenw lanzhenw requested a review from a team as a code owner April 9, 2026 20:57
@lanzhenw lanzhenw changed the title [SSP] Trigger new sort run from similarity popover in the grid/sample modal [SSP] Trigger new sort run from similarity popover in the grid/sample modal + bugfix + remove feature flag Apr 9, 2026
@lanzhenw lanzhenw force-pushed the feat/wires-up-grid-action branch from c69c432 to 3dc98bf Compare April 9, 2026 21:40
@lanzhenw
Copy link
Copy Markdown
Member Author

lanzhenw commented Apr 9, 2026

When no index applicable, clicking the icon opens the panel directly;
Added processing state on the popover icon;

Screen.Recording.2026-04-09.at.5.02.08.PM.mov

@lanzhenw lanzhenw enabled auto-merge April 9, 2026 22:05
@lanzhenw lanzhenw merged commit b86af47 into develop Apr 9, 2026
22 of 23 checks passed
@lanzhenw lanzhenw deleted the feat/wires-up-grid-action branch April 9, 2026 22:08
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.

3 participants