diff --git a/packages/ui/src/components/instance-service-status.tsx b/packages/ui/src/components/instance-service-status.tsx index c4971603..dc610416 100644 --- a/packages/ui/src/components/instance-service-status.tsx +++ b/packages/ui/src/components/instance-service-status.tsx @@ -56,11 +56,12 @@ const InstanceServiceStatus: Component = (props) => const includeMcp = createMemo(() => sections().includes("mcp")) const showHeadings = () => props.showSectionHeadings !== false - const metadata = createMemo(() => instance().metadata) + const metadataAccessor = metadataContext?.metadata ?? (() => instance().metadata) + const metadata = createMemo(() => metadataAccessor()) const hasLspMetadata = () => metadata()?.lspStatus !== undefined const hasMcpMetadata = () => metadata()?.mcpStatus !== undefined const lspServers = createMemo(() => metadata()?.lspStatus ?? []) - const mcpServers = createMemo(() => parseMcpStatus(metadata()?.mcpStatus)) + const mcpServers = createMemo(() => parseMcpStatus(metadata()?.mcpStatus ?? undefined)) const isLspLoading = () => isLoading() || !hasLspMetadata() const isMcpLoading = () => isLoading() || !hasMcpMetadata() @@ -166,34 +167,35 @@ const InstanceServiceStatus: Component = (props) =>
{server.name} -
-
-
+
+
+ + + + + + +
+
+
+ { + if (switchDisabled()) return + void toggleMcpServer(server.name, Boolean(checked)) + }} + /> +
-
- { - if (switchDisabled()) return - void toggleMcpServer(server.name, Boolean(checked)) - }} - /> - - - - - - -
-
+
{(error) => ( diff --git a/packages/ui/src/lib/contexts/instance-metadata-context.tsx b/packages/ui/src/lib/contexts/instance-metadata-context.tsx index f797d09c..d15d0970 100644 --- a/packages/ui/src/lib/contexts/instance-metadata-context.tsx +++ b/packages/ui/src/lib/contexts/instance-metadata-context.tsx @@ -1,6 +1,7 @@ import { Component, JSX, createContext, createEffect, createMemo, createSignal, useContext, type Accessor } from "solid-js" import type { Instance } from "../../types/instance" import { instances } from "../../stores/instances" +import { getInstanceMetadata } from "../../stores/instance-metadata" import { loadInstanceMetadata, hasMetadataLoaded } from "../hooks/use-instance-metadata" interface InstanceMetadataContextValue { @@ -28,7 +29,8 @@ export const InstanceMetadataProvider: Component return } - if (!force && hasMetadataLoaded(current.metadata)) { + const cachedMetadata = getInstanceMetadata(current.id) ?? current.metadata + if (!force && hasMetadataLoaded(cachedMetadata)) { setIsLoading(false) return } @@ -40,15 +42,24 @@ export const InstanceMetadataProvider: Component createEffect(() => { const current = resolvedInstance() - // Ensure metadata becomes a dependency so we re-check when store updates - void current?.metadata - void ensureMetadata() + if (!current) { + setIsLoading(false) + return + } + + const tracked = getInstanceMetadata(current.id) ?? current.metadata + if (!tracked || !hasMetadataLoaded(tracked)) { + void ensureMetadata() + return + } + + setIsLoading(false) }) const contextValue: InstanceMetadataContextValue = { isLoading, instance: resolvedInstance, - metadata: () => resolvedInstance().metadata, + metadata: () => getInstanceMetadata(resolvedInstance().id) ?? resolvedInstance().metadata, refreshMetadata: () => ensureMetadata(true), } diff --git a/packages/ui/src/lib/hooks/use-instance-metadata.ts b/packages/ui/src/lib/hooks/use-instance-metadata.ts index 929f7d86..d132c980 100644 --- a/packages/ui/src/lib/hooks/use-instance-metadata.ts +++ b/packages/ui/src/lib/hooks/use-instance-metadata.ts @@ -1,6 +1,7 @@ import type { Instance, RawMcpStatus } from "../../types/instance" -import { fetchLspStatus, updateInstance } from "../../stores/instances" +import { fetchLspStatus } from "../../stores/instances" import { getLogger } from "../../lib/logger" +import { getInstanceMetadata, mergeInstanceMetadata } from "../../stores/instance-metadata" const log = getLogger("session") const pendingMetadataRequests = new Set() @@ -17,7 +18,8 @@ export async function loadInstanceMetadata(instance: Instance, options?: { force return } - if (!options?.force && hasMetadataLoaded(instance.metadata)) { + const currentMetadata = getInstanceMetadata(instance.id) ?? instance.metadata + if (!options?.force && hasMetadataLoaded(currentMetadata)) { return } @@ -38,27 +40,25 @@ export async function loadInstanceMetadata(instance: Instance, options?: { force const mcpStatus = mcpResult.status === "fulfilled" ? (mcpResult.value.data as RawMcpStatus) : undefined const lspStatus = lspResult.status === "fulfilled" ? lspResult.value ?? [] : undefined - const nextMetadata: Instance["metadata"] = { - ...(instance.metadata ?? {}), - } + const updates: Instance["metadata"] = { ...(currentMetadata ?? {}) } if (projectResult.status === "fulfilled") { - nextMetadata.project = project ?? undefined + updates.project = project ?? null } if (mcpResult.status === "fulfilled") { - nextMetadata.mcpStatus = mcpStatus ?? nextMetadata.mcpStatus ?? {} + updates.mcpStatus = mcpStatus ?? {} } if (lspResult.status === "fulfilled") { - nextMetadata.lspStatus = lspStatus ?? [] + updates.lspStatus = lspStatus ?? [] } - if (!nextMetadata?.version && instance.binaryVersion) { - nextMetadata.version = instance.binaryVersion + if (!updates?.version && instance.binaryVersion) { + updates.version = instance.binaryVersion } - updateInstance(instance.id, { metadata: nextMetadata }) + mergeInstanceMetadata(instance.id, updates) } catch (error) { log.error("Failed to load instance metadata", error) } finally { @@ -68,3 +68,4 @@ export async function loadInstanceMetadata(instance: Instance, options?: { force export { hasMetadataLoaded } + diff --git a/packages/ui/src/stores/instance-metadata.ts b/packages/ui/src/stores/instance-metadata.ts new file mode 100644 index 00000000..4d2765b0 --- /dev/null +++ b/packages/ui/src/stores/instance-metadata.ts @@ -0,0 +1,35 @@ +import { createSignal } from "solid-js" +import type { InstanceMetadata } from "../types/instance" + +const [metadataMap, setMetadataMap] = createSignal>(new Map()) + +function getInstanceMetadata(instanceId: string): InstanceMetadata | undefined { + return metadataMap().get(instanceId) +} + +function setInstanceMetadata(instanceId: string, metadata: InstanceMetadata | undefined): void { + setMetadataMap((prev) => { + const next = new Map(prev) + if (metadata === undefined) { + next.delete(instanceId) + } else { + next.set(instanceId, metadata) + } + return next + }) +} + +function mergeInstanceMetadata(instanceId: string, updates: InstanceMetadata): void { + setMetadataMap((prev) => { + const next = new Map(prev) + const existing = next.get(instanceId) ?? {} + next.set(instanceId, { ...existing, ...updates }) + return next + }) +} + +function clearInstanceMetadata(instanceId: string): void { + setInstanceMetadata(instanceId, undefined) +} + +export { metadataMap, getInstanceMetadata, setInstanceMetadata, mergeInstanceMetadata, clearInstanceMetadata } diff --git a/packages/ui/src/stores/instances.ts b/packages/ui/src/stores/instances.ts index 967d7ec2..0d10adb4 100644 --- a/packages/ui/src/stores/instances.ts +++ b/packages/ui/src/stores/instances.ts @@ -20,6 +20,7 @@ import { setHasInstances } from "./ui" import { messageStoreBus } from "./message-v2/bus" import { clearCacheForInstance } from "../lib/global-cache" import { getLogger } from "../lib/logger" +import { mergeInstanceMetadata, clearInstanceMetadata } from "./instance-metadata" const log = getLogger("api") @@ -290,6 +291,7 @@ function removeInstance(id: string) { removeLogContainer(id) clearCommands(id) clearPermissionQueue(id) + clearInstanceMetadata(id) if (activeInstanceId() === id) { setActiveInstanceId(nextActiveId) @@ -570,17 +572,7 @@ sseManager.onLspUpdated = async (instanceId) => { if (!lspStatus) { return } - const instance = instances().get(instanceId) - if (!instance) { - log.warn("[LSP] Instance disappeared before metadata update", { instanceId }) - return - } - updateInstance(instanceId, { - metadata: { - ...(instance.metadata ?? {}), - lspStatus, - }, - }) + mergeInstanceMetadata(instanceId, { lspStatus }) } catch (error) { log.error("Failed to refresh LSP status", error) } diff --git a/packages/ui/src/types/instance.ts b/packages/ui/src/types/instance.ts index 142c2751..34e8ff1f 100644 --- a/packages/ui/src/types/instance.ts +++ b/packages/ui/src/types/instance.ts @@ -22,12 +22,13 @@ export type RawMcpStatus = Record export interface InstanceMetadata { - project?: ProjectInfo - mcpStatus?: RawMcpStatus - lspStatus?: LspStatus[] + project?: ProjectInfo | null + mcpStatus?: RawMcpStatus | null + lspStatus?: LspStatus[] | null version?: string } + export interface Instance { id: string folder: string