Summary
npm run dev launches, the Electron window appears, but the renderer stays on the index.html preload skeleton (.maka-preload) forever. No error reaches the terminal. Reproduced on Windows and Linux at current main (b39e8d3). Packaged builds are unaffected.
Root cause
The renderer startup graph pulls a Node-only module into the browser through the protocol barrel:
src/renderer/main.tsx → app.tsx → app-shell.tsx
→ use-app-shell-session-workspace.ts → session-workspace-actions.ts
→ import { MESSAGE_QUEUE_MAX_ENTRIES } from '@maka/runtime-host/protocol'
→ packages/runtime-host/src/protocol/index.ts: export * from './client-capability.js'
→ client-capability.ts: import { createHash } from 'node:crypto'
Vite dev does no tree-shaking, so export * eagerly loads client-capability.js in the browser. Vite externalizes node:crypto for browser compatibility, and the served module begins with a top-level destructure:
const createHash = __vite__cjsImport0_node_crypto["createHash"];
That getter throws at module evaluation, which kills the whole renderer import graph before React mounts. The window is still revealed by the 4s SHOW_FALLBACK_TIMEOUT_MS fallback in main-window.ts, so the failure presents as "window opens, skeleton forever, silence in the terminal" — renderer console errors never reach the terminal unless ELECTRON_ENABLE_LOGGING=1.
clientCapabilityEntityId is the only createHash consumer, and it is used exclusively by main-process code (runtime-host-boot.ts, runtime-host-native-capabilities.ts); the renderer never calls it.
Reproduction
npm run dev
# window opens, skeleton stays forever
# with ELECTRON_ENABLE_LOGGING=1 the terminal shows the node:crypto error
Confirmed via CDP attach (--remote-debugging-port): Runtime.exceptionThrown stack ends at packages/runtime-host/dist/protocol/client-capability.js triggered from __vite-browser-external:node:crypto.
Timeline
Together they break vite dev mode on every platform.
Why CI did not catch it
windows-baseline.yml's Electron smoke is continue-on-error: true and exercises the dist build (smoke:windows:dist); vite build tree-shakes the unused client-capability.js out, so only vite dev mode breaks.
check-renderer-architecture.mjs forbids Node builtins in renderer sources but only scans the renderer's own imports — it does not walk the transitive graph through workspace package barrels.
Proposed fix
Move clientCapabilityEntityId (and the node:crypto import) out of protocol/client-capability.ts into a Node-only module that the protocol barrel does not re-export, and point the two main-process call sites at it. The renderer keeps importing browser-safe values from the barrel.
Follow-up worth considering: extend check-renderer-architecture.mjs (or add a vite-level guard) to reject Node builtins anywhere in the renderer's transitive dependency graph, so the next barrel re-export cannot silently reintroduce this.
Summary
npm run devlaunches, the Electron window appears, but the renderer stays on theindex.htmlpreload skeleton (.maka-preload) forever. No error reaches the terminal. Reproduced on Windows and Linux at currentmain(b39e8d3). Packaged builds are unaffected.Root cause
The renderer startup graph pulls a Node-only module into the browser through the protocol barrel:
Vite dev does no tree-shaking, so
export *eagerly loadsclient-capability.jsin the browser. Vite externalizesnode:cryptofor browser compatibility, and the served module begins with a top-level destructure:That getter throws at module evaluation, which kills the whole renderer import graph before React mounts. The window is still revealed by the 4s
SHOW_FALLBACK_TIMEOUT_MSfallback inmain-window.ts, so the failure presents as "window opens, skeleton forever, silence in the terminal" — renderer console errors never reach the terminal unlessELECTRON_ENABLE_LOGGING=1.clientCapabilityEntityIdis the onlycreateHashconsumer, and it is used exclusively by main-process code (runtime-host-boot.ts,runtime-host-native-capabilities.ts); the renderer never calls it.Reproduction
Confirmed via CDP attach (
--remote-debugging-port):Runtime.exceptionThrownstack ends atpackages/runtime-host/dist/protocol/client-capability.jstriggered from__vite-browser-external:node:crypto.Timeline
892d8b1c): protocol barrel gainsexport * from './client-capability.js'17aae7c5): renderer startup graph gains a value import of the barrel (MESSAGE_QUEUE_MAX_ENTRIESinsession-workspace-actions.ts)Together they break vite dev mode on every platform.
Why CI did not catch it
windows-baseline.yml's Electron smoke iscontinue-on-error: trueand exercises the dist build (smoke:windows:dist);vite buildtree-shakes the unusedclient-capability.jsout, so only vite dev mode breaks.check-renderer-architecture.mjsforbids Node builtins in renderer sources but only scans the renderer's own imports — it does not walk the transitive graph through workspace package barrels.Proposed fix
Move
clientCapabilityEntityId(and thenode:cryptoimport) out ofprotocol/client-capability.tsinto a Node-only module that the protocol barrel does not re-export, and point the two main-process call sites at it. The renderer keeps importing browser-safe values from the barrel.Follow-up worth considering: extend
check-renderer-architecture.mjs(or add a vite-level guard) to reject Node builtins anywhere in the renderer's transitive dependency graph, so the next barrel re-export cannot silently reintroduce this.