Skip to content

Commit 3bf054c

Browse files
authored
fix(app): restore desktop prod legacy flows (#28919)
1 parent 14c511e commit 3bf054c

8 files changed

Lines changed: 245 additions & 185 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { describe, expect, test } from "bun:test"
2+
import { createStore } from "solid-js/store"
3+
import { QueryClient } from "@tanstack/solid-query"
4+
import type { Config, OpencodeClient, Project } from "@opencode-ai/sdk/v2/client"
5+
import type { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
6+
import { bootstrapDirectory } from "./bootstrap"
7+
import type { State, VcsCache } from "./types"
8+
9+
const provider = { all: new Map(), connected: [], default: {} } satisfies NormalizedProviderListResponse
10+
11+
describe("bootstrapDirectory", () => {
12+
test("marks a loading directory partial during bootstrap and complete after success", async () => {
13+
const [store, setStore] = createStore<State>({
14+
status: "loading",
15+
agent: [],
16+
command: [],
17+
project: "",
18+
projectMeta: undefined,
19+
icon: undefined,
20+
provider_ready: true,
21+
provider,
22+
config: {},
23+
path: { state: "", config: "", worktree: "/project", directory: "/project", home: "/home" },
24+
session: [],
25+
sessionTotal: 0,
26+
session_status: {},
27+
session_working(id: string) {
28+
return this.session_status[id]?.type !== "idle"
29+
},
30+
session_diff: {},
31+
todo: {},
32+
permission: {},
33+
question: {},
34+
mcp_ready: true,
35+
mcp: {},
36+
lsp_ready: true,
37+
lsp: [],
38+
vcs: undefined,
39+
limit: 5,
40+
message: {},
41+
part: {},
42+
part_text_accum_delta: {},
43+
})
44+
45+
await bootstrapDirectory({
46+
directory: "/project",
47+
global: {
48+
config: {} satisfies Config,
49+
path: { state: "", config: "", worktree: "/project", directory: "/project", home: "/home" },
50+
project: [{ id: "project", worktree: "/project" } as Project],
51+
provider,
52+
},
53+
sdk: {
54+
app: { agents: async () => ({ data: [{ name: "build", mode: "primary" }] }) },
55+
config: { get: async () => ({ data: {} }) },
56+
session: { status: async () => ({ data: {} }) },
57+
vcs: { get: async () => ({ data: undefined }) },
58+
command: { list: async () => ({ data: [] }) },
59+
permission: { list: async () => ({ data: [] }) },
60+
question: { list: async () => ({ data: [] }) },
61+
mcp: { status: async () => ({ data: {} }) },
62+
provider: { list: async () => ({ data: { all: [], connected: [], default: {} } }) },
63+
} as unknown as OpencodeClient,
64+
store,
65+
setStore,
66+
vcsCache: { setStore() {} } as unknown as VcsCache,
67+
loadSessions() {},
68+
translate: (key) => key,
69+
queryClient: new QueryClient(),
70+
})
71+
72+
expect(store.status).toBe("partial")
73+
74+
await new Promise((resolve) => setTimeout(resolve, 80))
75+
76+
expect(store.status).toBe("complete")
77+
})
78+
})

packages/app/src/context/global-sync/bootstrap.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ export async function bootstrapDirectory(input: {
220220
if (Object.keys(input.store.config).length === 0 && Object.keys(input.global.config).length > 0) {
221221
input.setStore("config", reconcile(input.global.config, { merge: false }))
222222
}
223+
if (loading) input.setStore("status", "partial")
223224

224225
const rev = (providerRev.get(input.directory) ?? 0) + 1
225226
providerRev.set(input.directory, rev)
@@ -326,5 +327,7 @@ export async function bootstrapDirectory(input: {
326327
description: formatServerError(slowErrs[0], input.translate),
327328
})
328329
}
330+
331+
if (loading && slowErrs.length === 0) input.setStore("status", "complete")
329332
})()
330333
}

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

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,63 @@
1-
import { describe, expect, test } from "bun:test"
2-
import { createRoot, getOwner } from "solid-js"
1+
import { beforeAll, describe, expect, mock, test } from "bun:test"
2+
import { createRoot, getOwner, type Owner } from "solid-js"
33
import { createStore } from "solid-js/store"
4+
import type { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
45
import type { State } from "./types"
5-
import { createChildStoreManager } from "./child-store"
6+
import type { QueryOptionsApi } from "../global-sync"
7+
8+
let createChildStoreManager: typeof import("./child-store").createChildStoreManager
69

710
const child = () => createStore({} as State)
11+
const provider = { all: new Map(), connected: [], default: {} } satisfies NormalizedProviderListResponse
12+
13+
const queryOptionsApi = {
14+
globalConfig: () => ({ queryKey: ["globalConfig"], queryFn: async () => ({}) }),
15+
projects: () => ({ queryKey: ["projects"], queryFn: async () => [] }),
16+
providers: (directory: string | null) => ({ queryKey: [directory, "providers"], queryFn: async () => provider }),
17+
path: (directory: string | null) => ({
18+
queryKey: [directory, "path"],
19+
queryFn: async () => ({
20+
state: "",
21+
config: "",
22+
worktree: "",
23+
directory: directory ?? "",
24+
home: "",
25+
}),
26+
}),
27+
agents: (directory: string) => ({ queryKey: [directory, "agents"], queryFn: async () => [] }),
28+
mcp: (directory: string) => ({ queryKey: [directory, "mcp"], queryFn: async () => ({}) }),
29+
lsp: (directory: string) => ({ queryKey: [directory, "lsp"], queryFn: async () => [] }),
30+
sessions: (directory: string) => ({ queryKey: [directory, "loadSessions"] as const }),
31+
} as unknown as QueryOptionsApi
32+
33+
function createOwner(callback: (owner: Owner) => void) {
34+
return createRoot((dispose) => {
35+
const owner = getOwner()
36+
if (!owner) throw new Error("owner required")
37+
callback(owner)
38+
39+
return dispose
40+
})
41+
}
42+
43+
beforeAll(async () => {
44+
mock.module("@/utils/persist", () => ({
45+
Persist: {
46+
workspace: (...parts: string[]) => parts.join(":"),
47+
},
48+
persisted: (_target: string, store: unknown[]) => [store[0], store[1], null, () => true],
49+
}))
50+
mock.module("@tanstack/solid-query", () => ({
51+
useQueries: () => [
52+
{ isLoading: false, data: { state: "", config: "", worktree: "", directory: "", home: "" } },
53+
{ isLoading: false, data: {} },
54+
{ isLoading: false, data: [] },
55+
{ isLoading: false, data: provider },
56+
],
57+
}))
58+
59+
createChildStoreManager = (await import("./child-store")).createChildStoreManager
60+
})
861

962
describe("createChildStoreManager", () => {
1063
test("does not evict the active directory during mark", () => {
@@ -22,8 +75,8 @@ describe("createChildStoreManager", () => {
2275
onBootstrap() {},
2376
onDispose() {},
2477
translate: (key) => key,
25-
queryOptions: {} as any,
26-
global: { provider: null! },
78+
queryOptions: queryOptionsApi,
79+
global: { provider },
2780
})
2881

2982
Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => {
@@ -37,4 +90,35 @@ describe("createChildStoreManager", () => {
3790

3891
expect(manager.children[directory]).toBeDefined()
3992
})
93+
94+
test("starts new child stores as loading and bootstraps them on first access", () => {
95+
const bootstraps: string[] = []
96+
let manager: ReturnType<typeof createChildStoreManager> | undefined
97+
98+
const dispose = createOwner((owner) => {
99+
manager = createChildStoreManager({
100+
owner,
101+
isBooting: () => false,
102+
isLoadingSessions: () => false,
103+
onBootstrap(directory) {
104+
bootstraps.push(directory)
105+
},
106+
onDispose() {},
107+
translate: (key) => key,
108+
queryOptions: queryOptionsApi,
109+
global: { provider },
110+
})
111+
})
112+
113+
try {
114+
if (!manager) throw new Error("manager required")
115+
116+
const [store] = manager.child("/project")
117+
118+
expect(store.status).toBe("loading")
119+
expect(bootstraps).toEqual(["/project"])
120+
} finally {
121+
dispose()
122+
}
123+
})
40124
})

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export function createChildStoreManager(input: {
202202
return { state: "", config: "", worktree: "", directory: "", home: "" }
203203
return pathQuery.data
204204
},
205-
status: "complete" as const,
205+
status: "loading" as const,
206206
agent: [],
207207
command: [],
208208
session: [],

0 commit comments

Comments
 (0)