chore: cleanup
This commit is contained in:
@@ -24,6 +24,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
type Queued = { directory: string; payload: Event }
|
type Queued = { directory: string; payload: Event }
|
||||||
|
|
||||||
let queue: Array<Queued | undefined> = []
|
let queue: Array<Queued | undefined> = []
|
||||||
|
let buffer: Array<Queued | undefined> = []
|
||||||
const coalesced = new Map<string, number>()
|
const coalesced = new Map<string, number>()
|
||||||
let timer: ReturnType<typeof setTimeout> | undefined
|
let timer: ReturnType<typeof setTimeout> | undefined
|
||||||
let last = 0
|
let last = 0
|
||||||
@@ -41,10 +42,13 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
if (timer) clearTimeout(timer)
|
if (timer) clearTimeout(timer)
|
||||||
timer = undefined
|
timer = undefined
|
||||||
|
|
||||||
|
if (queue.length === 0) return
|
||||||
|
|
||||||
const events = queue
|
const events = queue
|
||||||
queue = []
|
queue = buffer
|
||||||
|
buffer = events
|
||||||
|
queue.length = 0
|
||||||
coalesced.clear()
|
coalesced.clear()
|
||||||
if (events.length === 0) return
|
|
||||||
|
|
||||||
last = Date.now()
|
last = Date.now()
|
||||||
batch(() => {
|
batch(() => {
|
||||||
@@ -53,6 +57,8 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
emitter.emit(event.directory, event.payload)
|
emitter.emit(event.directory, event.payload)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
buffer.length = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const schedule = () => {
|
const schedule = () => {
|
||||||
@@ -61,10 +67,6 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
timer = setTimeout(flush, Math.max(0, 16 - elapsed))
|
timer = setTimeout(flush, Math.max(0, 16 - elapsed))
|
||||||
}
|
}
|
||||||
|
|
||||||
const stop = () => {
|
|
||||||
flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const events = await eventSdk.global.event()
|
const events = await eventSdk.global.event()
|
||||||
let yielded = Date.now()
|
let yielded = Date.now()
|
||||||
@@ -87,12 +89,12 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
await new Promise<void>((resolve) => setTimeout(resolve, 0))
|
await new Promise<void>((resolve) => setTimeout(resolve, 0))
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
.finally(stop)
|
.finally(flush)
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
abort.abort()
|
abort.abort()
|
||||||
stop()
|
flush()
|
||||||
})
|
})
|
||||||
|
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
|
|||||||
@@ -119,6 +119,16 @@ type ChildOptions = {
|
|||||||
bootstrap?: boolean
|
bootstrap?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeProviderList(input: ProviderListResponse): ProviderListResponse {
|
||||||
|
return {
|
||||||
|
...input,
|
||||||
|
all: input.all.map((provider) => ({
|
||||||
|
...provider,
|
||||||
|
models: Object.fromEntries(Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated")),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createGlobalSync() {
|
function createGlobalSync() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
@@ -129,6 +139,21 @@ function createGlobalSync() {
|
|||||||
const metaCache = new Map<string, MetaCache>()
|
const metaCache = new Map<string, MetaCache>()
|
||||||
const iconCache = new Map<string, IconCache>()
|
const iconCache = new Map<string, IconCache>()
|
||||||
|
|
||||||
|
const sdkCache = new Map<string, ReturnType<typeof createOpencodeClient>>()
|
||||||
|
const sdkFor = (directory: string) => {
|
||||||
|
const cached = sdkCache.get(directory)
|
||||||
|
if (cached) return cached
|
||||||
|
|
||||||
|
const sdk = createOpencodeClient({
|
||||||
|
baseUrl: globalSDK.url,
|
||||||
|
fetch: platform.fetch,
|
||||||
|
directory,
|
||||||
|
throwOnError: true,
|
||||||
|
})
|
||||||
|
sdkCache.set(directory, sdk)
|
||||||
|
return sdk
|
||||||
|
}
|
||||||
|
|
||||||
const [projectCache, setProjectCache, , projectCacheReady] = persisted(
|
const [projectCache, setProjectCache, , projectCacheReady] = persisted(
|
||||||
Persist.global("globalSync.project", ["globalSync.project.v1"]),
|
Persist.global("globalSync.project", ["globalSync.project.v1"]),
|
||||||
createStore({ value: [] as Project[] }),
|
createStore({ value: [] as Project[] }),
|
||||||
@@ -183,7 +208,7 @@ function createGlobalSync() {
|
|||||||
setProjectCache("value", projects.map(sanitizeProject))
|
setProjectCache("value", projects.map(sanitizeProject))
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(async () => {
|
createEffect(() => {
|
||||||
if (globalStore.reload !== "complete") return
|
if (globalStore.reload !== "complete") return
|
||||||
if (bootstrapQueue.length) {
|
if (bootstrapQueue.length) {
|
||||||
for (const directory of bootstrapQueue) {
|
for (const directory of bootstrapQueue) {
|
||||||
@@ -203,14 +228,16 @@ function createGlobalSync() {
|
|||||||
function ensureChild(directory: string) {
|
function ensureChild(directory: string) {
|
||||||
if (!directory) console.error("No directory provided")
|
if (!directory) console.error("No directory provided")
|
||||||
if (!children[directory]) {
|
if (!children[directory]) {
|
||||||
const cache = runWithOwner(owner, () =>
|
const vcs = runWithOwner(owner, () =>
|
||||||
persisted(
|
persisted(
|
||||||
Persist.workspace(directory, "vcs", ["vcs.v1"]),
|
Persist.workspace(directory, "vcs", ["vcs.v1"]),
|
||||||
createStore({ value: undefined as VcsInfo | undefined }),
|
createStore({ value: undefined as VcsInfo | undefined }),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (!cache) throw new Error("Failed to create persisted cache")
|
if (!vcs) throw new Error("Failed to create persisted cache")
|
||||||
vcsCache.set(directory, { store: cache[0], setStore: cache[1], ready: cache[3] })
|
const vcsStore = vcs[0]
|
||||||
|
const vcsReady = vcs[3]
|
||||||
|
vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcsReady })
|
||||||
|
|
||||||
const meta = runWithOwner(owner, () =>
|
const meta = runWithOwner(owner, () =>
|
||||||
persisted(
|
persisted(
|
||||||
@@ -250,7 +277,7 @@ function createGlobalSync() {
|
|||||||
question: {},
|
question: {},
|
||||||
mcp: {},
|
mcp: {},
|
||||||
lsp: [],
|
lsp: [],
|
||||||
vcs: cache[0].value,
|
vcs: vcsStore.value,
|
||||||
limit: 5,
|
limit: 5,
|
||||||
message: {},
|
message: {},
|
||||||
part: {},
|
part: {},
|
||||||
@@ -258,6 +285,13 @@ function createGlobalSync() {
|
|||||||
|
|
||||||
children[directory] = child
|
children[directory] = child
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!vcsReady()) return
|
||||||
|
const cached = vcsStore.value
|
||||||
|
if (!cached?.branch) return
|
||||||
|
child[1]("vcs", (value) => value ?? cached)
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
child[1]("projectMeta", meta[0].value)
|
child[1]("projectMeta", meta[0].value)
|
||||||
})
|
})
|
||||||
@@ -297,7 +331,6 @@ function createGlobalSync() {
|
|||||||
const nonArchived = (x.data ?? [])
|
const nonArchived = (x.data ?? [])
|
||||||
.filter((s) => !!s?.id)
|
.filter((s) => !!s?.id)
|
||||||
.filter((s) => !s.time?.archived)
|
.filter((s) => !s.time?.archived)
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.id.localeCompare(b.id))
|
.sort((a, b) => a.id.localeCompare(b.id))
|
||||||
|
|
||||||
// Read the current limit at resolve-time so callers that bump the limit while
|
// Read the current limit at resolve-time so callers that bump the limit while
|
||||||
@@ -348,38 +381,18 @@ function createGlobalSync() {
|
|||||||
if (!cache) return
|
if (!cache) return
|
||||||
const meta = metaCache.get(directory)
|
const meta = metaCache.get(directory)
|
||||||
if (!meta) return
|
if (!meta) return
|
||||||
const sdk = createOpencodeClient({
|
const sdk = sdkFor(directory)
|
||||||
baseUrl: globalSDK.url,
|
|
||||||
fetch: platform.fetch,
|
|
||||||
directory,
|
|
||||||
throwOnError: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
setStore("status", "loading")
|
setStore("status", "loading")
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (!cache.ready()) return
|
|
||||||
const cached = cache.store.value
|
|
||||||
if (!cached?.branch) return
|
|
||||||
setStore("vcs", (value) => value ?? cached)
|
|
||||||
})
|
|
||||||
|
|
||||||
// projectMeta is synced from persisted storage in ensureChild.
|
// projectMeta is synced from persisted storage in ensureChild.
|
||||||
|
// vcs is seeded from persisted storage in ensureChild.
|
||||||
|
|
||||||
const blockingRequests = {
|
const blockingRequests = {
|
||||||
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
|
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
|
||||||
provider: () =>
|
provider: () =>
|
||||||
sdk.provider.list().then((x) => {
|
sdk.provider.list().then((x) => {
|
||||||
const data = x.data!
|
setStore("provider", normalizeProviderList(x.data!))
|
||||||
setStore("provider", {
|
|
||||||
...data,
|
|
||||||
all: data.all.map((provider) => ({
|
|
||||||
...provider,
|
|
||||||
models: Object.fromEntries(
|
|
||||||
Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated"),
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
||||||
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
|
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
|
||||||
@@ -432,10 +445,7 @@ function createGlobalSync() {
|
|||||||
"permission",
|
"permission",
|
||||||
sessionID,
|
sessionID,
|
||||||
reconcile(
|
reconcile(
|
||||||
permissions
|
permissions.filter((p) => !!p?.id).sort((a, b) => a.id.localeCompare(b.id)),
|
||||||
.filter((p) => !!p?.id)
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.id.localeCompare(b.id)),
|
|
||||||
{ key: "id" },
|
{ key: "id" },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -464,10 +474,7 @@ function createGlobalSync() {
|
|||||||
"question",
|
"question",
|
||||||
sessionID,
|
sessionID,
|
||||||
reconcile(
|
reconcile(
|
||||||
questions
|
questions.filter((q) => !!q?.id).sort((a, b) => a.id.localeCompare(b.id)),
|
||||||
.filter((q) => !!q?.id)
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.id.localeCompare(b.id)),
|
|
||||||
{ key: "id" },
|
{ key: "id" },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -750,13 +757,9 @@ function createGlobalSync() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "lsp.updated": {
|
case "lsp.updated": {
|
||||||
const sdk = createOpencodeClient({
|
sdkFor(directory)
|
||||||
baseUrl: globalSDK.url,
|
.lsp.status()
|
||||||
fetch: platform.fetch,
|
.then((x) => setStore("lsp", x.data ?? []))
|
||||||
directory,
|
|
||||||
throwOnError: true,
|
|
||||||
})
|
|
||||||
sdk.lsp.status().then((x) => setStore("lsp", x.data ?? []))
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -796,16 +799,7 @@ function createGlobalSync() {
|
|||||||
),
|
),
|
||||||
retry(() =>
|
retry(() =>
|
||||||
globalSDK.client.provider.list().then((x) => {
|
globalSDK.client.provider.list().then((x) => {
|
||||||
const data = x.data!
|
setGlobalStore("provider", normalizeProviderList(x.data!))
|
||||||
setGlobalStore("provider", {
|
|
||||||
...data,
|
|
||||||
all: data.all.map((provider) => ({
|
|
||||||
...provider,
|
|
||||||
models: Object.fromEntries(
|
|
||||||
Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated"),
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
retry(() =>
|
retry(() =>
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const [colors, setColors] = createStore<Record<string, AvatarColorKey>>({})
|
const [colors, setColors] = createStore<Record<string, AvatarColorKey>>({})
|
||||||
|
const colorRequested = new Map<string, AvatarColorKey>()
|
||||||
|
|
||||||
function pickAvailableColor(used: Set<string>): AvatarColorKey {
|
function pickAvailableColor(used: Set<string>): AvatarColorKey {
|
||||||
const available = AVATAR_COLOR_KEYS.filter((c) => !used.has(c))
|
const available = AVATAR_COLOR_KEYS.filter((c) => !used.has(c))
|
||||||
@@ -324,13 +325,21 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const projects = enriched()
|
const projects = enriched()
|
||||||
if (projects.length === 0) return
|
if (projects.length === 0) return
|
||||||
|
if (!globalSync.ready) return
|
||||||
|
|
||||||
if (globalSync.ready) {
|
for (const project of projects) {
|
||||||
for (const project of projects) {
|
if (!project.id) continue
|
||||||
if (!project.id) continue
|
if (project.id === "global") continue
|
||||||
if (project.id === "global") continue
|
globalSync.project.icon(project.worktree, project.icon?.override)
|
||||||
globalSync.project.icon(project.worktree, project.icon?.override)
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const projects = enriched()
|
||||||
|
if (projects.length === 0) return
|
||||||
|
|
||||||
|
for (const project of projects) {
|
||||||
|
if (project.icon?.color) colorRequested.delete(project.worktree)
|
||||||
}
|
}
|
||||||
|
|
||||||
const used = new Set<string>()
|
const used = new Set<string>()
|
||||||
@@ -341,18 +350,29 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
|
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
if (project.icon?.color) continue
|
if (project.icon?.color) continue
|
||||||
const existing = colors[project.worktree]
|
const worktree = project.worktree
|
||||||
|
const existing = colors[worktree]
|
||||||
const color = existing ?? pickAvailableColor(used)
|
const color = existing ?? pickAvailableColor(used)
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
used.add(color)
|
used.add(color)
|
||||||
setColors(project.worktree, color)
|
setColors(worktree, color)
|
||||||
}
|
}
|
||||||
if (!project.id) continue
|
if (!project.id) continue
|
||||||
|
|
||||||
|
const requested = colorRequested.get(worktree)
|
||||||
|
if (requested === color) continue
|
||||||
|
colorRequested.set(worktree, color)
|
||||||
|
|
||||||
if (project.id === "global") {
|
if (project.id === "global") {
|
||||||
globalSync.project.meta(project.worktree, { icon: { color } })
|
globalSync.project.meta(worktree, { icon: { color } })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
void globalSdk.client.project.update({ projectID: project.id, directory: project.worktree, icon: { color } })
|
|
||||||
|
void globalSdk.client.project
|
||||||
|
.update({ projectID: project.id, directory: worktree, icon: { color } })
|
||||||
|
.catch(() => {
|
||||||
|
if (colorRequested.get(worktree) === color) colorRequested.delete(worktree)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
const next = items
|
const next = items
|
||||||
.map((x) => x.info)
|
.map((x) => x.info)
|
||||||
.filter((m) => !!m?.id)
|
.filter((m) => !!m?.id)
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.id.localeCompare(b.id))
|
.sort((a, b) => a.id.localeCompare(b.id))
|
||||||
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
@@ -83,10 +82,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
"part",
|
"part",
|
||||||
message.info.id,
|
message.info.id,
|
||||||
reconcile(
|
reconcile(
|
||||||
message.parts
|
message.parts.filter((p) => !!p?.id).sort((a, b) => a.id.localeCompare(b.id)),
|
||||||
.filter((p) => !!p?.id)
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.id.localeCompare(b.id)),
|
|
||||||
{ key: "id" },
|
{ key: "id" },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -146,10 +142,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
const result = Binary.search(messages, input.messageID, (m) => m.id)
|
const result = Binary.search(messages, input.messageID, (m) => m.id)
|
||||||
messages.splice(result.index, 0, message)
|
messages.splice(result.index, 0, message)
|
||||||
}
|
}
|
||||||
draft.part[input.messageID] = input.parts
|
draft.part[input.messageID] = input.parts.filter((p) => !!p?.id).sort((a, b) => a.id.localeCompare(b.id))
|
||||||
.filter((p) => !!p?.id)
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.id.localeCompare(b.id))
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -291,7 +284,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
await client.session.list().then((x) => {
|
await client.session.list().then((x) => {
|
||||||
const sessions = (x.data ?? [])
|
const sessions = (x.data ?? [])
|
||||||
.filter((s) => !!s?.id)
|
.filter((s) => !!s?.id)
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.id.localeCompare(b.id))
|
.sort((a, b) => a.id.localeCompare(b.id))
|
||||||
.slice(0, store.limit)
|
.slice(0, store.limit)
|
||||||
setStore("session", reconcile(sessions, { key: "id" }))
|
setStore("session", reconcile(sessions, { key: "id" }))
|
||||||
|
|||||||
Reference in New Issue
Block a user