Skip to content

Conversation

@knollengewaechs
Copy link
Contributor

@knollengewaechs knollengewaechs commented Nov 21, 2025

URL of deployed dev instance (used for testing):

Steps to test:

  • From anywhere, open the command palette by pressing ctrl + P
  • click on "> View dataset..." and select any dataset -> you should be directed to its view mode
  • same for "> View annotaion..."
    • the list of annotations should be sorted like in the dashboard, so the annotation that was edited most recently should appear first
  • Do so in any admin view and within DS/annotation view mode
  • when having an annotation open, you should be able to select the annotation tools and also see the corresponding shortcuts

Notes

TODOs:

  • check whether closing can be done without changing the key, e.g. with pending promises in viewDatasetEntry -> didnt find anything better
  • fix typing errors
  • fix pipeline
  • [ wont do ] when hitting "view dataset" (or annotation), reset input -> does not seem feasable because input is not easy to be manipulated from outside the libs code (from what i can tell)
  • omit dangerouslySetInnerHTML

Issues:


(Please delete unneeded items, merge only when none are left open)

  • Added changelog entry (create a $PR_NUMBER.md file in unreleased_changes or use ./tools/create-changelog-entry.py)
  • Added migration guide entry if applicable (edit the same file as for the changelog)
  • Updated documentation if applicable
  • Adapted wk-libs python client if relevant API parts change
  • Removed dev-only changes like prints and application.conf edits
  • Considered common edge cases
  • Needs datastore update after deployment

@knollengewaechs knollengewaechs self-assigned this Nov 21, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

📝 Walkthrough

Walkthrough

Extracts dataset view URL generation into a public accessor and replaces inline URL construction across several components; adds dynamic dataset/annotation commands and navigation, view-mode entries, keyboard shortcuts, HTML sanitization, and scrolling to the command palette.

Changes

Cohort / File(s) Summary
Dataset URL Accessor
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts
Adds exported `getViewDatasetURL(dataset: APIDataset
Dataset URL Consumers
frontend/javascripts/dashboard/advanced_dataset/dataset_action_view.tsx, frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx, frontend/javascripts/admin/dataset/dataset_add_view.tsx, frontend/javascripts/admin/job/job_list_view.tsx, frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx, frontend/javascripts/viewer/view/right-border-tabs/dataset_info_tab_view.tsx
Replaces manual /datasets/${getReadableURLPart(...)}/view constructions and navigation assignments with calls to getViewDatasetURL(...). Imports updated accordingly.
Command Palette (features & types)
frontend/javascripts/viewer/view/components/command_palette.tsx
Introduces dynamic command sections for datasets/annotations, ExtendedCommand type, DynamicCommands enum, DOMPurify-based sanitizer, dataset/annotation loaders, view-mode toggle entries, keyboard-shortcut mapping for annotation tools, stateful command lists, and navigation improvements (uses navigate in places).
Command Palette Styling
frontend/stylesheets/command_palette.less
Adds vertical scrolling (overflow-y: auto) and scrollbar-width: thin to .command-palette-suggestionsContainerOpen.
Dependencies
package.json
Adds dompurify@^3.3.0 to dependencies and devDependencies.
Changelog
unreleased_changes/9087.md
Documents ability to open datasets and annotations from the command palette.
Misc docs
.github/common_edge_cases.md
Adds an edge-case note about annotations opened by multiple users concurrently.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • command_palette.tsx: review async loaders, Toast usage, DOMPurify sanitization, keyboard shortcut handling, and navigation changes.
  • dataset_accessor.ts: verify signature accepts the extended { name, id } shape and produces correct readable URL parts.
  • Spot-check representative consumer files (dataset_table, dataset_action_view) for correct import/update and no regressions from replacing template strings with accessor.
  • Check new dependency addition (dompurify) for build/test impact.

Possibly related issues

  • Follow-up Issue for Command Palette #8441 — This PR adds dynamic dataset/annotation command palette entries and uses the new getViewDatasetURL, aligning with the command-palette enhancements described in that issue.

Suggested reviewers

  • MichaelBuessemeyer
  • philippotto

Poem

🐰 I hopped through code and found a view,
URLs tucked safe where helpers brew.
Commands now bloom and lists scroll neat,
Shortcuts hum with every beat.
A rabbit cheers — navigation complete! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'View Annotations and Datasets from Command Palette' accurately and clearly summarizes the main feature being added: the ability to view datasets and annotations from the command palette.
Description check ✅ Passed The description is well-related to the changeset, providing testing steps, noting new dependencies (dompurify), documenting TODOs and completed items, and linking to the relevant issue #8441.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch improve-command-palette

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.

@knollengewaechs knollengewaechs changed the title Improve command palette View Annotations and Datasets from Command Palette Nov 21, 2025
@knollengewaechs knollengewaechs marked this pull request as ready for review November 24, 2025 15:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
frontend/javascripts/viewer/view/components/command_palette.tsx (3)

73-75: Remove leftover debug console.log statements

The console.log("Rendering Command Palette") and console.log("h") logs look like temporary debugging output and were already flagged to be removed in a previous review. They should be dropped before merging to avoid noisy consoles in production.

-  console.log("Rendering Command Palette");
...
-    console.log("h");

Also applies to: 117-119


269-285: View mode entries and tool shortcuts integration are well structured

getViewModeEntries (including “Reset layout”) and shortCutDictForTools/getToolEntries are wired cleanly:

  • View mode commands correctly dispatch setViewModeAction and emit LayoutEvents.resetLayout.
  • Tool commands include readable shortcut labels while still dispatching setToolAction.

Once the typing for shortcut is adjusted as suggested above, this section is good to go.

Also applies to: 286-297, 299-315


31-38: Fix command typing for shortcut and highlight to unblock TypeScript/CI

The new shortcut field on commands (line 310 in getToolEntries) and use of highlight in renderCommand (line 366) conflict with the Command/CommandWithoutId typings with strict TypeScript enabled:

  • Object literal with shortcut cannot be assigned to CommandWithoutId[] type.
  • Destructuring highlight and shortcut in renderCommand accesses fields not present on Command type.

Fix by extending the local CommandWithoutId type to include optional fields and casting in renderCommand:

- type CommandWithoutId = Omit<Command, "id">;
+ type CommandWithoutId = Omit<Command, "id"> & {
+   shortcut?: string;
+ };

And in renderCommand:

- renderCommand={(command) => {
-   const { name, shortcut, highlight: maybeDirtyString } = command;
+ renderCommand={(command) => {
+   const { name, shortcut, highlight: maybeDirtyString } = command as Command & {
+     shortcut?: string;
+     highlight?: string;
+   };

This allows your local commands to carry shortcuts and access the library's highlight field without TS errors.

Also remove debug console.log statements at lines 74 and 118.

🧹 Nitpick comments (2)
frontend/javascripts/dashboard/advanced_dataset/dataset_action_view.tsx (1)

23-24: Use getViewDatasetURL consistently for “View Dataset” navigation

The new use of getViewDatasetURL(dataset) for the inline “View Dataset” link looks good and keeps URL construction centralized.

To avoid future drift, you could also switch the context‑menu “View” action to use the same helper:

-            onClick: () => {
-              window.location.href = `/datasets/${getReadableURLPart(dataset)}/view`;
-            },
+            onClick: () => {
+              window.location.href = getViewDatasetURL(dataset);
+            },

This keeps all dataset view URLs in sync.

Also applies to: 262-265, 313-320

frontend/javascripts/viewer/view/components/command_palette.tsx (1)

69-72: Reconsider manual HTML sanitization before dangerouslySetInnerHTML

cleanStringOfMostHTML plus dangerouslySetInnerHTML is a reasonable first defense, but it’s still a hand‑rolled sanitizer around user‑influenced strings (e.g. dataset/annotation names via highlight). This is brittle and easy to get wrong over time.

If you keep dangerouslySetInnerHTML, consider:

  • Using a dedicated sanitizer (e.g. DOMPurify) with a strict allowlist (only <b>).
  • Or avoiding HTML altogether by rendering plain text and letting React handle highlighting via JSX instead of raw HTML.

That would fully address the XSS warning and make the code easier to reason about.

Also applies to: 365-381

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9816db and b0cb4ac.

📒 Files selected for processing (3)
  • frontend/javascripts/dashboard/advanced_dataset/dataset_action_view.tsx (2 hunks)
  • frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1 hunks)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (7 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
🧬 Code graph analysis (2)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
frontend/javascripts/types/api_types.ts (2)
  • APIDataset (243-246)
  • APIDatasetCompact (271-276)
frontend/javascripts/dashboard/advanced_dataset/dataset_action_view.tsx (1)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
  • getViewDatasetURL (753-755)
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 374-374: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 GitHub Actions: CI Pipeline
frontend/javascripts/viewer/view/components/command_palette.tsx

[error] 310-310: TypeScript error TS2353: Object literal may only specify known properties, and 'shortcut' does not exist in type 'CommandWithoutId'.

🪛 GitHub Check: backend-tests
frontend/javascripts/viewer/view/components/command_palette.tsx

[failure] 310-310:
Object literal may only specify known properties, and 'shortcut' does not exist in type 'CommandWithoutId'.


[failure] 366-366:
Property 'highlight' does not exist on type 'Command'.


[failure] 366-366:
Property 'shortcut' does not exist on type 'Command'.

🪛 GitHub Check: frontend-tests
frontend/javascripts/viewer/view/components/command_palette.tsx

[failure] 310-310:
Object literal may only specify known properties, and 'shortcut' does not exist in type 'CommandWithoutId'.


[failure] 366-366:
Property 'highlight' does not exist on type 'Command'.


[failure] 366-366:
Property 'shortcut' does not exist on type 'Command'.

🔇 Additional comments (2)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)

743-755: New URL helper is consistent and type‑safe

getViewDatasetURL correctly reuses getReadableURLPart and matches the existing /datasets/{readable}/view pattern for both APIDataset and APIDatasetCompact. No issues from a typing or correctness perspective.

frontend/javascripts/viewer/view/components/command_palette.tsx (1)

117-145: Dynamic dataset/annotation commands and palette reset logic look sound

The dynamic commands (viewDataset, viewAnnotation) plus handleSelect/closePalette and the static command aggregation in allStaticCommands are coherent:

  • Selecting “> View Dataset...” / “> View Annotation...” expands into concrete items via getDatasetItems / getAnnotationItems.
  • Empty lists are handled with a Toast message.
  • Final selections close the palette via closePalette and reset commands on onRequestClose.

No functional issues stand out here.

Also applies to: 146-157, 164-175, 177-181, 327-341

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0cb4ac and 62984ae.

📒 Files selected for processing (1)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (7 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 376-376: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 GitHub Check: frontend-tests
frontend/javascripts/viewer/view/components/command_palette.tsx

[failure] 368-368:
Property 'highlight' does not exist on type 'Command'.


[failure] 368-368:
Property 'shortcut' does not exist on type 'Command'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (1)

34-36: Fix extended command types for shortcut/highlight to satisfy TypeScript.

frontend-tests is failing because CommandWithoutId doesn’t declare shortcut, and renderCommand destructures shortcut/highlight from Command, which doesn’t include those fields in the lib typings. Extending the type locally and casting inside renderCommand keeps things type-safe while allowing the extra props.

-// duplicate fields because otherwise, optional fields of Command yield errors
-type CommandWithoutId = Omit<Command, "id">;
+// Extend react-command-palette's Command with our own optional fields.
+type ExtendedCommand = Command & {
+  shortcut?: string;
+  highlight?: string;
+};
+
+// duplicate fields because otherwise, optional fields of Command yield errors
+type CommandWithoutId = Omit<ExtendedCommand, "id">;
@@
-      renderCommand={(command) => {
-        const { name, shortcut, highlight: maybeDirtyString } = command;
+      renderCommand={(command) => {
+        const { name, shortcut = "", highlight: maybeDirtyString } =
+          command as ExtendedCommand;

This also resolves the object-literal error in getToolEntries, since shortcut becomes part of CommandWithoutId.

Also applies to: 285-297, 309-309, 365-367

🧹 Nitpick comments (2)
frontend/javascripts/viewer/view/components/command_palette.tsx (2)

71-74: Custom HTML cleaning + dangerouslySetInnerHTML is brittle; consider a safer approach.

cleanStringOfMostHTML plus dangerouslySetInnerHTML is narrowly scoped to allow only <b> tags, but regex-based HTML sanitizers are easy to get wrong and static analysis flags this as a potential XSS vector. If feasible, consider either using a small sanitizer (e.g., DOMPurify) on highlight, or parsing the <b> markers and rebuilding JSX instead of injecting raw HTML.

Also applies to: 373-377


117-145: Tighten handleSelect typing and add error handling for async dataset/annotation loads.

handleSelect is typed as Record<string, unknown>, and failures in getDatasets/getReadableAnnotations would currently bubble up and break the palette. You can leverage the lib’s Command type and surface backend failures via Toast instead.

-  const handleSelect = useCallback(async (command: Record<string, unknown>) => {
-    if (typeof command === "string") {
-      return;
-    }
-
-    if (command.name === DynamicCommands.viewDataset) {
-      const items = await getDatasetItems();
-      if (items.length > 0) {
-        setCommands(items);
-      } else {
-        Toast.info("No datasets available.");
-      }
-      return;
-    }
-
-    if (command.name === DynamicCommands.viewAnnotation) {
-      const items = await getAnnotationItems();
-      if (items.length > 0) {
-        setCommands(items);
-      } else {
-        Toast.info("No annotations available.");
-      }
-      return;
-    }
-
-    closePalette();
-  }, []);
+  const handleSelect = useCallback(
+    async (command: Command | string) => {
+      if (typeof command === "string") {
+        return;
+      }
+
+      if (command.name === DynamicCommands.viewDataset) {
+        try {
+          const items = await getDatasetItems();
+          if (items.length > 0) {
+            setCommands(items);
+          } else {
+            Toast.info("No datasets available.");
+          }
+        } catch {
+          Toast.info("Failed to load datasets.");
+        }
+        return;
+      }
+
+      if (command.name === DynamicCommands.viewAnnotation) {
+        try {
+          const items = await getAnnotationItems();
+          if (items.length > 0) {
+            setCommands(items);
+          } else {
+            Toast.info("No annotations available.");
+          }
+        } catch {
+          Toast.info("Failed to load annotations.");
+        }
+        return;
+      }
+
+      closePalette();
+    },
+    [],
+  );

This makes the callback type-accurate and avoids uncaught errors when the dataset/annotation requests fail.

Also applies to: 145-155, 163-175, 176-180

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c62998b and 320114e.

📒 Files selected for processing (1)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (7 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
🧬 Code graph analysis (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (4)
frontend/javascripts/admin/rest_api.ts (2)
  • getDatasets (995-1024)
  • getReadableAnnotations (472-479)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
  • getViewDatasetURL (753-757)
frontend/javascripts/viewer/constants.ts (1)
  • ViewModeValues (4-4)
frontend/javascripts/viewer/model/actions/settings_actions.ts (1)
  • setViewModeAction (115-119)
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 373-373: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 GitHub Check: frontend-tests
frontend/javascripts/viewer/view/components/command_palette.tsx

[failure] 309-309:
Object literal may only specify known properties, and 'shortcut' does not exist in type 'CommandWithoutId'.


[failure] 365-365:
Property 'highlight' does not exist on type 'Command'.


[failure] 365-365:
Property 'shortcut' does not exist on type 'Command'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (2)
frontend/javascripts/viewer/view/components/command_palette.tsx (2)

268-283: View-mode and tool commands + shortcuts look good; keep them aligned with actual keybindings.

getViewModeEntries and shortCutDictForTools make the palette much more discoverable and give users a clear mapping from tools to "Ctrl + K, …" combos. Just ensure these strings stay in sync with whatever actually binds those shortcuts so the palette doesn’t drift out of date.

Also applies to: 285-297, 298-314


326-339: Palette state and paletteKey handling are consistent with the dynamic submenu UX.

Using allStaticCommands as the baseline plus commands state, a paletteKey-driven remount, closeOnSelect={false}, and onRequestClose={() => setCommands(allStaticCommands)} matches the intended behaviour (expand datasets/annotations in-place, but reset to the static set whenever the palette is closed). Given the palette blocks interaction with the rest of the UI, this should keep the visible commands in sync with the viewer state on each open.

Also applies to: 341-349, 353-364

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
frontend/javascripts/viewer/view/components/command_palette.tsx (2)

367-384: Cast command to extended type to fix TypeScript errors.

The renderCommand callback receives a Command type from react-command-palette, which doesn't include shortcut or highlight properties, causing build failures.

Apply this diff to cast the command to your extended type:

       renderCommand={(command) => {
-        const { shortcut, highlight: maybeDirtyString, name } = command;
+        const { shortcut, highlight: maybeDirtyString, name } =
+          command as Command & { shortcut?: string; highlight?: string };
         const cleanString = cleanStringOfMostHTML(maybeDirtyString);

As per the static analysis hints. This mirrors the fix needed for the type definition at line 34.


34-34: Fix TypeScript build errors by extending the Command type.

The CommandWithoutId type doesn't include the shortcut and highlight properties you're using throughout the file, causing build failures at lines 312 and 368.

Apply this diff to extend the type:

-// duplicate fields because otherwise, optional fields of Command yield errors
-type CommandWithoutId = Omit<Command, "id">;
+// Extend Command type to include optional shortcut and highlight fields
+type CommandWithoutId = Omit<Command, "id"> & {
+  shortcut?: string;
+  highlight?: string;
+};

As per the static analysis hints.

🧹 Nitpick comments (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (1)

329-339: Consider memoizing allStaticCommands for performance.

The allStaticCommands array is recreated on every render, which calls multiple getter functions. While the performance impact is likely minor, memoizing it could avoid unnecessary work.

-  const allStaticCommands = [
+  const allStaticCommands = useMemo(() => [
     viewDatasetsItem,
     viewAnnotationItems,
     ...getNavigationEntries(),
     ...getThemeEntries(),
     ...getToolEntries(),
     ...getViewModeEntries(),
     ...mapMenuActionsToCommands(menuActions),
     ...getTabsAndSettingsMenuItems(),
     ...getSuperUserItems(),
-  ];
+  ], [
+    activeUser,
+    isInTracingView,
+    isViewMode,
+    userConfig,
+    restrictions,
+    menuActions,
+  ]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3994e16 and 6597d50.

📒 Files selected for processing (2)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (7 hunks)
  • frontend/stylesheets/command_palette.less (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
🧬 Code graph analysis (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (4)
frontend/javascripts/admin/rest_api.ts (2)
  • getDatasets (995-1024)
  • getReadableAnnotations (472-479)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
  • getViewDatasetURL (753-757)
frontend/javascripts/viewer/constants.ts (1)
  • ViewModeValues (4-4)
frontend/javascripts/viewer/model/actions/settings_actions.ts (1)
  • setViewModeAction (115-119)
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 376-376: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 GitHub Check: frontend-tests
frontend/javascripts/viewer/view/components/command_palette.tsx

[failure] 312-312:
Object literal may only specify known properties, and 'shortcut' does not exist in type 'CommandWithoutId'.


[failure] 368-368:
Property 'highlight' does not exist on type 'Command'.


[failure] 368-368:
Property 'shortcut' does not exist on type 'Command'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (2)
frontend/stylesheets/command_palette.less (1)

103-110: LGTM! Proper scrolling support for expanded suggestions.

The additions of overflow-y: scroll; and scrollbar-width: thin; correctly enable vertical scrolling in the suggestions container while maintaining the fixed 315px max-height. The CSS cascade is sound—overflow-y: scroll; properly overrides the y-axis behavior from the earlier overflow: hidden; shorthand, keeping the x-axis hidden to prevent horizontal scroll. This aligns well with the PR's goal of dynamically populating the palette with datasets and annotations, which can exceed the container height.

frontend/javascripts/viewer/view/components/command_palette.tsx (1)

288-299: Nice addition!

The shortcut dictionary provides helpful keyboard shortcut hints for users, improving discoverability of tool switching commands.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (1)

34-36: Fix TypeScript typings for extended command fields (shortcut / highlight)

Current typing causes the frontend TS build to fail:

  • CommandWithoutId = Omit<Command, "id"> doesn’t declare shortcut, yet several commands specify shortcut.
  • renderCommand destructures shortcut and highlight from the incoming command, which is typed as Command by react-command-palette and doesn’t expose those properties.

Define an extended command type and assert before destructuring so both object literals and renderCommand compile cleanly.

Suggested diff:

@@
-// duplicate fields because otherwise, optional fields of Command yield errors
-type CommandWithoutId = Omit<Command, "id">;
+type ExtendedCommand = Command & {
+  // Extra optional fields used in our UI but not declared on `react-command-palette`'s Command.
+  shortcut?: string;
+  highlight?: string;
+};
+
+type CommandWithoutId = Omit<ExtendedCommand, "id">;
@@
   const shortCutDictForTools: Record<string, string> = {
@@
       commands.push({
         name: `Switch to ${tool.readableName}`,
         command: () => Store.dispatch(setToolAction(tool)),
-        shortcut: shortCutDictForTools[tool.id] || "",
+        shortcut: shortCutDictForTools[tool.id] || "",
         color: commandEntryColor,
       });
@@
-      renderCommand={(command) => {
-        const { shortcut, highlight: maybeDirtyString, name } = command;
+      renderCommand={(command) => {
+        const { name, shortcut = "", highlight: maybeDirtyString } =
+          command as ExtendedCommand;
         const cleanString = cleanStringOfMostHTML(maybeDirtyString);
@@
-            <span>{shortcut}</span>
+            <span>{shortcut}</span>

After this change, rerun yarn typecheck / yarn test-frontend to confirm the TS errors about shortcut/highlight are gone.

#!/bin/bash
yarn typecheck

Also applies to: 298-323, 378-392

🧹 Nitpick comments (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (1)

118-149: Dynamic dataset/annotation commands: behaviour and UX look solid; consider optional limits

The async handling for > View Dataset… and > View Annotation… looks good:

  • Errors are surfaced via Toast.error, empty results via Toast.info.
  • Annotations are sorted by modified descending, matching the dashboard behaviour.
  • Dataset/annotation items navigate directly via getViewDatasetURL(dataset) and /annotations/${annotation.id}, which aligns with the rest of the UI.

One optional refinement: getDatasets() without a limit might become heavy in very large installations. If you start seeing performance issues, consider passing a reasonable limit (e.g., 100–200) to getDatasets and/or adding simple client-side filtering, but that’s not required for correctness now.

Also applies to: 154-193

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6597d50 and ca638cf.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (7 hunks)
  • package.json (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
🧬 Code graph analysis (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (4)
frontend/javascripts/admin/rest_api.ts (2)
  • getDatasets (995-1024)
  • getReadableAnnotations (472-479)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
  • getViewDatasetURL (753-757)
frontend/javascripts/viewer/constants.ts (1)
  • ViewModeValues (4-4)
frontend/javascripts/viewer/model/actions/settings_actions.ts (1)
  • setViewModeAction (115-119)
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 386-386: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 GitHub Check: frontend-tests
frontend/javascripts/viewer/view/components/command_palette.tsx

[failure] 322-322:
Object literal may only specify known properties, and 'shortcut' does not exist in type 'CommandWithoutId'.


[failure] 378-378:
Property 'highlight' does not exist on type 'Command'.


[failure] 378-378:
Property 'shortcut' does not exist on type 'Command'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (3)
package.json (1)

124-155: DOMPurify as a runtime dependency looks appropriate

Using DOMPurify from the viewer at runtime justifies adding it under dependencies (and only there). No issues spotted here.

If you want to double‑check compatibility, verify that dompurify@^3.3.0 is supported by your bundler/TS setup (e.g., yarn add dompurify@^3.3.0 in a throwaway branch and run yarn typecheck).

frontend/javascripts/viewer/view/components/command_palette.tsx (2)

281-297: View‑mode/layout commands and palette lifecycle wiring look consistent

The added view-mode entries (ViewModeValues + setViewModeAction) and “Reset layout” (layoutEmitter.emit(LayoutEvents.resetLayout)) integrate cleanly with the existing viewer state.

The palette lifecycle wiring (commands + commandsWithIds, paletteKey-based remount, closeOnSelect={false}, onRequestClose={() => setCommands(allStaticCommands)}) matches the dynamic-lists example from react-command-palette and should behave as intended (static commands on open, dynamic lists only after explicit selection).

No changes needed here from my side.

Also applies to: 339-357, 358-367, 372-377


71-75: Now let me check the web search results about DOMPurify 3.3.0 support for ALLOWED_ATTR:

DOMPurify 3.3.0 documentation ALLOWED_ATTR

And let me also check the official DOMPurify repository or changelog:

DOMPurify version 3.3.0 release notes ALLOWED_ATTR support

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca638cf and 396a931.

📒 Files selected for processing (1)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (7 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 394-394: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 GitHub Check: frontend-tests
frontend/javascripts/viewer/view/components/command_palette.tsx

[failure] 386-386:
Property 'highlight' does not exist on type 'Command'.


[failure] 386-386:
Property 'shortcut' does not exist on type 'Command'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (3)
frontend/javascripts/viewer/view/components/command_palette.tsx (3)

31-41: Command modeling and tool shortcut wiring look solid

Defining a local Command/CommandWithoutId shape with optional shortcut/highlight and using shortCutDictForTools to populate shortcut into getToolEntries, then aggregating everything via allStaticCommands, is clear and maintainable. The structure should make it easy to extend with more commands or shortcuts later without surprises.

Also applies to: 306-317, 330-330, 347-357


79-82: DOMPurify sanitization makes dangerouslySetInnerHTML acceptable here

Using cleanStringOfMostHTML with DOMPurify.sanitize and ALLOWED_TAGS: ["b"] before passing content into dangerouslySetInnerHTML is a reasonable mitigation against XSS in this context. The static warning about dangerouslySetInnerHTML can be considered addressed, since only sanitized markup with a single safe tag is rendered, and the fallback to plain {name} handles the non-highlighted case cleanly.

Also applies to: 393-399


386-401: Verify the proposed TypeScript fix by running the actual TypeScript compiler on the codebase after applying the change.

The review comment proposes casting the command parameter in the renderCommand callback to resolve TypeScript errors. However, based on examination of the codebase:

  1. Current code state: The file defines its own local Command type (lines 33–40) that includes shortcut? and highlight? as optional fields.

  2. Objects passed: The commandsWithIds (lines 366–370) constructs objects with { id: index, ...command } where command is CommandWithoutId, giving them all required properties including shortcut and highlight.

  3. Parameter typing issue: The renderCommand callback parameter at line 385 is not explicitly typed, so TypeScript infers its type from the ReactCommandPalette prop definition. If react-command-palette exports a Command type that doesn't include these optional fields, then destructuring them would indeed cause type errors.

  4. Proposed fix: Casting to CommandWithoutId & { highlight?: string } as shown in the diff would make the type explicit and safe.

However, I could not definitively verify:

  • Whether TypeScript errors actually exist (the "frontend-tests" mentioned are not located in the repository)
  • The exact exported Command type from @types/react-command-palette (node_modules not available in sandbox)

The fix appears reasonable for type safety, but confirmation requires running tsc or the actual test pipeline to verify the errors are resolved.

onRequestClose={() => setCommands(allStaticCommands)}
closeOnSelect={false}
renderCommand={(command) => {
const { shortcut, highlight: maybeDirtyString, name } = command;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@philippotto do you have any idea why this fails and how to fix it? no matter if use the import form command_palette.d.ts or whether I redefine the type in the module, this error occurs. Furthermore, it only occurs in the pipeline, not locally when running yarn typecheck.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

image

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll take over :D

Yes, I see whats going on here: The type definition of the method passed to renderCommand says that the callback has

export interface Command {
            id: number;
            color: string;
            name: string;
            command(this: Command): void;
        }

as a parameter. So shortcut & highlight does not actually exist.
So the library does not seem to allow adding additional fields to the command type. Maybe enforced type casting is a solution here.

Maybe this stuff from the example only works because of the propType definitions or so 🤔?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe before diving too deep into this making a type case could be helpful? The additional properties are possibly undefined as well. So typescript should still enforce that we should be able to handle the cases were some of them are not defined.

So:

Suggested change
const { shortcut, highlight: maybeDirtyString, name } = command;
const { shortcut, highlight: maybeDirtyString, name } = command as Command;

So no perfect solution but regarding working time maybe the wise one

Copy link
Contributor Author

Choose a reason for hiding this comment

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

its working, thank you!!! 🥳

I dont understand why, to be honest. in my editor, this is shown:
image
And even if I remove the type definition above! So if I am just using the libs type. How did you figure it out? 💡

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
frontend/javascripts/viewer/view/components/command_palette.tsx (2)

363-365: Optional: Reset commands when closing to prevent stuck dynamic list (repeat suggestion).

closePalette() bumps the key to remount ReactCommandPalette but doesn't reset commands back to allStaticCommands. If a future change adds dynamic items that don't navigate (currently all items use window.location.href which reloads the page), users could get stuck viewing only the dynamic list on subsequent opens.

For correctness and future-proofing, consider adding setCommands(allStaticCommands) before bumping the key, as previously suggested.

 const closePalette = () => {
+  setCommands(allStaticCommands);
   setPaletteKey((prevKey) => prevKey + 1);
 };

387-387: TypeScript error: Cast command to extended type before destructuring.

The frontend-tests pipeline fails because renderCommand receives a Command type from the library (react-command-palette) which lacks shortcut and highlight properties. The code attempts to destructure these properties without casting, causing TypeScript errors.

Apply this fix:

-      renderCommand={(command) => {
-        const { shortcut, highlight: maybeDirtyString, name } = command;
+      renderCommand={(command) => {
+        const { shortcut, highlight: maybeDirtyString, name } = command as Command;

Note: This was flagged in a previous review and marked as addressed, but the fix is not present in the current code.

🧹 Nitpick comments (3)
frontend/stylesheets/command_palette.less (1)

103-110: Scrolling behavior correctly added; consider consolidating overflow properties.

The addition of overflow-y: auto (Line 108) correctly overrides the y-axis behavior of overflow: hidden (Line 104), enabling vertical scrolling with scrollbar-width: thin. This change is appropriate for supporting an expanded suggestions list with datasets and annotations.

For clarity, consider replacing overflow: hidden with overflow-x: hidden to make the intent explicit, reducing cognitive load when reading the ruleset.

.command-palette-suggestionsContainerOpen {
-  overflow: hidden;
+  overflow-x: hidden;
   border: 1px solid var(--color-bg);
   max-height: 315px;
   margin-top: 10px;
   overflow-y: auto;
   scrollbar-width: thin;
}
frontend/javascripts/viewer/view/components/command_palette.tsx (2)

33-41: Consider addressing the TODO comment about the Command type definition.

The custom Command type redefines properties from the library to add shortcut and highlight. While this works, the TODO suggests exploring alternatives. However, given that the library doesn't export an extensible type and TypeScript doesn't allow easy augmentation of third-party types without module augmentation, this approach is pragmatic.

If you want to keep this pattern, consider removing the TODO and adding a brief comment explaining why the redefinition is necessary (e.g., "Extends react-command-palette's Command type with optional fields").


163-173: Consider using React Router navigation instead of full page reload.

The dataset navigation uses window.location.href = getViewDatasetURL(dataset) which triggers a full page reload. If the app uses React Router, consider using navigate() or history.push() to maintain SPA benefits (faster navigation, preserved state).

Same applies to annotation navigation at line 189.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 396a931 and 59d89c9.

📒 Files selected for processing (2)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (7 hunks)
  • frontend/stylesheets/command_palette.less (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 395-395: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 GitHub Check: frontend-tests
frontend/javascripts/viewer/view/components/command_palette.tsx

[failure] 387-387:
Property 'highlight' does not exist on type 'Command'.


[failure] 387-387:
Property 'shortcut' does not exist on type 'Command'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (5)
frontend/javascripts/viewer/view/components/command_palette.tsx (5)

79-82: LGTM! Proper HTML sanitization.

The use of DOMPurify with a strict allowlist (ALLOWED_TAGS: ["b"]) is the correct approach for sanitizing the highlighted text while preserving bold formatting. This effectively mitigates XSS risks.


182-195: LGTM! Annotation sorting and error handling.

The annotations are correctly sorted by modified date in descending order (most recent first), and error handling with Toast notifications provides good user feedback.


290-305: LGTM! View mode switching and layout reset.

The view mode entries are correctly guarded for tracing view, and the layout reset integration using layoutEmitter is appropriate.


367-371: LGTM! Proper command transformation with IDs.

The useMemo ensures commands are efficiently transformed with IDs only when the commands array changes.


394-396: LGTM! Safe use of dangerouslySetInnerHTML with DOMPurify.

The HTML content is sanitized with DOMPurify before rendering, with a strict allowlist of only <b> tags. This effectively prevents XSS while preserving the highlighted text formatting. The static analysis warning can be safely ignored in this case.

Copy link
Contributor

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

Thanks for making the command palette even more capable 💪

Sorry for the long text 🙈

First part

Sadly, during testing I noticed some bugs which might be unreladed to your changes but maybe should be fixed here:
When I opened an annotation and then opened the command palette enter somethings but then closed it via escape without triggering any command, I got an react error:

installHook.js:1 Warning: Cannot update a component (`Row`) while rendering a different component (`CommandPalette`). To locate the bad setState() call inside `CommandPalette`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render Error Component Stack
    at CommandPalette (command_palette.tsx:84:34)
    at ShortcutsInfo (statusbar.tsx:181:35)
    at span (<anonymous>)
    at Statusbar (<anonymous>)
    at footer (<anonymous>)
    at FlexLayoutWrapper (flex_layout_wrapper.tsx:85:5)
    at NmlUploadZoneContainer (nml_upload_zone_container.tsx:76:1)
    at TracingLayoutView (tracing_layout_view.tsx:94:5)
    at ComponentWithRouterProp (with_router_hoc.tsx:22:33)
    at TracingViewRouteWrapper (route_wrappers.tsx:177:33)
    at SecuredRoute (secured_route.tsx:23:3)
    at ErrorBoundary (error_boundary.tsx:15:5)
    at main (<anonymous>)
    at RootLayout (router.tsx:85:40)
    at GlobalThemeProvider (theme.tsx:122:3)
    at ErrorBoundary (error_boundary.tsx:15:5)

But after retrying to produce this error again, I failed. So maybe this was some kind of edge case? Anyway, could you give this a quick look whether there might be a setState in the rendering of the command palette that is not part of an useEffect or a callback happening upon e.g. a user interaction?

Second part

Besides this, I noticed that there is a bug with the tool that are available in the command palette, when opening a shared annotation. Reasoning:
Initially, when opening a shared annotation restrictions.allowUpdate is false until the mutex of the annotation is fetched successfully.
Thus, in the first rendering of the command palette

 const getToolEntries = () => {
    if (!isInTracingView) return [];
    const commands: CommandWithoutId[] = [];
    let availableTools = Object.values(AnnotationTool);
    if (isViewMode || !restrictions.allowUpdate) {
      availableTools = Toolkits.READ_ONLY_TOOLS;
    }
    availableTools.forEach((tool) => {
      commands.push({
        name: `Switch to ${tool.readableName}`,
        command: () => Store.dispatch(setToolAction(tool)),
        shortcut: shortCutDictForTools[tool.id] || "",
        color: commandEntryColor,
      });
    });
    return commands;
  };

runs with restrictions.allowUpdate = false and therefore the availableTools = Toolkits.READ_ONLY_TOOLS; path will be executed.

And below

const allStaticCommands = [
    viewDatasetsItem,
    viewAnnotationItems,
    ...getNavigationEntries(),
    ...getThemeEntries(),
    ...getToolEntries(),
    ...getViewModeEntries(),
    ...mapMenuActionsToCommands(menuActions),
    ...getTabsAndSettingsMenuItems(),
    ...getSuperUserItems(),
  ];

  const [commands, setCommands] = useState<CommandWithoutId[]>(allStaticCommands);

creates the state variable commands which contains all allStaticCommands. !!!But!! only the commands which are present in allStaticCommands during the first rendering. If allStaticCommands changes afterwards, these changes won't affect commands. for this a used effect would be necessary watching for changes of restrictions.allowUpdate.
Thus, some kind of update mechanism to the state variable commands would be needed upon restrictions.allowUpdate changes. Could you please implement this? It should be relatively easy to test whether your solution works:

  1. create a annotation
  2. open sharing settings & share it
  3. reload the annotation
  4. open the command palette: Skeleton Tool switching should be an option in the command palette. (Currently it is not due to the described bug)

Comment on lines +167 to +169
command: () => {
window.location.href = getViewDatasetURL(dataset);
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Using useNavigate might be more react-router agnostic 🤔: https://reactrouter.com/api/hooks/useNavigate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to use it, but somehow in the annotation view, opening datasets wont do anything with useNavigate. So I left it the way it is. Same below for annotations. But I did update other paths, somehow the problems wont occur there (neither from admin nor annotation views)

onRequestClose={() => setCommands(allStaticCommands)}
closeOnSelect={false}
renderCommand={(command) => {
const { shortcut, highlight: maybeDirtyString, name } = command;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'll take over :D

Yes, I see whats going on here: The type definition of the method passed to renderCommand says that the callback has

export interface Command {
            id: number;
            color: string;
            name: string;
            command(this: Command): void;
        }

as a parameter. So shortcut & highlight does not actually exist.
So the library does not seem to allow adding additional fields to the command type. Maybe enforced type casting is a solution here.

Maybe this stuff from the example only works because of the propType definitions or so 🤔?

onRequestClose={() => setCommands(allStaticCommands)}
closeOnSelect={false}
renderCommand={(command) => {
const { shortcut, highlight: maybeDirtyString, name } = command;
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe before diving too deep into this making a type case could be helpful? The additional properties are possibly undefined as well. So typescript should still enforce that we should be able to handle the cases were some of them are not defined.

So:

Suggested change
const { shortcut, highlight: maybeDirtyString, name } = command;
const { shortcut, highlight: maybeDirtyString, name } = command as Command;

So no perfect solution but regarding working time maybe the wise one

>
{cleanString ? (
// biome-ignore lint/security/noDangerouslySetInnerHtml: modified from https://github.com/asabaylus/react-command-palette/blob/main/src/default-command.js
<span dangerouslySetInnerHTML={{ __html: cleanString }} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure, but why is this needed? Cant we avoid this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok I see. It is for the text highlighting. I do not see an immediate problem with that besides adding a new dependency and using dangerouslySetInnerHTML. But in this context it should kinda be save as long as the get the command palette to contain a certain string when e.g. opening a specifically crafted link. But the command palette does not support such a thing right now. So it should be okisch.

But I honestly do not want to decide this on my own. @philippotto do you have an opinion on this/ can we talk about this? Maybe the fuzzy highlighting isn't that important 🤔

Copy link
Contributor Author

@knollengewaechs knollengewaechs Dec 17, 2025

Choose a reason for hiding this comment

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

Yeah, and the library does this, too, I just modified the code 😅 Of course if we find a solution without it it would be perfect! I didnt manage to find an alternative

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (7)
frontend/javascripts/viewer/view/components/command_palette.tsx (7)

33-33: Address or remove the TODO comment.

This TODO has been flagged previously. Please either resolve it or remove the comment if the current implementation is acceptable.


167-167: Consider adding a separator for clarity.

As previously suggested, adding a separator like : or - would improve readability:

name: `View Dataset: ${dataset.name} (id ${dataset.id})`,

168-170: Consider using React Router's navigation instead of full page reload.

Using window.location.href causes a full page reload. For a better user experience with client-side navigation, consider using React Router's useNavigate hook:

const navigate = useNavigate();
// ...
command: () => {
  navigate(getViewDatasetURL(dataset));
}

This would provide faster navigation and preserve React state where appropriate.


188-188: Consider adding a separator for clarity.

As previously suggested, adding a separator would improve readability:

name: `View Annotation: ${annotation.name.length > 0 ? `${annotation.name} (id ${annotation.id})` : annotation.id}`,

189-191: Consider using React Router's navigation instead of full page reload.

Using window.location.href causes a full page reload. Consider using React Router's useNavigate hook for client-side navigation as suggested for the dataset items.


308-319: Note: Tool shortcuts are display-only.

As previously discussed, these shortcuts are shown in the UI but are not currently functional (no keyboard handlers registered). This is acceptable per the time constraints noted in earlier comments.


364-366: Reset commands when closing the palette.

As previously noted, closePalette should reset commands back to allStaticCommands to avoid showing only the last dynamic list (datasets or annotations) on the next open:

 const closePalette = () => {
+  setCommands(allStaticCommands);
   setPaletteKey((prevKey) => prevKey + 1);
 };

This ensures every new palette open starts with the full command list.

🧹 Nitpick comments (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (1)

349-359: Memoize allStaticCommands to avoid unnecessary recalculations.

allStaticCommands is rebuilt on every render, which is inefficient since it includes multiple function calls and array spreads. Wrap it in useMemo with appropriate dependencies:

-  const allStaticCommands = [
-    viewDatasetsItem,
-    viewAnnotationItems,
+  const allStaticCommands = useMemo(() => [
+    viewDatasetsItem,
+    viewAnnotationItems,
     ...getNavigationEntries(),
     ...getThemeEntries(),
     ...getToolEntries(),
     ...getViewModeEntries(),
     ...mapMenuActionsToCommands(menuActions),
     ...getTabsAndSettingsMenuItems(),
     ...getSuperUserItems(),
-  ];
+  ], [
+    activeUser,
+    annotationId,
+    annotationOwner,
+    annotationType,
+    isAnnotationLockedByUser,
+    isInTracingView,
+    isViewMode,
+    menuActions,
+    restrictions,
+    task,
+    userConfig,
+  ]);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59d89c9 and e7e1a43.

📒 Files selected for processing (3)
  • frontend/javascripts/admin/job/job_list_view.tsx (3 hunks)
  • frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx (4 hunks)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/javascripts/admin/job/job_list_view.tsx
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-06-25T13:16:26.966Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8672
File: frontend/javascripts/admin/account/account_settings_view.tsx:50-50
Timestamp: 2025-06-25T13:16:26.966Z
Learning: When parsing URL pathnames in React Router applications, avoid using `pathname.split("/").pop()` as it fails with trailing slashes. Instead, use `pathname.split("/").filter(Boolean)` to remove empty segments and explicitly find the desired segment by index for more robust URL parsing.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:25:53.526Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/statistic/time_tracking_overview.tsx:261-279
Timestamp: 2025-12-11T15:25:53.526Z
Learning: Ant Design v6 Select: when using the Select component, consider supplying a prefix prop to render an icon or element before the input for better visual context. Apply this guideline to TS and TSX files across the codebase where Ant Design Select is used; ensure prefix usage is accessible (e.g., provide meaningful aria-label if needed) and avoid unnecessary prefixes on simple inputs.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
  • frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx
📚 Learning: 2025-12-11T15:33:06.880Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/task/task_search_form.tsx:151-151
Timestamp: 2025-12-11T15:33:06.880Z
Learning: In Ant Design v6, do not use optionFilterProp as a standalone prop on Select. Instead, pass it inside showSearch as optionFilterProp, e.g. showSearch={{ optionFilterProp: 'label' }}. showSearch accepts boolean or an object with keys like optionFilterProp, filterOption, autoClearSearchValue. Update all Select components that use optionFilterProp to adopt the new pattern and adjust types accordingly to maintain compatibility and avoid deprecation warnings.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
  • frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx
📚 Learning: 2025-12-11T15:54:47.778Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx:131-134
Timestamp: 2025-12-11T15:54:47.778Z
Learning: In Ant Design v6, Dropdown uses a flat classNames structure: classNames={{ root: '…' }}. Other components (Select, AutoComplete, Cascader, TreeSelect) use a nested structure. The deprecated overlayClassName prop for Dropdown should be replaced with classNames.root. In reviews, flag Dropdown usage that relies on overlayClassName and replace it with classNames={{ root: '…' }}. If you encounter related components, verify the correct classNames shape (flat for Dropdown, nested for others) and update accordingly. This guideline covers TSX files under the frontend codebase where Ant Design components are used.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
  • frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx
🧬 Code graph analysis (2)
frontend/javascripts/viewer/view/components/command_palette.tsx (4)
frontend/javascripts/admin/rest_api.ts (2)
  • getDatasets (1001-1030)
  • getReadableAnnotations (472-479)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
  • getViewDatasetURL (753-757)
frontend/javascripts/viewer/constants.ts (1)
  • ViewModeValues (5-5)
frontend/javascripts/viewer/model/actions/settings_actions.ts (1)
  • setViewModeAction (115-119)
frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx (1)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
  • getViewDatasetURL (753-757)
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 396-396: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push
🔇 Additional comments (7)
frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx (1)

43-43: LGTM! Clean refactor to centralize dataset URL construction.

The import and usage of getViewDatasetURL correctly replaces manual URL construction across all three call sites (thumbnail link, name link, and double-click navigation). Type compatibility is maintained since all call sites pass APIDatasetCompact, which matches the helper's signature. This refactor improves maintainability by providing a single source of truth for dataset view URLs, which aligns well with the PR's objective of enabling dataset navigation from the command palette.

Also applies to: 335-335, 345-345, 810-810

frontend/javascripts/viewer/view/components/command_palette.tsx (6)

79-82: LGTM: HTML sanitization properly implemented.

The DOMPurify usage correctly sanitizes HTML with an allowlist of only <b> tags, effectively preventing XSS attacks. The null check is also appropriate.


127-162: LGTM: Well-structured async command handling.

The error handling with try-catch blocks and user feedback via Toast notifications is well implemented. The logic clearly handles both dynamic commands (datasets/annotations) and falls through to close for regular commands.


185-185: LGTM: Sorting annotations by most recently modified.

The sorting logic correctly places the most recently modified annotations first, which matches the dashboard behavior as mentioned in the PR objectives.


291-306: LGTM: View mode switching and layout reset.

The implementation correctly dispatches view mode actions and uses the layout emitter for reset functionality. The commands array construction is clean and appropriate.


368-372: LGTM: Proper memoization of commands with IDs.

The useMemo hook correctly memoizes the transformation of commands to include IDs, with appropriate dependencies.


387-404: LGTM: Safe custom command rendering with proper sanitization.

The custom renderCommand implementation correctly:

  • Uses DOMPurify to sanitize HTML before rendering with dangerouslySetInnerHTML
  • Falls back to plain text when no highlight is present
  • Displays shortcuts in a clean layout

The static analysis warning about dangerouslySetInnerHTML is a false positive here because the content is properly sanitized with DOMPurify's allowlist.

@knollengewaechs
Copy link
Contributor Author

Thank you so much for taking the time to have a look at my PR!

To your point

But after retrying to produce this error again, I failed. So maybe this was some kind of edge case? Anyway, could you give this a quick look whether there might be a setState in the rendering of the command palette that is not part of an useEffect or a callback happening upon e.g. a user interaction?

I couldnt find anything in the code and wasnt able to reproduce it either 😅

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (1)

364-366: Reset commands in closePalette to avoid stale dynamic lists.

After selecting a dataset or annotation command, closePalette() is called (line 161) but only increments paletteKey without resetting commands. This means the next time the palette opens, it may still show the last dynamic list (datasets or annotations) instead of the full static command list. This creates a confusing user experience.

The onRequestClose callback (line 385) does reset commands, but it's only triggered when closing via ESC or overlay click, not when closing after a selection.

Apply this fix:

  const closePalette = () => {
+   setCommands(allStaticCommands);
    setPaletteKey((prevKey) => prevKey + 1);
  };
🧹 Nitpick comments (3)
frontend/javascripts/viewer/view/components/command_palette.tsx (3)

164-174: Prefer client-side navigation over full page reload.

Using window.location.href triggers a full page reload, which is slower and loses React state. Consider using React Router's useNavigate hook for client-side navigation instead.

Based on learnings from past reviews, using useNavigate aligns better with React Router patterns.

Apply this pattern:

+import { useNavigate } from "react-router-dom";
+
 export const CommandPalette = ({ label }: { label: string | JSX.Element | null }) => {
+  const navigate = useNavigate();
   // ... other hooks ...
   
   const getDatasetItems = useCallback(async () => {
     const datasets = await getDatasets();
     return datasets.map((dataset) => ({
       name: `View Dataset ${dataset.name} (id ${dataset.id})`,
       command: () => {
-        window.location.href = getViewDatasetURL(dataset);
+        navigate(getViewDatasetURL(dataset));
       },
       color: commandEntryColor,
       id: dataset.id,
     }));
-  }, []);
+  }, [navigate]);

183-196: Prefer client-side navigation over full page reload.

Similar to the dataset navigation, using window.location.href triggers a full page reload. Use React Router's useNavigate hook instead.

Apply this pattern:

   const getAnnotationItems = useCallback(async () => {
     const annotations = await getReadableAnnotations(false);
     const sortedAnnotations = _.sortBy(annotations, (a) => a.modified).reverse();
     return sortedAnnotations.map((annotation) => {
       return {
         name: `View Annotation ${annotation.name.length > 0 ? `${annotation.name} (id ${annotation.id})` : annotation.id}`,
         command: () => {
-          window.location.href = `/annotations/${annotation.id}`;
+          navigate(`/annotations/${annotation.id}`);
         },
         color: commandEntryColor,
         id: annotation.id,
       };
     });
-  }, []);
+  }, [navigate]);

291-292: Consider renaming for consistency with store naming.

The store property is named isInAnnotationView (line 87), but the local variable is named isInTracingView. This naming inconsistency could cause confusion. Consider renaming the local variable to match the store property.

- const isInTracingView = useWkSelector((state) => state.uiInformation.isInAnnotationView);
+ const isInAnnotationView = useWkSelector((state) => state.uiInformation.isInAnnotationView);

Then update all usages throughout the file accordingly.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7e1a43 and 0016b40.

📒 Files selected for processing (2)
  • frontend/javascripts/admin/job/job_list_view.tsx (2 hunks)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/javascripts/admin/job/job_list_view.tsx
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-06-25T13:16:26.966Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8672
File: frontend/javascripts/admin/account/account_settings_view.tsx:50-50
Timestamp: 2025-06-25T13:16:26.966Z
Learning: When parsing URL pathnames in React Router applications, avoid using `pathname.split("/").pop()` as it fails with trailing slashes. Instead, use `pathname.split("/").filter(Boolean)` to remove empty segments and explicitly find the desired segment by index for more robust URL parsing.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:25:53.526Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/statistic/time_tracking_overview.tsx:261-279
Timestamp: 2025-12-11T15:25:53.526Z
Learning: Ant Design v6 Select: when using the Select component, consider supplying a prefix prop to render an icon or element before the input for better visual context. Apply this guideline to TS and TSX files across the codebase where Ant Design Select is used; ensure prefix usage is accessible (e.g., provide meaningful aria-label if needed) and avoid unnecessary prefixes on simple inputs.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:33:06.880Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/task/task_search_form.tsx:151-151
Timestamp: 2025-12-11T15:33:06.880Z
Learning: In Ant Design v6, do not use optionFilterProp as a standalone prop on Select. Instead, pass it inside showSearch as optionFilterProp, e.g. showSearch={{ optionFilterProp: 'label' }}. showSearch accepts boolean or an object with keys like optionFilterProp, filterOption, autoClearSearchValue. Update all Select components that use optionFilterProp to adopt the new pattern and adjust types accordingly to maintain compatibility and avoid deprecation warnings.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:54:47.778Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx:131-134
Timestamp: 2025-12-11T15:54:47.778Z
Learning: In Ant Design v6, Dropdown uses a flat classNames structure: classNames={{ root: '…' }}. Other components (Select, AutoComplete, Cascader, TreeSelect) use a nested structure. The deprecated overlayClassName prop for Dropdown should be replaced with classNames.root. In reviews, flag Dropdown usage that relies on overlayClassName and replace it with classNames={{ root: '…' }}. If you encounter related components, verify the correct classNames shape (flat for Dropdown, nested for others) and update accordingly. This guideline covers TSX files under the frontend codebase where Ant Design components are used.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 396-396: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-smoketest-push
  • GitHub Check: frontend-tests
  • GitHub Check: backend-tests
🔇 Additional comments (2)
frontend/javascripts/viewer/view/components/command_palette.tsx (2)

79-82: LGTM: Proper HTML sanitization with DOMPurify.

The sanitization correctly uses DOMPurify with a strict allowlist (only <b> tags), which provides adequate XSS protection for the command palette's fuzzy-match highlighting.


387-404: LGTM: Custom rendering with proper sanitization.

The custom renderCommand implementation correctly:

  • Casts to the extended Command type to access custom properties
  • Sanitizes HTML content using DOMPurify before injecting it
  • Falls back to plain text rendering when no highlight is present
  • Displays shortcuts in a justified layout

The use of dangerouslySetInnerHTML is acceptable here because the content is sanitized with DOMPurify's strict allowlist (lines 79-82).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
frontend/javascripts/viewer/view/components/command_palette.tsx (2)

380-397: Update type cast after fixing CommandWithoutId definition.

Once you extend CommandWithoutId to include shortcut and highlight properties (as suggested in the earlier comment), update the type cast here to use the extended type for consistency:

 renderCommand={(command) => {
-  const { shortcut, highlight: maybeDirtyString, name } = command as Command;
+  const { shortcut, highlight: maybeDirtyString, name } = command as CommandWithoutId;
   const cleanString = cleanStringOfMostHTML(maybeDirtyString);

Note: The dangerouslySetInnerHTML usage is safe here because the content is sanitized through cleanStringOfMostHTML using DOMPurify.


34-34: Extend CommandWithoutId to include custom properties.

The base Command type from react-command-palette doesn't include shortcut or highlight properties, causing TypeScript build failures on lines 325 and 381. Extend the type definition to support these custom fields.

Apply this diff to fix the type definition:

-type CommandWithoutId = Omit<Command, "id">;
+type CommandWithoutId = Omit<Command, "id"> & {
+  shortcut?: string;
+  highlight?: string;
+};
🧹 Nitpick comments (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (1)

357-359: Consider resetting commands in closePalette for consistent UX.

After selecting a dataset or annotation from a dynamic list, closePalette() only increments paletteKey without resetting commands to allStaticCommands. This means the next time the palette opens, it might still show the dynamic list instead of the full menu. While onRequestClose handles ESC/overlay closes properly, programmatic closes via selection don't reset.

Apply this diff to ensure every palette open starts fresh:

 const closePalette = () => {
+  setCommands(allStaticCommands);
   setPaletteKey((prevKey) => prevKey + 1);
 };
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0016b40 and 853e27f.

📒 Files selected for processing (2)
  • conf/application.conf (1 hunks)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (10 hunks)
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-06-25T13:16:26.966Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8672
File: frontend/javascripts/admin/account/account_settings_view.tsx:50-50
Timestamp: 2025-06-25T13:16:26.966Z
Learning: When parsing URL pathnames in React Router applications, avoid using `pathname.split("/").pop()` as it fails with trailing slashes. Instead, use `pathname.split("/").filter(Boolean)` to remove empty segments and explicitly find the desired segment by index for more robust URL parsing.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:25:53.526Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/statistic/time_tracking_overview.tsx:261-279
Timestamp: 2025-12-11T15:25:53.526Z
Learning: Ant Design v6 Select: when using the Select component, consider supplying a prefix prop to render an icon or element before the input for better visual context. Apply this guideline to TS and TSX files across the codebase where Ant Design Select is used; ensure prefix usage is accessible (e.g., provide meaningful aria-label if needed) and avoid unnecessary prefixes on simple inputs.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:33:06.880Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/task/task_search_form.tsx:151-151
Timestamp: 2025-12-11T15:33:06.880Z
Learning: In Ant Design v6, do not use optionFilterProp as a standalone prop on Select. Instead, pass it inside showSearch as optionFilterProp, e.g. showSearch={{ optionFilterProp: 'label' }}. showSearch accepts boolean or an object with keys like optionFilterProp, filterOption, autoClearSearchValue. Update all Select components that use optionFilterProp to adopt the new pattern and adjust types accordingly to maintain compatibility and avoid deprecation warnings.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:54:47.778Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx:131-134
Timestamp: 2025-12-11T15:54:47.778Z
Learning: In Ant Design v6, Dropdown uses a flat classNames structure: classNames={{ root: '…' }}. Other components (Select, AutoComplete, Cascader, TreeSelect) use a nested structure. The deprecated overlayClassName prop for Dropdown should be replaced with classNames.root. In reviews, flag Dropdown usage that relies on overlayClassName and replace it with classNames={{ root: '…' }}. If you encounter related components, verify the correct classNames shape (flat for Dropdown, nested for others) and update accordingly. This guideline covers TSX files under the frontend codebase where Ant Design components are used.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2024-11-25T10:11:52.591Z
Learnt from: frcroth
Repo: scalableminds/webknossos PR: 8193
File: conf/application.conf:119-119
Timestamp: 2024-11-25T10:11:52.591Z
Learning: In `conf/application.conf`, the `termsOfService.enabled` setting should remain `false` by default and be enabled (`true`) only in specific deployed instances.

Applied to files:

  • conf/application.conf
📚 Learning: 2025-01-13T09:06:12.362Z
Learnt from: MichaelBuessemeyer
Repo: scalableminds/webknossos PR: 8221
File: frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx:530-535
Timestamp: 2025-01-13T09:06:12.362Z
Learning: In the webknossos codebase, the evaluation settings for split-merger evaluation in neuron inference tasks (`useSparseTracing`, `maxEdgeLength`, `sparseTubeThresholdInNm`, `minimumMergerPathLengthInNm`) are designed to be optional parameters.

Applied to files:

  • conf/application.conf
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 389-389: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 GitHub Check: frontend-tests
frontend/javascripts/viewer/view/components/command_palette.tsx

[failure] 325-325:
Object literal may only specify known properties, and 'shortcut' does not exist in type 'CommandWithoutId'.


[failure] 381-381:
Property 'highlight' does not exist on type 'Command'.


[failure] 381-381:
Property 'shortcut' does not exist on type 'Command'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push
🔇 Additional comments (6)
conf/application.conf (1)

161-162: Verify if these feature flag changes are intentional and related to command palette functionality.

These feature flag changes enable jobsEnabled and voxelyticsEnabled by default, which appear disconnected from the stated PR purpose of adding command palette entries for viewing datasets and annotations. Line 160 indicates that enabling jobs requires database activation using yarn enable-jobs.

Please clarify:

  • Are these feature flags related to the new command palette functionality?
  • Should these flags be enabled by default for all environments?
  • Has the database setup requirement for jobs been considered?
frontend/javascripts/viewer/view/components/command_palette.tsx (5)

70-73: LGTM! Proper sanitization implemented.

The function correctly uses DOMPurify to sanitize HTML content, allowing only <b> tags. This properly mitigates XSS risks when rendering user-generated content with dangerouslySetInnerHTML.


121-155: LGTM! Dynamic command handling is well-structured.

The callback properly distinguishes between dynamic entry commands (which load dataset/annotation lists) and concrete commands (which close the palette). Error handling with Toast notifications provides good user feedback.


157-167: LGTM! Dataset command loading is clean and well-structured.

The function properly fetches datasets via the API and formats them into navigable commands using the getViewDatasetURL helper. The display format includes both dataset name and ID for clarity.


176-189: LGTM! Annotation sorting matches dashboard behavior.

The function correctly sorts annotations by modified date (most recent first) and handles the case where annotation names are empty by falling back to the ID. This provides a consistent user experience with the dashboard.


284-299: LGTM! View mode commands are properly implemented.

The function correctly generates commands for all view modes and includes layout reset functionality. The guard against non-annotation views prevents showing irrelevant commands.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (1)

343-360: Consider memoizing allStaticCommands for clarity and performance.

allStaticCommands is recalculated on every render, but the useEffect only updates commands state when allowUpdate changes. While this works in practice (because the palette blocks interaction and resets on close), memoizing allStaticCommands would make the data flow more explicit and prevent unnecessary recalculations.

-  const allStaticCommands = [
+  const allStaticCommands = useMemo(() => [
     viewDatasetsItem,
     viewAnnotationItems,
     ...getNavigationEntries(),
     ...getThemeEntries(),
     ...getToolEntries(),
     ...getViewModeEntries(),
     ...mapMenuActionsToCommands(menuActions),
     ...getTabsAndSettingsMenuItems(),
     ...getSuperUserItems(),
-  ];
+  ], [
+    viewDatasetsItem,
+    viewAnnotationItems,
+    activeUser,
+    isInAnnotationView,
+    isViewMode,
+    allowUpdate,
+    menuActions,
+    userConfig,
+  ]);

   const [commands, setCommands] = useState<CommandWithoutId[]>(allStaticCommands);

-  // biome-ignore lint/correctness/useExhaustiveDependencies: only rerun if allowUpdate changes
   useEffect(() => {
     setCommands(allStaticCommands);
-  }, [allowUpdate]);
+  }, [allStaticCommands]);

This makes the dependencies explicit and ensures commands update whenever any relevant state changes.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 853e27f and 045f3e7.

📒 Files selected for processing (1)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (9 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-06-25T13:16:26.966Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8672
File: frontend/javascripts/admin/account/account_settings_view.tsx:50-50
Timestamp: 2025-06-25T13:16:26.966Z
Learning: When parsing URL pathnames in React Router applications, avoid using `pathname.split("/").pop()` as it fails with trailing slashes. Instead, use `pathname.split("/").filter(Boolean)` to remove empty segments and explicitly find the desired segment by index for more robust URL parsing.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:25:53.526Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/statistic/time_tracking_overview.tsx:261-279
Timestamp: 2025-12-11T15:25:53.526Z
Learning: Ant Design v6 Select: when using the Select component, consider supplying a prefix prop to render an icon or element before the input for better visual context. Apply this guideline to TS and TSX files across the codebase where Ant Design Select is used; ensure prefix usage is accessible (e.g., provide meaningful aria-label if needed) and avoid unnecessary prefixes on simple inputs.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:33:06.880Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/task/task_search_form.tsx:151-151
Timestamp: 2025-12-11T15:33:06.880Z
Learning: In Ant Design v6, do not use optionFilterProp as a standalone prop on Select. Instead, pass it inside showSearch as optionFilterProp, e.g. showSearch={{ optionFilterProp: 'label' }}. showSearch accepts boolean or an object with keys like optionFilterProp, filterOption, autoClearSearchValue. Update all Select components that use optionFilterProp to adopt the new pattern and adjust types accordingly to maintain compatibility and avoid deprecation warnings.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:54:47.778Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx:131-134
Timestamp: 2025-12-11T15:54:47.778Z
Learning: In Ant Design v6, Dropdown uses a flat classNames structure: classNames={{ root: '…' }}. Other components (Select, AutoComplete, Cascader, TreeSelect) use a nested structure. The deprecated overlayClassName prop for Dropdown should be replaced with classNames.root. In reviews, flag Dropdown usage that relies on overlayClassName and replace it with classNames={{ root: '…' }}. If you encounter related components, verify the correct classNames shape (flat for Dropdown, nested for others) and update accordingly. This guideline covers TSX files under the frontend codebase where Ant Design components are used.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
🧬 Code graph analysis (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (5)
frontend/javascripts/libs/react_hooks.ts (1)
  • useWkSelector (299-301)
frontend/javascripts/admin/rest_api.ts (2)
  • getDatasets (1001-1030)
  • getReadableAnnotations (472-479)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
  • getViewDatasetURL (753-757)
frontend/javascripts/viewer/constants.ts (1)
  • ViewModeValues (5-5)
frontend/javascripts/viewer/model/actions/settings_actions.ts (1)
  • setViewModeAction (115-119)
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 394-394: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 GitHub Check: frontend-tests
frontend/javascripts/viewer/view/components/command_palette.tsx

[failure] 386-386:
Property 'highlight' does not exist on type 'Command'.


[failure] 386-386:
Property 'shortcut' does not exist on type 'Command'.


[failure] 326-326:
Object literal may only specify known properties, and 'shortcut' does not exist in type 'CommandWithoutId'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push
🔇 Additional comments (10)
frontend/javascripts/viewer/view/components/command_palette.tsx (10)

1-30: LGTM!

The new imports are appropriate for the enhanced command palette functionality. The addition of DOMPurify properly addresses the XSS sanitization requirements, and the use of useNavigate follows React Router 6 best practices.


70-73: LGTM!

The sanitization implementation using DOMPurify is correct and secure. Restricting to only <b> tags is appropriate for command palette highlighting, and the null handling is proper.


88-114: LGTM!

Component setup is clean. The use of isInAnnotationView is consistent with the store naming, and useNavigate follows React Router 6 patterns correctly.


135-169: LGTM!

The handleSelect callback correctly implements the dynamic loading pattern with appropriate error handling via Toast messages. The empty dependency array is safe because all referenced functions are stable (setState callbacks and memoized functions).


171-188: LGTM!

Dataset loading implementation is clean and follows best practices. The use of getViewDatasetURL helper and navigate for routing is correct.


190-210: LGTM!

Annotation loading correctly implements the sorting by most recently modified first (matching dashboard behavior as intended). Navigation and callback memoization are properly implemented.


225-269: LGTM!

Navigation entries are well-structured with proper permission filtering. The use of navigate instead of window.location.href correctly implements React Router 6 navigation patterns.


298-313: LGTM!

The view mode switching implementation is clean and properly integrates with the layout reset functionality via layoutEmitter. The conditional rendering ensures these commands only appear in annotation views.


315-331: LGTM!

Tool entries are properly memoized with correct dependencies and implement appropriate filtering based on view mode and edit permissions.


372-384: LGTM!

The ReactCommandPalette configuration is well thought out. The combination of closeOnSelect={false} (to keep palette open while browsing dynamic lists), onRequestClose (to reset on ESC), and paletteKey-based remounting creates a smooth UX for the dynamic dataset/annotation viewing workflow.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
frontend/javascripts/viewer/view/components/command_palette.tsx (3)

140-174: Consider logging errors for debugging.

The error handling provides good user feedback via Toast messages, but the caught errors are silently discarded. For debugging purposes, consider logging the actual error while still showing the user-friendly message:

🔎 Suggested enhancement
     if (command.name === DynamicCommands.viewDataset) {
       try {
         const items = await getDatasetItems();
         if (items.length > 0) {
           setCommands(items);
         } else {
           Toast.info("No datasets available.");
         }
-      } catch (_e) {
+      } catch (error) {
+        console.error("Failed to load datasets:", error);
         Toast.error("Failed to load datasets.");
       }
       return;
     }

     if (command.name === DynamicCommands.viewAnnotation) {
       try {
         const items = await getAnnotationItems();
         if (items.length > 0) {
           setCommands(items);
         } else {
           Toast.info("No annotations available.");
         }
-      } catch (_e) {
+      } catch (error) {
+        console.error("Failed to load annotations:", error);
         Toast.error("Failed to load annotations.");
       }
       return;
     }

195-208: Optional: Consider a helper function for annotation URLs.

The dataset commands use getViewDatasetURL() for URL generation (line 181), while annotation commands use a hardcoded path template (line 202: `/annotations/${annotation.id}`). For consistency and maintainability, consider creating a similar helper function like getViewAnnotationURL() if one doesn't already exist.


348-365: Optional: Consider memoizing allStaticCommands for performance.

allStaticCommands is recreated on every render since it's not wrapped in useMemo. While this isn't a critical issue for a command palette component, memoizing it could improve performance, especially since it involves multiple function calls and depends on several state slices:

🔎 Optional optimization
-  const allStaticCommands = [
+  const allStaticCommands = useMemo(() => [
     viewDatasetsItem,
     viewAnnotationItems,
     ...getNavigationEntries(),
     ...getThemeEntries(),
     ...getToolEntries(),
     ...getViewModeEntries(),
     ...mapMenuActionsToCommands(menuActions),
     ...getTabsAndSettingsMenuItems(),
     ...getSuperUserItems(),
-  ];
+  ], [
+    viewDatasetsItem,
+    viewAnnotationItems,
+    activeUser,
+    navigate,
+    isInAnnotationView,
+    isViewMode,
+    allowUpdate,
+    menuActions,
+    userConfig,
+  ]);

-  // biome-ignore lint/correctness/useExhaustiveDependencies: only rerun if allowUpdate changes
   useEffect(() => {
     setCommands(allStaticCommands);
-  }, [allowUpdate]);
+  }, [allStaticCommands]);

Note: The current approach works correctly due to the paletteKey reset mechanism, but memoization would reduce unnecessary recreations.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 045f3e7 and 7f2ede5.

📒 Files selected for processing (2)
  • conf/application.conf (1 hunks)
  • frontend/javascripts/viewer/view/components/command_palette.tsx (9 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2024-11-25T10:11:52.591Z
Learnt from: frcroth
Repo: scalableminds/webknossos PR: 8193
File: conf/application.conf:119-119
Timestamp: 2024-11-25T10:11:52.591Z
Learning: In `conf/application.conf`, the `termsOfService.enabled` setting should remain `false` by default and be enabled (`true`) only in specific deployed instances.

Applied to files:

  • conf/application.conf
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-06-25T13:16:26.966Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8672
File: frontend/javascripts/admin/account/account_settings_view.tsx:50-50
Timestamp: 2025-06-25T13:16:26.966Z
Learning: When parsing URL pathnames in React Router applications, avoid using `pathname.split("/").pop()` as it fails with trailing slashes. Instead, use `pathname.split("/").filter(Boolean)` to remove empty segments and explicitly find the desired segment by index for more robust URL parsing.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:25:53.526Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/statistic/time_tracking_overview.tsx:261-279
Timestamp: 2025-12-11T15:25:53.526Z
Learning: Ant Design v6 Select: when using the Select component, consider supplying a prefix prop to render an icon or element before the input for better visual context. Apply this guideline to TS and TSX files across the codebase where Ant Design Select is used; ensure prefix usage is accessible (e.g., provide meaningful aria-label if needed) and avoid unnecessary prefixes on simple inputs.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:33:06.880Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/task/task_search_form.tsx:151-151
Timestamp: 2025-12-11T15:33:06.880Z
Learning: In Ant Design v6, do not use optionFilterProp as a standalone prop on Select. Instead, pass it inside showSearch as optionFilterProp, e.g. showSearch={{ optionFilterProp: 'label' }}. showSearch accepts boolean or an object with keys like optionFilterProp, filterOption, autoClearSearchValue. Update all Select components that use optionFilterProp to adopt the new pattern and adjust types accordingly to maintain compatibility and avoid deprecation warnings.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
📚 Learning: 2025-12-11T15:54:47.778Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx:131-134
Timestamp: 2025-12-11T15:54:47.778Z
Learning: In Ant Design v6, Dropdown uses a flat classNames structure: classNames={{ root: '…' }}. Other components (Select, AutoComplete, Cascader, TreeSelect) use a nested structure. The deprecated overlayClassName prop for Dropdown should be replaced with classNames.root. In reviews, flag Dropdown usage that relies on overlayClassName and replace it with classNames={{ root: '…' }}. If you encounter related components, verify the correct classNames shape (flat for Dropdown, nested for others) and update accordingly. This guideline covers TSX files under the frontend codebase where Ant Design components are used.

Applied to files:

  • frontend/javascripts/viewer/view/components/command_palette.tsx
🧬 Code graph analysis (1)
frontend/javascripts/viewer/view/components/command_palette.tsx (6)
frontend/javascripts/libs/react_hooks.ts (1)
  • useWkSelector (299-301)
frontend/javascripts/admin/rest_api.ts (2)
  • getDatasets (1001-1030)
  • getReadableAnnotations (472-479)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
  • getViewDatasetURL (753-757)
frontend/javascripts/viewer/constants.ts (1)
  • ViewModeValues (5-5)
frontend/javascripts/viewer/model/actions/settings_actions.ts (1)
  • setViewModeAction (115-119)
frontend/javascripts/viewer/view/action-bar/view_dataset_actions_view.tsx (1)
  • viewDatasetMenu (57-62)
🪛 ast-grep (0.40.0)
frontend/javascripts/viewer/view/components/command_palette.tsx

[warning] 399-399: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: frontend-tests
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push
🔇 Additional comments (4)
frontend/javascripts/viewer/view/components/command_palette.tsx (4)

34-39: LGTM! Type definitions properly extended.

The ExtendedCommand type correctly extends the library's Command type with optional shortcut and highlight properties, and CommandWithoutId properly derives from it. This addresses the previous TypeScript errors while maintaining type safety.


75-78: Excellent: Secure HTML sanitization implemented.

DOMPurify with ALLOWED_TAGS: ["b"] provides robust XSS protection while supporting the command highlighting feature. This properly addresses the previous security concerns.


303-318: LGTM! Clean view mode switching implementation.

The view mode entries are well-structured, properly gated by isInAnnotationView, and include the useful "Reset layout" action. The integration with layoutEmitter maintains good separation of concerns.


390-407: LGTM! Safe and well-structured custom rendering.

The custom renderCommand implementation properly:

  • Casts to ExtendedCommand to access the extended properties
  • Sanitizes HTML content via DOMPurify before using dangerouslySetInnerHTML
  • Falls back to plain text when no highlight is present
  • Displays shortcuts in a clean flex layout

The use of dangerouslySetInnerHTML is acceptable here since the content is properly sanitized with a restrictive tag whitelist.

@knollengewaechs
Copy link
Contributor Author

@MichaelBuessemeyer I had a look at your comments and incorporated your feedback, unless I had some remaining questions (see threads). I also fixed the

Second part

-> so the scenario where multiple users open an annotation and allowUpdate changes.
If you find the time, could you have another look?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c5246e and 44a92ab.

📒 Files selected for processing (1)
  • .github/common_edge_cases.md (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
.github/common_edge_cases.md

12-12: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: backend-tests
  • GitHub Check: frontend-tests
  • GitHub Check: build-smoketest-push

- The user is logged out and views a public dataset or annotation
- User uses dark mode / light mode
- There is no local datastore/tracingstore module (Compare [instructions to test this locally](https://github.com/scalableminds/webknossos/wiki/Set-up-a-standalone-datastore-locally))
- An annotation is opened by multiple users at the same time
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix markdown indentation to comply with linting rules.

The newly added edge case has incorrect indentation. According to the markdownlint-cli2 rule MD007 (ul-indent), the list item should have 0 leading spaces instead of 1.

🔎 Apply this diff to fix the indentation:
- - An annotation is opened by multiple users at the same time
+- An annotation is opened by multiple users at the same time

Note: If this is intentional formatting to nest the list under "Will this also work if", consider applying the same indentation change to all list items (lines 4–10) for consistency.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- An annotation is opened by multiple users at the same time
- An annotation is opened by multiple users at the same time
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

12-12: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)

🤖 Prompt for AI Agents
.in .github/common_edge_cases.md around line 12: the list item "- An annotation
is opened by multiple users at the same time" has one leading space which
violates MD007 (ul-indent); remove the single leading space so the list item
starts at column 0, and if the intent was to nest the whole block under "Will
this also work if" then instead adjust all sibling list items (lines 4–10) to
the same indentation for consistency.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants