# Gotchas — landmines this codebase has already stepped on Every one of these caused a real bug or debugging spiral. Read before touching the adjacent code. ## Angular (v22, zoneless, signals, OnPush) - **Never consume push streams through signal+effect.** An effect that reads a signal and (directly or transitively) writes signals re-triggers itself — this froze the entire app on the first transfer. Push messages (transferUpdate etc.) go through plain callback registries in RpcService. Signal coalescing would also silently drop intermediate progress snapshots. - **No signal writes during template render** (NG0600). Anything that loads panels from code paths reachable during render defers with `queueMicrotask` (see `PanelsService.newWorkspace`). - **`[value]` on ``: utilities outrank the base layer, so a `px` utility would erase the padding-right the drawn caret lives in. - **A custom `::-webkit-scrollbar` turns off macOS overlay scrollbars.** The moment the pseudo-element is styled, WKWebView draws classic always-visible scrollbars that take layout width. Deliberate here (square design); don't reach for it to "tint" scrollbars without accepting that. ## The interface scale (root font-size lever) - **electrobun's native page zoom is a silent no-op on Linux — do not reach for it.** `webviewSetPageZoom` in the shipped `libNativeWrapper.so` (v1.18.1, and the `_cef.so`, and the copy in our own build output) is a compiled empty-body stub: the disassembly has no `call` instruction, and `webkit_web_view_set_zoom_level` is not even in the library's imports. `webviewGetPageZoom` returns a hardcoded `1.0` from `.rodata`, so the failure is unobservable through the API — set 2.0, read back "1.0 = success". The JSDoc's "(WebKit only)" means macOS WKWebView. This is why the scale is CSS. - **Everything the UI sizes with must be rem.** The scale works by setting the root font-size, so a px value freezes while everything around it grows. The px reservoirs were the panel-chrome tokens (`--row-h` etc.) and 93 `text-[11px]` arbitraries. 1px hairlines and focus rects stay px deliberately (crisp at any scale). - **px constants in TS that mirror rem-sized CSS are latent scale bugs.** The context menu's `MENU_W = 224` was a hardcoded mirror of `w-56` (14rem): box grew, math didn't, flip fired at the wrong edge. Convert through `@core/metrics` `remPx()` at use time — it reads the LIVE root font-size, and per call, since the scale changes mid-session. The editor's canvas-based `charWidth` is cached keyed on the computed font string for the same reason. ## Electrobun - **The prebuilt CLI binary ships with a broken ad-hoc signature**, so macOS SIGKILLs it at exec and `bun run start` prints nothing and exits **0** — `bin/electrobun.cjs` spawns the real binary and forwards `process.exit(code || 0)`, and a signal death has `code === null`. The binary is a Bun single-file executable whose payload is appended *after* the linker signed it, so `codesign -v` reports "code or signature have been modified"; the arm64 and x64 CLI tarballs are both affected upstream (confirmed by hashing a fresh download against the shipped copy — it is not local corruption, and re-installing does not help). `scripts/fix-electrobun-cli.ts` re-seals it with `codesign --force --sign -`. It runs from `postinstall` **and** ahead of every electrobun invocation, because the wrapper downloads the binary **lazily on first use**, not at install time — postinstall alone would no-op on a fresh checkout. It also fixes `.cache/electrobun`, which the wrapper restores `bin/` from. Never read a silent `exit 0` from the electrobun CLI as "nothing happened": run `codesign -v` on it first. - **Window `close` is not cancellable** (response type `{}`; only the app-level `before-quit` supports `event.response = {allow:false}`). All unsaved-state guards must either intercept app-controlled close paths (⌘W, buttons) or survive the close (editor draft mirroring). - **⌘W in any secondary window arrives as the MAIN window's `close-tab` menu action** (macOS: the app menu intercepts it before the webview sees the key). bun must check the focused secondary window first — the Settings window, then editors — and close THAT; a new window kind that skips this check makes ⌘W close a main-window tab behind it. - **One rpc instance per window** — BrowserView binding steals the rpc's transport; a shared instance silently sends to the wrong webview. - **RPC before socket open is silently lost.** Everything gates on the socket-open promise (RpcService `ready`). - The emitter has no `off()`. Window event closures must not capture records you want garbage collected. - `electrobun build` defaults to the **dev** channel; release builds need `--env=stable`. The stable app name differs from the dev one (`miniSCP` vs `miniSCP-dev`), including the app-data dir implications when testing. - Tray: never call `setImage` after construction on macOS — the template treatment is lost and the icon renders black on dark menu bars. - `ApplicationMenu` is macOS-only in our usage; accelerators on Windows are single-character only. - **`ContextMenu.showContextMenu` is a silent no-op on Linux** (electrobun's own `dist/api/bun/proc/linux.md` says so; the app also prints "Application menus are not supported on Linux" at boot). Because the UI suppresses the webview's own right-click menu, that left EVERY panel context menu dead on Linux — no error, nothing happens. The menu is plain HTML now (`ui/src/app/skins/shadlike/context-menu.ts`) on every platform; don't reintroduce the native one for "nativeness" on mac/win without keeping a Linux path. - **Window-open flash prevention is three-layer** — remove any one and the white/inverted blink returns: (1) windows are created `hidden: true` and revealed by `showWhenReady` on the webview's first `dom-ready` (800 ms fallback), (2) the bun preload bakes the persisted `data-theme`/tone attributes and the interface scale before first paint (`buildPreload`), (3) `ui/src/index.html` carries an inline `