Previous commit only guarded SortableWorkspace. LocalWorkspace (rendered from layout.tsx:2243 with the same project={project()!} pattern) still accessed props.project.worktree directly inside four createMemo / useQuery bodies and threw the same 'worktree of undefined' during a server swap cascade. Route every worktree read through a worktree memo that returns the string or '', and guard the projectId prop on WorkspaceHeader.
The WSL controller previously only stored a failed startup message in its renderer state, so Ubuntu-style silent failures left no trace in main.log. Inject the main-process logger into the controller and emit wsl sidecar ready / wsl sidecar failed to start entries. Also flatten the [server health] renderer logs into a single string argument because Electron's console-message bridge truncates extra args to [object Object].
bootstrapDirectory's setStore fires while a server swap is cascading; currentProject() can already be undefined by the time these memos re-run, which threw 'Cannot read properties of undefined (reading worktree)' from local/workspaceValue/WorkspaceActions root. Optional-chain the props.project reads so the component survives the transient undefined before the enclosing Show unmounts it.
ServerKey's keyed <Show> remount is a multi-second synchronous cascade (dispose + rebuild of the whole app subtree) that used to leave the UI looking frozen. A tiny module-level serverSwitching signal now gates a fullscreen Splash rendered above the ServerKey boundary, and the status-popover click handler setTimeout-defers the batched navigate+setActive so the browser paints the splash before the freeze begins and dismisses it after the new subtree paints.
Serialize non-Error promise rejections so unhandled rejections print type/ctor/keys/JSON instead of the unreadable '[object Object]'. Also emit [server health] logs when a health poll returns unhealthy and when polling switches servers, so a red dot in the status popover comes with a logged URL and auth presence. Minor cosmetic: restore session-header StatusPopover import position after the earlier titlebar experiment.
Terminal onCleanup ran persistTerminal synchronously during a dispose cascade, which flowed through props.onCleanup -> ops.update -> update() in context/terminal.tsx and fired setStore on the terminal store. That store write reentered the reactive graph mid cleanNode iteration; solid then nulled an ancestors owned while an outer cleanNode recursion was still iterating it, crashing with Cannot read properties of null reading 1 at node.owned[i]. Wrapping finalize in queueMicrotask pushes the store write past the current synchronous cleanup cascade so the teardown cannot race with cleanNodes owned walk.
Same nested-dispose-in-onCleanup bug as 7f36ac2481 but in three more
places: TerminalProvider.disposeAll, PromptProvider.disposeAll, and
scoped-cache.clear() (covers viewCache.clear and comments cache.clear).
All of them synchronously call createRoot dispose() on cached entries
inside onCleanup, which during a server switch nests into the outer
cleanNode cascade and throws TypeError at chunk-*.js:992.
Snapshot the pending disposers, clear the cache synchronously, and
fire the disposers on a microtask so the outer cleanup finishes first.