# Files changed · PR \#23147 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23147/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(mobile\): the iPhone Duo searches from a field at the bottom of its list](https://git.cafe/pingdotgg/t3code/pull/23147.md)

Version: 9

Head commit: 938862484c8f2c780419d0e1f0589720f5bffa3f

Base commit: b3ac8cde0a0c1db8804d1d45b6f965d0c2f6ac55

Comparison base: b3ac8cde0a0c1db8804d1d45b6f965d0c2f6ac55

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/mobile/src/features/layout/createWorkspaceStackNavigator.ios.tsx

Status: modified; +0; −3

```diff
@@ -73,7 +73,6 @@
   readonly onDismiss: (routeKey: string) => void;
   readonly onAppearance: (type: "willAppear" | "didAppear" | "willDisappear", key: string) => void;
   readonly onCompose?: () => void;
-  readonly covered?: boolean;
   readonly navigation: ViewProps["navigation"];
 }) {
   const { descriptor } = props;
@@ -117,7 +116,6 @@
             canGoBack={!props.primary}
             column={props.primary ? "primary" : "detail"}
             onCompose={props.onCompose}
-            covered={props.covered}
           />
           <ColumnContent
             primary={props.primary}
@@ -253,7 +251,6 @@
             selectedPullRequestKey={selectedPullRequestKey}
             onDismiss={handleNativeDismiss}
             onAppearance={followCollapsedSplit}
-            covered={!layout.usesSplitView && activeDetailKey !== undefined}
             navigation={props.navigation}
           />
         </Split.Stack>
```

## apps/mobile/src/features/workspace/WorkspaceRootScreen.tsx

Status: modified; +12; −9

```diff
@@ -5,6 +5,7 @@
 import { SafeAreaInsetsContext, useSafeAreaInsets } from "react-native-safe-area-context";
 
 import { CompactBrandTitle } from "../../components/CompactBrandTitle";
+import { isDuoDevice } from "../../lib/device-type";
 import { AndroidScreenHeader } from "../../components/ui/AndroidScreenHeader";
 import { NativeStackScreenOptions } from "../../native/StackHeader";
 import { NativePrimaryColumnContext } from "../../native/v5-workspace-context";
@@ -24,7 +25,7 @@
   useWorkspaceTabs,
 } from "./WorkspaceTabsContext";
 import { WorkspaceToolbar } from "./WorkspaceToolbar";
+import { WORKSPACE_DOCK, WORKSPACE_TOOLBAR, workspaceToolbarHeight } from "./workspace-tabs-state";
-import { WORKSPACE_DOCK, workspaceToolbarHeight } from "./workspace-tabs-state";
 import { WorkspaceSidebarTabs } from "./WorkspaceSidebarTabs";
 import { useSidebarWorkspaceTabs, useSystemWorkspaceTabBar } from "./useSystemWorkspaceTabBar";
 
@@ -88,14 +89,16 @@
   );
   const tab = state.tabs[state.selectedTab];
   const bottomClearance =
+    Platform.OS === "ios" && isDuoDevice(deviceType)
+      ? Math.max(insets.bottom, 12, keyboardHeight + 8) + WORKSPACE_TOOLBAR.height + 16
+      : Platform.OS === "ios"
+        ? Math.max(insets.bottom, keyboardHeight) +
+          (systemTabBar || sidebarTabs || tab.searchMode !== "resting"
+            ? 16
+            : WORKSPACE_DOCK.height + WORKSPACE_DOCK.bottomGap + 16)
+        : keyboardHeight > 0 && tab.searchMode !== "resting"
-    Platform.OS === "ios"
-      ? Math.max(insets.bottom, keyboardHeight) +
-        (systemTabBar || sidebarTabs || tab.searchMode !== "resting"
-          ? 16
-          : WORKSPACE_DOCK.height + WORKSPACE_DOCK.bottomGap + 16)
+          ? keyboardHeight + 64
+          : Math.max(insets.bottom, 12) + workspaceToolbarHeight(tab.searchMode) + 16;
-      : keyboardHeight > 0 && tab.searchMode !== "resting"
-        ? keyboardHeight + 64
-        : Math.max(insets.bottom, 12) + workspaceToolbarHeight(tab.searchMode) + 16;
   const context = useMemo(() => ({ ...workspace, bottomClearance }), [workspace, bottomClearance]);
 
   return (
```

## apps/mobile/src/features/workspace/WorkspaceSearchField.tsx

Status: added; +60; −0

```diff
@@ -1,0 +1,60 @@
+import type { Ref } from "react";
+import { Keyboard, Pressable, TextInput, View, type TextInputInstance } from "react-native";
+import { useResolveClassNames } from "uniwind";
+
+import { SymbolView } from "../../components/ui/AppSymbol";
+import { GlassSurface } from "../../components/ui/GlassSurface";
+import { WORKSPACE_TOOLBAR, type WorkspaceTab } from "./workspace-tabs-state";
+
+export function WorkspaceSearchField({
+  ref,
+  ...props
+}: {
+  readonly ref?: Ref<TextInputInstance>;
+  readonly tab: WorkspaceTab;
+  readonly query: string;
+  readonly onChangeQuery: (query: string) => void;
+  readonly onFocus: () => void;
+  readonly onBlur?: () => void;
+  readonly onClear: () => void;
+}) {
+  const inputStyle = useResolveClassNames("text-foreground font-sans text-base");
+  const placeholderStyle = useResolveClassNames("text-foreground-muted");
+  const label = props.tab === "pullRequests" ? "Search pull requests" : "Search threads";
+  return (
+    <GlassSurface
+      className="min-w-0 flex-1"
+      style={{ height: WORKSPACE_TOOLBAR.height, borderRadius: WORKSPACE_TOOLBAR.height / 2 }}
+    >
+      <View className="flex-1 flex-row items-center pl-4">
+        <SymbolView name="magnifyingglass" size={20} tintColorClassName="accent-icon-muted" />
+        <TextInput
+          ref={ref}
+          accessibilityLabel={label}
+          placeholder={label}
+          placeholderTextColor={placeholderStyle.color}
+          value={props.query}
+          onChangeText={props.onChangeQuery}
+          onFocus={props.onFocus}
+          onBlur={props.onBlur}
+          onSubmitEditing={() => Keyboard.dismiss()}
+          autoCapitalize="none"
+          autoCorrect={false}
+          returnKeyType="search"
+          maxLength={props.tab === "pullRequests" ? 200 : undefined}
+          style={[inputStyle, { flex: 1, minWidth: 0, paddingHorizontal: 8, paddingVertical: 0 }]}
+        />
+        {props.query ? (
+          <Pressable
+            accessibilityRole="button"
+            accessibilityLabel="Clear search"
+            onPress={props.onClear}
+            className="h-11 w-11 items-center justify-center"
+          >
+            <SymbolView name="xmark.circle.fill" size={18} tintColorClassName="accent-icon-muted" />
+          </Pressable>
+        ) : null}
+      </View>
+    </GlassSurface>
+  );
+}
```

## apps/mobile/src/features/workspace/WorkspaceToolbar.ios.tsx

Status: modified; +103; −28

```diff
@@ -1,17 +1,24 @@
 import { Toolbar } from "../../components/ui/Toolbar";
 import { useFocusEffect, useNavigation } from "@react-navigation/native";
+import { use, useCallback, useEffect, useRef, type ReactNode } from "react";
+import { Platform, Pressable, type TextInputInstance } from "react-native";
+import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
+import Animated, { useAnimatedStyle } from "react-native-reanimated";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
-import { use, useCallback, useEffect, useRef } from "react";
-import { Platform } from "react-native";
 import { isDuoDevice } from "../../lib/device-type";
 import { useDeviceType } from "../../native/native-layout-metrics";
 import type { SearchBarCommands } from "react-native-screens";
 
+import { SymbolView } from "../../components/ui/AppSymbol";
+import { GlassSurface } from "../../components/ui/GlassSurface";
 import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
 import { NativeStackScreenOptions } from "../../native/StackHeader";
 import { NativeWorkspaceTabActivityContext } from "../../native/v5-workspace-context";
 import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands";
 import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";
 import { useWorkspaceTabs } from "./WorkspaceTabsContext";
+import { WorkspaceSearchField } from "./WorkspaceSearchField";
+import { WORKSPACE_TOOLBAR } from "./workspace-tabs-state";
 import { useSidebarWorkspaceTabs } from "./useSystemWorkspaceTabBar";
 import { useSelectWorkspaceTab } from "./useSelectWorkspaceTab";
 import { useMobileNavigationTheme } from "../../lib/useMobileNavigationTheme";
@@ -19,6 +26,34 @@
 // Tabler's git-pull-request geometry, shared with the UIKit tab bar.
 const PULL_REQUEST_ICON = require("../../../assets/icons/pull-request.png");
 
+/**
+ * Rises once the keyboard reaches its content. UIKit also reports keyboards without height, with a
+ * hardware keyboard and around a fold, and those must not move it.
+ */
+function AboveKeyboard(props: { readonly bottom: number; readonly children: ReactNode }) {
+  const keyboard = useReanimatedKeyboardAnimation();
+  const { bottom } = props;
+  const lift = useAnimatedStyle(() => ({
+    transform: [{ translateY: Math.min(0, keyboard.height.value + bottom - 8) }],
+  }));
+  return (
+    <Animated.View
+      pointerEvents="box-none"
+      className="absolute inset-x-0 bottom-0 flex-row items-center"
+      style={[
+        {
+          gap: WORKSPACE_TOOLBAR.gap,
+          paddingHorizontal: WORKSPACE_TOOLBAR.edge,
+          paddingBottom: bottom,
+        },
+        lift,
+      ]}
+    >
+      {props.children}
+    </Animated.View>
+  );
+}
+
 export function WorkspaceToolbar(props: {
   readonly onNewTask: () => void;
   readonly sidebar?: boolean;
@@ -35,16 +70,16 @@
     (isDuoDevice(deviceType)
       ? deviceType === "duo-folded" && workspace.state.selectedTab === "threads"
       : deviceType === "pad");
+  // The Duo searches from a field at the bottom of the list in every pose. UIKit has no such
+  // placement once the vertical bar holds the toolbar, and re-placing its search controller on
+  // each fold replaces the search field.
-  // The expanded Duo sidebar keeps a full search field in its bottom toolbar. UIKit holds the
-  // integrated field there, but moves the button form into the detail column's vertical bar.
-  // Folded, the workspace tab bar owns the bottom of the vertical bar, so search keeps UIKit's
-  // own placement.
-  const toolbarSearch =
-    NATIVE_LIQUID_GLASS_SUPPORTED && deviceType === "duo-unfolded" && props.sidebar === true;
+  const listSearch = isDuoDevice(deviceType);
+  const insets = useSafeAreaInsets();
   const { isTabActive } = workspace;
   const tabActive = use(NativeWorkspaceTabActivityContext)?.active ?? true;
   const { panes, togglePrimarySidebar } = useAdaptiveWorkspaceLayout();
   const searchRef = useRef<SearchBarCommands>(null);
+  const fieldRef = useRef<TextInputInstance>(null);
   const tab = workspace.state.tabs[workspace.state.selectedTab];
   const selectedTab = workspace.state.selectedTab;
   const enterSearch = useCallback(() => {
@@ -58,10 +93,25 @@
   }, [workspace, selectedTab, props.sidebar, panes.primarySidebarVisible, togglePrimarySidebar]);
   const focusSearch = useCallback(() => {
     if (!workspace.isTabActive(selectedTab)) return false;
+    if (listSearch) {
+      fieldRef.current?.focus();
+      return true;
+    }
     if (!NATIVE_LIQUID_GLASS_SUPPORTED) enterSearch();
     searchRef.current?.focus();
     return true;
+  }, [workspace, selectedTab, listSearch, enterSearch]);
+  const cancelSearch = useCallback(() => {
+    workspace.recordScrollOffset(selectedTab, tab.searchEntryOffset);
+    workspace.dispatch({ type: "cancel", tab: selectedTab });
+  }, [workspace, selectedTab, tab.searchEntryOffset]);
+  const searchFocused = useCallback(
+    () =>
+      tab.searchMode === "resting"
+        ? enterSearch()
+        : workspace.dispatch({ type: "search", tab: selectedTab }),
+    [tab.searchMode, enterSearch, workspace, selectedTab],
-  }, [workspace, selectedTab, enterSearch]);
+  );
   useHardwareKeyboardCommand("focusSearch", focusSearch);
   const restoreSearch = useCallback(() => {
     if (isTabActive(selectedTab) && tab.searchMode !== "resting")
@@ -86,7 +136,7 @@
           // Liquid Glass keeps search mounted as UIKit's own header button, which
           // morphs into the field; swapping a stacked bar in and out cannot animate.
           headerSearchBarOptions:
+            !listSearch && (NATIVE_LIQUID_GLASS_SUPPORTED || tab.searchMode !== "resting")
-            NATIVE_LIQUID_GLASS_SUPPORTED || tab.searchMode !== "resting"
               ? {
                   ref: searchRef,
                   autoFocus: tab.searchMode === "active",
@@ -94,22 +144,12 @@
                   hideNavigationBar: false,
                   hideWhenScrolling: false,
                   obscureBackground: false,
+                  placement: NATIVE_LIQUID_GLASS_SUPPORTED ? "integratedButton" : "stacked",
-                  placement: toolbarSearch
-                    ? "integrated"
-                    : NATIVE_LIQUID_GLASS_SUPPORTED
+                  allowToolbarIntegration: NATIVE_LIQUID_GLASS_SUPPORTED,
-                      ? "integratedButton"
-                      : "stacked",
-                  // Folded, the tab bar owns the bottom of the vertical bar; refusing toolbar
-                  // integration also makes UIKit re-place a visible search in the navigation bar.
-                  allowToolbarIntegration:
-                    NATIVE_LIQUID_GLASS_SUPPORTED && deviceType !== "duo-folded",
                   placeholder:
                     selectedTab === "pullRequests" ? "Search pull requests" : "Search threads",
                   cancelButtonText: "✕",
-                  onFocus: () =>
+                  onFocus: searchFocused,
-                    tab.searchMode === "resting"
-                      ? enterSearch()
-                      : workspace.dispatch({ type: "search", tab: selectedTab }),
                   onBlur: () => workspace.dispatch({ type: "expand", tab: selectedTab }),
                   onSearchButtonPress: () => searchRef.current?.blur(),
                   onChangeText: ({ nativeEvent }) =>
@@ -118,10 +158,7 @@
                       query: nativeEvent.text,
                       tab: selectedTab,
                     }),
-                  onCancelButtonPress: () => {
-                    workspace.recordScrollOffset(selectedTab, tab.searchEntryOffset);
-                    workspace.dispatch({ type: "cancel", tab: selectedTab });
-                  },
+                  onCancelButtonPress: cancelSearch,
                 }
               : undefined,
         }}
@@ -168,7 +205,6 @@
           />
         ) : null}
         {nativeToolbarCompose ? <Toolbar.Spacer flexible /> : null}
-        {toolbarSearch ? <Toolbar.SearchBarSlot /> : null}
         {nativeToolbarCompose ? (
           <Toolbar.Button
             identifier="workspace-compose"
@@ -179,6 +215,45 @@
           />
         ) : null}
       </Toolbar>
+      {listSearch ? (
+        <AboveKeyboard bottom={Math.max(insets.bottom, 12)}>
+          <WorkspaceSearchField
+            ref={fieldRef}
+            tab={selectedTab}
+            query={tab.query}
+            onChangeQuery={(query) =>
+              workspace.dispatch({ type: "query", query, tab: selectedTab })
+            }
+            onFocus={searchFocused}
+            onBlur={() => workspace.dispatch({ type: "expand", tab: selectedTab })}
+            onClear={() => {
+              workspace.dispatch({ type: "query", query: "", tab: selectedTab });
+              fieldRef.current?.focus();
+            }}
+          />
+          {tab.searchMode === "resting" ? null : (
+            <GlassSurface
+              style={{
+                width: WORKSPACE_TOOLBAR.height,
+                height: WORKSPACE_TOOLBAR.height,
+                borderRadius: WORKSPACE_TOOLBAR.height / 2,
+              }}
+            >
+              <Pressable
+                accessibilityRole="button"
+                accessibilityLabel="Cancel search"
+                onPress={() => {
+                  cancelSearch();
+                  fieldRef.current?.blur();
+                }}
+                className="flex-1 items-center justify-center"
+              >
+                <SymbolView name="xmark" size={20} tintColorClassName="accent-icon" />
+              </Pressable>
+            </GlassSurface>
+          )}
+        </AboveKeyboard>
+      ) : null}
     </>
   );
 }
```

## apps/mobile/src/features/workspace/WorkspaceToolbar.tsx

Status: modified; +12; −48

```diff
@@ -5,19 +5,18 @@
   Keyboard,
   Platform,
   Pressable,
-  TextInput,
   View,
   useWindowDimensions,
   type TextInputInstance,
 } from "react-native";
 import { KeyboardStickyView, useKeyboardState } from "react-native-keyboard-controller";
 import { useSafeAreaInsets } from "react-native-safe-area-context";
-import { useResolveClassNames } from "uniwind";
 
 import { SymbolView, type AppSymbolName } from "../../components/ui/AppSymbol";
 import { GlassSurface } from "../../components/ui/GlassSurface";
 import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands";
 import { WorkspaceDock } from "./WorkspaceDock";
+import { WorkspaceSearchField } from "./WorkspaceSearchField";
 import { useWorkspaceTabs } from "./WorkspaceTabsContext";
 import { WORKSPACE_TOOLBAR, type WorkspaceTab } from "./workspace-tabs-state";
 import { PullRequestGlyph } from "../pull-requests/pullRequestGlyphs";
@@ -60,8 +59,6 @@
   const edge = width < 360 ? 12 : WORKSPACE_TOOLBAR.edge;
   const gap = width < 360 ? 6 : WORKSPACE_TOOLBAR.gap;
   const keyboardVisible = useKeyboardState((state) => state.isVisible);
-  const inputStyle = useResolveClassNames("text-foreground font-sans text-base");
-  const placeholderStyle = useResolveClassNames("text-foreground-muted");
   const focusSearch = useCallback(() => {
     if (!workspace) return false;
     const tab = workspace.state.selectedTab;
@@ -126,50 +123,17 @@
                 onPress={() => dispatch({ type: "expand" })}
               />
             ) : null}
-            <GlassSurface className="min-w-0 flex-1" style={{ height: 48, borderRadius: 24 }}>
-              <View className="flex-1 flex-row items-center pl-4">
-                <SymbolView
-                  name="magnifyingglass"
-                  size={20}
-                  tintColorClassName="accent-icon-muted"
-                />
-                <TextInput
-                  ref={input}
-                  accessibilityLabel={`Search ${activeTab.label}`}
-                  placeholder={`Search ${activeTab.label === "PRs" ? "pull requests" : "threads"}`}
-                  placeholderTextColor={placeholderStyle.color}
-                  value={tab.query}
-                  onChangeText={(query) => dispatch({ type: "query", query })}
-                  onFocus={() => dispatch({ type: "search" })}
-                  onSubmitEditing={() => Keyboard.dismiss()}
-                  autoCapitalize="none"
-                  autoCorrect={false}
-                  returnKeyType="search"
-                  maxLength={state.selectedTab === "pullRequests" ? 200 : undefined}
-                  style={[
-                    inputStyle,
-                    { flex: 1, minWidth: 0, paddingHorizontal: 8, paddingVertical: 0 },
-                  ]}
-                />
-                {tab.query ? (
-                  <Pressable
-                    accessibilityRole="button"
-                    accessibilityLabel="Clear search"
-                    onPress={() => {
-                      dispatch({ type: "query", query: "" });
-                      focusSearch();
-                    }}
-                    className="h-11 w-11 items-center justify-center"
-                  >
-                    <SymbolView
-                      name="xmark.circle.fill"
-                      size={18}
-                      tintColorClassName="accent-icon-muted"
-                    />
+            <WorkspaceSearchField
+              ref={input}
+              tab={state.selectedTab}
+              query={tab.query}
+              onChangeQuery={(query) => dispatch({ type: "query", query })}
+              onFocus={() => dispatch({ type: "search" })}
+              onClear={() => {
+                dispatch({ type: "query", query: "" });
-                  </Pressable>
+                focusSearch();
+              }}
+            />
-                ) : null}
-              </View>
-            </GlassSurface>
             <ToolbarButton label="Cancel search" icon="xmark" size={48} onPress={cancelSearch} />
           </View>
         ) : null}
```

## apps/mobile/src/native/NativeHeaderToolbar.test.tsx

Status: modified; +19; −6

```diff
@@ -3,22 +3,34 @@
 import { createRoot, type Root } from "react-dom/client";
 import { afterEach, expect, it, vi } from "vite-plus/test";
 
+type HeaderOptions = {
+  unstable_headerLeftItems?: () => NativeStackHeaderItem[];
+  unstable_headerRightItems?: () => NativeStackHeaderItem[];
+  unstable_headerToolbarItems?: () => NativeStackHeaderItem[];
+};
+
 const harness = vi.hoisted(() => ({
   params: { path: ["first", "guide.pdf"] },
+  appliedOptions: {} as HeaderOptions,
   renderedItems: [] as NativeStackHeaderItem[],
   setOptionsCalls: 0,
   openedDocuments: [] as string[],
 }));
 
+// Native headers retain the actions materialized when navigation applies options. Like React
+// Navigation, the header renders again only when the options are no longer shallowly equal.
-// Native headers retain the actions materialized when navigation applies options.
 vi.mock("@react-navigation/native", () => {
   const navigation = {
-    setOptions(options: {
+    setOptions(options: HeaderOptions) {
-      unstable_headerLeftItems?: () => NativeStackHeaderItem[];
-      unstable_headerRightItems?: () => NativeStackHeaderItem[];
-      unstable_headerToolbarItems?: () => NativeStackHeaderItem[];
-    }) {
       harness.setOptionsCalls += 1;
+      const previous: Record<string, unknown> = harness.appliedOptions;
+      const next: Record<string, unknown> = { ...previous, ...options };
+      harness.appliedOptions = next;
+      if (
+        Object.keys(next).length === Object.keys(previous).length &&
+        Object.keys(next).every((key) => next[key] === previous[key])
+      )
+        return;
       harness.renderedItems =
         options.unstable_headerLeftItems?.() ??
         options.unstable_headerRightItems?.() ??
@@ -104,6 +116,7 @@
 afterEach(() => {
   act(() => root?.unmount());
   root = null;
+  harness.appliedOptions = {};
   harness.renderedItems = [];
   harness.setOptionsCalls = 0;
   harness.openedDocuments = [];
```

## apps/mobile/src/native/StackHeader.tsx

Status: modified; +13; −2

```diff
@@ -199,8 +199,19 @@
       return;
     }
     lastAppliedOptionsSignatureRef.current = signature;
+    // React Navigation keeps a screen's descriptor while its options are shallowly equal. The
+    // stabilized factories keep their identity, so one whose output changed needs a new wrapper
+    // to reach the header.
+    optionFunctionWrappersRef.current = new Map();
+    navigation.setOptions(
+      stabilizeOptionFunctions(
+        normalizedOptions,
+        "options",
+        latestOptionFunctionsRef.current,
+        optionFunctionWrappersRef.current,
+      ) as NativeStackNavigationOptions,
+    );
-    navigation.setOptions(stableOptions);
+  }, [navigation, normalizedOptions, props.optionsVersion, stableOptions, tabActive]);
-  }, [navigation, props.optionsVersion, stableOptions, tabActive]);
 
   useEffect(() => {
     if (!navigation || !props.listeners) {
```

## apps/mobile/src/native/V5StackHeader.ios.tsx

Status: modified; +2; −19

```diff
@@ -3,7 +3,7 @@
   NativeStackHeaderItemMenu,
   NativeStackNavigationOptions,
 } from "@react-navigation/native-stack";
-import { isValidElement, useEffect, useRef, useState } from "react";
+import { isValidElement, useEffect, useRef } from "react";
 import { StyleSheet } from "react-native";
 import { useMobileNavigationTheme } from "../lib/useMobileNavigationTheme";
 import { useDeviceType, useNativeLayoutMetrics } from "../native/native-layout-metrics";
@@ -145,8 +145,6 @@
   readonly column?: "primary" | "detail";
   /** The workspace's compose action, shown as its own group in this column's bar. */
   readonly onCompose?: () => void;
-  /** Whether another column covers this one in a collapsed split. */
-  readonly covered?: boolean;
 }) {
   const { options } = props;
   const theme = useMobileNavigationTheme();
@@ -239,20 +237,6 @@
       ? options.headerTitle({ children: title ?? "", tintColor: options.headerTintColor })
       : undefined;
   const searchOptions = options.headerSearchBarOptions;
-  const searchPlacement = bottomSearch ? "integrated" : (searchOptions?.placement ?? "automatic");
-  // UIKit re-places a visible search controller when its placement changes, but not one whose
-  // column is covered; that one needs a new controller to place once the column shows again.
-  // A hidden tab only sees the new placement as it returns, after its detail uncovered the list.
-  const covered = props.covered === true;
-  const [search, setSearch] = useState({ placement: searchPlacement, covered, epoch: 0 });
-  if (search.placement !== searchPlacement || search.covered !== covered)
-    setSearch({
-      placement: searchPlacement,
-      covered,
-      epoch:
-        search.epoch +
-        (search.placement !== searchPlacement && (search.covered || covered) ? 1 : 0),
-    });
   const autofocusRef = searchOptions?.ref ?? searchRef;
   const searchHasFocus = useRef(false);
   useEffect(() => {
@@ -299,13 +283,12 @@
         searchBar:
           mailSearch || searchOptions ? (
             <SearchBar
-              key={search.epoch}
               {...searchOptions}
               ref={searchOptions?.ref ?? searchRef}
               hideNavigationBar={false}
               hideWhenScrolling={false}
               allowToolbarIntegration={searchOptions?.allowToolbarIntegration ?? true}
-              placement={searchPlacement}
+              placement={bottomSearch ? "integrated" : (searchOptions?.placement ?? "automatic")}
               placeholder={mailSearch?.placeholder ?? searchOptions?.placeholder ?? "Search"}
               onFocus={(event) => {
                 searchHasFocus.current = true;
```
