perf(mobile): defer picker search filtering with useDeferredValue - #6095
Open
iscekic wants to merge 1 commit into
Open
perf(mobile): defer picker search filtering with useDeferredValue#6095iscekic wants to merge 1 commit into
iscekic wants to merge 1 commit into
Conversation
Surface: the mobile app (apps/mobile).
Surface: the mobile app (apps/mobile).
# Problem
Both picker search surfaces keep the query in React state and use it directly for the expensive local list derivation and a FlatList render, so every keystroke does O(n) filtering and a full list reconciliation synchronously on the JS thread, blocking the text input.
Evidence at current head:
- apps/mobile/src/components/agents/model-picker-content.tsx:35 `const [search, setSearch] = useState('');`; :84-87 `const rows = useMemo<ModelPickerRow[]>(() => buildModelPickerRows({ models: bridge?.options ?? [], search, favoriteIds }), [bridge, search, favoriteIds]);`; :174 `onChangeText={setSearch}`; :199 `<FlatList ... data={rows}`.
- apps/mobile/src/app/(app)/agent-chat/repo-picker.tsx:26 `const [search, setSearch] = useState('');`; :50-53 `const filtered = useMemo(() => filterRepoPickerOptions({ repositories: bridge?.repositories ?? [], search }), [bridge, search]);`; :58-77 `listItems` rebuilt from `search`; :119 `onChangeText={setSearch}`; :139 `<FlatList ... data={listItems}`.
The model picker is populated from the full session model catalog (`bridge.options`), so its size grows with the CLI/provider catalog; the repository picker is populated from a user's full repository list. On a large catalog/repo set, typing in either picker drops frames.
Primary source for the requested technique: React `useDeferredValue` — https://react.dev/reference/react/useDeferredValue (defer updating a slo
Contributor
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Both pickers now split urgent input state from the deferred list value, and every decision that drives the rendered rows, the grouped-vs-flat branch, and the empty state reads Files Reviewed (3 files)
Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0 Review guidance: REVIEW.md from base branch |
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.
Changelog for users
Changelog for maintainers
searchas the urgent input state and adddeferredSearch = useDeferredValue(search)for the list.buildModelPickerRowsandfilterRepoPickerOptions, plus the derivedlistItems, now readdeferredSearch.deferredSearch, so the rendered rows and the empty/grouped state agree.modelPickerSlot/repoPickerSlotbridge, the sheet/route structure, icons, and copy are unchanged; no new dependency.FlatListwhiledeferredSearchis unchanged; checkdata,renderItem, andkeyExtractorstability if list rendering regresses.model-picker-content.tsxandrepo-picker.tsx; the risk is a decision still reading the urgentsearch, which would show rows that disagree with the grouped/empty state.E2E proof
e1-recording.mp4
e2-recording.mp4
E2E proof — log excerpts
/home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-freestyle-1b41/e2e-mobile-app/p1-verify.log/home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-freestyle-1b41/e2e-mobile-app/mobile.log/home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-freestyle-1b41/e2e-mobile-app/p3-verify.log/home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-freestyle-1b41/e2e-mobile-app/p2-run.log/home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-freestyle-1b41/e2e-mobile-app/p2-states.logOwner request