Combobox: open on focus even when the options have not arrived yet - #28
Merged
Merged
Conversation
The focus handler was `open = filtered.length > 0`, so a click into the field opened the list only if options were already present — and nothing reopened it when they arrived later. For a static list that is invisible. For the remote-search shape, where a consumer replaces `options` with each debounced response, it is a race between the click and the first response: the user who loses it clicks into the field, sees nothing, and only typing or ArrowDown recovers. Found the expensive way, which is why the story pins the exact sequence. The consuming app's impersonation picker passed its browser test locally on every run — the response wins by milliseconds there — and failed all three CI retries, where the runner is slow enough that the click reliably wins. A race a fast machine can never lose is a race a test on a fast machine can never catch, so the RemoteOptions story manufactures it: options arrive 600ms after mount, the play function clicks first, and the listbox must appear with no typing, no ArrowDown and no second click. It fails on the previous handler (verified by inverting the fix) and also asserts the flag alone paints nothing while the list is still empty and message-less. Focus now sets the open flag unconditionally; the listbox itself is still gated on having options or an empty-message to show. One visible change besides the fix: a filter that matches nothing now shows the empty-message on refocus, where before the closed list hid it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
verify:version refuses a package.json equal to an existing tag, and it is right to: v1.19.0 is spent whether or not its release succeeded. The CHANGELOG's Unreleased heading becomes the version for the same reason — this repo's entries are named at PR time, not at release time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Combobox's focus handler wasopen = filtered.length > 0— a click into the field opens the list only if options are already present, and nothing reopens it when they arrive later.For a static options list that is invisible. For the remote-search shape, where a consumer replaces
optionswith each debounced response, it is a race between the click and the first response. The user who loses it clicks into the field, sees nothing, and only typing or ArrowDown recovers.How it was found
The consuming app's impersonation picker (
flows.portal.docuhub.app,ImpersonateModal) passed its browser test locally on every run — the response wins the race by milliseconds there — and failed all three CI retries, where the runner is slow enough that the click reliably wins. Screenshots showed a focused combobox with no dropdown, options long since loaded.The fix
@focus="open = true"— unconditional. The listbox itself is still gated on having options or anempty-messageto show (v-if="open && (filtered.length > 0 || emptyMessage !== null)"), so an open flag over a truly empty, message-less list renders nothing; but when options land while the field is focused, they now appear.The story that pins it
A race a fast machine never loses is a race a test on a fast machine never catches — so
RemoteOptionsmanufactures it: options arrive 600 ms after mount, the play function clicks first, and the listbox must appear with no typing, no ArrowDown, no second click. Verified red on the previous handler by inverting the fix. It also asserts the open flag alone paints nothing while the list is still empty, and that the late-arriving list is live (ArrowDown+Enter picks from it).One visible change besides the fix
A filter that matches nothing now shows the
empty-messageon refocus, where before the closed list hid it. No call site in the consuming app depends on the old behavior.Verified
npx playwright test— 247 passed (smoke suite executes every play function)npm run build(incl.verify:externals,verify:dev-warnings,verify:props),npm run typecheck,npm run lint(0 errors; the 7 warnings pre-exist in untouched files)🤖 Generated with Claude Code