Refactor instance metadata handling

This commit is contained in:
Shantur Rathore
2025-12-15 16:08:28 +00:00
parent 14497f2082
commit ff5c698131
6 changed files with 101 additions and 59 deletions

View File

@@ -56,11 +56,12 @@ const InstanceServiceStatus: Component<InstanceServiceStatusProps> = (props) =>
const includeMcp = createMemo(() => sections().includes("mcp")) const includeMcp = createMemo(() => sections().includes("mcp"))
const showHeadings = () => props.showSectionHeadings !== false 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 hasLspMetadata = () => metadata()?.lspStatus !== undefined
const hasMcpMetadata = () => metadata()?.mcpStatus !== undefined const hasMcpMetadata = () => metadata()?.mcpStatus !== undefined
const lspServers = createMemo(() => metadata()?.lspStatus ?? []) const lspServers = createMemo(() => metadata()?.lspStatus ?? [])
const mcpServers = createMemo(() => parseMcpStatus(metadata()?.mcpStatus)) const mcpServers = createMemo(() => parseMcpStatus(metadata()?.mcpStatus ?? undefined))
const isLspLoading = () => isLoading() || !hasLspMetadata() const isLspLoading = () => isLoading() || !hasLspMetadata()
const isMcpLoading = () => isLoading() || !hasMcpMetadata() const isMcpLoading = () => isLoading() || !hasMcpMetadata()
@@ -168,6 +169,16 @@ const InstanceServiceStatus: Component<InstanceServiceStatusProps> = (props) =>
<span class="text-xs text-primary font-medium truncate">{server.name}</span> <span class="text-xs text-primary font-medium truncate">{server.name}</span>
<div class="flex items-center gap-3 flex-shrink-0"> <div class="flex items-center gap-3 flex-shrink-0">
<div class="flex items-center gap-1.5 text-xs text-secondary"> <div class="flex items-center gap-1.5 text-xs text-secondary">
<Show when={isPending()}>
<svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</Show>
<div class={statusDotClass()} style={statusDotStyle()} /> <div class={statusDotClass()} style={statusDotStyle()} />
</div> </div>
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
@@ -182,18 +193,9 @@ const InstanceServiceStatus: Component<InstanceServiceStatusProps> = (props) =>
void toggleMcpServer(server.name, Boolean(checked)) void toggleMcpServer(server.name, Boolean(checked))
}} }}
/> />
<Show when={isPending()}>
<svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</Show>
</div> </div>
</div> </div>
</div> </div>
<Show when={server.error}> <Show when={server.error}>
{(error) => ( {(error) => (

View File

@@ -1,6 +1,7 @@
import { Component, JSX, createContext, createEffect, createMemo, createSignal, useContext, type Accessor } from "solid-js" import { Component, JSX, createContext, createEffect, createMemo, createSignal, useContext, type Accessor } from "solid-js"
import type { Instance } from "../../types/instance" import type { Instance } from "../../types/instance"
import { instances } from "../../stores/instances" import { instances } from "../../stores/instances"
import { getInstanceMetadata } from "../../stores/instance-metadata"
import { loadInstanceMetadata, hasMetadataLoaded } from "../hooks/use-instance-metadata" import { loadInstanceMetadata, hasMetadataLoaded } from "../hooks/use-instance-metadata"
interface InstanceMetadataContextValue { interface InstanceMetadataContextValue {
@@ -28,7 +29,8 @@ export const InstanceMetadataProvider: Component<InstanceMetadataProviderProps>
return return
} }
if (!force && hasMetadataLoaded(current.metadata)) { const cachedMetadata = getInstanceMetadata(current.id) ?? current.metadata
if (!force && hasMetadataLoaded(cachedMetadata)) {
setIsLoading(false) setIsLoading(false)
return return
} }
@@ -40,15 +42,24 @@ export const InstanceMetadataProvider: Component<InstanceMetadataProviderProps>
createEffect(() => { createEffect(() => {
const current = resolvedInstance() const current = resolvedInstance()
// Ensure metadata becomes a dependency so we re-check when store updates if (!current) {
void current?.metadata setIsLoading(false)
return
}
const tracked = getInstanceMetadata(current.id) ?? current.metadata
if (!tracked || !hasMetadataLoaded(tracked)) {
void ensureMetadata() void ensureMetadata()
return
}
setIsLoading(false)
}) })
const contextValue: InstanceMetadataContextValue = { const contextValue: InstanceMetadataContextValue = {
isLoading, isLoading,
instance: resolvedInstance, instance: resolvedInstance,
metadata: () => resolvedInstance().metadata, metadata: () => getInstanceMetadata(resolvedInstance().id) ?? resolvedInstance().metadata,
refreshMetadata: () => ensureMetadata(true), refreshMetadata: () => ensureMetadata(true),
} }

View File

@@ -1,6 +1,7 @@
import type { Instance, RawMcpStatus } from "../../types/instance" import type { Instance, RawMcpStatus } from "../../types/instance"
import { fetchLspStatus, updateInstance } from "../../stores/instances" import { fetchLspStatus } from "../../stores/instances"
import { getLogger } from "../../lib/logger" import { getLogger } from "../../lib/logger"
import { getInstanceMetadata, mergeInstanceMetadata } from "../../stores/instance-metadata"
const log = getLogger("session") const log = getLogger("session")
const pendingMetadataRequests = new Set<string>() const pendingMetadataRequests = new Set<string>()
@@ -17,7 +18,8 @@ export async function loadInstanceMetadata(instance: Instance, options?: { force
return return
} }
if (!options?.force && hasMetadataLoaded(instance.metadata)) { const currentMetadata = getInstanceMetadata(instance.id) ?? instance.metadata
if (!options?.force && hasMetadataLoaded(currentMetadata)) {
return 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 mcpStatus = mcpResult.status === "fulfilled" ? (mcpResult.value.data as RawMcpStatus) : undefined
const lspStatus = lspResult.status === "fulfilled" ? lspResult.value ?? [] : undefined const lspStatus = lspResult.status === "fulfilled" ? lspResult.value ?? [] : undefined
const nextMetadata: Instance["metadata"] = { const updates: Instance["metadata"] = { ...(currentMetadata ?? {}) }
...(instance.metadata ?? {}),
}
if (projectResult.status === "fulfilled") { if (projectResult.status === "fulfilled") {
nextMetadata.project = project ?? undefined updates.project = project ?? null
} }
if (mcpResult.status === "fulfilled") { if (mcpResult.status === "fulfilled") {
nextMetadata.mcpStatus = mcpStatus ?? nextMetadata.mcpStatus ?? {} updates.mcpStatus = mcpStatus ?? {}
} }
if (lspResult.status === "fulfilled") { if (lspResult.status === "fulfilled") {
nextMetadata.lspStatus = lspStatus ?? [] updates.lspStatus = lspStatus ?? []
} }
if (!nextMetadata?.version && instance.binaryVersion) { if (!updates?.version && instance.binaryVersion) {
nextMetadata.version = instance.binaryVersion updates.version = instance.binaryVersion
} }
updateInstance(instance.id, { metadata: nextMetadata }) mergeInstanceMetadata(instance.id, updates)
} catch (error) { } catch (error) {
log.error("Failed to load instance metadata", error) log.error("Failed to load instance metadata", error)
} finally { } finally {
@@ -68,3 +68,4 @@ export async function loadInstanceMetadata(instance: Instance, options?: { force
export { hasMetadataLoaded } export { hasMetadataLoaded }

View File

@@ -0,0 +1,35 @@
import { createSignal } from "solid-js"
import type { InstanceMetadata } from "../types/instance"
const [metadataMap, setMetadataMap] = createSignal<Map<string, InstanceMetadata | undefined>>(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 }

View File

@@ -20,6 +20,7 @@ import { setHasInstances } from "./ui"
import { messageStoreBus } from "./message-v2/bus" import { messageStoreBus } from "./message-v2/bus"
import { clearCacheForInstance } from "../lib/global-cache" import { clearCacheForInstance } from "../lib/global-cache"
import { getLogger } from "../lib/logger" import { getLogger } from "../lib/logger"
import { mergeInstanceMetadata, clearInstanceMetadata } from "./instance-metadata"
const log = getLogger("api") const log = getLogger("api")
@@ -290,6 +291,7 @@ function removeInstance(id: string) {
removeLogContainer(id) removeLogContainer(id)
clearCommands(id) clearCommands(id)
clearPermissionQueue(id) clearPermissionQueue(id)
clearInstanceMetadata(id)
if (activeInstanceId() === id) { if (activeInstanceId() === id) {
setActiveInstanceId(nextActiveId) setActiveInstanceId(nextActiveId)
@@ -570,17 +572,7 @@ sseManager.onLspUpdated = async (instanceId) => {
if (!lspStatus) { if (!lspStatus) {
return return
} }
const instance = instances().get(instanceId) mergeInstanceMetadata(instanceId, { lspStatus })
if (!instance) {
log.warn("[LSP] Instance disappeared before metadata update", { instanceId })
return
}
updateInstance(instanceId, {
metadata: {
...(instance.metadata ?? {}),
lspStatus,
},
})
} catch (error) { } catch (error) {
log.error("Failed to refresh LSP status", error) log.error("Failed to refresh LSP status", error)
} }

View File

@@ -22,12 +22,13 @@ export type RawMcpStatus = Record<string, {
}> }>
export interface InstanceMetadata { export interface InstanceMetadata {
project?: ProjectInfo project?: ProjectInfo | null
mcpStatus?: RawMcpStatus mcpStatus?: RawMcpStatus | null
lspStatus?: LspStatus[] lspStatus?: LspStatus[] | null
version?: string version?: string
} }
export interface Instance { export interface Instance {
id: string id: string
folder: string folder: string