Electron desktop dev workbench: manage multiple project workspaces, each with a Monaco code editor, node-pty terminals, a git source-control panel, and in-app web tabs. Think lightweight, workspace-centric VS Code.
- Electron 36 (main + preload) ↔ React 18 + TypeScript renderer, bundled by Vite 6.
- monaco-editor (editor), xterm + node-pty (terminals), zustand (renderer state), react-resizable-panels (layout).
- Tests: vitest. Package:
electron-builder→ Windows x64 portable/NSIS outputs inrelease/.
Three TS programs, two tsconfigs:
- Main (
electron/,electron/tsconfig.json→dist-electron/): Node-side services. All IO (fs, git, pty, dialogs, persistence) lives here, exposed only viaipcMain.handlechannels nameddomain:action(seeelectron/main.ts). - Preload (
electron/preload.ts):contextBridgeexposes a typedwindow.stackdockAPI.contextIsolation: true,nodeIntegration: false. - Renderer (
src/, roottsconfig.json→dist/): React UI. Touches the backend only throughapi(src/lib/api.ts=window.stackdock). No direct Node access.dist/is renderer build input to packaging, not the final packaged app output.
Contract: every IPC arg is validated in electron/validation.ts (assert* helpers) before a service runs. The full API + all data shapes are declared in src/shared/types.ts (StackDockApi interface) — this is the source of truth shared by both sides; update it when changing any channel.
Persistence: JSON files in the Electron userData dir (electron/storage.ts sets up dirs). Settings → configStore.ts, automation → automationStore.ts, workspaces + per-workspace layouts → workspaceStore.ts.
Repository map lives in PROJECT_MAP.md. Update PROJECT_MAP.md when changing folder structure, adding/removing major files, or changing architecture flows.
To create or refresh the project map, use the standalone project-mapping prompt, not this file.
Use LeanCTX for repo exploration where available. Prefer small, targeted changes. Do not modify generated files, lockfiles, dependency files, or git state unless explicitly requested.
npm run dev— build once (npm run build) then launch Electron with--agent-browser --builtsupport.--builtforces the unpackaged app to loaddist/index.htmlinstead of the Vite dev server. No Vite/tsc watch, no HMR, no auto-restart; rerun after source changes when testing latest build. Remote debugging is available for agent-browser interaction.npm run build— tsc (electron) + vite build intodist-electron/anddist/.npm run build:app— clean generated package artifacts, build, then create a maximally compressed Windows x64 portable.exewithelectron-builder; output goes torelease/. Stopnpm run devfirst so nodemon does not relaunch the app while packaging.npm run build:installer— same build path but creates an NSIS installer inrelease/.npm run build:web-installer— creates an NSIS web installer inrelease/(smaller initial installer, downloads payload during install).npm run dist/npm run dist:x64:min— legacy aliases; preferbuild:appfor current packaged-app checks.npm test— vitest.npm run typecheck— both tsconfigs,--noEmit.
- Built-in workspace UI surfaces (Explorer, Source Control/Git, Sessions, status items) are bundled extensions. Prefer adding new workspace side/bottom/status UI as extension contributions instead of hardcoding panels in
WorkspaceShell.tsx. - Local extension JavaScript must run only inside sandboxed iframes served through the extension asset protocol.
- Never dynamically import local package code into the StackDock renderer; local packages communicate with the host via the typed iframe bridge only.
- Extension IPC and bridge payloads must use typed shared contracts from
src/shared/types.tsand validated main-process boundaries. - New extension loader/enablement/bridge tests should run with
npm test.
- When asked to interact with or test the running Electron app, use the native
agent_browsertool against thenpm run devapp launched with--agent-browser; do not use manual browser-driving scripts unless explicitly requested. - New backend capability = add channel in
main.ts+ arg guards invalidation.ts+ bridge method inpreload.ts+ type inshared/types.tsStackDockApi. Keep all four in sync. - Renderer never imports Node/
electron; go throughapi. - Global terminal active-session fallback prefers another session in the same workspace before falling back to other workspaces, so session highlighting and restore state stay aligned.
- Windows browser-capture helpers should avoid visible console windows; Plannotator/browser opens route through the hidden bridge helper into StackDock web tabs.
- Theming is unified: a single
themeIddrives both Monaco and the app via CSS variables inthemeSupport.ts. VS Code*-color-theme.jsonfiles can be imported by users. - IPC channel naming:
domain:action. Renderer→main events listened viaonTerminalData/onTerminalExit/onTerminalStatus/onWorkspaceChanged. - Packaging size notes: renderer-only libraries live in
devDependenciesbecause Vite bundles them intodist/; keep only true main-process runtime modules (currentlynode-pty) independencies. Electron Builder outputs torelease/to avoid recursively packagingdist/win-unpacked; keep generated package artifacts out ofdist/or explicitly excluded inbuild.files. node-ptypackaging is x64-trimmed: onlynode_modules/node-pty/prebuilds/win32-x64/**/*.{node,dll,exe}is unpacked, and arm64 prebuilds/PDBs/source/deps are excluded for smaller Windows x64 builds.