Skip to content

Commit f5c3d35

Browse files
authored
fix(app): require query functions for sync queries (#25939)
1 parent e117397 commit f5c3d35

6 files changed

Lines changed: 66 additions & 99 deletions

File tree

packages/app/src/components/dialog-select-mcp.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Dialog } from "@opencode-ai/ui/dialog"
66
import { List } from "@opencode-ai/ui/list"
77
import { Switch } from "@opencode-ai/ui/switch"
88
import { useLanguage } from "@/context/language"
9-
import { loadMcpQuery } from "@/context/global-sync"
9+
import { mcpQueryKey } from "@/context/global-sync"
1010

1111
const statusLabels = {
1212
connected: "mcp.status.connected",
@@ -32,7 +32,7 @@ export const DialogSelectMcp: Component = () => {
3232
if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name })
3333
else await sdk.client.mcp.connect({ name })
3434
},
35-
onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
35+
onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
3636
}))
3737

3838
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)

packages/app/src/components/prompt-input.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from "@/context/prompt"
1717
import { useLayout } from "@/context/layout"
1818
import { useSDK } from "@/context/sdk"
19+
import { useGlobalSDK } from "@/context/global-sdk"
1920
import { useSync } from "@/context/sync"
2021
import { useComments } from "@/context/comments"
2122
import { Button } from "@opencode-ai/ui/button"
@@ -102,6 +103,7 @@ const NON_EMPTY_TEXT = /[^\s\u200B]/
102103

103104
export const PromptInput: Component<PromptInputProps> = (props) => {
104105
const sdk = useSDK()
106+
const globalSDK = useGlobalSDK()
105107

106108
const sync = useSync()
107109
const local = useLocal()
@@ -1253,7 +1255,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
12531255
}
12541256

12551257
const [agentsQuery, globalProvidersQuery, providersQuery] = useQueries(() => ({
1256-
queries: [loadAgentsQuery(sdk.directory), loadProvidersQuery(null), loadProvidersQuery(sdk.directory)],
1258+
queries: [
1259+
loadAgentsQuery(sdk.directory, sdk.client),
1260+
loadProvidersQuery(null, globalSDK.client),
1261+
loadProvidersQuery(sdk.directory, sdk.client),
1262+
],
12571263
}))
12581264

12591265
const agentsLoading = () => agentsQuery.isLoading

packages/app/src/components/status-popover-body.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { useSDK } from "@/context/sdk"
1515
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
1616
import { useSync } from "@/context/sync"
1717
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
18-
import { loadMcpQuery } from "@/context/global-sync"
18+
import { mcpQueryKey } from "@/context/global-sync"
1919

2020
const pollMs = 10_000
2121

@@ -145,7 +145,7 @@ const useMcpToggleMutation = () => {
145145
const status = sync.data.mcp[name]
146146
await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name }))
147147
},
148-
onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
148+
onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
149149
onError: (err) => {
150150
showToast({
151151
variant: "error",

packages/app/src/context/global-sync.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
clearProviderRev,
2121
loadGlobalConfigQuery,
2222
loadPathQuery,
23-
loadProjectsQuery,
2423
loadProvidersQuery,
2524
} from "./global-sync/bootstrap"
2625
import { createChildStoreManager } from "./global-sync/child-store"
@@ -31,7 +30,7 @@ import { trimSessions } from "./global-sync/session-trim"
3130
import type { ProjectMeta } from "./global-sync/types"
3231
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
3332
import { formatServerError } from "@/utils/server-errors"
34-
import { queryOptions, skipToken, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
33+
import { queryOptions, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
3534
import { createRefreshQueue } from "./global-sync/queue"
3635
import { directoryKey } from "./global-sync/utils"
3736

@@ -49,19 +48,22 @@ type GlobalStore = {
4948
reload: undefined | "pending" | "complete"
5049
}
5150

52-
export const loadSessionsQuery = (directory: string) =>
53-
queryOptions<null>({ queryKey: [directory, "loadSessions"], queryFn: skipToken })
51+
export const loadSessionsQueryKey = (directory: string) => [directory, "loadSessions"] as const
5452

55-
export const loadMcpQuery = (directory: string, sdk?: OpencodeClient) =>
53+
export const mcpQueryKey = (directory: string) => [directory, "mcp"] as const
54+
55+
export const loadMcpQuery = (directory: string, sdk: OpencodeClient) =>
5656
queryOptions({
57-
queryKey: [directory, "mcp"],
58-
queryFn: sdk ? () => sdk.mcp.status().then((r) => r.data ?? {}) : skipToken,
57+
queryKey: mcpQueryKey(directory),
58+
queryFn: () => sdk.mcp.status().then((r) => r.data ?? {}),
5959
})
6060

61-
export const loadLspQuery = (directory: string, sdk?: OpencodeClient) =>
61+
export const lspQueryKey = (directory: string) => [directory, "lsp"] as const
62+
63+
export const loadLspQuery = (directory: string, sdk: OpencodeClient) =>
6264
queryOptions({
63-
queryKey: [directory, "lsp"],
64-
queryFn: sdk ? () => sdk.lsp.status().then((r) => r.data ?? []) : skipToken,
65+
queryKey: lspQueryKey(directory),
66+
queryFn: () => sdk.lsp.status().then((r) => r.data ?? []),
6567
})
6668

6769
function createGlobalSync() {
@@ -76,7 +78,11 @@ function createGlobalSync() {
7678
const sessionMeta = new Map<string, { limit: number }>()
7779

7880
const [configQuery, providerQuery, pathQuery] = useQueries(() => ({
79-
queries: [loadGlobalConfigQuery(), loadProvidersQuery(null), loadPathQuery(null), loadProjectsQuery()],
81+
queries: [
82+
loadGlobalConfigQuery(globalSDK.client),
83+
loadProvidersQuery(null, globalSDK.client),
84+
loadPathQuery(null, globalSDK.client),
85+
],
8086
}))
8187

8288
const [globalStore, setGlobalStore] = createStore<GlobalStore>({
@@ -233,7 +239,7 @@ function createGlobalSync() {
233239
const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT)
234240
const promise = queryClient
235241
.fetchQuery({
236-
...loadSessionsQuery(key),
242+
queryKey: loadSessionsQueryKey(key),
237243
queryFn: () =>
238244
loadRootSessionsWithFallback({
239245
directory,

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

Lines changed: 30 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
1818
import type { State, VcsCache } from "./types"
1919
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
2020
import { formatServerError } from "@/utils/server-errors"
21-
import { QueryClient, queryOptions, skipToken } from "@tanstack/solid-query"
21+
import { QueryClient, queryOptions } from "@tanstack/solid-query"
2222
import { loadMcpQuery } from "../global-sync"
2323

2424
type GlobalStore = {
@@ -83,44 +83,25 @@ function showErrors(input: {
8383
})
8484
}
8585

86-
export const loadGlobalConfigQuery = (
87-
sdk?: OpencodeClient,
88-
transform?: (x: Awaited<ReturnType<OpencodeClient["global"]["config"]["get"]>>) => void,
89-
) =>
86+
export const loadGlobalConfigQuery = (sdk: OpencodeClient) =>
9087
queryOptions({
9188
queryKey: ["config"],
92-
queryFn: sdk
93-
? () =>
94-
retry(() =>
95-
sdk.global.config.get().then((x) => {
96-
transform?.(x)
97-
return x.data!
98-
}),
99-
)
100-
: skipToken,
89+
queryFn: () => retry(() => sdk.global.config.get().then((x) => x.data!)),
10190
})
10291

103-
export const loadProjectsQuery = (
104-
sdk?: OpencodeClient,
105-
transform?: (x: Awaited<ReturnType<OpencodeClient["project"]["list"]>>["data"]) => void,
106-
) =>
92+
export const loadProjectsQuery = (sdk: OpencodeClient) =>
10793
queryOptions({
10894
queryKey: ["project"],
109-
queryFn: sdk
110-
? () =>
111-
retry(() =>
112-
sdk.project
113-
.list()
114-
.then((x) => {
115-
return (x.data ?? [])
116-
.filter((p) => !!p?.id)
117-
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
118-
.slice()
119-
.sort((a, b) => cmp(a.id, b.id))
120-
})
121-
.then(transform),
122-
)
123-
: skipToken,
95+
queryFn: () =>
96+
retry(() =>
97+
sdk.project.list().then((x) => {
98+
return (x.data ?? [])
99+
.filter((p) => !!p?.id)
100+
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
101+
.slice()
102+
.sort((a, b) => cmp(a.id, b.id))
103+
}),
104+
),
124105
})
125106

126107
export async function bootstrapGlobal(input: {
@@ -136,9 +117,9 @@ export async function bootstrapGlobal(input: {
136117
() => input.queryClient.fetchQuery(loadProvidersQuery(null, input.globalSDK)),
137118
() => input.queryClient.fetchQuery(loadPathQuery(null, input.globalSDK)),
138119
() =>
139-
input.queryClient.fetchQuery(
140-
loadProjectsQuery(input.globalSDK, (data) => input.setGlobalStore("project", data ?? [])),
141-
),
120+
input.queryClient
121+
.fetchQuery(loadProjectsQuery(input.globalSDK))
122+
.then((data) => input.setGlobalStore("project", data)),
142123
]
143124
await runAll(slow)
144125
// showErrors({
@@ -197,46 +178,22 @@ function warmSessions(input: {
197178
).then(() => undefined)
198179
}
199180

200-
export const loadProvidersQuery = (directory: string | null, sdk?: OpencodeClient) =>
181+
export const loadProvidersQuery = (directory: string | null, sdk: OpencodeClient) =>
201182
queryOptions({
202183
queryKey: [directory, "providers"],
203-
queryFn: sdk ? () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))) : skipToken,
184+
queryFn: () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))),
204185
})
205186

206-
export const loadAgentsQuery = (
207-
directory: string | null,
208-
sdk?: OpencodeClient,
209-
transform?: (x: Awaited<ReturnType<OpencodeClient["app"]["agents"]>>) => void,
210-
) =>
187+
export const loadAgentsQuery = (directory: string | null, sdk: OpencodeClient) =>
211188
queryOptions({
212189
queryKey: [directory, "agents"],
213-
queryFn: sdk
214-
? () =>
215-
retry(() =>
216-
sdk.app.agents().then((x) => {
217-
transform?.(x)
218-
return x.data!
219-
}),
220-
)
221-
: skipToken,
190+
queryFn: () => retry(() => sdk.app.agents().then((x) => normalizeAgentList(x.data))),
222191
})
223192

224-
export const loadPathQuery = (
225-
directory: string | null,
226-
sdk?: OpencodeClient,
227-
transform?: (x: Awaited<ReturnType<OpencodeClient["path"]["get"]>>) => void,
228-
) =>
193+
export const loadPathQuery = (directory: string | null, sdk: OpencodeClient) =>
229194
queryOptions<Path>({
230195
queryKey: [directory, "path"],
231-
queryFn: sdk
232-
? () =>
233-
retry(() =>
234-
sdk.path.get().then(async (x) => {
235-
transform?.(x)
236-
return x.data!
237-
}),
238-
)
239-
: skipToken,
196+
queryFn: () => retry(() => sdk.path.get().then((x) => x.data!)),
240197
})
241198

242199
export async function bootstrapDirectory(input: {
@@ -271,22 +228,20 @@ export async function bootstrapDirectory(input: {
271228
const slow = [
272229
() => Promise.resolve(input.loadSessions(input.directory)),
273230
() =>
274-
input.queryClient.ensureQueryData(
275-
loadAgentsQuery(input.directory, input.sdk, (x) => input.setStore("agent", normalizeAgentList(x.data))),
276-
),
231+
input.queryClient
232+
.ensureQueryData(loadAgentsQuery(input.directory, input.sdk))
233+
.then((data) => input.setStore("agent", data)),
277234
() =>
278235
retry(() => input.sdk.config.get().then((x) => input.setStore("config", reconcile(x.data!, { merge: false })))),
279236
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
280237
!seededProject &&
281238
(() => retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id))),
282239
!seededPath &&
283240
(() =>
284-
input.queryClient.ensureQueryData(
285-
loadPathQuery(input.directory, input.sdk, (x) => {
286-
const next = projectID(x.data?.directory ?? input.directory, input.global.project)
287-
if (next) input.setStore("project", next)
288-
}),
289-
)),
241+
input.queryClient.ensureQueryData(loadPathQuery(input.directory, input.sdk)).then((data) => {
242+
const next = projectID(data.directory ?? input.directory, input.global.project)
243+
if (next) input.setStore("project", next)
244+
})),
290245
() =>
291246
retry(() =>
292247
input.sdk.vcs.get().then((x) => {

packages/app/src/pages/layout/sidebar-workspace.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import { Spinner } from "@opencode-ai/ui/spinner"
1414
import { Tooltip } from "@opencode-ai/ui/tooltip"
1515
import { type Session } from "@opencode-ai/sdk/v2/client"
1616
import { type LocalProject } from "@/context/layout"
17-
import { loadSessionsQuery, useGlobalSync } from "@/context/global-sync"
17+
import { loadSessionsQueryKey, useGlobalSync } from "@/context/global-sync"
1818
import { useLanguage } from "@/context/language"
1919
import { pathKey } from "@/utils/path-key"
2020
import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items"
2121
import { sortedRootSessions } from "./helpers"
22-
import { useQuery } from "@tanstack/solid-query"
22+
import { useIsFetching } from "@tanstack/solid-query"
2323

2424
type InlineEditorComponent = (props: {
2525
id: string
@@ -320,9 +320,9 @@ export const SortableWorkspace = (props: {
320320
const boot = createMemo(() => open() || active())
321321
const count = createMemo(() => sessions()?.length ?? 0)
322322
const hasMore = createMemo(() => workspaceStore.sessionTotal > count())
323-
const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
323+
const fetching = useIsFetching(() => ({ queryKey: loadSessionsQueryKey(props.directory) }))
324324
const busy = createMemo(() => props.ctx.isBusy(props.directory))
325-
const loading = () => query.isLoading && count() === 0
325+
const loading = () => fetching() > 0 && count() === 0
326326
const touch = createMediaQuery("(hover: none)")
327327
const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id)))
328328
const loadMore = async () => {
@@ -427,7 +427,7 @@ export const SortableWorkspace = (props: {
427427
mobile={props.mobile}
428428
ctx={props.ctx}
429429
showNew={showNew}
430-
loading={() => query.isLoading && count() === 0}
430+
loading={loading}
431431
sessions={sessions}
432432
hasMore={hasMore}
433433
loadMore={loadMore}
@@ -454,9 +454,9 @@ export const LocalWorkspace = (props: {
454454
const slug = createMemo(() => base64Encode(props.project.worktree))
455455
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
456456
const count = createMemo(() => sessions()?.length ?? 0)
457-
const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
457+
const fetching = useIsFetching(() => ({ queryKey: loadSessionsQueryKey(props.project.worktree) }))
458458
const hasMore = createMemo(() => workspace().store.sessionTotal > count())
459-
const loading = () => query.isLoading && count() === 0
459+
const loading = () => fetching() > 0 && count() === 0
460460
const loadMore = async () => {
461461
workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
462462
await globalSync.project.loadSessions(props.project.worktree)

0 commit comments

Comments
 (0)