From 8567721ac10f559745433f9874b185b9d4a25340 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:01:16 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Add=20SolidJS=20batch()=20t?= =?UTF-8?q?o=20SSE=20event=20listener?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: Wrapped the global SSE event listener (`globalSDK.event.listen`) and optimistic update functions in `packages/app/src/context/global-sync.tsx` and `sync.tsx` with SolidJS `batch()`. 🎯 Why: SSE events from the backend were triggering multiple reactive signal updates synchronously, which caused intermediate, incomplete states to be evaluated and rendered by SolidJS. 📊 Impact: Batching ensures that all signal updates from a single SSE event or optimistic update are deferred until the block completes, reducing unnecessary reactivity recalculations and re-renders in the frontend, thereby dropping CPU usage and UI flicker on heavily active sessions. 🔬 Measurement: Verify by watching solid reactivity timing with and without this patch when an active session streams changes rapidly. Co-authored-by: PrakharMNNIT <73683289+PrakharMNNIT@users.noreply.github.com> --- .jules/bolt.md | 3 + bun.lock | 1 - packages/app/src/context/global-sync.tsx | 77 ++++++++++++------------ packages/app/src/context/sync.tsx | 44 ++++++++------ 4 files changed, 67 insertions(+), 58 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 000000000000..b5330b510a8f --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## YYYY-MM-DD - [Title] +**Learning:** [Insight] +**Action:** [How to apply next time]. \ No newline at end of file diff --git a/bun.lock b/bun.lock index 8df1d6456c2b..0edc32b54d21 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 1, "workspaces": { "": { "name": "opencode", diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 112bc9240eab..5b1c8bc7ac62 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -10,6 +10,7 @@ import type { import { showToast } from "@opencode-ai/ui/toast" import { getFilename } from "@opencode-ai/util/path" import { + batch, createContext, createEffect, getOwner, @@ -251,47 +252,49 @@ function createGlobalSync() { } const unsub = globalSDK.event.listen((e) => { - const directory = e.name - const event = e.details - - if (directory === "global") { - applyGlobalEvent({ - event, - project: globalStore.project, - refresh: queue.refresh, - setGlobalProject(next) { - if (typeof next === "function") { - setGlobalStore("project", produce(next)) - return + batch(() => { + const directory = e.name + const event = e.details + + if (directory === "global") { + applyGlobalEvent({ + event, + project: globalStore.project, + refresh: queue.refresh, + setGlobalProject(next) { + if (typeof next === "function") { + setGlobalStore("project", produce(next)) + return + } + setGlobalStore("project", next) + }, + }) + if (event.type === "server.connected" || event.type === "global.disposed") { + for (const directory of Object.keys(children.children)) { + queue.push(directory) } - setGlobalStore("project", next) - }, - }) - if (event.type === "server.connected" || event.type === "global.disposed") { - for (const directory of Object.keys(children.children)) { - queue.push(directory) } + return } - return - } - const existing = children.children[directory] - if (!existing) return - children.mark(directory) - const [store, setStore] = existing - applyDirectoryEvent({ - event, - directory, - store, - setStore, - push: queue.push, - setSessionTodo, - vcsCache: children.vcsCache.get(directory), - loadLsp: () => { - sdkFor(directory) - .lsp.status() - .then((x) => setStore("lsp", x.data ?? [])) - }, + const existing = children.children[directory] + if (!existing) return + children.mark(directory) + const [store, setStore] = existing + applyDirectoryEvent({ + event, + directory, + store, + setStore, + push: queue.push, + setSessionTodo, + vcsCache: children.vcsCache.get(directory), + loadLsp: () => { + sdkFor(directory) + .lsp.status() + .then((x) => setStore("lsp", x.data ?? [])) + }, + }) }) }) diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx index 60888b1a6fd9..99323a351eef 100644 --- a/packages/app/src/context/sync.tsx +++ b/packages/app/src/context/sync.tsx @@ -63,30 +63,34 @@ export function applyOptimisticRemove(draft: OptimisticStore, input: OptimisticR } function setOptimisticAdd(setStore: (...args: unknown[]) => void, input: OptimisticAddInput) { - setStore("message", input.sessionID, (messages: Message[] | undefined) => { - if (!messages) return [input.message] - const result = Binary.search(messages, input.message.id, (m) => m.id) - const next = [...messages] - next.splice(result.index, 0, input.message) - return next + batch(() => { + setStore("message", input.sessionID, (messages: Message[] | undefined) => { + if (!messages) return [input.message] + const result = Binary.search(messages, input.message.id, (m) => m.id) + const next = [...messages] + next.splice(result.index, 0, input.message) + return next + }) + setStore("part", input.message.id, sortParts(input.parts)) }) - setStore("part", input.message.id, sortParts(input.parts)) } function setOptimisticRemove(setStore: (...args: unknown[]) => void, input: OptimisticRemoveInput) { - setStore("message", input.sessionID, (messages: Message[] | undefined) => { - if (!messages) return messages - const result = Binary.search(messages, input.messageID, (m) => m.id) - if (!result.found) return messages - const next = [...messages] - next.splice(result.index, 1) - return next - }) - setStore("part", (part: Record) => { - if (!(input.messageID in part)) return part - const next = { ...part } - delete next[input.messageID] - return next + batch(() => { + setStore("message", input.sessionID, (messages: Message[] | undefined) => { + if (!messages) return messages + const result = Binary.search(messages, input.messageID, (m) => m.id) + if (!result.found) return messages + const next = [...messages] + next.splice(result.index, 1) + return next + }) + setStore("part", (part: Record) => { + if (!(input.messageID in part)) return part + const next = { ...part } + delete next[input.messageID] + return next + }) }) }