Migrate to Electrobun 2.0.2 (Hutch)
Electrobun 2.x replaces the vendored CLI with Hutch: the npm package is now a
bootstrap whose exports are a throwing stub, and the SDK is projected into
.hutch/devkit. The app stays on the Bun main process — `mainProcess: "bun"` is
load-bearing, since the 2.x default (Cottontail) ignores build.bun and with it
the ssh2 externals — and the bundle now ships the pinned Bun 1.4.0.
- electrobun 2.0.2 as an exact devDependency. `electrobun prepare` takes the
old CLI re-sign step's slot in postinstall and every dev/build/start/release
script: tsc (root tsconfig extends the devkit's) and the Angular build
(electrobun/view mapped into the devkit) resolve the SDK there.
- Imports move to electrobun/main. The CLI re-sign script and the v1 "three"
type shim are gone (Hutch's binaries are validly signed).
- UI: Electroview's bunSocket is hostSocket; noImplicitReturns is off because
the devkit's raw-TS browser SDK compiles inside the UI program.
- Updater.applyUpdate() now consults before-quit, so the transfers/unsaved
guard's "Quit Anyway" re-runs the apply instead of quitting without it.
- 2.x applies trafficLightOffset from macOS's default button position (1.18.1
clamped it inside the titlebar); {12, 4} centres the lights in the header.
- Releases: mac-x64 dropped (2.x publishes no macOS x64 core, so no Rosetta
path). artifacts/ is emptied before each build and published verbatim.
Installers lost the stable- prefix (the update feed kept it), so release.ts,
deploy.sh and the site links follow. Linux ships Electrobun's Setup
installer instead of the portable tarball and its miniSCP.sh wrapper.
- Docs cover all of the above, plus re-checks against 2.0.2: still no native
modal windows; window close is now cancellable through will-close.
Verified: tsc, build:ui, a stable mac-arm64 release (dmg + the v1-compatible
stable-macos-arm64 feed) and dev launches (RPC, Settings window, traffic
lights). Windows/Linux builds and the v1 -> 2.x Windows update bridge still
need their first run on native machines.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+80
-38
@@ -87,6 +87,8 @@ touching the adjacent code.
|
||||
`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.
|
||||
(Disassembled on 1.18.1; 2.0.2's zoom fixes are CEF-only — re-check the
|
||||
native Linux wrapper before trusting it.)
|
||||
- **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
|
||||
@@ -101,25 +103,39 @@ touching the adjacent code.
|
||||
|
||||
## 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).
|
||||
- **The `electrobun` npm package (2.x) is a bootstrap whose every export is a
|
||||
throwing stub** ("Electrobun 2.x APIs come from the Hutch devkit, not
|
||||
node_modules"). Every `electrobun/*` import must resolve through the
|
||||
projected `.hutch/devkit/`: the root tsconfig extends
|
||||
`.hutch/devkit/tsconfig.json`, and `ui/tsconfig.json` maps `electrobun/view`
|
||||
to `../.hutch/devkit/api/browser/index.ts` (the exact export-map target). Let
|
||||
an import fall through to `node_modules/electrobun` and the webview throws at
|
||||
load. tsc and the UI build fail on unresolved `electrobun/*` imports until
|
||||
`bunx electrobun prepare` has run (postinstall and every dev/build script do
|
||||
it). Never point anything into `~/.hutch` — that store is Hutch-owned.
|
||||
- **`build.mainProcess` defaults to Cottontail in 2.x**, and Cottontail ignores
|
||||
the whole `build.bun` block — the ssh2 externals included, silently (verified
|
||||
with `bunx electrobun config --env=stable`). `mainProcess: "bun"` in
|
||||
`electrobun.config.ts` is load-bearing.
|
||||
- **The browser SDK is raw TS compiled inside the Angular program**, so the UI's
|
||||
strictness flags apply to upstream code: 2.0.2's `createTransport().send`
|
||||
returns on one path only, which is why `ui/tsconfig.json` has
|
||||
`noImplicitReturns: false`. Its `import "./global.d.ts"` also prints "File
|
||||
'…/global.d.ts' not found in TypeScript compilation" on every build — benign,
|
||||
and adding the file to the program does not silence it.
|
||||
- **Window `close` is not cancellable, but 2.x added `will-close`.** The `close`
|
||||
event's response type is `{}`; `will-close` (fired by the titlebar/traffic-light
|
||||
close and `win.requestClose()`) honors `event.response = {allow:false}`, and
|
||||
`win.close()` skips it. The editor close safety predates this: all
|
||||
unsaved-state guards still intercept app-controlled close paths (⌘W, buttons)
|
||||
or survive the close (editor draft mirroring) — a `will-close` prompt is a
|
||||
possible follow-up.
|
||||
- **`Updater.applyUpdate()` asks the `before-quit` handlers first (2.x)**, so the
|
||||
transfers/unsaved guard can veto an update restart. Its "Quit Anyway" must
|
||||
re-run `Updater.applyUpdate()` (`events.applyUpdate` flags the quit as
|
||||
update-initiated) — a plain `Utils.quit()` there quits without installing.
|
||||
`Utils.quit()` runs the same approval, which is why the guard checks
|
||||
`quitConfirmed` first.
|
||||
- **⌘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
|
||||
@@ -127,8 +143,10 @@ touching the adjacent code.
|
||||
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`).
|
||||
- **RPC before socket open was silently lost on 1.18.1.** 2.x Electroview queues
|
||||
sends while its `hostSocket` (v1: `bunSocket`) is CONNECTING and falls back to
|
||||
the native bridge; RpcService still gates everything on the socket-open
|
||||
promise (`ready`) as belt-and-braces.
|
||||
- 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
|
||||
@@ -140,12 +158,13 @@ touching the adjacent code.
|
||||
- `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.
|
||||
`.hutch/devkit/api/sdks/main/proc/linux.md` says so, still in 2.0.2; 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
|
||||
@@ -161,7 +180,8 @@ touching the adjacent code.
|
||||
URL, never hardcode `views://`.
|
||||
- Upstream Bun panic (`FFI_Callback_threadsafe_call` → `web_worker.spin`):
|
||||
rare segfault under long sessions; upstream electrobun×Bun issue, restart
|
||||
recovers, nothing app-side to fix.
|
||||
recovers, nothing app-side to fix. Seen on Bun 1.3.13 (Electrobun 1.18.1);
|
||||
2.0.2 ships Bun 1.4.0 — unconfirmed whether that fixed it.
|
||||
- **Preloads and frames are construction-time only.** The preload is a
|
||||
persistent `WebKitUserScript` at DOCUMENT_START, frozen at webview
|
||||
construction — `webview.loadURL()` re-runs the OLD one
|
||||
@@ -171,13 +191,25 @@ touching the adjacent code.
|
||||
correct live over RPC. Recreating the main window is not an escape hatch:
|
||||
`exitOnLastWindowClosed`, push closures and the tray capture it, and there
|
||||
is no `off()`.
|
||||
- **`trafficLightOffset` changed meaning in 2.x.** 1.18.1 nudged the buttons
|
||||
by the offset but clamped them inside the stock titlebar, so `{12, 13}` really
|
||||
meant "as low as the titlebar allows" (left 21 / top 18 on macOS 26). 2.x adds
|
||||
the full offset to macOS's default position (9/9 in the 32pt Tahoe titlebar)
|
||||
and grows the titlebar to fit — the same numbers dropped the lights 4pt.
|
||||
`chromeOptions()` now passes `{12, 4}`: left 21 / top 13, the 14pt buttons
|
||||
vertically centred in the 40px `h-10` header (1.18.1's clamped spot, top 18,
|
||||
was 5pt low). The default shifts with macOS's titlebar metrics (28pt / 7
|
||||
before macOS 26), so re-check after an OS release. `win.setWindowButtonPosition(x, top)` is the absolute
|
||||
alternative (re-applied on resize/fullscreen) if per-version drift matters.
|
||||
- **Drag regions ignore `preventDefault`.** The preload starts a window move
|
||||
from a bubble-phase `mousedown` on `document` and never reads
|
||||
`defaultPrevented`; a `pointerdown` handler doesn't stop it either (the
|
||||
compat `mousedown` still fires). `isAppRegionDrag` checks
|
||||
`.electrobun-webkit-app-region-no-drag` first — that class on the element is
|
||||
the only reliable suppressor (`dist/api/bun/preload/dragRegions.ts`).
|
||||
- **No resize, maximize-event or double-click-maximize primitives (1.18.1).**
|
||||
compat `mousedown` still fires). `isAppRegionDragTarget` lets a `no-drag`
|
||||
region win — the `.electrobun-webkit-app-region-no-drag` class (or, since
|
||||
2.x, `app-region: no-drag` CSS) on the element is the only reliable
|
||||
suppressor (`.hutch/devkit/api/preload/dragRegions.ts`; re-checked on 2.0.2).
|
||||
- **No resize, maximize-event or double-click-maximize primitives (1.18.1, still
|
||||
absent from 2.0.2's `BrowserWindow`).**
|
||||
`titleBarStyle: "hidden"` drops the whole frame (Cocoa `Titled` — the only
|
||||
way to remove the traffic lights; `gtk_window_set_decorated(false)` on
|
||||
Linux) and with it the WM resize borders; nothing restores them (no
|
||||
@@ -204,12 +236,15 @@ touching the adjacent code.
|
||||
- **The app cannot apply this to itself.** WebKit reads the variable when it
|
||||
forks its web process, and **Bun's `process.env` writes do not propagate to
|
||||
child processes** (verified: a spawned child sees it unset). Setting it in
|
||||
`src/bun/index.ts` — even in a module imported before `electrobun/bun` —
|
||||
`src/bun/index.ts` — even in a module imported before `electrobun/main` —
|
||||
does nothing. It must be in the environment before the app process starts.
|
||||
- Hence every launch path injects it: `scripts/linux-gpu.ts` (`launchEnv()`)
|
||||
for `bun run dev` / `bun run start`, and the `miniSCP.sh` wrapper that
|
||||
`release.ts` writes beside `bin/launcher` in the tarball. **A new launch
|
||||
path must call `launchEnv()` or it will render black on NVIDIA.**
|
||||
- Only the dev launch paths inject it: `scripts/linux-gpu.ts` (`launchEnv()`)
|
||||
for `bun run dev` / `bun run start`. **A new dev launch path must call
|
||||
`launchEnv()` or it will render black on NVIDIA.** The release is the
|
||||
official 2.x Setup installer now (the v1 portable tarball and its
|
||||
`miniSCP.sh` wrapper are gone), so an installed build on NVIDIA needs the
|
||||
user to launch it with `WEBKIT_DISABLE_DMABUF_RENDERER=1` (e.g. in the
|
||||
desktop entry's `Exec`).
|
||||
- Gated on `/proc/driver/nvidia` so Intel/AMD keep hardware compositing, and
|
||||
an explicit `WEBKIT_DISABLE_DMABUF_RENDERER` from the user always wins.
|
||||
- A "does it boot?" log check does NOT catch this — grep for the GBM line, or
|
||||
@@ -296,7 +331,14 @@ touching the adjacent code.
|
||||
breaks `ng`.
|
||||
- ssh2's optional native deps must stay `external: ["cpu-features", "*.node"]`
|
||||
in `electrobun.config.ts` — bundling them breaks the build; ssh2 falls back
|
||||
to pure JS at runtime.
|
||||
to pure JS at runtime. (Inert without `mainProcess: "bun"` — see Electrobun.)
|
||||
- **2.x installers dropped the `stable-` prefix; the updater feed kept it.**
|
||||
`macos-arm64-miniSCP.dmg` / `win-x64-miniSCP-Setup.zip` /
|
||||
`linux-x64-miniSCP-Setup.tar.gz` next to `stable-<os>-<arch>-update.json`,
|
||||
`.tar.zst` and `.patch`. Anything that globs `artifacts/stable-*` misses the
|
||||
installers — release.ts collects by `<os>-<arch>-`, deploy.sh publishes
|
||||
`artifacts/*` verbatim (release.ts empties `artifacts/` first), and the site
|
||||
links the new names.
|
||||
- `bun run --cwd ui build` (flag order matters; `bun --cwd ui run build` does
|
||||
not do what you want).
|
||||
- `version.gen.ts` is gitignored and regenerated by postinstall + every build
|
||||
|
||||
Reference in New Issue
Block a user