diff --git a/apps/mobile/src/app/(app)/agent-chat/repo-picker.tsx b/apps/mobile/src/app/(app)/agent-chat/repo-picker.tsx index b13a81b91e..0af4c0c161 100644 --- a/apps/mobile/src/app/(app)/agent-chat/repo-picker.tsx +++ b/apps/mobile/src/app/(app)/agent-chat/repo-picker.tsx @@ -107,18 +107,38 @@ export default function RepoPickerScreen() { headerContent={ - + {/* The placeholder is a single-line Text overlay, not the input's own + placeholder: Android lays the native hint out at the field's width + with no line cap, so copy wider than a narrow field wraps onto a + second line that the field's fixed height clips against its + border. A tail-ellipsized Text truncates the copy at any width + instead. Both texts share leading-[normal] and a centred text rect + so the overlay sits exactly where the typed text will. */} + + + {search.length === 0 ? ( + + + {t('agentChat.repoPicker.searchPlaceholder')} + + + ) : null} + } > diff --git a/apps/mobile/src/components/agents/picker-search.mounted.test.tsx b/apps/mobile/src/components/agents/picker-search.mounted.test.tsx index b558b3268f..b71920efe0 100644 --- a/apps/mobile/src/components/agents/picker-search.mounted.test.tsx +++ b/apps/mobile/src/components/agents/picker-search.mounted.test.tsx @@ -147,6 +147,35 @@ describe('repository picker Bitbucket scope note', () => { }); }); +describe('repository picker search placeholder', () => { + const copy = 'Search repositories...'; + + it('renders a single tail-ellipsized line instead of a wrapping native hint', async () => { + const renderer = await mount(RepoPickerScreen); + const input = hosts(renderer, 'TextInput')[0]; + if (!input) { + throw new Error('Picker search input did not mount'); + } + // The native hint is what Android lays out at the field width with no line + // cap, wrapping onto a second line the fixed-height field then clips. + expect(input.props.placeholder).toBeUndefined(); + + const placeholder = hosts(renderer, 'Text').find(node => node.props.children === copy); + if (!placeholder) { + throw new Error('Search placeholder overlay did not mount'); + } + expect(placeholder.props.numberOfLines).toBe(1); + expect(placeholder.props.ellipsizeMode).toBe('tail'); + expect(placeholder.parent?.props.pointerEvents).toBe('none'); + + const changeSearch = input.props.onChangeText as (text: string) => void; + act(() => { + changeSearch('org/repo'); + }); + expect(hosts(renderer, 'Text').some(node => node.props.children === copy)).toBe(false); + }); +}); + async function mount(Component: () => ReactNode) { const mounted = await renderWithProviders(createElement(Component)); onTestFinished(mounted.unmount);