浏览代码

fix(app): show project sessions before path sync resolves (#30167)

Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
Michael Hart 3 月之前
父节点
当前提交
2725eed992

+ 40 - 0
packages/app/e2e/regression/session-list-path-loading.spec.ts

@@ -0,0 +1,40 @@
+import { expect, test } from "@playwright/test"
+import { fixture, pageMessages } from "../smoke/session-timeline.fixture"
+import { mockOpenCodeServer } from "../utils/mock-server"
+
+test("shows loaded sessions before the directory path request resolves", async ({ page }) => {
+  await mockOpenCodeServer(page, {
+    sessions: fixture.sessions,
+    provider: fixture.provider,
+    directory: fixture.directory,
+    project: fixture.project,
+    pageMessages,
+  })
+
+  let releasePath!: () => void
+  const pathBlocked = new Promise<void>((resolve) => {
+    releasePath = resolve
+  })
+  await page.route("**/path?*", async (route) => {
+    if (!new URL(route.request().url()).searchParams.has("directory")) return route.fallback()
+    await pathBlocked
+    return route.fallback()
+  })
+
+  await page.addInitScript((directory) => {
+    localStorage.setItem(
+      "opencode.global.dat:server",
+      JSON.stringify({
+        projects: { local: [{ worktree: directory, expanded: true }] },
+        lastProject: { local: directory },
+      }),
+    )
+  }, fixture.directory)
+
+  await page.goto("/")
+  try {
+    await expect(page.getByText(fixture.expected.sourceTitle).first()).toBeVisible({ timeout: 5_000 })
+  } finally {
+    releasePath()
+  }
+})

+ 30 - 1
packages/app/src/context/global-sync/child-store.test.ts

@@ -52,7 +52,7 @@ beforeAll(async () => {
     useQueries: (options: () => { queries: Array<{ enabled?: boolean }> }) => {
       queryGroups.push(options)
       return [
-        { isLoading: false, data: { state: "", config: "", worktree: "", directory: "", home: "" } },
+        { isLoading: true, data: undefined },
         { isLoading: false, data: {} },
         { isLoading: false, data: [] },
         { isLoading: false, data: provider },
@@ -128,6 +128,35 @@ describe("createChildStoreManager", () => {
     }
   })
 
+  test("provides the requested directory while the path query is pending", () => {
+    let manager: ReturnType<typeof createChildStoreManager> | undefined
+
+    const dispose = createOwner((owner) => {
+      manager = createChildStoreManager({
+        owner,
+        isBooting: () => false,
+        isLoadingSessions: () => false,
+        onBootstrap() {},
+        onMcp() {},
+        onDispose() {},
+        translate: (key) => key,
+        queryOptions: queryOptionsApi,
+        global: { provider },
+      })
+    })
+
+    try {
+      if (!manager) throw new Error("manager required")
+
+      const [store] = manager.child("/project", { bootstrap: false })
+
+      expect(store.path.directory).toBe("/project")
+      expect(store.path.worktree).toBe("")
+    } finally {
+      dispose()
+    }
+  })
+
   test("enables MCP only when requested for the directory", () => {
     let manager: ReturnType<typeof createChildStoreManager> | undefined
     const offset = queryGroups.length

+ 2 - 3
packages/app/src/context/global-sync/child-store.ts

@@ -204,9 +204,8 @@ export function createChildStoreManager(input: {
             },
             config: {},
             get path() {
-              if (pathQuery.isLoading || !pathQuery.data)
-                return { state: "", config: "", worktree: "", directory: "", home: "" }
-              return pathQuery.data
+              if (pathQuery.data) return pathQuery.data
+              return { state: "", config: "", worktree: "", directory, home: "" }
             },
             status: "loading" as const,
             agent: [],