The win7 skin is frameless so 7.css can draw the Aero caption, and on GTK that means undecorated, which takes the window manager's resize borders and corner grips with it. All three win7 windows were completely unresizable. electrobun 1.18.1 offers nothing to re-enable, verified against the shipped binary rather than assumed: libNativeWrapper exports startWindowMove/ stopWindowMove with no resize counterpart, imports gtk_window_begin_move_drag but not gtk_window_begin_resize_drag, dragRegions.ts implements moving only, and BrowserWindow has neither setResizable nor min-size options. But the window is still programmatically resizable, so only the input affordance is missing: eight handles overlay the frame and drag it through a new windowGetFrame/windowSetFrame pair on the shared windowControlHandlers factory, which gives all three window kinds resize with no per-window wiring. Three things that would have made this quietly wrong: - dragRegions.ts starts a window MOVE from a mousedown listener on document, never checks defaultPrevented, and is not suppressed by a pointerdown handler either (the compatibility mousedown still fires). Every handle carries app-region-no-drag, which short-circuits its check first. - deltas come from screenX, not clientX: a north/west resize moves the window origin under a stationary pointer, so client coords feed that back into the next delta and the window runs away. - the min-size clamp re-anchors the opposite edge; clamping width alone lets x keep tracking the pointer, walking the window off-screen while it looks frozen. This is a stopgap and the point is that it is removable on sight. The frame math is a dependency-free module so scripts/resize-math-probe.ts asserts all eight edges without a window (verified it catches the unanchored-clamp bug: x jumps to 5100 instead of parking at 460). scripts/check-electrobun-resize.ts scans the installed electrobun for the primitives this is waiting on and prints the removal checklist when they appear; it runs from postinstall so a version bump surfaces it, never fails an install, and is silent otherwise. Both trigger paths were simulated. The component header carries the full checklist; docs cover the platform consequence and the dragRegions traps. Harness diffs exactly 0 in both themes; hover and menu probes still clean. Not yet exercised with a real pointer — that needs a live window. Co-Authored-By: Claude <noreply@anthropic.com>
202 lines
12 KiB
Markdown
202 lines
12 KiB
Markdown
# 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 (modern skin) | `src/bun/windows.ts`, `index.ts` | hiddenInset + traffic-light offset, UI drag header | native titlebar, compact header | same as win |
|
||
| Window chrome (win7 skin) | `src/bun/windows.ts chromeOptions` | `hidden` (clears Titled → no traffic lights) | frameless | `gtk_window_set_decorated(false)` |
|
||
| Window **resize** | `ui/.../7like/window-resize/` | native frame resizes; win7 frameless uses the synthesized handles | as mac | as mac |
|
||
| 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/skins/*/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`).
|
||
|
||
### Frameless windows lose the WM's resize borders (win7 skin, every platform)
|
||
|
||
`titleBarStyle: "hidden"` is what lets 7.css draw the Aero caption — on Linux the
|
||
GTK wrapper honours it with `gtk_window_set_decorated(false)`. Undecorating also
|
||
removes the window manager's resize borders and corner grips, and electrobun
|
||
1.18.1 offers **no** native resize gesture to put back: the shipped
|
||
`libNativeWrapper` exports `startWindowMove`/`stopWindowMove` with no resize
|
||
counterpart, does not link `gtk_window_begin_resize_drag`, and `BrowserWindow`
|
||
has neither `setResizable` nor min-size constructor options. The window is still
|
||
*programmatically* resizable (`setFrame`/`getFrame` work, and
|
||
`gtk_window_set_resizable(false)` is never called), so the app **synthesizes the
|
||
input affordance**: eight handles in the webview drag the native frame over RPC
|
||
(`ui/src/app/skins/7like/window-resize/`).
|
||
|
||
That is a **stopgap** — each drag tick is an RPC hop plus an FFI call, where a
|
||
real WM grab is compositor-side. `scripts/check-electrobun-resize.ts` watches the
|
||
installed electrobun for the missing primitives and prints the removal checklist
|
||
when they appear; it runs from `postinstall`, so an electrobun bump surfaces it
|
||
without anyone remembering to look. The modern skin keeps the native frame and
|
||
needs none of this.
|
||
|
||
## 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://app`** WKWebView 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 dev` inflates 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 `entries` arrays; 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:
|
||
|
||
```sh
|
||
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](https://extensions.gnome.org/extension/615/appindicator-support/).
|
||
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 `showContextMenu` are
|
||
both no-ops (electrobun's `dist/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=1` when `/proc/driver/nvidia` exists —
|
||
`scripts/linux-gpu.ts` for dev, the `miniSCP.sh` wrapper in the tarball for
|
||
releases. The app can't do it itself (Bun's `process.env` writes don't reach
|
||
child processes); see [gotchas.md](gotchas.md#linux).
|
||
- **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`, `.desktop` entry or icon installation. Unpack and run
|
||
`miniSCP.sh` (which execs `bin/launcher`).
|
||
|
||
## First-Linux-boot smoke test
|
||
|
||
1. 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 for
|
||
`Failed to create GBM buffer` in the log), and the tray icon appears (or
|
||
doesn't, on bare GNOME — that's the extension caveat, not a failure).
|
||
2. 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.
|
||
3. Right-click empty panel space (New Folder / New File / Refresh / Show in File
|
||
Manager) and a column header (checkmarks toggle columns live).
|
||
4. 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`.
|
||
5. local↔remote transfers both directions; external editor round-trip
|
||
(temp download → save → auto-upload) with the inotify watcher.
|
||
6. Move to Trash lands the item in the desktop's trash; connections.json is 0600.
|
||
7. `bun scripts/release.ts --target linux-x64` produces
|
||
`release/linux-x64/miniSCP-Build<N>-linux-x64.tar.gz`; unpack it elsewhere and
|
||
run `miniSCP.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.
|
||
- `hiddenInset` degrades to `hidden` on Windows — irrelevant here because
|
||
non-mac windows use the native titlebar.
|
||
- `findInPage` is a `window.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:
|
||
|
||
1. Build: `bun scripts/release.ts --target win-x64` on the Windows machine —
|
||
core binaries download from GitHub releases on first run; Setup.zip AND
|
||
the portable zip appear under `release/win-x64/` (the portable step warns
|
||
instead of failing if the assembled-app folder layout differs — fix the
|
||
`appDir` path in release.ts if so).
|
||
2. App boots, tray icon renders colored, window has a native titlebar.
|
||
3. Drives dropdown in each panel lists `C:\` (+ others); navigating to a
|
||
drive root hides the `'..'` row; the path bar shows consistent `\` paths.
|
||
4. local↔remote transfers both directions; self-copy guard still blocks
|
||
same-path copies.
|
||
5. 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).
|
||
6. 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.
|
||
7. Ctrl+T/W/F/S and Ctrl+digit tab jumps; tooltips read "Ctrl+…".
|
||
8. connections.json ACL: `icacls connections.json` shows only the current
|
||
user after saving a connection.
|
||
9. Editor/manager windows open, modal bounce works, About shows from the
|
||
options popover.
|
||
10. Known-issue confirmation: HiDPI blur on a scaled display (expected until
|
||
upstream fixes it).
|