Skip to content

fix(useFilter): keep labels and separators in place while sorting - #6995

Open
giaBaoJS wants to merge 2 commits into
nuxt:v4from
giaBaoJS:fix/filter-groups-structural-order
Open

giaBaoJS wants to merge 2 commits into
nuxt:v4from
giaBaoJS:fix/filter-groups-structural-order

Conversation

@giaBaoJS

Copy link
Copy Markdown
Contributor

🔗 Linked issue

None. Found while auditing filterGroups.

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

filterGroups gives labels and separators the sentinel score -1, then sorts the whole group by score, so every structural item lands ahead of every match. Its own docstring promises the opposite: "Structural items (labels, separators) are kept at their relative position."

A separator inside a flat items array is documented usage, and typing moves the rule to the top of the menu:

filterGroups([['Banana', { type: 'separator' }, 'Aubergine', 'Broccoli']], 'b', { fields: ['label'], isStructural })
// [{ type: 'separator' }, 'Banana', 'Broccoli', 'Aubergine']

Through SelectMenu, InputMenu, Listbox or DropdownMenu that renders a separator above the first option.

Only the matches are sorted now, and structural items keep the slots they hold in the filtered group. A leading label still comes out first because it was already first, so grouped items are unaffected.

One case I left alone: a separator whose surrounding items are all filtered out still renders. filterGroups([['Apple', { type: 'separator' }, 'Carrot']], 'carrot', ...) returns [{ type: 'separator' }, 'Carrot']. Deciding when a separator has become redundant is a separate behaviour change.

test/composables/useFilter.spec.ts is new and covers the ordering.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 8a0abbb9-ae3e-4f02-a667-1cc676678bfe

📥 Commits

Reviewing files that changed from the base of the PR and between 0976abf and a9d68ac.

📒 Files selected for processing (2)
  • src/runtime/composables/useFilter.ts
  • test/composables/useFilter.spec.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

filterGroups now sorts only matching items by relevance. Labels and separators remain in their original positions. New tests cover structural item placement, relevance ordering, and removal of groups that contain no matches.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to a9d68

This change keeps labels and separators in place while sorting matching options within their sections, with coverage for ordering and empty groups. No merge-blocking risk is identified.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the fix and the affected behavior: useFilter now keeps labels and separators in place while sorting.
Description check ✅ Passed The description accurately explains the filtering bug, the structural-item behavior, the scope of the fix, and the added tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

@codspeed

codspeed Bot commented Sep 21, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing giaBaoJS:fix/filter-groups-structural-order (a9d68ac) with v4 (bab8c5a)

Open in CodSpeed

@pkg-pr-new

pkg-pr-new Bot commented Sep 21, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/@nuxt/ui@6995

commit: a9d68ac

Comment thread src/runtime/composables/useFilter.ts Outdated
Comment on lines +91 to +95
// Sorting `result` itself would pull every structural item to the front,
// so only the matches are reordered and they refill the slots they held.
const matches = result.filter(({ score }) => score !== -1).sort((a, b) => a.score - b.score)
let index = 0
return result.map(({ item, score }) => score === -1 ? item : matches[index++]!.item)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorting the whole group lets items cross a label or separator in a flat array, e.g. Aubergine ends up under Fruits when searching a. Sorting each run on its own avoids that:

Suggested change
// Sorting `result` itself would pull every structural item to the front,
// so only the matches are reordered and they refill the slots they held.
const matches = result.filter(({ score }) => score !== -1).sort((a, b) => a.score - b.score)
let index = 0
return result.map(({ item, score }) => score === -1 ? item : matches[index++]!.item)
// Sort each run of matches on its own so items never cross a label or separator.
let start = 0
for (let i = 0; i <= result.length; i++) {
if (i === result.length || result[i]!.score === -1) {
result.splice(start, i - start, ...result.slice(start, i).sort((a, b) => a.score - b.score))
start = i + 1
}
}
return result.map(({ item }) => item)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied your suggestion. You are right that the slot-refilling version let items cross a boundary: with [Fruits, 'Banana', Vegetables, 'Aubergine'] and a, it returned Aubergine under Fruits and Banana under Vegetables.

Added a test for exactly that case. It is the one the previous version failed while the other three stayed green, so the suite now pins the boundary rather than just the position of the labels.

One note while checking the loop: start = i behaves identically to start = i + 1, because a structural item scores -1, which is below every value score() returns (0, 1, 2), and sort is stable, so it stays at the front of the run either way. I kept your i + 1 since it says the intent. Full suite is 7452 passing, 6 skipped, and lint and typecheck are clean.

This branch was successfully deployed

1 active deployment
Preview – ui a9d68ac9 Deployed Sep 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants