FN-7409: Restore board search reopen button

Restore the board/list search affordance after users dismiss an active desktop search.

- Keep the non-mobile open-search button available once a closed search has an empty query.
- Cover empty-close and parent-cleared populated search flows in Header tests.
- Add a patch changeset for the published Fusion package.

Files changed:
 .changeset/restore-board-search-trigger.md         |  7 ++++
 packages/dashboard/app/components/Header.tsx       | 13 +++++---
 .../app/components/__tests__/Header.test.tsx       | 38 ++++++++++++++++++----
 3 files changed, 46 insertions(+), 12 deletions(-)

Fusion-Task-Id: FN-7409
Fusion-Task-Lineage: 9cb4ff80-8490-43be-8d18-a3f292603ecb
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-01 23:58:55 -07:00
parent 22261b683f
commit 98704282ac
3 changed files with 46 additions and 12 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Restore the board search button after closing the search panel.
category: fix
dev: Keeps the desktop/tablet Header search reopen affordance visible after empty-query dismissal and parent clear.

View File

@@ -230,14 +230,17 @@ export function Header({
// Keep mobile search open if there's an active search query
const shouldShowMobileSearch = isMobileSearchOpen || searchQuery.length > 0;
// Non-mobile search: toggled open OR has active query, but not if explicitly closed
const shouldShowNonMobileSearch = (isNonMobileSearchOpen || searchQuery.length > 0) && !isNonMobileSearchExplicitlyClosed;
// Show toggle when search is available, NOT currently shown, NOT explicitly closed, AND query is empty
const canShowNonMobileSearchToggle = (view === "board" || view === "list") && !isMobile && onSearchChange && !isNonMobileSearchExplicitlyClosed && searchQuery.length === 0;
const canShowNonMobileSearch = (view === "board" || view === "list") && !isMobile && onSearchChange;
// Non-mobile search: toggled open OR has active query, but not if explicitly closed.
const shouldShowNonMobileSearch = (isNonMobileSearchOpen || searchQuery.length > 0) && !isNonMobileSearchExplicitlyClosed;
/*
FNXC:BoardSearch 2026-07-01-23:38:
Closing board/list search must suppress the populated floating panel until App clears searchQuery, then immediately restore the Open search affordance. Keep the explicit-close state out of the empty-query toggle gate so an open-but-empty dismissal cannot strand the header without a search trigger.
*/
const canShowNonMobileSearchToggle = Boolean(canShowNonMobileSearch && !shouldShowNonMobileSearch && searchQuery.length === 0);
const showBoardBranchFilters = view === "board";
// Reset explicit close flag when query becomes empty (so toggle reappears)
// Reset explicit close flag when query becomes empty (so active-query reopen behavior is ready for the next search).
useEffect(() => {
if (searchQuery === "") {
setIsNonMobileSearchExplicitlyClosed(false);

View File

@@ -875,6 +875,7 @@ describe("Header", () => {
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
fireEvent.click(screen.getByLabelText("Close search"));
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
expect(screen.getAllByTestId("desktop-header-search-btn")).toHaveLength(1);
});
it("clears search query when close button is clicked", () => {
@@ -885,6 +886,33 @@ describe("Header", () => {
expect(onSearchChange).toHaveBeenCalledWith("");
});
it("restores the board search open button after closing a populated query and parent clear", () => {
const onSearchChange = vi.fn();
const { rerender } = renderHeader({ onSearchChange, view: "board", searchQuery: "blocked" });
expect(screen.getByDisplayValue("blocked")).toBeInTheDocument();
expect(screen.queryByTestId("desktop-header-search-btn")).toBeNull();
fireEvent.click(screen.getByLabelText("Close search"));
expect(onSearchChange).toHaveBeenCalledWith("");
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
mockMatchMedia("desktop");
rerender(
<Header
onOpenSettings={noop}
onOpenGitHubImport={noop}
onSearchChange={onSearchChange}
view="board"
searchQuery=""
/>
);
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
expect(screen.getAllByRole("button", { name: "Open search" })).toHaveLength(1);
expect(screen.getAllByTestId("desktop-header-search-btn")).toHaveLength(1);
});
it("keeps search open when searchQuery is non-empty", () => {
renderHeader({ onSearchChange: vi.fn(), view: "board", searchQuery: "test" });
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
@@ -933,18 +961,14 @@ describe("Header", () => {
expect(wrapper!.querySelector("header.header")).not.toBeNull();
});
it("toggling search twice reopens the search (use close button to dismiss)", () => {
it("hides the open toggle while search is open and restores it after close", () => {
renderHeader({ onSearchChange: vi.fn(), view: "board" });
fireEvent.click(screen.getByTestId("desktop-header-search-btn"));
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
// Second toggle click reopens search since first close was via toggle
// (toggle always opens, use close button to dismiss)
fireEvent.click(screen.getByTestId("desktop-header-search-btn"));
// Search stays open because toggle only opens
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
// Use close button to dismiss
expect(screen.queryByTestId("desktop-header-search-btn")).toBeNull();
fireEvent.click(screen.getByLabelText("Close search"));
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
expect(screen.getAllByTestId("desktop-header-search-btn")).toHaveLength(1);
});
it("supports search toggle flow on list view", () => {