Hooks DEV.hooks.afterCreateOwner to wrap every owner's .owned and .cleanups with accessor traps that record every mutation to a ring buffer with tags, stacks, and cleanup-depth context. On any 'Cannot read properties of null' TypeError the buffer is dumped so the offending cleanup/origin that nulled an owner's owned mid-iteration is visible post-hoc. Also wraps owned arrays in a Proxy so cleanNode's index reads are logged and the suspect ownerTag at crash time can be identified. Debug only; zero cost until a crash fires.
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.
disposeDirectory called a createRoot dispose() synchronously. When
triggered by pinForOwner's onCleanup during a parent remount (e.g.
switching to a WSL server re-keys the ServerKey Show), the inner
dispose ran a nested cleanNode cascade on a sibling root while the
outer cascade was mid-traversal, corrupting solid-js's graph walk
state and surfacing as TypeError: Cannot read properties of null
(reading '1') at chunk-*.js:992 after ~155 recursive cleanNode frames.
Queue the dispose on a microtask so synchronous bookkeeping still
runs (map deletes, onDispose cache invalidation) but the reactive
cleanup happens after the outer traversal finishes.
The status popover and select-server dialog used to call navigate('/') then
defer server.setActive to the next microtask. With multiple sidecars in v2,
that split triggered two separate disposal cascades - one for the route
change and a second for the ServerKey Show re-key - and the sidebar project
bucket also swaps (local -> wsl:Debian), tearing down every solid-dnd
sortable in the middle. Wrapping both calls in batch() lands them in a
single Solid update so disposal runs once.
Also raise Error.stackTraceLimit to 200 so future disposal crashes capture
the originating frame instead of truncating at the tenth cleanNode.
Electron's console-message event only surfaces {level, message, line, sourceId}
without the stack, so uncaught errors showed up as 'line 1028 of chunk-*.js'
(SolidJS's rethrow site) with no way to find the real origin. Attach
window error and unhandledrejection listeners that log the full stack via
console.error, and reshape the main-process log line so newlines in the
stack survive instead of being JSON-escaped into one unreadable blob.
The main process was resetting webContents zoom to 1 on every
\zoom-changed\ event, which fires not just for native Chromium zoom
gestures but also for the renderer's own setZoomFactor IPC calls. Paired
with a keydown listener that re-sent the current zoom on every
ctrl-combination (ctrl+backspace, ctrl+z, ctrl+v, ...), this created a
self-triggered race that intermittently snapped the factor back to 1.
Make the renderer the single source of zoom truth: keyboard, wheel, and
menu all drive the same Solid signal, preventDefault blocks Chromium's
built-in accelerators before they race, and the main process disables
pinch zoom and no longer listens to zoom-changed.
Local Server is always Windows-native now; WSL lives as a separate list
of one-or-more distro-bound sidecars spawned alongside it. Manage Servers
shows an Add WSL button on Windows, each WSL server appears as its own
row with remove + retry, and the wizard runs scoped to a new distro.