diff --git a/frontend/src/components/shared/detail-navigation/detail-navigation-sheet.test.tsx b/frontend/src/components/shared/detail-navigation/detail-navigation-sheet.test.tsx index 0c176f80..a19fc29a 100644 --- a/frontend/src/components/shared/detail-navigation/detail-navigation-sheet.test.tsx +++ b/frontend/src/components/shared/detail-navigation/detail-navigation-sheet.test.tsx @@ -505,3 +505,65 @@ describe('DetailNavigationSheet — virtualization (>100 items)', () => { }); }); }); + +// Controlled mode (the documented "page-level search box" use case): the parent +// owns searchQuery, so the sheet's local input mirror must re-seed from the +// controller when it changes outside the sheet. +const ControlledSheet = ({ open, query }: { open: boolean; query: string }) => { + const nav = useDetailNavigation({ + currentId: 'c', + getHref, + getLabel, + getSearchableText, + items: ITEMS, + onOpenChange: () => {}, + onSearchQueryChange: () => {}, + open, + searchDebounceMs: 0, + searchQuery: query, + }); + + return ( + + controller={nav} + sheetTitle="Items" + /> + ); +}; + +const renderControlled = (props: { open: boolean; query: string }) => + render(, { + wrapper: ({ children }: { children: ReactNode }) => ( + + + + {children}} + path="/items/:id" + /> + + + + ), + }); + +describe('DetailNavigationSheet — controlled search resync', () => { + it('reflects an external searchQuery change made while the sheet was closed', () => { + const { rerender } = renderControlled({ open: false, query: '' }); + + rerender( + , + ); + rerender( + , + ); + + expect(screen.getByRole('textbox').value).toBe('Bravo'); + }); +}); diff --git a/frontend/src/components/shared/detail-navigation/detail-navigation-sheet.tsx b/frontend/src/components/shared/detail-navigation/detail-navigation-sheet.tsx index b8df16fa..6fa7e647 100644 --- a/frontend/src/components/shared/detail-navigation/detail-navigation-sheet.tsx +++ b/frontend/src/components/shared/detail-navigation/detail-navigation-sheet.tsx @@ -140,6 +140,14 @@ export function DetailNavigationSheet({ if (lastOpen !== open) { setLastOpen(open); + + // Re-seed the input mirror from the controller on open so a controlled + // searchQuery changed from outside isn't shown stale. The sheet is modal, + // so searchQuery only changes externally while closed — on-open re-seed + // suffices and never clobbers in-progress typing. + if (open) { + setLocalQuery(searchQuery); + } } if (desiredFocusId !== focusedId) {