Improve autocomplete behaviour #8669
Conversation
Three related fixes around the native-input widget's autocomplete: 1. Avoid spurious "loading from scratch" on widget re-open with same text. showNativeInput dropped the onClearAutocomplete call that emptied searchResults and re-triggered a fetch; restores the suggestions list visibility from the adapter when the cache matches the prefill. 2. Reveal the browser behind the closing widget earlier. hideNtp now runs at the start of hideNativeInput so the webpage is visible during the exit animation, instead of after the 200ms exit + 150ms fade leaves the NTP background filling the screen. 3. Always keep an autocomplete cache for the omnibar's text so in-widget typing never permanently displaces the omnibar's results. Adds an always-on parallel pipeline in BrowserTabViewModel (omnibarAutocompleteCache) fed by onOmnibarTextChanged; the widget restores from it on close (non-navigation) and on open if the cache matches the prefill. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dismiss the IME and cancel any in-flight enter animation when the hosting tab is backgrounded so the keyboard does not leak onto the next tab, and seed beginEnterAnimationPreview's state locally instead of reading from NativeInputStateProvider — ViewScope injection isn't guaranteed to be in place by the time it runs on a tab-switch attach. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fbc586b. Configure here.
fbc586b to
16e361d
Compare
16e361d to
3cffed1
Compare
| flowOf(AutoCompleteResult("", emptyList())) | ||
| } else { | ||
| omnibarTextStateFlow | ||
| .debounce(300) |
There was a problem hiding this comment.
Not Blocking: I think we could gain more speed on cold start by immediately firing the first emission before debouncing. We already have a utility function for that that we are using in duckchat but will probably need to be move out or replicated. Will leave it to you to decide if you want to do that, but I don’t see a reason to delay the first fetch.
There was a problem hiding this comment.
This is only the cache, not the actual user query execution, so the effect of it is very minimal. For the user to feel it, they have to try to hit the cache within 300ms of starting a new search.
There was a problem hiding this comment.
Ah right. We can do a follow up for the actual query execution to speed up the first tap. but not part of this PR.
| */ | ||
| fun restoreOmnibarAutocomplete(forQuery: String): AutoCompleteResult? { | ||
| val cached = omnibarAutocompleteCache.value | ||
| if (cached.query != forQuery || cached.suggestions.isEmpty()) return null |
There was a problem hiding this comment.
What happens if the suggestions are actually empty(e.g. searching for random thing like zxcsdcz)? Shouldn't we still update autoCompleteViewState to reflect the empty result? Right now it would still hold the previous query data because we return early and never set it. Unless I’m missing something.
There was a problem hiding this comment.
Suggestions are never empty, at the very least you get 2 options one for duck.ai and one for executing the search on whatever keywoard you entered anyway.
There was a problem hiding this comment.
I’m just worried that some settings might turn off those (we have too many settings to keep up) so I usually implement it with that in mind. But for this, it’s fine I don’t think we’ll get to that scenario.
| if (cacheRestored) { | ||
| rootView.findViewById<RecyclerView?>(R.id.autoCompleteSuggestionsList)?.let { list -> | ||
| if ((list.adapter?.itemCount ?: 0) > 0) { | ||
| list.show() |
There was a problem hiding this comment.
nit: the renderAutocomplete function manipulates the visibility of the list every time autoCompleteViewState changes. Calling show here would add another code path that controls visibility. It’s nit because I didn’t find a scenario currently where the 2 code paths disagree and might cause one to override the decision of the other, but it’s good to keep in mind if we can have a quick change to have only one owner control the visibility instead of 2 (maybe add a flag?). Not blocker just something to think about
There was a problem hiding this comment.
You are right, it's cleaner to have one owner.
YoussefKeyrouz
left a comment
There was a problem hiding this comment.
Looks good. Thanks!. Tested few scenarios and they worked as expected.
Left few nits that we can address later as follow ups.

Task/Issue URL: https://app.asana.com/1/137249556945/project/1214157224317277/task/1214964760144656?focus=true
Description
Three related fixes around the native-input widget's autocomplete:
Avoid spurious "loading from scratch" on widget re-open with same text. showNativeInput dropped the onClearAutocomplete call that emptied searchResults and re-triggered a fetch; restores the suggestions list visibility from the adapter when the cache matches the prefill.
Reveal the browser behind the closing widget earlier. hideNtp now runs at the start of hideNativeInput so the webpage is visible during the exit animation, instead of after the 200ms exit + 150ms fade leaves the NTP background filling the screen.
Always keep an autocomplete cache for the omnibar's text so in-widget typing never permanently displaces the omnibar's results. Adds an always-on parallel pipeline in BrowserTabViewModel (omnibarAutocompleteCache) fed by onOmnibarTextChanged; the widget restores from it on close (non-navigation) and on open if the cache matches the prefill.
Steps to test this PR
UI changes
Note
Medium Risk
Adds a second autocomplete pipeline and changes native-input open/close timing; behavior is gated on the native-input setting and covered by new ViewModel tests, but autocomplete and tab lifecycle edge cases warrant manual QA.
Overview
Improves native-input omnibar autocomplete so reopening the keyboard can show prior suggestions without refetching, and closing the widget feels more like returning to the page you were on.
Browser tab / ViewModel: Adds an
omnibarAutocompleteCachepipeline (debounced, IO-backed) driven byonOmnibarTextRenderedwhen omnibar text is rendered—not user typing. It runs only when the native-input setting is on, skips full URLs and Duck Player URIs, and mirrors autocomplete settings.restoreOmnibarAutocompletereapplies cached results into view state; the fragment also refreshes the suggestions adapter when the cache matches.Native input manager: Wires
restoreOmnibarAutocompleteon widget close (non-navigation) and before prefill on open; removesonClearAutocompleteon open and manually shows the list when cache restore succeeds. CallshideNtp()at the start of hide in browser mode so the exit animation plays over the live page. GuardsonEnterCompletewithisAttachedToWindowto avoid raising the IME after detach.Widget enter animation:
beginEnterAnimationPreview(isBottom)applies per-tabNativeInputState(or a position-only fallback) before measure so toggle/layout does not snap mid-animation.Tests: Coverage for restore/no-match, cache gating (setting off, autocomplete off, blank query), and telemetry state after restore.
Reviewed by Cursor Bugbot for commit 3cffed1. Bugbot is set up for automated code reviews on this repo. Configure here.