Refactor instance metadata handling
This commit is contained in:
@@ -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) => (
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
35
packages/ui/src/stores/instance-metadata.ts
Normal file
35
packages/ui/src/stores/instance-metadata.ts
Normal 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 }
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user