diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 18c6fef30a..bf8138fcde 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -82,7 +82,15 @@ declare global { } function QueryProvider(props: ParentProps) { - const client = new QueryClient() + const client = new QueryClient({ + defaultOptions: { + queries: { + refetchOnReconnect: false, + refetchOnMount: false, + refetchOnWindowFocus: false, + }, + }, + }) return {props.children} } diff --git a/packages/app/src/components/dialog-select-mcp.tsx b/packages/app/src/components/dialog-select-mcp.tsx index 98f262ce5a..9bb36d32d8 100644 --- a/packages/app/src/components/dialog-select-mcp.tsx +++ b/packages/app/src/components/dialog-select-mcp.tsx @@ -1,13 +1,12 @@ -import { useMutation } from "@tanstack/solid-query" -import { Component, createEffect, createMemo, on, Show } from "solid-js" -import { createStore } from "solid-js/store" +import { useMutation, useQueryClient } from "@tanstack/solid-query" +import { Component, createMemo, Show } from "solid-js" import { useSync } from "@/context/sync" import { useSDK } from "@/context/sdk" import { Dialog } from "@opencode-ai/ui/dialog" import { List } from "@opencode-ai/ui/list" import { Switch } from "@opencode-ai/ui/switch" -import { showToast } from "@opencode-ai/ui/toast" import { useLanguage } from "@/context/language" +import { loadMcpQuery } from "@/context/global-sync" const statusLabels = { connected: "mcp.status.connected", @@ -20,48 +19,7 @@ export const DialogSelectMcp: Component = () => { const sync = useSync() const sdk = useSDK() const language = useLanguage() - const [state, setState] = createStore({ - done: false, - loading: false, - }) - - createEffect( - on( - () => sync.data.mcp_ready, - (ready, prev) => { - if (!ready && prev) setState("done", false) - }, - { defer: true }, - ), - ) - - createEffect(() => { - if (state.done || state.loading) return - if (sync.data.mcp_ready) { - setState("done", true) - return - } - - setState("loading", true) - void sdk.client.mcp - .status() - .then((result) => { - sync.set("mcp", result.data ?? {}) - sync.set("mcp_ready", true) - setState("done", true) - }) - .catch((err) => { - setState("done", true) - showToast({ - variant: "error", - title: language.t("common.requestFailed"), - description: err instanceof Error ? err.message : String(err), - }) - }) - .finally(() => { - setState("loading", false) - }) - }) + const queryClient = useQueryClient() const items = createMemo(() => Object.entries(sync.data.mcp ?? {}) @@ -71,16 +29,10 @@ export const DialogSelectMcp: Component = () => { const toggle = useMutation(() => ({ mutationFn: async (name: string) => { - const status = sync.data.mcp[name] - if (status?.status === "connected") { - await sdk.client.mcp.disconnect({ name }) - } else { - await sdk.client.mcp.connect({ name }) - } - - const result = await sdk.client.mcp.status() - if (result.data) sync.set("mcp", result.data) + if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name }) + else await sdk.client.mcp.connect({ name }) }, + onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }), })) const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length) diff --git a/packages/app/src/components/status-popover-body.tsx b/packages/app/src/components/status-popover-body.tsx index 0f6a1c1355..952e3eac64 100644 --- a/packages/app/src/components/status-popover-body.tsx +++ b/packages/app/src/components/status-popover-body.tsx @@ -3,7 +3,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog" import { Icon } from "@opencode-ai/ui/icon" import { Switch } from "@opencode-ai/ui/switch" import { Tabs } from "@opencode-ai/ui/tabs" -import { useMutation } from "@tanstack/solid-query" +import { useMutation, useQueryClient } from "@tanstack/solid-query" import { showToast } from "@opencode-ai/ui/toast" import { useNavigate } from "@solidjs/router" import { type Accessor, createEffect, createMemo, For, type JSXElement, onCleanup, Show } from "solid-js" @@ -15,6 +15,7 @@ import { useSDK } from "@/context/sdk" import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server" import { useSync } from "@/context/sync" import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health" +import { loadMcpQuery } from "@/context/global-sync" const pollMs = 10_000 @@ -137,14 +138,14 @@ const useMcpToggleMutation = () => { const sync = useSync() const sdk = useSDK() const language = useLanguage() + const queryClient = useQueryClient() return useMutation(() => ({ mutationFn: async (name: string) => { const status = sync.data.mcp[name] await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name })) - const result = await sdk.client.mcp.status() - if (result.data) sync.set("mcp", result.data) }, + onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }), onError: (err) => { showToast({ variant: "error", @@ -162,14 +163,6 @@ export function StatusPopoverBody(props: { shown: Accessor }) { const dialog = useDialog() const language = useLanguage() const navigate = useNavigate() - const sdk = useSDK() - - const [load, setLoad] = createStore({ - lspDone: false, - lspLoading: false, - mcpDone: false, - mcpLoading: false, - }) const fail = (err: unknown) => { showToast({ @@ -181,40 +174,6 @@ export function StatusPopoverBody(props: { shown: Accessor }) { createEffect(() => { if (!props.shown()) return - - if (!sync.data.mcp_ready && !load.mcpDone && !load.mcpLoading) { - setLoad("mcpLoading", true) - void sdk.client.mcp - .status() - .then((result) => { - sync.set("mcp", result.data ?? {}) - sync.set("mcp_ready", true) - }) - .catch((err) => { - setLoad("mcpDone", true) - fail(err) - }) - .finally(() => { - setLoad("mcpLoading", false) - }) - } - - if (!sync.data.lsp_ready && !load.lspDone && !load.lspLoading) { - setLoad("lspLoading", true) - void sdk.client.lsp - .status() - .then((result) => { - sync.set("lsp", result.data ?? []) - sync.set("lsp_ready", true) - }) - .catch((err) => { - setLoad("lspDone", true) - fail(err) - }) - .finally(() => { - setLoad("lspLoading", false) - }) - } }) let dialogRun = 0 diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 86496bad50..2c80f31b19 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -14,17 +14,25 @@ import { createStore, produce, reconcile } from "solid-js/store" import { useLanguage } from "@/context/language" import type { InitError } from "../pages/error" import { useGlobalSDK } from "./global-sdk" -import { bootstrapDirectory, bootstrapGlobal, clearProviderRev } from "./global-sync/bootstrap" +import { + bootstrapDirectory, + bootstrapGlobal, + clearProviderRev, + loadGlobalConfigQuery, + loadPathQuery, + loadProjectsQuery, + loadProvidersQuery, +} from "./global-sync/bootstrap" import { createChildStoreManager } from "./global-sync/child-store" import { applyDirectoryEvent, applyGlobalEvent, cleanupDroppedSessionCaches } from "./global-sync/event-reducer" -import { createRefreshQueue } from "./global-sync/queue" import { clearSessionPrefetchDirectory } from "./global-sync/session-prefetch" import { estimateRootSessionTotal, loadRootSessionsWithFallback } from "./global-sync/session-load" import { trimSessions } from "./global-sync/session-trim" import type { ProjectMeta } from "./global-sync/types" import { SESSION_RECENT_LIMIT } from "./global-sync/types" import { formatServerError } from "@/utils/server-errors" -import { queryOptions, skipToken, useQueryClient } from "@tanstack/solid-query" +import { queryOptions, skipToken, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query" +import { createRefreshQueue } from "./global-sync/queue" type GlobalStore = { ready: boolean @@ -43,6 +51,18 @@ type GlobalStore = { export const loadSessionsQuery = (directory: string) => queryOptions({ queryKey: [directory, "loadSessions"], queryFn: skipToken }) +export const loadMcpQuery = (directory: string, sdk?: OpencodeClient) => + queryOptions({ + queryKey: [directory, "mcp"], + queryFn: sdk ? () => sdk.mcp.status().then((r) => r.data ?? {}) : skipToken, + }) + +export const loadLspQuery = (directory: string, sdk?: OpencodeClient) => + queryOptions({ + queryKey: [directory, "lsp"], + queryFn: sdk ? () => sdk.lsp.status().then((r) => r.data ?? []) : skipToken, + }) + function createGlobalSync() { const globalSDK = useGlobalSDK() const language = useLanguage() @@ -54,15 +74,34 @@ function createGlobalSync() { const sessionLoads = new Map>() const sessionMeta = new Map() + const [configQuery, providerQuery, pathQuery] = useQueries(() => ({ + queries: [loadGlobalConfigQuery(), loadProvidersQuery(null), loadPathQuery(null), loadProjectsQuery()], + })) + const [globalStore, setGlobalStore] = createStore({ - ready: false, - path: { state: "", config: "", worktree: "", directory: "", home: "" }, + get ready() { + return bootstrap.isPending + }, project: [], session_todo: {}, - provider: { all: [], connected: [], default: {} }, provider_auth: {}, - config: {}, - reload: undefined, + get path() { + const EMPTY = { state: "", config: "", worktree: "", directory: "", home: "" } + if (pathQuery.isLoading) return EMPTY + return pathQuery.data ?? EMPTY + }, + get provider() { + const EMPTY = { all: [], connected: [], default: {} } + if (providerQuery.isLoading) return EMPTY + return providerQuery.data ?? EMPTY + }, + get config() { + if (configQuery.isLoading) return {} + return configQuery.data ?? {} + }, + get reload() { + return updateConfigMutation.isPending ? "pending" : undefined + }, }) const queryClient = useQueryClient() @@ -88,6 +127,22 @@ function createGlobalSync() { return (setGlobalStore as (...args: unknown[]) => unknown)(...input) }) as typeof setGlobalStore + const bootstrap = useQuery(() => ({ + queryKey: ["bootstrap"], + queryFn: async () => { + await bootstrapGlobal({ + globalSDK: globalSDK.client, + requestFailedTitle: language.t("common.requestFailed"), + translate: language.t, + formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }), + setGlobalStore: setBootStore, + queryClient, + }) + bootedAt = Date.now() + return bootedAt + }, + })) + const set = ((...input: unknown[]) => { if (input[0] === "project" && (Array.isArray(input[1]) || typeof input[1] === "function")) { setProjects(input[1] as Project[] | ((draft: Project[]) => Project[])) @@ -114,10 +169,21 @@ function createGlobalSync() { const queue = createRefreshQueue({ paused, - bootstrap, + bootstrap: () => queryClient.fetchQuery({ queryKey: ["bootstrap"] }), bootstrapInstance, }) + const sdkFor = (directory: string) => { + const cached = sdkCache.get(directory) + if (cached) return cached + const sdk = globalSDK.createClient({ + directory, + throwOnError: true, + }) + sdkCache.set(directory, sdk) + return sdk + } + const children = createChildStoreManager({ owner, isBooting: (directory) => booting.has(directory), @@ -133,19 +199,9 @@ function createGlobalSync() { clearSessionPrefetchDirectory(directory) }, translate: language.t, + getSdk: sdkFor, }) - const sdkFor = (directory: string) => { - const cached = sdkCache.get(directory) - if (cached) return cached - const sdk = globalSDK.createClient({ - directory, - throwOnError: true, - }) - sdkCache.set(directory, sdk) - return sdk - } - async function loadSessions(directory: string) { const pending = sessionLoads.get(directory) if (pending) return pending @@ -264,26 +320,13 @@ function createGlobalSync() { const event = e.details const recent = bootingRoot || Date.now() - bootedAt < 1500 - if (event.type === "session.error") { - const error = event.properties.error - if (error?.name !== "MessageAbortedError") { - console.error("[global-sync] session error", { - scope: directory === "global" ? "global" : "workspace", - directory: directory === "global" ? undefined : directory, - project: directory === "global" ? undefined : getFilename(directory), - sessionID: event.properties.sessionID, - error, - }) - } - } - if (directory === "global") { applyGlobalEvent({ event, project: globalStore.project, refresh: () => { if (recent) return - queue.refresh() + bootstrap.refetch() }, setGlobalProject: setProjects, }) @@ -309,12 +352,7 @@ function createGlobalSync() { setSessionTodo, vcsCache: children.vcsCache.get(directory), loadLsp: () => { - void sdkFor(directory) - .lsp.status() - .then((x) => { - setStore("lsp", x.data ?? []) - setStore("lsp_ready", true) - }) + void queryClient.fetchQuery(loadLspQuery(directory, sdkFor(directory))) }, }) }) @@ -329,23 +367,6 @@ function createGlobalSync() { } }) - async function bootstrap() { - bootingRoot = true - try { - await bootstrapGlobal({ - globalSDK: globalSDK.client, - requestFailedTitle: language.t("common.requestFailed"), - translate: language.t, - formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }), - setGlobalStore: setBootStore, - queryClient, - }) - bootedAt = Date.now() - } finally { - bootingRoot = false - } - } - onMount(() => { if (typeof requestAnimationFrame === "function") { eventFrame = requestAnimationFrame(() => { @@ -361,7 +382,6 @@ function createGlobalSync() { void globalSDK.event.start() }, 0) } - void bootstrap() }) const projectApi = { @@ -374,21 +394,10 @@ function createGlobalSync() { }, } - const updateConfig = async (config: Config) => { - setGlobalStore("reload", "pending") - return globalSDK.client.global.config - .update({ config }) - .then(bootstrap) - .then(() => { - queue.refresh() - setGlobalStore("reload", undefined) - queue.refresh() - }) - .catch((error) => { - setGlobalStore("reload", undefined) - throw error - }) - } + const updateConfigMutation = useMutation(() => ({ + mutationFn: (config: Config) => globalSDK.client.global.config.update({ config }), + onSuccess: () => bootstrap.refetch(), + })) return { data: globalStore, @@ -401,8 +410,8 @@ function createGlobalSync() { }, child: children.child, peek: children.peek, - bootstrap, - updateConfig, + // bootstrap, + updateConfig: updateConfigMutation.mutateAsync, project: projectApi, todo: { set: setSessionTodo, diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index a83030fad2..451531835d 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -19,6 +19,7 @@ import type { State, VcsCache } from "./types" import { cmp, normalizeAgentList, normalizeProviderList } from "./utils" import { formatServerError } from "@/utils/server-errors" import { QueryClient, queryOptions, skipToken } from "@tanstack/solid-query" +import { loadMcpQuery } from "../global-sync" type GlobalStore = { ready: boolean @@ -66,6 +67,62 @@ function runAll(list: Array<() => Promise>) { return Promise.allSettled(list.map((item) => item())) } +function showErrors(input: { + errors: unknown[] + title: string + translate: (key: string, vars?: Record) => string + formatMoreCount: (count: number) => string +}) { + if (input.errors.length === 0) return + const message = formatServerError(input.errors[0], input.translate) + const more = input.errors.length > 1 ? input.formatMoreCount(input.errors.length - 1) : "" + showToast({ + variant: "error", + title: input.title, + description: message + more, + }) +} + +export const loadGlobalConfigQuery = ( + sdk?: OpencodeClient, + transform?: (x: Awaited>) => void, +) => + queryOptions({ + queryKey: ["config"], + queryFn: sdk + ? () => + retry(() => + sdk.global.config.get().then((x) => { + transform?.(x) + return x.data! + }), + ) + : skipToken, + }) + +export const loadProjectsQuery = ( + sdk?: OpencodeClient, + transform?: (x: Awaited>["data"]) => void, +) => + queryOptions({ + queryKey: ["project"], + queryFn: sdk + ? () => + retry(() => + sdk.project + .list() + .then((x) => { + return (x.data ?? []) + .filter((p) => !!p?.id) + .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test")) + .slice() + .sort((a, b) => cmp(a.id, b.id)) + }) + .then(transform), + ) + : skipToken, + }) + export async function bootstrapGlobal(input: { globalSDK: OpencodeClient requestFailedTitle: string @@ -74,53 +131,15 @@ export async function bootstrapGlobal(input: { setGlobalStore: SetStoreFunction queryClient: QueryClient }) { - const fast = [ - () => - retry(() => - input.globalSDK.global.config.get().then((x) => { - input.setGlobalStore("config", reconcile(x.data!, { merge: false })) - }), - ), - ] - const slow = [ + () => input.queryClient.fetchQuery(loadGlobalConfigQuery(input.globalSDK)), + () => input.queryClient.fetchQuery(loadProvidersQuery(null, input.globalSDK)), + () => input.queryClient.fetchQuery(loadPathQuery(null, input.globalSDK)), () => - input.queryClient.fetchQuery({ - ...loadProvidersQuery(null), - queryFn: () => - retry(() => - input.globalSDK.provider.list().then((x) => { - input.setGlobalStore("provider", normalizeProviderList(x.data!)) - return null - }), - ), - }), - () => - retry(() => - input.globalSDK.path.get().then((x) => { - input.setGlobalStore("path", x.data!) - }), - ), - () => - retry(() => - input.globalSDK.project.list().then((x) => { - const projects = (x.data ?? []) - .filter((p) => !!p?.id) - .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test")) - .slice() - .sort((a, b) => cmp(a.id, b.id)) - input.setGlobalStore("project", projects) - }), + input.queryClient.fetchQuery( + loadProjectsQuery(input.globalSDK, (data) => input.setGlobalStore("project", data ?? [])), ), ] - await runAll(fast) - // showErrors({ - // errors: errors(await runAll(fast)), - // title: input.requestFailedTitle, - // translate: input.translate, - // formatMoreCount: input.formatMoreCount, - // }) - await waitForPaint() await runAll(slow) // showErrors({ // errors: errors(), @@ -128,7 +147,6 @@ export async function bootstrapGlobal(input: { // translate: input.translate, // formatMoreCount: input.formatMoreCount, // }) - input.setGlobalStore("ready", true) } function groupBySession(input: T[]) { @@ -179,26 +197,28 @@ function warmSessions(input: { ).then(() => undefined) } -export const loadProvidersQuery = (directory: string | null) => - queryOptions({ queryKey: [directory, "providers"], queryFn: skipToken }) +export const loadProvidersQuery = (directory: string | null, sdk?: OpencodeClient) => + queryOptions({ + queryKey: [directory, "providers"], + queryFn: sdk ? () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))) : skipToken, + }) export const loadAgentsQuery = ( directory: string | null, sdk?: OpencodeClient, transform?: (x: Awaited>) => void, ) => - queryOptions({ + queryOptions({ queryKey: [directory, "agents"], - queryFn: - sdk && transform - ? () => - retry(() => - sdk.app - .agents() - .then(transform) - .then(() => null), - ) - : skipToken, + queryFn: sdk + ? () => + retry(() => + sdk.app.agents().then((x) => { + transform?.(x) + return x.data! + }), + ) + : skipToken, }) export const loadPathQuery = ( @@ -208,16 +228,15 @@ export const loadPathQuery = ( ) => queryOptions({ queryKey: [directory, "path"], - queryFn: - sdk && transform - ? () => - retry(() => - sdk.path.get().then(async (x) => { - transform(x) - return x.data! - }), - ) - : skipToken, + queryFn: sdk + ? () => + retry(() => + sdk.path.get().then(async (x) => { + transform?.(x) + return x.data! + }), + ) + : skipToken, }) export async function bootstrapDirectory(input: { @@ -247,13 +266,6 @@ export async function bootstrapDirectory(input: { if (Object.keys(input.store.config).length === 0 && Object.keys(input.global.config).length > 0) { input.setStore("config", reconcile(input.global.config, { merge: false })) } - if (loading || input.store.provider.all.length === 0) { - input.setStore("provider_ready", false) - } - input.setStore("mcp_ready", false) - input.setStore("mcp", {}) - input.setStore("lsp_ready", false) - input.setStore("lsp", []) if (loading) input.setStore("status", "partial") const rev = (providerRev.get(input.directory) ?? 0) + 1 @@ -340,33 +352,15 @@ export async function bootstrapDirectory(input: { }), ), () => Promise.resolve(input.loadSessions(input.directory)), + () => input.queryClient.fetchQuery(loadMcpQuery(input.directory, input.sdk)), () => - retry(() => - input.sdk.mcp.status().then((x) => { - input.setStore("mcp", x.data!) - input.setStore("mcp_ready", true) - }), - ), - () => - input.queryClient.ensureQueryData({ - ...loadProvidersQuery(input.directory), - queryFn: () => - retry(() => input.sdk.provider.list()) - .then((x) => { - if (providerRev.get(input.directory) !== rev) return - input.setStore("provider", normalizeProviderList(x.data!)) - input.setStore("provider_ready", true) - }) - .catch((err) => { - if (providerRev.get(input.directory) !== rev) console.error("Failed to refresh provider list", err) - const project = getFilename(input.directory) - showToast({ - variant: "error", - title: input.translate("toast.project.reloadFailed.title", { project }), - description: formatServerError(err, input.translate), - }) - }) - .then(() => null), + input.queryClient.fetchQuery(loadProvidersQuery(input.directory, input.sdk)).catch((err) => { + const project = getFilename(input.directory) + showToast({ + variant: "error", + title: input.translate("toast.project.reloadFailed.title", { project }), + description: formatServerError(err, input.translate), + }) }), ].filter(Boolean) as (() => Promise)[] diff --git a/packages/app/src/context/global-sync/child-store.test.ts b/packages/app/src/context/global-sync/child-store.test.ts index eee763f16d..24b4a46500 100644 --- a/packages/app/src/context/global-sync/child-store.test.ts +++ b/packages/app/src/context/global-sync/child-store.test.ts @@ -22,6 +22,7 @@ describe("createChildStoreManager", () => { onBootstrap() {}, onDispose() {}, translate: (key) => key, + getSdk: () => null!, }) Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => { diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index f3b613a7f2..d3b82894a4 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -1,7 +1,7 @@ import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js" import { createStore, type SetStoreFunction, type Store } from "solid-js/store" import { Persist, persisted } from "@/utils/persist" -import type { VcsInfo } from "@opencode-ai/sdk/v2/client" +import type { OpencodeClient, VcsInfo } from "@opencode-ai/sdk/v2/client" import { DIR_IDLE_TTL_MS, MAX_DIR_STORES, @@ -14,8 +14,9 @@ import { type VcsCache, } from "./types" import { canDisposeDirectory, pickDirectoriesToEvict } from "./eviction" -import { useQuery } from "@tanstack/solid-query" -import { loadPathQuery } from "./bootstrap" +import { useQueries } from "@tanstack/solid-query" +import { loadPathQuery, loadProvidersQuery } from "./bootstrap" +import { loadLspQuery, loadMcpQuery } from "../global-sync" export function createChildStoreManager(input: { owner: Owner @@ -24,6 +25,7 @@ export function createChildStoreManager(input: { onBootstrap: (directory: string) => void onDispose: (directory: string) => void translate: (key: string, vars?: Record) => string + getSdk: (directory: string) => OpencodeClient }) { const children: Record, SetStoreFunction]> = {} const vcsCache = new Map() @@ -156,14 +158,27 @@ export function createChildStoreManager(input: { const init = () => createRoot((dispose) => { + const sdk = input.getSdk(directory) + + const initialMeta = meta[0].value const initialIcon = icon[0].value - const pathQuery = useQuery(() => loadPathQuery(directory)) + const [pathQuery, mcpQuery, lspQuery, providerQuery] = useQueries(() => ({ + queries: [ + loadPathQuery(directory, sdk), + loadMcpQuery(directory, sdk), + loadLspQuery(directory, sdk), + loadProvidersQuery(directory, sdk), + ], + })) + const child = createStore({ project: "", - projectMeta: undefined, + projectMeta: initialMeta, icon: initialIcon, - provider_ready: false, + get provider_ready() { + return providerQuery.isLoading + }, provider: { all: [], connected: [], default: {} }, config: {}, get path() { @@ -181,10 +196,18 @@ export function createChildStoreManager(input: { todo: {}, permission: {}, question: {}, - mcp_ready: false, - mcp: {}, - lsp_ready: false, - lsp: [], + get mcp_ready() { + return mcpQuery.isLoading + }, + get mcp() { + return mcpQuery.isLoading ? {} : (mcpQuery.data ?? {}) + }, + get lsp_ready() { + return lspQuery.isLoading + }, + get lsp() { + return lspQuery.isLoading ? [] : (lspQuery.data ?? []) + }, vcs: vcsStore.value, limit: 5, message: {}, @@ -207,6 +230,11 @@ export function createChildStoreManager(input: { child[1]("vcs", (value) => value ?? cached) }) + onPersistedInit(meta[2], () => { + if (child[0].projectMeta !== initialMeta) return + child[1]("projectMeta", meta[0].value) + }) + onPersistedInit(icon[2], () => { if (child[0].icon !== initialIcon) return child[1]("icon", icon[0].value)