A few things I ran into while trying a fix locally, in case they save whoever picks this up some time. File references are from main @ feb49e78.
Clear after Home is covered, not on tap. Home stays on screen until its transitionEnd with closing: true (Stack.tsx wires this to recordHomeTransitionEnd in home-route-visibility.ts). If the query is cleared in handleSelectThread right after selectDetail, the list swaps to the full unfiltered list and jumps to the saved scroll offset while it's still sliding away. The whole list also rebuilds during the push. On devices without Liquid Glass, and on the Duo folded, the header search bar also unmounts mid-transition. Deferring the clear to that closing transitionEnd, and dropping the intent if Home reappears first (for example a cancelled back swipe), avoids all of that.
Use the cancel action, not an empty query. In workspace-tabs-state.ts, cancel sets the query to "", sets searchMode back to "resting", and restores the pre-search scroll offset. Dispatching { type: "query", query: "" } leaves searchMode active, so restoreSearch in WorkspaceToolbar.ios.tsx reactivates the native search bar when Home reappears. The toolbar's own cancelSearch also calls recordScrollOffset before dispatching, so a shared helper would keep the two paths from drifting.
The native search field keeps its own text. restoreSearch only calls setText(tab.query) when the tab isn't resting. After a cancel that didn't come from the search bar itself, the field can still show the old query when you come back. Clearing the field when the tab is resting with an empty query fixed that, and it does nothing after a normal Cancel, since the field is already empty then.
Two smaller things:
- Split-view check:
layout.usesSplitViewalone isn't quite the right test for "the list stays beside the thread". On the Duo unfolded in portrait,primarySidebarOverlaysContentis true and the overlay hides after selection, so the thread covers the list there too. - Pending tasks: they show up in filtered results (
useThreadListV2matchespendingTask.titleagainst the query). Opening one goes throughselectThreadRouteinstead ofhandleSelectThread, so it needs the same treatment.
I only checked this with unit tests and typecheck, not on a device.