Skip to content

Migrate LanguageFilter to KMultiSelect - #6093

Open
Prashant-thakur77 wants to merge 2 commits into
learningequality:unstablefrom
Prashant-thakur77:feature/kmultiselect-language-filter
Open

Migrate LanguageFilter to KMultiSelect#6093
Prashant-thakur77 wants to merge 2 commits into
learningequality:unstablefrom
Prashant-thakur77:feature/kmultiselect-language-filter

Conversation

@Prashant-thakur77

@Prashant-thakur77 Prashant-thakur77 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates LanguageFilter from Vuetify's VAutocomplete to KDS KMultiSelect, as part of moving Studio off Vuetify.

KMultiSelect renders its own chips and options, so the hand-rolled #selection and #item slot templates, the StudioChip import, the search helper, the scroll-reset method, and the Vuetify-specific styles all go away.

Partof: learningequality/kolibri-design-system#1259
Notable points:

  • Search parity is preserved. The old field passed a function as item-text returning name + related_names + id, then overrode both slots to display only name the concatenation existed purely for filtering. That's now expressed directly asitemText="name" for display plus :searchKeys="['related_names', 'id']" for matching,
    so searching by native name, English name, or language code all still work.
  • StudioChip is no longer needed here because KMultiSelect renders chips itself using KChip, which is the same component upstreamed into KDS (identical markup and styles).No spec file existed for this component and none is added; behavior was verified as described below.

References

Reviewer guidance

Check for no regrressions then before and proper refractoring.

Screencast.From.2026-08-17.02-43-29.mp4

AI usage

I used Claude to write this description and made the changes as needed.

@learning-equality-bot

Copy link
Copy Markdown

👋 Hi @Prashant-thakur77, thanks for contributing!

For the review process to begin, please verify that the following is satisfied:

  • Contribution is aligned with our contributing guidelines

  • Pull request description has correctly filled AI usage section & follows our AI guidance:

    AI guidance

    State explicitly whether you didn't use or used AI & how.

    If you used it, ensure that the PR is aligned with Using AI as well as our DEEP framework. DEEP asks you:

    • Disclose — Be open about when you've used AI for support.
    • Engage critically — Question what is generated. Review code for correctness and unnecessary complexity.
    • Edit — Review and refine AI output. Remove unnecessary code and verify it still works after your edits.
    • Process sharing — Explain how you used the AI so others can learn.

    Examples of good disclosures:

    "I used Claude Code to implement the component, prompting it to follow the pattern in ComponentX. I reviewed the generated code, removed unnecessary error handling, and verified the tests pass."

    "I brainstormed the approach with Gemini, then had it write failing tests for the feature. After reviewing the tests, I used Claude Code to generate the implementation. I refactored the output to reduce verbosity and ran the full test suite."

Also check that issue requirements are satisfied & you ran pre-commit locally.

Pull requests that don't follow the guidelines will be closed.

Reviewer assignment can take up to 2 weeks.

@Prashant-thakur77

Copy link
Copy Markdown
Contributor Author

@LianaHarris360 I’ve created the PR for the LanguageFilter with KMultiSelect. :)

@Prashant-thakur77
Prashant-thakur77 force-pushed the feature/kmultiselect-language-filter branch from 8b58fe3 to 3d58905 Compare August 20, 2026 14:38
@learning-equality-bot

Copy link
Copy Markdown

📢✨ Before we assign a reviewer, we'll turn on @rtibblesbot to pre-review. Its comments are generated by an LLM, and should be evaluated accordingly.

@rtibblesbot

rtibblesbot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

🟡 Waiting for changes

Last updated: 2026-08-20 18:37 UTC

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #6093 — the migration is faithful: itemText="name" + :searchKeys="['related_names', 'id']" reproduces what the old concatenated item-text matched (KDS 5.9.0 useMultiSelectList filters itemText first, then each searchKeys entry, unwrapping arrays), and the messages map covers every key KMultiSelect's validator requires. Nice side-effect: display and matching are now separate, so a query straddling the old join('') boundary no longer false-matches.

CI passing. Manual QA did not run for this review — the two layout behaviours below are unverified in a browser.

  • suggestion — dropdown now renders in place inside the panel's overflow-y: auto container, with no flip logic (LanguageFilter.vue:3)
  • suggestion — deleted chip-width guard has no KDS equivalent (LanguageFilter.vue:14)
  • suggestion — no spec covers the migrated behaviour, unlike #6092 (LanguageFilter.vue:9)
  • nitpick ×4 — dir on native names, redundant :multiple, duplicated messages map, missing context on new $trs

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Ran a phased review pipeline over the pull request diff:

  • Classified the diff to select review passes (core, frontend, backend) and whether manual QA was required
  • Core review pass checked correctness, design, architecture, testing, completeness, and DRY/SRP/Rule-of-Three principles
  • Specialized frontend/backend review passes applied framework-specific lenses where those files changed
  • For UI changes: manual QA and an accessibility audit against a live dev server, when available
  • Checked CI status and linked issue acceptance criteria
  • Synthesized one review from those passes and chose the verdict from the findings, CI status, and QA evidence

<template>

<VAutocomplete
<KMultiSelect

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: The menu moves from a detached overlay into a scrolling ancestor.

VAutocomplete was used bare, so Vuetify's Detachable mixin appended the menu to [data-app]. KDS KMultiSelectDropdown renders in place (position: absolute; top: 100%; max-height: 256px) and useMultiSelectDropdown.js has no viewport/flip logic — no getBoundingClientRect, no top branch. Its ancestor clips: CatalogFilterPanelContent.vue .filters-container { min-height: 0; overflow-y: auto; }.

This is what shared/views/form/DropdownWrapper.vue exists for — the sibling MultiSelect and CountryField in the same panel use it to flip upward when there isn't 300px below; KMultiSelect can't.

Manual QA didn't run here, so this is unconfirmed. The field is 2nd in the panel so tall desktop viewports likely clear it. Worth checking: short viewport (~600–700px) with the panel scrolled so the field sits in the lower 256px, and windowIsSmall where the panel renders inside SidePanelModal. If it does clip, it belongs upstream in KDS.

</VAutocomplete>
:noResultsText="$tr('noMatchingLanguageText')"
:messages="messages"
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: The deleted style block carried its own reason — "Need to set otherwise chips will exceed width of selection box" — plus max-width: 100% on chip content and a 250px truncate on option rows.

KDS 5.9.0 reintroduces that condition: KChip is white-space: nowrap with no max-width, .kmselect-input is flex-wrap: wrap with no overflow/min-width: 0, and KListboxOption doesn't truncate. Longest native names in window.publicLanguages (português brasileiro, Bahasa Indonesia, српски језик) in the narrow desktop sidebar are the case to look at.

The #chip/#option slots are the hook if it overflows. Note the old markup wrapped both chip and option in a VTooltip showing the full name — that was the readability affordance for the truncation, and it was hover-only, so prefer wrapping over reintroducing truncate+tooltip.

multiple
itemValue="id"
itemText="name"
:searchKeys="['related_names', 'id']"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: Nothing tests the behaviour this line is responsible for. CatalogFilterPanelContent.spec.js:82 only asserts the Languages label renders.

b63db298c (#6092), the same migration one commit earlier, added contentNodeFields/__tests__/categoryOptions.spec.js covering selection, clear, and the clear-all button by role. Those three apply here, plus the one behaviour unique to this component: typing an English name or a language code still matches. A KDS bump or prop rename would otherwise break search silently.

publicLanguages is built at module load from window.publicLanguages, so the spec has to set that global before importing the component.

box
multiple
itemValue="id"
itemText="name"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: itemText="name" resolves to native_name — content text in its own script, so Arabic/Hebrew/Farsi entries are RTL inside an LTR app. Neither KMultiSelectNode nor KChip sets dir (KDS 5.9.0); shared/views/form/MultiSelect.vue:34 sets dir="auto" on its option text.

Asking rather than asserting, because the old code deliberately went the other way — :labelDir="null" suppressed KCheckbox's dir="auto". If that was working around the checkbox row specifically, the #option/#chip slots would let you scope dir="auto" to the text span.

itemValue="id"
itemText="name"
:searchKeys="['related_names', 'id']"
:multiple="true"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: multiple defaults to true (KMultiSelect/index.vue:586); CategoryOptions omits it.

return;
}
firstItem.$el.scrollIntoView();
messages() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: Near-verbatim copy of CategoryOptions.vue:200-214 — same two translators, only itemsSelected and cleared differ. Two is under the Rule of Three, so leaving it is right; flagging for whoever migrates the third (MultiSelect.vue / CountryField.vue), at which point a shared/strings/ helper taking the two component-specific messages earns its keep.

$trs: {
languageLabel: 'Languages',
noMatchingLanguageText: 'No language matches the search',
itemsSelected: '{count, plural, one {# language selected} other {# languages selected}}',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: Both new strings are screen-reader announcements with no context — "Cleared # selections" is hard to translate without knowing what's cleared. No $trs block in channelList/ carries context today, so this isn't the current convention; commonStrings.js, where the neighbouring messages keys come from, does.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants