diff --git a/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts b/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts index 136232c21..29eb680c2 100644 --- a/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts +++ b/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts @@ -60,6 +60,39 @@ test("keeps the file-browser sidebar mounted when switching file tabs", async ({ await expect.poll(() => viewport.evaluate((element) => element.scrollTop)).toBe(scrolled) }) +test("keeps previous file search results visible while the next search loads", async ({ page }) => { + const searchPending = Promise.withResolvers() + await setup(page, async ({ query }) => { + if (query === "file-0") return ["file-00.ts"] + if (query === "file-7") { + await searchPending.promise + return ["file-79.ts"] + } + return [] + }) + + await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`) + await expectSessionTitle(page, title) + + const panel = page.locator("#review-panel") + await panel.getByRole("button", { name: "Open file" }).click() + const filter = panel.getByRole("combobox", { name: "Filter files" }) + await filter.fill("file-0") + await expect(panel.getByRole("option", { name: "file-00.ts" })).toBeVisible() + + const nextSearch = page.waitForRequest((request) => { + const url = new URL(request.url()) + return url.pathname === "/find/file" && url.searchParams.get("query") === "file-7" + }) + await filter.fill("file-7") + await nextSearch + await expect(panel.getByRole("option", { name: "file-00.ts" })).toBeVisible() + + searchPending.resolve() + await expect(panel.getByRole("option", { name: "file-79.ts" })).toBeVisible() + await expect(panel.getByRole("option", { name: "file-00.ts" })).toBeHidden() +}) + type Probed = HTMLElement & { __e2eProbe?: string } async function writeProbe(page: Page) { @@ -74,7 +107,10 @@ async function readProbe(page: Page) { .evaluate((el) => (el as Probed).__e2eProbe) } -async function setup(page: Page) { +async function setup( + page: Page, + findFiles?: (input: { query: string; dirs?: string; limit?: number }) => unknown | Promise, +) { await mockOpenCodeServer(page, { directory, project: { @@ -119,6 +155,7 @@ async function setup(page: Page) { })) }, fileContent: (path) => ({ type: "text", content: `contents:${path}` }), + findFiles, pageMessages: () => ({ items: [] }), }) diff --git a/packages/app/e2e/utils/mock-server.ts b/packages/app/e2e/utils/mock-server.ts index 76987421b..a581eda8c 100644 --- a/packages/app/e2e/utils/mock-server.ts +++ b/packages/app/e2e/utils/mock-server.ts @@ -26,7 +26,7 @@ export interface MockServerConfig { questions?: unknown[] | (() => unknown[]) fileList?: (path: string) => unknown | Promise fileContent?: (path: string) => unknown | Promise - findFiles?: (input: { query: string; dirs?: string; limit?: number }) => unknown + findFiles?: (input: { query: string; dirs?: string; limit?: number }) => unknown | Promise sessionStatus?: Record | (() => Record) } diff --git a/packages/app/src/pages/session/v2/session-file-browser-tab.tsx b/packages/app/src/pages/session/v2/session-file-browser-tab.tsx index 639429e80..c9e0d3816 100644 --- a/packages/app/src/pages/session/v2/session-file-browser-tab.tsx +++ b/packages/app/src/pages/session/v2/session-file-browser-tab.tsx @@ -1,5 +1,5 @@ import { createMemo, createSignal, createUniqueId, Show } from "solid-js" -import { createQuery } from "@tanstack/solid-query" +import { createQuery, keepPreviousData } from "@tanstack/solid-query" import { Icon } from "@opencode-ai/ui/icon" import { SessionFilePanelV2, SessionFilePanelV2Empty } from "@opencode-ai/session-ui/v2/session-file-panel-v2" import { SessionReviewV2Sidebar } from "@opencode-ai/session-ui/v2/session-review-v2" @@ -50,6 +50,7 @@ export function SessionFileBrowserTab(props: { queryKey: ["session-open-file", workspaceKey(), value] as const, enabled: value.length > 0, queryFn: ({ signal }) => file.searchFiles(value, { limit: 200, signal }), + placeholderData: keepPreviousData, } }) const files = createMemo(() => {