Skip to content

feat: Node search UX updates#9714

Open
pythongosssss wants to merge 28 commits intomainfrom
pysssss/node-search-feedback
Open

feat: Node search UX updates#9714
pythongosssss wants to merge 28 commits intomainfrom
pysssss/node-search-feedback

Conversation

@pythongosssss
Copy link
Member

@pythongosssss pythongosssss commented Mar 10, 2026

Summary

Addresses feedback from the initial v2 node search implementation for improved UI and UX

Changes

  • What:
  • add root filter buttons
  • remove all extra tree categories leaving only "Most relevant"
  • replace input/output selection with popover
  • replace price badge with one from node header
  • add chevrons and additional styling to category tree
  • hide empty categories
  • fix bug with hovering selecting item under mouse automatically
  • fix tailwind merge with custom sizes removing them
  • keyboard navigation
  • general tidy/refactor/test

Screenshots (if applicable)

zsearch.mp4

┆Issue is synchronized with this Notion page by Unito

- add root filter buttons
- replace input/output selection with popover
- replace price badge with one from node header
- fix bug with hovering selecting item under mouse automatically
- fix tailwind merge with custom sizes removing them
- general tidy/refactor/test
@github-actions
Copy link

github-actions bot commented Mar 10, 2026

🎭 Playwright: ✅ 666 passed, 0 failed · 5 flaky

📊 Browser Reports
  • chromium: View Report (✅ 652 / ❌ 0 / ⚠️ 5 / ⏭️ 10)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 11 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

@github-actions
Copy link

github-actions bot commented Mar 10, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 03/20/2026, 09:55:36 PM UTC

Links

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds CreditBadge and a multi-select type-filter popover; refactors V2 node search to a roving-focus sidebar, data-driven filter bar, unified input, and new content/list flows; extends tailwind-merge; adds node-source helpers, i18n keys, browser test helpers, highlight matching enhancements, and removes the old FilterPanel.

Changes

Cohort / File(s) Summary
Tailwind utils
packages/tailwind-utils/src/index.ts
Replace direct twMerge import with extendTailwindMerge and create an extended twMerge that adds a font-size class group for text-xxs/text-xxxs.
Credit badge & replacements
src/components/node/CreditBadge.vue, src/components/node/NodePricingBadge.vue, src/renderer/extensions/vueNodes/components/NodeHeader.vue
Add CreditBadge.vue; replace BadgePill-based price badge rendering with CreditBadge usages.
Node preview & popover scaling
src/components/node/NodePreviewCard.vue, src/components/searchbox/NodeSearchBoxPopover.vue
Introduce scaleFactor prop and BASE_WIDTH_PX/BASE_SCALE; apply inline scale transforms and pass scale-factor + inert to popover usage.
Category sidebar & tree (keyboard focus)
src/components/searchbox/v2/NodeSearchCategorySidebar.vue, src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue, src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts
Convert sidebar/tree to RovingFocusGroup/Item; export DEFAULT_CATEGORY; add props (hideChevrons, hidePresets, nodeDefs, rootLabel, rootKey) and autoExpand emit; implement Right/Left keyboard navigation and update tests.
Filter bar & type-filter popover
src/components/searchbox/v2/NodeSearchFilterBar.vue, src/components/searchbox/v2/NodeSearchFilterBar.test.ts, src/components/searchbox/v2/NodeSearchTypeFilterPopover.vue, src/components/searchbox/v2/NodeSearchTypeFilterPopover.test.ts
Refactor chips into data-driven categoryButtons; add NodeSearchTypeFilterPopover multi-select popover; add emits (selectCategory, toggleFilter, clearFilterGroup, focusSearch) and corresponding tests.
Search input, content, list & tests
src/components/searchbox/v2/NodeSearchInput.vue, src/components/searchbox/v2/NodeSearchInput.test.ts, src/components/searchbox/v2/NodeSearchContent.vue, src/components/searchbox/v2/NodeSearchContent.test.ts, src/components/searchbox/v2/NodeSearchListItem.vue
Remove activeFilter dual-mode; always use filters; add rootFilter logic, rootFilteredNodeDefs, helpers getMostRelevantResults/getCategoryResults; rewire keyboard navigation, list rendering, focus management, and update tests.
Removed component
src/components/searchbox/v2/NodeSearchFilterPanel.vue
Delete old NodeSearchFilterPanel.vue; its functionality consolidated into the new popover.
Node source types & helpers
src/types/nodeSource.ts, src/types/nodeSource.test.ts
Add BLUEPRINT_CATEGORY, NodeDefLike interface, and exported helpers isEssentialNode and isCustomNode with tests.
i18n & test fixtures
src/locales/en/main.json, src/locales/en/settings.json, src/components/searchbox/v2/__test__/testUtils.ts
Add bookmarked, blueprints, partnerNodes keys; consolidate pluralization to g.itemSelected with count; remove a settings tooltip key and flatten test i18n fixtures.
Browser tests & helpers
browser_tests/fixtures/components/ComfyNodeSearchBoxV2.ts, browser_tests/tests/nodeSearchBoxV2.spec.ts, browser_tests/fixtures/helpers/NodeOperationsHelper.ts, browser_tests/tests/nodeGhostPlacement.spec.ts
Add filterSearch locator; switch input lookup to combobox/textbox; add getSelectedNodeIds and addNode helpers; update ghost-placement tests to use helpers and NodeReference returns.
Highlighting & format util
packages/shared-frontend-utils/src/formatUtil.ts, packages/shared-frontend-utils/src/formatUtil.test.ts
Make highlightQuery support cross-word/spaced-character matches by building a per-character \s* pattern; add a cross-word match test.
Misc UI, accessibility & styling tweaks
src/components/*, packages/design-system/src/css/style.css, src/renderer/extensions/vueNodes/components/LGraphNode.vue
Various layout/truncation/ARIA/class updates; MultiSelect header now uses count-based translation; remove two CSS utility declarations; guard/inert node interactions during ghost-placement.
Tests added/updated
many src/components/searchbox/v2/*.test.ts and new NodeSearchTypeFilterPopover.test.ts
Extensive test updates to reflect new UI flows, keyboard behavior, filter interactions, bookmark handling, and popover behavior.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Input as "NodeSearchInput"
    participant Sidebar as "Category Sidebar"
    participant FilterBar as "Filter Bar"
    participant TypePopover as "Type Filter Popover"
    participant Content as "NodeSearchContent"
    participant Results as "Results List"

    User->>Input: type query / navigate / select
    Input->>Content: emit navigate / select / update searchQuery
    User->>Sidebar: select/expand category
    Sidebar->>Content: emit selectCategory / autoExpand
    User->>FilterBar: open type filters / click category button
    FilterBar->>TypePopover: open popover
    User->>TypePopover: toggle type(s) / clear
    TypePopover->>FilterBar: emit toggle / clear
    FilterBar->>Content: emit toggleFilter / selectCategory
    Content->>Results: compute results (searchQuery + rootFilter + category + types)
    Results->>User: render updated list
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 I hopped through roving-focus trees,
Badges jingled, popovers peeked,
Filters twitched and results took flight,
I stretched each char to bridge the night,
A tiny hop — the search feels sleek.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Node search UX updates' clearly summarizes the main change—improving the node search UI and user experience with multiple enhancements.
Description check ✅ Passed The PR description covers the Summary and Changes sections well, listing all major updates. However, the 'What' section is incomplete with only bullet points and no explanatory text, and the optional 'Review Focus' section is missing entirely.
End-To-End Regression Coverage For Fixes ✅ Passed PR contains bug-fix language and modifies multiple browser_tests/ files including test cases and helpers, satisfying the E2E test coverage condition.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pysssss/node-search-feedback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
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: 8

🧹 Nitpick comments (1)
src/components/searchbox/v2/NodeSearchContent.test.ts (1)

448-459: Add the no-movement assertion for the hover regression.

This only proves pointermove selects. It does not protect the bug called out in the PR, where merely hovering without movement was enough. Assert that pointerenter leaves the current item unchanged before the pointermove step.

💡 Suggested test shape
   const results = getResultItems(wrapper)
+  await results[1].trigger('pointerenter')
+  await nextTick()
+  expect(results[0].attributes('aria-selected')).toBe('true')
+  expect(results[1].attributes('aria-selected')).not.toBe('true')
   await results[1].trigger('pointermove')
   await nextTick()
Based on learnings: For Vue component testing, use Vue Test Utils and aim for behavioral coverage of critical and new features.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/searchbox/v2/NodeSearchContent.test.ts` around lines 448 -
459, The test "should select item on hover via pointermove" currently only
asserts pointermove behavior; to guard against the regression where pointerenter
(hover without movement) incorrectly selects an item, first trigger a
pointerenter on the target result (use
getResultItems(wrapper)[1].trigger('pointerenter')) and assert the selection
state did not change (e.g., the previously-selected item's aria-selected remains
unchanged or the hovered item's aria-selected is not 'true') before proceeding
to trigger('pointermove') and the existing assertion; use the existing helpers
setupFavorites and getResultItems to locate items.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/node/CreditBadge.vue`:
- Around line 2-17: Wrap the two spans in CreditBadge.vue inside a single
internal container so the badge halves are rendered together (making the seam
self-contained) instead of relying on external layout; specifically, create a
wrapper element that contains the existing root span (the one using cn(...) with
'rounded-l-full pr-1' / 'rounded-full') and the trailing span (the v-if="rest"
element that currently uses '-ml-2.5 ... rounded-r-full'), remove the dependency
on parent-level negative margin by moving overlap/positioning into the new
container (use inline-flex/relative positioning and internal negative spacing so
the left and right halves join consistently), and keep the same per-half classes
(the cn(...) span and the v-if="rest" span) so their visuals remain unchanged
while the seam is controlled internally.

In `@src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue`:
- Around line 12-37: The chevron hover state is using the global selector
"group-hover/categories" so every row responds to sidebar hover; add a local
group on this row by including "group/categories" (or just "group") in the
button's class list (the button that emits 'select' for node.key) and update the
icon class in the i element (where node.children?.length and !isExpanded are
used) to use "group-hover:opacity-100" instead of
"group-hover/categories:opacity-100" so the chevron opacity change is scoped to
the hovered row only.

In `@src/components/searchbox/v2/NodeSearchContent.test.ts`:
- Around line 57-68: The mockBookmarks helper is masking "no bookmarks"
scenarios because its bookmarkList defaults to ['placeholder']; change
mockBookmarks so its bookmarkList default is an empty array (bookmarkList:
string[] = []) and update any tests that relied on the placeholder to explicitly
pass a non-empty list; ensure when tests call mockBookmarks(false) they either
omit the bookmarkList (to simulate no bookmarks) or explicitly pass [] so the
Favorites visibility logic is exercised properly (refer to mockBookmarks,
useNodeBookmarkStore, and the isBookmarked spy).

In `@src/components/searchbox/v2/NodeSearchContent.vue`:
- Around line 214-223: The onSelectCategory handler should preserve the current
searchQuery instead of clearing it; modify the onSelectCategory function
(referencing rootFilter, selectedCategory, searchQuery, searchInputRef, and
nextTick) to remove the line that sets searchQuery.value = '' so switching root
filters keeps the typed query and lets getMostRelevantResults combine rootFilter
with searchResults; keep the rest of the logic (toggling rootFilter, setting
selectedCategory to 'most-relevant', and focusing searchInputRef) unchanged.

In `@src/components/searchbox/v2/NodeSearchFilterBar.vue`:
- Around line 25-37: When tf.values is non-empty, add an accessible, localized
summary inside the button (hidden visually with the sr-only class) so screen
readers hear the selected type names; locate the button rendering around the
chipClass(...) usage in NodeSearchFilterBar.vue and insert an sr-only span
(rendered only when tf.values.length > 0) that uses vue-i18n ($t) to produce a
string from src/locales/en/main.json (e.g., a key like main.selectedTypes with a
types interpolation) and pass the selected values joined (tf.values.slice(0,
MAX_VISIBLE_DOTS) or tf.values.join(', ') as appropriate) so the screen reader
reads the selected types along with tf.chip.label, keeping existing visual dots
and getLinkTypeColor behavior unchanged.

In `@src/components/searchbox/v2/NodeSearchListItem.vue`:
- Around line 24-52: The source/provider badges are icon-only or aria-hidden
when showCategory is false, leaving no accessible label; update the spans that
render source/provider (the v-if block using showSourceBadge, isCustom,
nodeDef.nodeSource.displayText, providerName, badgePillClass, ComfyLogo,
getProviderIcon) to include an accessible text alternative: add an offscreen
text element (e.g., a visually-hidden/sr-only span) or an aria-label on the
badge container that contains nodeDef.nodeSource.displayText or providerName,
and ensure the decorative icons keep aria-hidden while the container exposes the
accessible name so screen readers still get the source/provider when
showCategory is false.

In `@src/components/searchbox/v2/NodeSearchTypeFilterPopover.test.ts`:
- Around line 9-11: The test is using createMockChip()'s default sorted fixture
which masks regressions; modify the assertions that currently call
createMockChip() (around the test verifying sorting) to pass an explicitly
unsorted array (e.g., ['MODEL','IMAGE','LATENT']) into createMockChip so the
component must actually sort values to satisfy the assertion; update all
occurrences in that test block (the assertions between lines ~82-91) to use the
unsorted fixture instead of the default.

In `@src/components/searchbox/v2/NodeSearchTypeFilterPopover.vue`:
- Around line 36-39: The template in NodeSearchTypeFilterPopover.vue uses
t('g.itemsSelected', { selectedCount: selectedValues.length }) which prevents
vue-i18n pluralization; change the template to call t('g.itemSelected', { count:
selectedValues.length }) and update the locale entry to a single pluralized key
(e.g., "itemSelected": "{count} item selected | {count} items selected") so
vue-i18n can pick singular/plural correctly; ensure you update any occurrences
of itemsSelected to the new itemSelected plural key and keep the binding using
selectedValues.length.

---

Nitpick comments:
In `@src/components/searchbox/v2/NodeSearchContent.test.ts`:
- Around line 448-459: The test "should select item on hover via pointermove"
currently only asserts pointermove behavior; to guard against the regression
where pointerenter (hover without movement) incorrectly selects an item, first
trigger a pointerenter on the target result (use
getResultItems(wrapper)[1].trigger('pointerenter')) and assert the selection
state did not change (e.g., the previously-selected item's aria-selected remains
unchanged or the hovered item's aria-selected is not 'true') before proceeding
to trigger('pointermove') and the existing assertion; use the existing helpers
setupFavorites and getResultItems to locate items.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c94f1c40-0661-448a-936d-9af22522f870

📥 Commits

Reviewing files that changed from the base of the PR and between 1d7a5b9 and fd31f9d.

📒 Files selected for processing (24)
  • packages/tailwind-utils/src/index.ts
  • src/components/node/CreditBadge.vue
  • src/components/node/NodePreviewCard.vue
  • src/components/node/NodePricingBadge.vue
  • src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts
  • src/components/searchbox/v2/NodeSearchCategorySidebar.vue
  • src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue
  • src/components/searchbox/v2/NodeSearchContent.test.ts
  • src/components/searchbox/v2/NodeSearchContent.vue
  • src/components/searchbox/v2/NodeSearchFilterBar.test.ts
  • src/components/searchbox/v2/NodeSearchFilterBar.vue
  • src/components/searchbox/v2/NodeSearchFilterPanel.vue
  • src/components/searchbox/v2/NodeSearchInput.test.ts
  • src/components/searchbox/v2/NodeSearchInput.vue
  • src/components/searchbox/v2/NodeSearchListItem.vue
  • src/components/searchbox/v2/NodeSearchTypeFilterPopover.test.ts
  • src/components/searchbox/v2/NodeSearchTypeFilterPopover.vue
  • src/components/searchbox/v2/__test__/testUtils.ts
  • src/components/sidebar/ComfyMenuButton.vue
  • src/locales/en/main.json
  • src/locales/en/settings.json
  • src/renderer/extensions/vueNodes/components/NodeHeader.vue
  • src/types/nodeSource.test.ts
  • src/types/nodeSource.ts
💤 Files with no reviewable changes (1)
  • src/components/searchbox/v2/NodeSearchFilterPanel.vue

@pythongosssss pythongosssss added the Design Used to request Product feedback on design decisions label Mar 11, 2026
- improved keyboard navigation and aria
- fixed alignment of elements
- updated fonts and sizes
- more tidy + nits
- tests
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/components/searchbox/v2/NodeSearchFilterBar.vue (1)

25-37: ⚠️ Potential issue | 🟠 Major

Expose active type selections to screen readers.

When selections are shown as dots, this button still announces only the base chip label. The active filter state disappears for assistive tech users. Add a localized sr-only summary of the selected values inside the trigger.

Suggested fix
       <button type="button" :class="chipClass(false, tf.values.length > 0)">
+        <span v-if="tf.values.length > 0" class="sr-only">
+          {{
+            t('g.nodeSearchTypeFilterSummary', {
+              label: tf.chip.label,
+              values: tf.values.join(', ')
+            })
+          }}
+        </span>
         <span v-if="tf.values.length > 0" class="flex items-center">
           <span
             v-for="val in tf.values.slice(0, MAX_VISIBLE_DOTS)"

As per coding guidelines: Use vue-i18n for ALL user-facing strings, configured in src/locales/en/main.json.

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

In `@src/components/searchbox/v2/NodeSearchFilterBar.vue` around lines 25 - 37,
The button currently only renders visual dots and the base chip label, which
hides the active selections from screen readers; add an invisible
screen-reader-only summary inside the same button that lists the selected values
using vue-i18n. Concretely, in NodeSearchFilterBar.vue next to the visual dots
(inside the <button> that uses chipClass and tf.chip.label), insert an element
with class "sr-only" that uses the component's i18n (e.g. $t or useI18n) to
render a localized string key (add it to src/locales/en/main.json) and pass the
selected values derived from tf.values (respecting MAX_VISIBLE_DOTS if you want
the same truncation) — reference tf.values, MAX_VISIBLE_DOTS, tf.chip.label and
getLinkTypeColor to locate the spot — so assistive tech reads "label: value1,
value2…" while keeping visuals unchanged.
🧹 Nitpick comments (1)
src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts (1)

40-48: Type the mount helper against the component props.

props = {} is fully loose here, so a typo in a new prop silently turns into a no-op test setup. Typing this as Partial<ComponentProps<typeof NodeSearchCategorySidebar>> keeps the helper aligned with the component contract as the API evolves.

Based on learnings: In test files, for test helpers like mountComponent, type the props parameter as Partial<ComponentProps<typeof Component>>.

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

In `@src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts` around lines
40 - 48, The createWrapper test helper currently accepts an untyped props = {}
which allows silent typos; change its signature to accept props:
Partial<ComponentProps<typeof NodeSearchCategorySidebar>> and update the mount
call to use that typed props variable; also import ComponentProps from 'vue' (or
the appropriate typing source) at the top of the test file so the type resolves.
This keeps the helper aligned with NodeSearchCategorySidebar's props contract
and prevents silent setup mistakes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/searchbox/v2/NodeSearchCategorySidebar.vue`:
- Around line 181-189: The watcher on categoryTree currently emits 'autoExpand'
for a single-root tree even when the current selection is a deeper descendant,
which clobbers deeper selections; modify the watcher (the watch over
categoryTree that inspects nodes) to first check the current selected category
(e.g., the prop/ref named selectedCategory) and skip emitting emit('autoExpand',
nodes[0].key) if selectedCategory is already a descendant of
nodes[0].key—implement a small helper like isDescendant(selectedCategory,
rootKey) (used by the watcher) that returns true when the selectedCategory is
the root or any deeper child so the autoExpand event only fires when there is no
deeper selection to preserve.

In `@src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue`:
- Around line 2-20: The wrapper currently carries role="treeitem" and
aria-expanded while RovingFocusItem focuses the inner button, so screen readers
miss expanded/selected state; move the treeitem role and state attributes onto
the focus target by applying role="treeitem",
:aria-expanded="node.children?.length ? isExpanded : undefined", and
:aria-selected="selectedCategory === node.key || undefined" to the
RovingFocusItem's child button (the element referenced by buttonEl) or make the
wrapper itself the roving focus target in RovingFocusItem; ensure aria-current
remains or is replaced by aria-selected so the focused button exposes selection
and expanded state (use the same symbols: selectedCategory, isExpanded,
node.key, buttonEl, RovingFocusItem).

In `@src/components/searchbox/v2/NodeSearchContent.vue`:
- Around line 186-198: rootFilteredNodeDefs is filtering blueprint children
using the full BLUEPRINT_CATEGORY/... path while getCategoryResults compares
against root-relative keys, causing mismatches; update filtering in
rootFilteredNodeDefs (and similarly in getCategoryResults at the other location)
to normalize category forms before comparing: when rootFilter.value is
BLUEPRINT_CATEGORY treat child keys as full-path by prefixing BLUEPRINT_CATEGORY
+ '/' (or conversely strip the BLUEPRINT_CATEGORY/ prefix from node.category) so
both sides use the same representation; adjust the BLUEPRINT_CATEGORY case and
the partner/default branches to perform this normalization using the existing
rootFilter, BLUEPRINT_CATEGORY constant, node.category and the
sourceCategoryFilters mapping.

---

Duplicate comments:
In `@src/components/searchbox/v2/NodeSearchFilterBar.vue`:
- Around line 25-37: The button currently only renders visual dots and the base
chip label, which hides the active selections from screen readers; add an
invisible screen-reader-only summary inside the same button that lists the
selected values using vue-i18n. Concretely, in NodeSearchFilterBar.vue next to
the visual dots (inside the <button> that uses chipClass and tf.chip.label),
insert an element with class "sr-only" that uses the component's i18n (e.g. $t
or useI18n) to render a localized string key (add it to
src/locales/en/main.json) and pass the selected values derived from tf.values
(respecting MAX_VISIBLE_DOTS if you want the same truncation) — reference
tf.values, MAX_VISIBLE_DOTS, tf.chip.label and getLinkTypeColor to locate the
spot — so assistive tech reads "label: value1, value2…" while keeping visuals
unchanged.

---

Nitpick comments:
In `@src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts`:
- Around line 40-48: The createWrapper test helper currently accepts an untyped
props = {} which allows silent typos; change its signature to accept props:
Partial<ComponentProps<typeof NodeSearchCategorySidebar>> and update the mount
call to use that typed props variable; also import ComponentProps from 'vue' (or
the appropriate typing source) at the top of the test file so the type resolves.
This keeps the helper aligned with NodeSearchCategorySidebar's props contract
and prevents silent setup mistakes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 81701592-fd0c-43e8-9919-215f4e140234

📥 Commits

Reviewing files that changed from the base of the PR and between fd31f9d and 3cba424.

📒 Files selected for processing (15)
  • browser_tests/fixtures/components/ComfyNodeSearchBoxV2.ts
  • browser_tests/tests/nodeSearchBoxV2.spec.ts
  • src/components/node/NodePreviewCard.vue
  • src/components/searchbox/NodeSearchBoxPopover.vue
  • src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts
  • src/components/searchbox/v2/NodeSearchCategorySidebar.vue
  • src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue
  • src/components/searchbox/v2/NodeSearchContent.test.ts
  • src/components/searchbox/v2/NodeSearchContent.vue
  • src/components/searchbox/v2/NodeSearchFilterBar.vue
  • src/components/searchbox/v2/NodeSearchInput.vue
  • src/components/searchbox/v2/NodeSearchListItem.vue
  • src/components/searchbox/v2/NodeSearchTypeFilterPopover.test.ts
  • src/components/searchbox/v2/NodeSearchTypeFilterPopover.vue
  • src/components/searchbox/v2/__test__/testUtils.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/searchbox/v2/test/testUtils.ts
  • src/components/searchbox/v2/NodeSearchContent.test.ts

- update plural item selected entry
- update mock bookmarts to default empty
- fix test testing already sorted data
- prevent autoExpand already expanded
- fix aria role
- add test + fix path matching
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue (2)

13-20: ⚠️ Potential issue | 🟠 Major

Expose selection with aria-selected on the focused treeitem.

The focus target now has role="treeitem" and aria-expanded, but selection is still only conveyed via aria-current. In a selectable tree, assistive tech looks for aria-selected; without it, the focused node's selected state is not announced.

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

In `@src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue` around lines 13 -
20, Add an aria-selected attribute to the treeitem button so assistive tech can
announce selection: on the element referenced by buttonEl (the button with
role="treeitem" and :data-testid using node.key) bind aria-selected to the same
check used for aria-current (e.g. selectedCategory === node.key) so it returns a
boolean selection state; keep the existing :aria-current and :aria-expanded
logic (isExpanded) unchanged.

23-40: ⚠️ Potential issue | 🟡 Minor

Scope chevron reveal to the current row.

The icon still uses group-hover/categories and group-focus-within/categories, which bind to the sidebar-level group. Hovering or focusing any category will therefore reveal chevrons for unrelated rows too. Add a local group on this button and target that instead.

♻️ Possible fix
-            'flex w-full cursor-pointer items-center gap-2 rounded-lg border-none bg-transparent py-2.5 pr-3 text-left font-inter text-sm transition-colors',
+            'group flex w-full cursor-pointer items-center gap-2 rounded-lg border-none bg-transparent py-2.5 pr-3 text-left font-inter text-sm transition-colors',
             selectedCategory === node.key
               ? CATEGORY_SELECTED_CLASS
               : CATEGORY_UNSELECTED_CLASS
@@
-                ? 'icon-[lucide--chevron-down] opacity-0 group-focus-within/categories:opacity-100 group-hover/categories:opacity-100'
+                ? 'icon-[lucide--chevron-down] opacity-0 group-focus-within:opacity-100 group-hover:opacity-100'
                 : '',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue` around lines 23 -
40, The chevron reveal selectors currently target the sidebar-level group
(group-hover/categories and group-focus-within/categories), causing chevrons to
show for unrelated rows; add a local per-row group class on the button (e.g.,
"group/category-row" or similar) in the root element that renders each node (the
element using cn(...) and `@click`="$emit('select', node.key)"), then update the
chevron class selectors in the <i> element (the computed string that contains
'icon-[lucide--chevron-down] opacity-0 ...') to use the new local group
selectors (group-hover/<your-group> and group-focus-within/<your-group>) so
hover/focus only affect that row.
🧹 Nitpick comments (2)
src/components/searchbox/v2/NodeSearchTypeFilterPopover.test.ts (2)

76-167: Add a keyboard-path test for escapeClose.

This suite covers the click flows, but it never asserts the escapeClose emit or any Escape-key interaction even though that behavior is part of the component API and this PR is explicitly about keyboard/ARIA refinements. A focused test that opens the popover, sends Escape, and checks wrapper.emitted('escapeClose') would protect the new UX from regressions.

Based on learnings: "Applies to src/**/*.test.ts : For Vue component testing, use Vue Test Utils and follow advice about making components easy to test; aim for behavioral coverage of critical and new features."

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

In `@src/components/searchbox/v2/NodeSearchTypeFilterPopover.test.ts` around lines
76 - 167, Add a test that opens the popover (reuse createWrapper() and await
openPopover(wrapper)), then simulate pressing the Escape key on the popover or
document (e.g., trigger a 'keydown' with key === 'Escape' on wrapper or
wrapper.element), await nextTick(), and assert that
wrapper.emitted('escapeClose') exists and has length 1; reference existing
helpers getOptions, createWrapper, openPopover, and wrapper.emitted to locate
where to add the test.

9-49: Keep the test helpers tied to the real component types.

createWrapper() duplicates the prop contract, and createMockChip() sidesteps the filter contract with as unknown as. That makes this suite drift-prone: a prop or FilterChip['filter'] change can stop being type-checked here. Please derive props from ComponentProps<typeof NodeSearchTypeFilterPopover> and build the mock with the concrete filter type/satisfies instead of the double-cast.

♻️ Suggested direction
+import type { ComponentProps } from 'vue-component-type-helpers'
...
-  function createWrapper(
-    props: {
-      chip?: FilterChip
-      selectedValues?: string[]
-    } = {}
-  ) {
+  function createWrapper(
+    props: Partial<ComponentProps<typeof NodeSearchTypeFilterPopover>> = {}
+  ) {

Based on learnings: "In TypeScript test files, avoid duplicating interface/type definitions..." and "for test helper functions that construct mock objects implementing an interface, prefer using satisfies InterfaceType for shape validation instead of type assertions like as Partial as InterfaceType or as any."

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

In `@src/components/searchbox/v2/NodeSearchTypeFilterPopover.test.ts` around lines
9 - 49, Tests currently duplicate the component prop contract and cast the mock
filter with as unknown as, making type drift likely; change createWrapper to
type its props parameter from ComponentProps<typeof NodeSearchTypeFilterPopover>
(or derive the chip/selectedValues types from that) and rebuild createMockChip
so its returned object uses TypeScript's satisfies with the concrete FilterChip
and its filter type (e.g. return {...} satisfies FilterChip and ensure filter
conforms to FilterChip['filter'] or the concrete filter interface) instead of
using as unknown as, so the compiler will validate the mock shape against the
real component types (reference NodeSearchTypeFilterPopover, createWrapper,
createMockChip, FilterChip, ComponentProps, and use satisfies).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/searchbox/v2/NodeSearchCategorySidebar.vue`:
- Around line 181-195: The watch handler that emits 'autoExpand' (and the other
watcher updating selectedCategory at lines noted) fails to reset the collapsed
state, so a parent-driven selection can remain collapsed; update the handlers
that respond to external selectedCategory changes (the watch on categoryTree
that emits('autoExpand') and the watcher that sets selectedCategory externally)
to also clear selectedCollapsed (the same flag cleared inside selectCategory()),
i.e., set selectedCollapsed.value = false whenever you programmatically change
or accept a new selectedCategory/rootKey so the newly selected node is expanded.

In `@src/components/searchbox/v2/NodeSearchContent.test.ts`:
- Around line 229-233: The test is currently finding the "Essentials" button
across the whole dialog and may match a sidebar control; change the lookup to
scope it inside the filter bar component (e.g., locate the NodeSearchFilterBar
via wrapper.findComponent(NodeSearchFilterBar) or a data-test selector for the
filter bar) then search that component's buttons for the one whose text includes
"Essentials" and trigger the click; update the test so references to
essentialsBtn come from the filter-bar wrapper rather than wrapper.findAll(...)
to ensure the root-filter control is exercised.

In `@src/components/searchbox/v2/NodeSearchContent.vue`:
- Around line 62-73: The per-row keyboard handlers currently rely on the global
selectedIndex and can become desynced when onPointerMove mutates selectedIndex
or when displayedResults changes; update the template handlers so each row's key
actions reference the row's own index (use the local index in
`@keydown.up/`@keydown.down/@keydown.enter and in the click handler instead of
reading selectedIndex), ensure navigateResults and selectCurrentResult accept
and use an index parameter (e.g., navigateResults(delta, fromKeyboard, rowIndex)
/ selectCurrentResult(rowIndex)), and add logic (a watcher or computed) to clamp
or reset selectedIndex whenever displayedResults changes so it is always within
0..displayedResults.length-1 (or set to -1 when empty) to avoid out-of-range
activation; keep onPointerMove behavior but ensure it focuses the hovered row or
updates selectedIndex consistently with the per-row handlers.

---

Duplicate comments:
In `@src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue`:
- Around line 13-20: Add an aria-selected attribute to the treeitem button so
assistive tech can announce selection: on the element referenced by buttonEl
(the button with role="treeitem" and :data-testid using node.key) bind
aria-selected to the same check used for aria-current (e.g. selectedCategory ===
node.key) so it returns a boolean selection state; keep the existing
:aria-current and :aria-expanded logic (isExpanded) unchanged.
- Around line 23-40: The chevron reveal selectors currently target the
sidebar-level group (group-hover/categories and group-focus-within/categories),
causing chevrons to show for unrelated rows; add a local per-row group class on
the button (e.g., "group/category-row" or similar) in the root element that
renders each node (the element using cn(...) and `@click`="$emit('select',
node.key)"), then update the chevron class selectors in the <i> element (the
computed string that contains 'icon-[lucide--chevron-down] opacity-0 ...') to
use the new local group selectors (group-hover/<your-group> and
group-focus-within/<your-group>) so hover/focus only affect that row.

---

Nitpick comments:
In `@src/components/searchbox/v2/NodeSearchTypeFilterPopover.test.ts`:
- Around line 76-167: Add a test that opens the popover (reuse createWrapper()
and await openPopover(wrapper)), then simulate pressing the Escape key on the
popover or document (e.g., trigger a 'keydown' with key === 'Escape' on wrapper
or wrapper.element), await nextTick(), and assert that
wrapper.emitted('escapeClose') exists and has length 1; reference existing
helpers getOptions, createWrapper, openPopover, and wrapper.emitted to locate
where to add the test.
- Around line 9-49: Tests currently duplicate the component prop contract and
cast the mock filter with as unknown as, making type drift likely; change
createWrapper to type its props parameter from ComponentProps<typeof
NodeSearchTypeFilterPopover> (or derive the chip/selectedValues types from that)
and rebuild createMockChip so its returned object uses TypeScript's satisfies
with the concrete FilterChip and its filter type (e.g. return {...} satisfies
FilterChip and ensure filter conforms to FilterChip['filter'] or the concrete
filter interface) instead of using as unknown as, so the compiler will validate
the mock shape against the real component types (reference
NodeSearchTypeFilterPopover, createWrapper, createMockChip, FilterChip,
ComponentProps, and use satisfies).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a3116984-109c-4568-82ba-a979aee6312b

📥 Commits

Reviewing files that changed from the base of the PR and between d18243e and 08666d8.

📒 Files selected for processing (9)
  • src/components/input/MultiSelect.vue
  • src/components/searchbox/v2/NodeSearchCategorySidebar.vue
  • src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue
  • src/components/searchbox/v2/NodeSearchContent.test.ts
  • src/components/searchbox/v2/NodeSearchContent.vue
  • src/components/searchbox/v2/NodeSearchTypeFilterPopover.test.ts
  • src/components/searchbox/v2/NodeSearchTypeFilterPopover.vue
  • src/components/searchbox/v2/__test__/testUtils.ts
  • src/locales/en/main.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/searchbox/v2/NodeSearchTypeFilterPopover.vue
  • src/components/searchbox/v2/test/testUtils.ts

@pythongosssss pythongosssss added the preview-cpu Creates a preview ephemeral environment for this PR (CPU only) label Mar 12, 2026
- fix expanding after manual categ collapse
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)
src/components/searchbox/v2/NodeSearchContent.test.ts (1)

136-146: ⚠️ Potential issue | 🟡 Minor

These “no bookmarks” cases still fabricate a visible Favorites category.

mockBookmarks(false, ['placeholder']) keeps bookmarks.length > 0, so both tests exercise an impossible UI state: Favorites is rendered even though the test says no bookmarks exist. Use mockBookmarks(false) when you want to verify Favorites is hidden, and drive the empty-results case through a reachable path instead of a fake non-empty bookmark list.

Based on learnings: "Do not write tests that just test the mocks; ensure tests fail when code behaves unexpectedly."

Also applies to: 535-543

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

In `@src/components/searchbox/v2/NodeSearchContent.test.ts` around lines 136 -
146, The test is fabricating a visible Favorites category by calling
mockBookmarks(false, ['placeholder']) which leaves bookmarks.length > 0; change
calls to mockBookmarks(false) so the mocked bookmarks array is empty, then use
createWrapper() and trigger the real UI path (e.g., clicking the favorites
toggle via the '[data-testid="category-favorites"]' selector) to assert that
Favorites is hidden and that the empty-results text ("No results") appears.
Update both the test around the createMockNodeDef + mockBookmarks call shown and
the duplicated case at lines ~535-543 to use mockBookmarks(false) and drive the
empty-state through the real UI interactions (createWrapper, trigger, nextTick)
rather than relying on a non-empty placeholder bookmark.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/searchbox/v2/NodeSearchCategorySidebar.vue`:
- Around line 132-139: The sourceCategories computed currently always pushes the
'custom' category; change it to only include 'custom' when there are actual
custom nodes by checking the same node set used for categoryTree (e.g., inspect
nodeDefs / visibleNodeDefs for any items whose source/type indicates custom) or
accept a hasCustomNodes flag and gate the push on that boolean; update the
computed (sourceCategories) to use hasEssentialNodes and the new custom-presence
check instead of unconditionally pushing { id: 'custom', ... } so empty custom
categories are not rendered.

---

Duplicate comments:
In `@src/components/searchbox/v2/NodeSearchContent.test.ts`:
- Around line 136-146: The test is fabricating a visible Favorites category by
calling mockBookmarks(false, ['placeholder']) which leaves bookmarks.length > 0;
change calls to mockBookmarks(false) so the mocked bookmarks array is empty,
then use createWrapper() and trigger the real UI path (e.g., clicking the
favorites toggle via the '[data-testid="category-favorites"]' selector) to
assert that Favorites is hidden and that the empty-results text ("No results")
appears. Update both the test around the createMockNodeDef + mockBookmarks call
shown and the duplicated case at lines ~535-543 to use mockBookmarks(false) and
drive the empty-state through the real UI interactions (createWrapper, trigger,
nextTick) rather than relying on a non-empty placeholder bookmark.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7802cf32-1953-489b-8f0d-46d863cb0d16

📥 Commits

Reviewing files that changed from the base of the PR and between 08666d8 and 0772f2a.

📒 Files selected for processing (2)
  • src/components/searchbox/v2/NodeSearchCategorySidebar.vue
  • src/components/searchbox/v2/NodeSearchContent.test.ts

@pythongosssss pythongosssss added preview-cpu Creates a preview ephemeral environment for this PR (CPU only) and removed preview-cpu Creates a preview ephemeral environment for this PR (CPU only) labels Mar 16, 2026
@pythongosssss pythongosssss added preview-cpu Creates a preview ephemeral environment for this PR (CPU only) and removed preview-cpu Creates a preview ephemeral environment for this PR (CPU only) labels Mar 18, 2026
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

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

Inline comments:
In `@src/components/searchbox/v2/NodeSearchCategorySidebar.vue`:
- Around line 219-223: collapseCategory currently clears expandedCategory
entirely which over-collapses nested trees; change it so expandedCategory is set
to the parent path of the collapsed node instead of ''. In the
collapseCategory(categoryId: string) function compute the parent path of
categoryId (e.g., remove the last segment after the path delimiter used for
category ids; if no parent exists, set expandedCategory.value = ''), then assign
that parent path to expandedCategory.value while still updating
lastEmittedCategory = categoryId and selectedCategory.value = categoryId; use
the existing symbols collapseCategory, expandedCategory, lastEmittedCategory,
and selectedCategory to locate and update the logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 628189fd-5b38-4db3-b384-508a23aabf2a

📥 Commits

Reviewing files that changed from the base of the PR and between bd66617 and 2a531ff.

📒 Files selected for processing (4)
  • browser_tests/tests/nodeSearchBoxV2Extended.spec.ts
  • src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts
  • src/components/searchbox/v2/NodeSearchCategorySidebar.vue
  • src/components/searchbox/v2/NodeSearchCategoryTreeNode.vue

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 18, 2026
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.

🧹 Nitpick comments (3)
src/components/searchbox/v2/NodeSearchCategorySidebar.vue (2)

168-182: Variable shadowing: rootKey in watch shadows the prop.

Line 172 declares const rootKey = nodes[0].key which shadows the rootKey prop from line 101. While this doesn't cause a bug (the prop isn't used in the watch), it reduces clarity.

♻️ Suggested rename
   (nodes) => {
     if (nodes.length === 1 && nodes[0].children?.length) {
-      const rootKey = nodes[0].key
+      const singleRootKey = nodes[0].key
       if (
-        selectedCategory.value !== rootKey &&
-        !selectedCategory.value.startsWith(rootKey + '/')
+        selectedCategory.value !== singleRootKey &&
+        !selectedCategory.value.startsWith(singleRootKey + '/')
       ) {
-        emit('autoExpand', rootKey)
+        emit('autoExpand', singleRootKey)
       }
     }
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/searchbox/v2/NodeSearchCategorySidebar.vue` around lines 168 -
182, The local variable rootKey inside the watch callback shadows the component
prop rootKey; rename the local variable (for example to firstNodeKey or
rootNodeKey) in the watch callback where it is defined and used (the watch on
categoryTree that compares selectedCategory.value and emits 'autoExpand') and
update all references in that callback (the const declaration, the
selectedCategory.value checks, and the emit('autoExpand', ...) call) to use the
new name so the prop rootKey is no longer shadowed.

125-125: Clarify intent: sourceCategories always returns empty array.

If source categories have been permanently removed from this sidebar, consider removing the sourceCategories computed, the related template section (lines 27-47), and simplifying the conditional styling on line 57. This would reduce dead code.

Alternatively, if this is intentional scaffolding for future use, a brief comment would help.

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

In `@src/components/searchbox/v2/NodeSearchCategorySidebar.vue` at line 125, The
computed sourceCategories currently always returns an empty array
(sourceCategories) which leaves dead code in the template; either remove the
computed property and the matching template block that renders source categories
(the sidebar source categories section) and simplify any conditional class or
v-if/v-show logic that depends on sourceCategories, or if you intend it as
scaffolding, add a concise comment above the sourceCategories computed
explaining it's intentionally empty and reserved for future use so reviewers
understand it's deliberate; update any class bindings or conditionals that
reference sourceCategories to reflect its removal or documented intent.
src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts (1)

468-478: Test relies on implementation detail of tree item structure.

The test queries closest('[role="treeitem"]') which depends on the internal DOM structure of NodeSearchCategoryTreeNode. If that component's structure changes, this test could break even if behavior is correct.

Consider adding a data-testid to the treeitem element in NodeSearchCategoryTreeNode for more robust selection, or document this coupling.

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

In `@src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts` around lines
468 - 478, The test in NodeSearchCategorySidebar.test.ts relies on the DOM
structure by calling closest('[role="treeitem"]'), which couples it to
NodeSearchCategoryTreeNode internals; instead add a stable hook: add a
data-testid (e.g. data-testid="treeitem-<category>"/or generic
data-testid="category-treeitem") to the treeitem element rendered by
NodeSearchCategoryTreeNode, and update the test to select that data-testid (e.g.
wrapper.find('[data-testid="category-treeitem"]')) rather than using closest, so
the test targets the component contract not its internal markup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts`:
- Around line 468-478: The test in NodeSearchCategorySidebar.test.ts relies on
the DOM structure by calling closest('[role="treeitem"]'), which couples it to
NodeSearchCategoryTreeNode internals; instead add a stable hook: add a
data-testid (e.g. data-testid="treeitem-<category>"/or generic
data-testid="category-treeitem") to the treeitem element rendered by
NodeSearchCategoryTreeNode, and update the test to select that data-testid (e.g.
wrapper.find('[data-testid="category-treeitem"]')) rather than using closest, so
the test targets the component contract not its internal markup.

In `@src/components/searchbox/v2/NodeSearchCategorySidebar.vue`:
- Around line 168-182: The local variable rootKey inside the watch callback
shadows the component prop rootKey; rename the local variable (for example to
firstNodeKey or rootNodeKey) in the watch callback where it is defined and used
(the watch on categoryTree that compares selectedCategory.value and emits
'autoExpand') and update all references in that callback (the const declaration,
the selectedCategory.value checks, and the emit('autoExpand', ...) call) to use
the new name so the prop rootKey is no longer shadowed.
- Line 125: The computed sourceCategories currently always returns an empty
array (sourceCategories) which leaves dead code in the template; either remove
the computed property and the matching template block that renders source
categories (the sidebar source categories section) and simplify any conditional
class or v-if/v-show logic that depends on sourceCategories, or if you intend it
as scaffolding, add a concise comment above the sourceCategories computed
explaining it's intentionally empty and reserved for future use so reviewers
understand it's deliberate; update any class bindings or conditionals that
reference sourceCategories to reflect its removal or documented intent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 65a12518-129d-4a6c-b020-6cae9fe1c34f

📥 Commits

Reviewing files that changed from the base of the PR and between 2a531ff and da2fede.

📒 Files selected for processing (2)
  • src/components/searchbox/v2/NodeSearchCategorySidebar.test.ts
  • src/components/searchbox/v2/NodeSearchCategorySidebar.vue

@pythongosssss pythongosssss marked this pull request as ready for review March 18, 2026 20:06
@pythongosssss pythongosssss requested a review from a team March 18, 2026 20:06
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Mar 18, 2026
Yourz
Yourz previously approved these changes Mar 19, 2026
@pythongosssss pythongosssss assigned viva-jinyi and unassigned Yourz Mar 19, 2026
@pythongosssss pythongosssss dismissed stale reviews from Yourz and coderabbitai[bot] via 8e5dc15 March 19, 2026 19:45
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 19, 2026
)
"
>
<i class="icon-[lucide--component] h-full bg-amber-400" />
Copy link
Member

Choose a reason for hiding this comment

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

can we use font size instead of h-full?

Copy link
Member Author

Choose a reason for hiding this comment

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

This was taken directly from src/renderer/extensions/vueNodes/components/NodeHeader.vue, i'd rather not change it in case there is some issue with it rendering there

class="-mt-1 truncate text-xs text-muted-foreground"
>
{{ nodeDef.category.replaceAll('/', ' > ') }}
{{ nodeDef.category.replaceAll('/', ' / ') }}
Copy link
Member

Choose a reason for hiding this comment

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

I think it'd be better to use computed!

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

<p
v-if="nodeDef.description"
class="m-0 text-[11px] leading-normal font-normal text-muted-foreground"
class="m-0 max-h-[30vh] overflow-y-auto text-[11px] leading-normal font-normal text-muted-foreground"
Copy link
Member

Choose a reason for hiding this comment

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

can we use text-xs instead of using text-[11px]?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not aware of the specifics of this, but since this was recently redesigned i'd imagine this was a specific part of the design

>
{{ preset.label }}
</button>
<button
Copy link
Member

Choose a reason for hiding this comment

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

I think we can use common button component

Copy link
Member Author

Choose a reason for hiding this comment

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

Using the Button component means needing to fight against the styling it adds or adding a new variant that would only be used in this context - I think it adds more overhead for little benefit to use it here

/>
</template>
<RovingFocusItem as-child>
<button
Copy link
Member

Choose a reason for hiding this comment

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

it would be better to be replaced with button component

Copy link
Member Author

Choose a reason for hiding this comment

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

Using the Button component means needing to fight against the styling it adds or adding a new variant that would only be used in this context - I think it adds more overhead for little benefit to use it here

root: {
class: useSearchBoxV2
? 'w-4/5 min-w-[32rem] max-w-[56rem] border-0 bg-transparent mt-[10vh] max-md:w-[95%] max-md:min-w-0 overflow-visible'
? 'w-4/5 min-w-[32rem] max-w-[56rem] border-0 bg-transparent max-md:w-[95%] max-md:min-w-0 overflow-visible'
Copy link
Member

Choose a reason for hiding this comment

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

I think w-full max-w-[56rem] min-w-[32rem] max-md:min-w-0 bg-transparent border-0 would work too?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's a bit different, as that then removes the spacing around at smaller screen sizes, but that's probably not a bad thing, so done 👍

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

Labels

Design Used to request Product feedback on design decisions preview-cpu Creates a preview ephemeral environment for this PR (CPU only) size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants