On the NVIDIA proprietary driver the app booted, logged "miniSCP started", reported no error — and painted a completely black window, with stderr showing "Failed to create GBM buffer of size WxH: Invalid argument" and an X11 GLXBadWindow. WebKitGTK's DMABUF renderer cannot allocate buffers there; WEBKIT_DISABLE_DMABUF_RENDERER=1 is the upstream workaround. Pre-existing on Linux, unrelated to the context-menu change — it just had never been looked at because nothing on Linux had been run before. 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), so setting it in src/bun/index.ts is inert even from a module imported before `electrobun/bun`. It has to be in the environment before the app process starts, which means every launch path must inject it: - scripts/linux-gpu.ts — shared `launchEnv()` helper, gated on /proc/driver/nvidia so Intel/AMD keep hardware compositing, and never overriding an explicit value from the user's environment. - scripts/dev.ts — uses it for `bun run dev`. - scripts/start.ts (new) — `bun run start` previously called `bunx electrobun dev` straight from package.json, where no env could be set; it now goes through a script like dev does. - scripts/release.ts — writes a `miniSCP.sh` wrapper beside bin/launcher in the Linux tarball, since a packaged app goes through none of the above. Documented in gotchas.md (including that a "does it boot?" log check does NOT catch this — the log looks healthy) and platform-notes.md, with the smoke test updated to require that the UI actually renders. Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean, and `bun run start` now boots with zero GBM/GLX errors. Co-Authored-By: Claude <noreply@anthropic.com>
10 KiB
Platform notes — macOS / Windows / Linux behavior matrix
macOS is the primary target; Windows 11+ x64 and Linux x64 are beta targets
(win-arm64 and linux-arm64 deferred). Every platform seam carries a
PORTABILITY: comment at the branch site.
Seam inventory (where platform branches live)
| Concern | File | mac | win | linux |
|---|---|---|---|---|
| Window chrome | src/bun/windows.ts, index.ts |
hiddenInset + traffic-light offset, UI drag header | native titlebar, compact header | same as win |
| External editor launch | src/bun/edits.ts launchEditor |
open [-a app] |
cmd /c start "" [app] file |
app directly / xdg-open |
| Editor app picker | rpc-handlers.ts pickEditorApp |
/Applications, .app |
%ProgramFiles%, .exe |
home, any |
| Owner/group names | fs/local.ts resolveId |
id -nu + dscacheutil |
undefined → "—" | id -nu + getent |
| Drive roots | fs/local.ts listLocalDrives |
empty | fsutil fsinfo drives |
empty |
| chmod/Permissions | context-menu.service.ts, file-panel, file-actions | full | hidden for LOCAL panels (only flips read-only); SFTP unaffected | full |
| Shell wording | ui context-menu.service.ts |
Reveal in Finder / Move to Trash | Show in Explorer / Move to Recycle Bin | Show in File Manager |
| Shortcut labels | ui platform.ts MOD_LABEL/ALT_LABEL |
⌘ / ⌥ | Ctrl+ / Alt+ | Ctrl+ / Alt+ |
| Tray | src/bun/tray.ts |
22 pt template PNG | 16 px colored PNG | colored (needs AppIndicator, see below) |
| App menu | src/bun/menu.ts |
full menu incl. About | none (early return) — About lives in the options popover | none (unsupported upstream) |
| connections.json protection | store/connections.ts |
chmod 0600 | icacls current-user ACL (0600 is meaningless) |
0600 |
| App data dir | store/paths.ts |
~/Library/Application Support | %APPDATA% | $XDG_CONFIG_HOME/.config |
| ssh-agent | fs/sftp.ts |
$SSH_AUTH_SOCK | \\.\pipe\openssh-ssh-agent fallback |
$SSH_AUTH_SOCK |
| Paths in UI | ui format.ts joinPath/isRootPath |
"/" | separator detected from the dir string; drive-root regex | "/" |
The context menu is HTML on every platform (ui/src/app/context-menu.ts +
context-menu.service.ts), not a native electrobun menu — see the Linux section
below for why. Only the three shell actions it can't do itself (reveal, open with
the default app, copy path) cross to bun, via the shellItemAction RPC.
Remote (SFTP) paths are ALWAYS POSIX regardless of host or server OS
(path.posix throughout fs/sftp.ts).
Memory footprint (expected, not a bug)
Investigated 2026-07 — packaged build. Roughly ~200 MB macOS / ~300 MB Windows (Linux sits near macOS: WebKitGTK, like WKWebView, is lighter than Chromium), which is normal for a Bun + system-WebView app (30 MB is native / no-web-engine territory; Tauri-class apps run ~80–200 MB, Electron 150–400 MB). Not reducible to tens of MB without dropping the web UI.
- macOS splits across several "miniSCP" processes: the Bun main (~120 MB:
backend + the bundled electrobun runtime + live SSH sessions — the largest and
least reducible piece), the
views://appWKWebView content process (~60 MB, the Angular UI), and the standard WebKit Networking / Graphics and Media helpers. All are one app; WebKit just names them per-role. - Windows is higher because WebView2 is Chromium (multi-process: browser + GPU + renderer + utility + crashpad), vs macOS's lighter WKWebKit.
- Measure against a packaged build —
bun run devinflates the WebContent heap (ng-serve, unminified, sourcemaps, live-reload) and is not representative. - Verified non-issues: transfers stream (no whole-file buffering), icons tree-shaken to ~33 glyphs, zoneless Angular, 364 KB prod bundle.
- Only real growth levers if this ever matters: the file list is not
virtualized (a directory with tens of thousands of entries retains a DOM row
each), and each open tab keeps both panels' full
entriesarrays; terminal transfers are retained until cleared. None affect normal use.
Linux runtime dependencies (REQUIRED — the app will not start without them)
electrobun's libNativeWrapper.so links AppIndicator hard for the tray, so a
missing library is a hard boot failure (ERR_DLOPEN_FAILED out of bun:ffi
dlopen), not a degraded tray. The transitive deps (libdbusmenu-glib,
libdbusmenu-gtk3, libayatana-indicator3, libayatana-ido3) come in as
package dependencies:
| Distro | Install |
|---|---|
| Arch / CachyOS | sudo pacman -S libayatana-appindicator |
| Debian / Ubuntu | sudo apt install libayatana-appindicator3-1 |
| Fedora | sudo dnf install libayatana-appindicator-gtk3 |
GTK/WebKit itself comes from the distro too — electrobun uses the system webview
(bundleCEF: false), so webkit2gtk must be present.
Diagnosing a boot failure:
ldd build/dev-linux-x64/*/bin/libNativeWrapper.so | grep 'not found'
libasar.so => not found in that output is expected and harmless — it ships
in the same bin/ folder and resolves at load time; only an unresolved
libayatana-appindicator3.so.1 is fatal.
Tray visibility is a separate matter from linking: GNOME 3.26+ hides the legacy tray, so the icon needs the AppIndicator extension. KDE Plasma, XFCE, MATE and Cinnamon show it out of the box. The app runs fine either way — the tray is a convenience, and every action it offers exists in-app.
Linux known issues (upstream electrobun)
- No native menus of any kind. Application menus and
showContextMenuare both no-ops (electrobun'sdist/api/bun/proc/linux.md; the app logs "Application menus are not supported on Linux" at boot). The app menubar was already macOS-only, and the panel context menus are now HTML on every platform, so nothing is missing at the UI level — but do not add a feature that depends on a native menu. - NVIDIA renders black without the DMABUF workaround. WebKitGTK can't
allocate GBM buffers on the proprietary driver, so the window paints nothing
while the log looks perfectly healthy. Every launch path sets
WEBKIT_DISABLE_DMABUF_RENDERER=1when/proc/driver/nvidiaexists —scripts/linux-gpu.tsfor dev, theminiSCP.shwrapper in the tarball for releases. The app can't do it itself (Bun'sprocess.envwrites don't reach child processes); see gotchas.md. - Benign startup noise: a deprecation warning pointing at
libayatana-appindicator-glib. Not fatal. - Unsigned, unpackaged: the release artifact is a portable tarball — no
AppImage,
.deb,.desktopentry or icon installation. Unpack and runminiSCP.sh(which execsbin/launcher).
First-Linux-boot smoke test
- Install the AppIndicator package for your distro (table above), then
bun run start. The window opens and actually renders the UI (a black window on NVIDIA means the DMABUF workaround didn't apply — check forFailed to create GBM bufferin the log), and the tray icon appears (or doesn't, on bare GNOME — that's the extension caveat, not a failure). - Right-click a file row: menu appears at the pointer, Send to Other Panel is first, Permissions is present, Escape / click-outside / arrow keys + Enter all work, and a right-click near the bottom-right flips the menu instead of overflowing.
- Right-click empty panel space (New Folder / New File / Refresh / Show in File Manager) and a column header (checkmarks toggle columns live).
- Copy Path puts the path on the clipboard; Show in File Manager opens the
distro's file manager; Open (local file) launches the default app via
xdg-open. - local↔remote transfers both directions; external editor round-trip (temp download → save → auto-upload) with the inotify watcher.
- Move to Trash lands the item in the desktop's trash; connections.json is 0600.
bun scripts/release.ts --target linux-x64producesrelease/linux-x64/miniSCP-Build<N>-linux-x64.tar.gz; unpack it elsewhere and runminiSCP.sh.
Windows known issues (upstream electrobun, documented not fixed)
- HiDPI blur — WebView2 renders at 1x on scaled displays (blackboardsh/electrobun#324, open). Most user-visible defect; wait for upstream DPI awareness.
- Notifications are legacy balloon style (
Shell_NotifyIcon), not Windows toasts. - Unsigned binaries — Setup.exe and the portable exe both trigger
SmartScreen / Mark-of-the-Web on first run. No Authenticode support in
electrobun 1.18.1 (signtool post-hook or electrobun-builder are options
later). Installer unpacks to
%LOCALAPPDATA%, desktop shortcut only — no Start Menu entry or uninstaller. hiddenInsetdegrades tohiddenon Windows — irrelevant here because non-mac windows use the native titlebar.findInPageis awindow.find()shim on WebView2 — irrelevant here, the editor find is custom.
First-Windows-boot smoke test
Code paths written blind (no Windows box was available); verify in order:
- Build:
bun scripts/release.ts --target win-x64on the Windows machine — core binaries download from GitHub releases on first run; Setup.zip AND the portable zip appear underrelease/win-x64/(the portable step warns instead of failing if the assembled-app folder layout differs — fix theappDirpath in release.ts if so). - App boots, tray icon renders colored, window has a native titlebar.
- Drives dropdown in each panel lists
C:\(+ others); navigating to a drive root hides the'..'row; the path bar shows consistent\paths. - local↔remote transfers both directions; self-copy guard still blocks same-path copies.
- External editor: default app opens via
cmd /c start; the Choose… picker starts in Program Files and accepts an .exe; remote temp-edit → save → auto-upload works (directory watcher on NTFS). - Context menus: Send to Other Panel is the first row item; Show in Explorer, Move to Recycle Bin (item restorable from the bin), Copy Path; Permissions ABSENT on local rows, present on remote.
- Ctrl+T/W/F/S and Ctrl+digit tab jumps; tooltips read "Ctrl+…".
- connections.json ACL:
icacls connections.jsonshows only the current user after saving a connection. - Editor/manager windows open, modal bounce works, About shows from the options popover.
- Known-issue confirmation: HiDPI blur on a scaled display (expected until upstream fixes it).