diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx index 01cb280e5d..93eaf0df49 100644 --- a/packages/app/src/components/dialog-select-server.tsx +++ b/packages/app/src/components/dialog-select-server.tsx @@ -8,7 +8,7 @@ import { List } from "@opencode-ai/ui/list" import { TextField } from "@opencode-ai/ui/text-field" import { useMutation } from "@tanstack/solid-query" import { showToast } from "@opencode-ai/ui/toast" -import { createEffect, createMemo, createResource, onCleanup, Show } from "solid-js" +import { batch, createEffect, createMemo, createResource, onCleanup, Show } from "solid-js" import { createStore, reconcile } from "solid-js/store" import { DialogWslServer } from "@/components/dialog-wsl-server" import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row" @@ -364,8 +364,10 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) { props.onNavigateHome?.() return } - props.onNavigateHome?.() - queueMicrotask(() => server.setActive(ServerConnection.key(conn))) + batch(() => { + props.onNavigateHome?.() + server.setActive(ServerConnection.key(conn)) + }) } const handleAddChange = (value: string) => { diff --git a/packages/app/src/components/status-popover-body.tsx b/packages/app/src/components/status-popover-body.tsx index e94c9f112e..0075f501de 100644 --- a/packages/app/src/components/status-popover-body.tsx +++ b/packages/app/src/components/status-popover-body.tsx @@ -6,7 +6,7 @@ import { Tabs } from "@opencode-ai/ui/tabs" import { useMutation } from "@tanstack/solid-query" import { showToast } from "@opencode-ai/ui/toast" import { useNavigate } from "@solidjs/router" -import { type Accessor, createEffect, createMemo, For, type JSXElement, onCleanup, Show } from "solid-js" +import { type Accessor, batch, createEffect, createMemo, For, type JSXElement, onCleanup, Show } from "solid-js" import { createStore, reconcile } from "solid-js/store" import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row" import { useLanguage } from "@/context/language" @@ -292,8 +292,13 @@ export function StatusPopoverBody(props: { shown: Accessor }) { aria-disabled={blocked()} onClick={() => { if (blocked()) return - navigate("/") - queueMicrotask(() => server.setActive(key)) + // Run navigate + setActive in the same tick so Solid + // disposes the old subtree once instead of cascading + // the route change disposal into the ServerKey remount. + batch(() => { + navigate("/") + server.setActive(key) + }) }} > diff --git a/packages/desktop-electron/src/renderer/index.tsx b/packages/desktop-electron/src/renderer/index.tsx index 79a599f354..5e26f65efc 100644 --- a/packages/desktop-electron/src/renderer/index.tsx +++ b/packages/desktop-electron/src/renderer/index.tsx @@ -1,5 +1,11 @@ // @refresh reload +// V8's default Error.stackTraceLimit truncates at 10 frames, which is exactly +// the depth of the recursive cleanNode crash — the real trigger (our code +// calling dispose, or a store update racing disposal) is beyond that. Raise +// it so stacks contain the origin frame. +Error.stackTraceLimit = 200 + // Install global error listeners before any other module runs so that // uncaught errors and rejected promises reach the main process with their // full stacks intact. Electron's `console-message` event only forwards the