Skip to content

feat: Allow freeform text in VirtualMultiSelect#2622

Merged
kodiakhq[bot] merged 2 commits into
mainfrom
drew/virtual-multi-select-freeform
Jul 13, 2026
Merged

feat: Allow freeform text in VirtualMultiSelect#2622
kodiakhq[bot] merged 2 commits into
mainfrom
drew/virtual-multi-select-freeform

Conversation

@pulpdrew

Copy link
Copy Markdown
Contributor

Summary

This PR updates the VirtualMultiSelect component to

  1. Auto-sort the values (previously the parent had to pass pre-sorted values into the component, which not all parents were doing)
  2. Allow the user to input a custom value, which may or may not be listed as an option

Screenshots or video

Screen.Recording.2026-07-10.at.2.00.11.PM.mov

How to test on Vercel preview

This can be tested by adding a filter to a dashboard and using the resulting multi-select.

References

  • Linear Issue: Closes HDX-4751
  • Related PRs:

@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 77e0d7f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Patch
@hyperdx/api Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Jul 13, 2026 11:24am
hyperdx-storybook Ready Ready Preview, Comment Jul 13, 2026 11:24am

Request Review

@github-actions github-actions Bot added the review/tier-2 Low risk — AI review + quick human skim label Jul 10, 2026
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

🔵 Tier 2 — Low Risk

Small, isolated change with no API route or data model modifications.

Why this tier:

  • Standard feature/fix — introduces new logic or modifies core functionality

Review process: AI review + quick human skim (target: 5–15 min). Reviewer validates AI assessment and checks for domain-specific concerns.
SLA: Resolve within 4 business hours.

Stats
  • Production files changed: 2
  • Production lines changed: 36 (+ 153 in test files, excluded from tier calculation)
  • Branch: drew/virtual-multi-select-freeform
  • Author: pulpdrew

To override this classification, remove the review/tier-2 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds freeform text entry to VirtualMultiSelect, letting users press Enter to add arbitrary values not present in the dropdown. It also moves auto-sorting into the component itself (using toSorted to avoid mutation), addressing both prior review comments in one go.

  • VirtualMultiSelect.tsx: adds a separate sorted memo (only recomputes when data changes), filters blank items from options, and intercepts the Enter key — deferring to Mantine's default combobox selection when an option is keyboard-highlighted and adding the typed text as a freeform value otherwise.
  • DashboardFilters.tsx: removes the now-redundant .toSorted() call from the parent, replacing it with a simple useMemo that stabilises the empty-fallback array reference.
  • dashboard.spec.ts / DashboardPage.ts: adds a complete E2E flow covering type → "Nothing found..." assertion → Enter → pill visible → filter applied → backspace removal.

Confidence Score: 5/5

Safe to merge — the changes are well-scoped to the VirtualMultiSelect component and its parent, both prior review comments have been addressed, and an end-to-end test validates the new flow.

The freeform-entry logic correctly gates on getSelectedOptionIndex() === -1 to avoid conflicting with keyboard-highlighted dropdown selections. The sort mutation issue is fixed via toSorted. No regressions are visible in the changed paths.

No files require special attention.

Important Files Changed

Filename Overview
packages/app/src/components/VirtualMultiSelect/VirtualMultiSelect.tsx Core feature change: adds freeform value entry via Enter, moves sort into a dedicated memo using toSorted, and filters blank dropdown items. Logic is correct and addresses both prior review comments.
packages/app/src/DashboardFilters.tsx Removes the now-redundant parent-side sort; replaces it with a stable empty-fallback memo. Clean and correct.
packages/app/tests/e2e/features/dashboard.spec.ts New E2E test covers the complete freeform-value flow end-to-end, including the Nothing found state, pill appearance, filter effect, and backspace removal.
packages/app/tests/e2e/page-objects/DashboardPage.ts Adds well-documented page-object helpers for the freeform filter flow. Correctly notes that the Combobox.Dropdown is portaled outside the filter select subtree.
.changeset/tall-eels-vanish.md Patch-level changeset entry for the app package. Correct classification.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant PillsInput
    participant handleKeyDown
    participant Combobox
    participant onChange

    User->>PillsInput: Type text
    PillsInput->>Combobox: updateSelectedOptionIndex()
    Combobox-->>PillsInput: filtered options shown

    alt Option highlighted via arrow keys
        User->>PillsInput: Press Enter
        PillsInput->>handleKeyDown: getSelectedOptionIndex() not -1
        handleKeyDown-->>Combobox: defer to Mantine default
        Combobox->>onChange: handleSelectValue(highlightedOption)
    else No option highlighted (freeform)
        User->>PillsInput: Press Enter
        PillsInput->>handleKeyDown: "getSelectedOptionIndex() === -1"
        handleKeyDown->>onChange: handleAddValue(trimmedText)
        handleKeyDown->>PillsInput: setSearch('')
    end

    alt Backspace with empty search
        User->>PillsInput: Press Backspace
        PillsInput->>handleKeyDown: "search.length === 0"
        handleKeyDown->>onChange: handleRemoveValue(values[last])
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant PillsInput
    participant handleKeyDown
    participant Combobox
    participant onChange

    User->>PillsInput: Type text
    PillsInput->>Combobox: updateSelectedOptionIndex()
    Combobox-->>PillsInput: filtered options shown

    alt Option highlighted via arrow keys
        User->>PillsInput: Press Enter
        PillsInput->>handleKeyDown: getSelectedOptionIndex() not -1
        handleKeyDown-->>Combobox: defer to Mantine default
        Combobox->>onChange: handleSelectValue(highlightedOption)
    else No option highlighted (freeform)
        User->>PillsInput: Press Enter
        PillsInput->>handleKeyDown: "getSelectedOptionIndex() === -1"
        handleKeyDown->>onChange: handleAddValue(trimmedText)
        handleKeyDown->>PillsInput: setSearch('')
    end

    alt Backspace with empty search
        User->>PillsInput: Press Backspace
        PillsInput->>handleKeyDown: "search.length === 0"
        handleKeyDown->>onChange: handleRemoveValue(values[last])
    end
Loading

Reviews (4): Last reviewed commit: "Merge branch 'main' into drew/virtual-mu..." | Re-trigger Greptile

Comment thread packages/app/src/components/VirtualMultiSelect/VirtualMultiSelect.tsx Outdated
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 235 passed • 3 skipped • 1444s

Status Count
✅ Passed 235
❌ Failed 0
⚠️ Flaky 2
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

@pulpdrew pulpdrew force-pushed the drew/virtual-multi-select-freeform branch from 6be6738 to 684b4ae Compare July 10, 2026 18:11
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Deep Review

Freeform-text support in VirtualMultiSelect plus in-component auto-sorting. No P0/P1 ship-blockers: the diff is client-side UI, adds no auth/data/query surface, and the happy path (adding a value absent from the dropdown) is exercised by a new e2e test. The findings below are correctness-risk and coverage recommendations.

✅ No critical issues found.

🟡 P2 — recommended

  • packages/app/src/components/VirtualMultiSelect/VirtualMultiSelect.tsx:98 — Enter gates freeform-add on combobox.getSelectedOptionIndex() === -1, but onDropdownOpen runs updateSelectedOptionIndex('active') (highlighting an already-selected option) and handleChange's updateSelectedOptionIndex() re-syncs rather than clearing the highlight, so typing text that still matches a selected+visible option leaves the index >= 0 and Enter submits that option (toggling the existing pill off) instead of adding the typed value.
    • Fix: Call combobox.resetSelectedOption() inside handleChange, or track arrow-navigation intent with an explicit ref, so the freeform branch does not depend on Mantine's internal highlight index.
    • correctness, kieran-typescript, julik-frontend-races
  • packages/app/src/components/VirtualMultiSelect/VirtualMultiSelect.tsx:85 — The new behavior is covered only by a single e2e happy path; the highlighted-option Enter branch (the one implicated above), the localeCompare auto-sort ordering, the blank-item filter (item.trim().length > 0), and the whitespace-only and duplicate-add no-ops all lack assertions.
    • Fix: Add colocated component tests (the app already uses jest + testing-library) exercising each new branch, prioritizing the highlighted-option Enter path.
    • testing, correctness
🔵 P3 nitpicks (2)
  • packages/app/src/components/VirtualMultiSelect/VirtualMultiSelect.tsx:99 — When the typed value exactly equals an already-selected value, handleAddValue no-ops (add-only, unlike the toggling handleSelectValue) yet setSearch('') still fires, so the input clears with no visible change and no feedback.
    • Fix: Route an exact match through handleSelectValue, or skip clearing search on a no-op so the user sees the value is already present.
  • packages/app/tests/e2e/features/dashboard.spec.ts:535 — The new freeform-filter test creates a dashboard, tile, and filter (requiring API/Mongo persistence) but is not tagged @full-stack, so it will not be skipped correctly under local-mode runs.
    • Fix: Add { tag: '@full-stack' } to the test options.

Reviewers (8): correctness, testing, maintainability, project-standards, kieran-typescript, julik-frontend-races, agent-native, learnings-researcher.

Testing gaps: No component/unit tests accompany the new logic — only one e2e happy path; the arrow-key-into-virtualized-off-screen-option-then-Enter interaction and the type-then-Enter same-tick timing were flagged as unverified residual risks worth a targeted test.

@pulpdrew

Copy link
Copy Markdown
Contributor Author

packages/app/src/components/VirtualMultiSelect/VirtualMultiSelect.tsx:97 — Enter treats combobox.getSelectedOptionIndex() === -1 as "no option highlighted," but onDropdownOpen calls updateSelectedOptionIndex('active') which highlights an already-selected value on focus, and handleChange's updateSelectedOptionIndex() keeps it highlighted while the typed text stays a substring of it; typing a prefix of a selected value and pressing Enter then defers to the combobox default, which toggles that value off instead of adding the freeform text — silently dropping a selected filter value.

This doesn't appear to be true.

@pulpdrew pulpdrew requested review from a team and wrn14897 and removed request for a team July 10, 2026 18:29

@knudtty knudtty 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.

LGTM

@brandon-pereira brandon-pereira left a comment

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.

LGTM

@kodiakhq kodiakhq Bot merged commit f865d88 into main Jul 13, 2026
20 checks passed
@kodiakhq kodiakhq Bot deleted the drew/virtual-multi-select-freeform branch July 13, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge review/tier-2 Low risk — AI review + quick human skim

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants