A Keyboard section in the Settings window edits the shared keymap: rows by group with a search field, a chip per key (x removes), + to record, reset per row and Reset all. Recording takes the keydown before every other key in the window (Cmd+W records instead of closing), refuses reserved combos without preventDefault (Cmd+Q still quits), requires a chord or F-key for editor commands, and offers Replace when another command in an overlapping scope owns the key. Rows flag conflicts and plain character keys that would stop starting a type-to-filter query. Fixed keys are listed read-only, in the platform's spelling. Remaps are ordinary settings (only rows that differ from the defaults), so every window, the menu hints and the tooltips follow live — and the macOS menu bar: its accelerators are built from the keymap (only the combos Electrobun's parser expresses faithfully) and rebuilt on a remap. A main-window menu action fired from the editor or Settings window brings the main window forward first. Also: the "Enter / double-click on a file" setting under File list (do nothing / open local files in their default app), arrow keys in the Settings sidebar with focus on open and a hand-walked Tab, translations for all seven languages (the spec now checks every language labels every command), and a README shortcut table generated from the keymap. Co-Authored-By: Claude <noreply@anthropic.com>
193 lines
12 KiB
Markdown
193 lines
12 KiB
Markdown
# Platform notes — macOS / Windows / Linux behavior matrix
|
||
|
||
macOS (Apple Silicon) is the primary target; Windows 11+ x64 and Linux x64 are
|
||
**beta** targets (win-arm64 and linux-arm64 deferred). **Intel Macs were
|
||
dropped** with the move to Electrobun 2.x, which publishes no macOS x64 core.
|
||
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 chromeOptions` | hiddenInset + traffic-light offset, UI drag header | native titlebar, compact header | same as win |
|
||
| Scrollbars | `ui/src/styles.css` (`::-webkit-scrollbar`) | custom square, always visible (styling them turns off overlay scrollbars) | custom square | custom square |
|
||
| 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 | `src/shared/keymap.ts formatCombo` (+ `keys.names.*`) | ⌘⇧⌥⌃ glyphs: ⇧⌘N | Ctrl+Shift+N, localized key names (Strg+Entf) | as Windows |
|
||
| Command modifier | `keymap.ts` `Mod` | ⌘ only (⌃ stays Cocoa's text keys) | Ctrl | Ctrl |
|
||
| Key equivalents | `shortcuts.service.ts claim` | page first, unclaimed → app menu | page only (no app menu) | page only |
|
||
| Tab focus | `core/focus-cycle.ts cycleFocus` | WKWebView skips buttons/checkboxes without Keyboard navigation → walked by hand | native order (same walk) | native order (same walk) |
|
||
| Reload keys | `platform.ts blockReloadKeys` | — | WebView2 reloads on F5 / Ctrl+R → swallowed when unclaimed | — |
|
||
| 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; accelerators follow the keymap (remaps included), ⌘Q fixed | none (early return) — Settings opens from the tab-bar gear (Ctrl+,), About from its About section | 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/shadlike/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://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. (Hard link and path verified on
|
||
1.18.1 — re-check both on the first 2.x Linux build.)
|
||
|
||
**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 `.hutch/devkit/api/sdks/main/proc/linux.md`, still in
|
||
2.0.2; 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. The dev launches
|
||
(`scripts/linux-gpu.ts`) set `WEBKIT_DISABLE_DMABUF_RENDERER=1` when
|
||
`/proc/driver/nvidia` exists; an installed build (the official Setup
|
||
installer) does not — launch it with that variable set (e.g. in the desktop
|
||
entry's `Exec`). 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; Electrobun's own installer**: the release artifact is
|
||
`linux-x64-miniSCP-Setup.tar.gz` — a self-extracting `installer` + README
|
||
(2.x does the desktop-entry packaging and ships an uninstaller); no AppImage
|
||
or `.deb`. Layout unverified until the first 2.x Linux build.
|
||
|
||
## 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-Setup.tar.gz`; unpack it
|
||
elsewhere, run its `installer`, then launch the installed app (on NVIDIA with
|
||
`WEBKIT_DISABLE_DMABUF_RENDERER=1`).
|
||
|
||
## Windows known issues (upstream electrobun, documented not fixed)
|
||
|
||
- **HiDPI blur** — WebView2 renders at 1x on scaled displays
|
||
(blackboardsh/electrobun#324, seen on 1.18.1). Most user-visible defect; 2.x
|
||
lists per-monitor DPI setup among its Windows fixes — re-check on the first
|
||
2.x Windows build.
|
||
- **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 (2.x:
|
||
"Windows release signing is not currently part of Hutch's packaging
|
||
pipeline"; a signtool post-hook is an option later). Installer unpacks to
|
||
`%LOCALAPPDATA%`, desktop shortcut only; 2.x installer builds add an
|
||
uninstaller (`%LOCALAPPDATA%\<identifier>\<install-root>\uninstall.exe`).
|
||
- `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 —
|
||
Hutch, the core and the pinned Bun download into `~/.hutch` 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). A v1 0.1.78 install must pick up
|
||
the published 2.x build through its updater (the bridge release).
|
||
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/Settings windows open, modal bounce works, Ctrl+, opens
|
||
Settings and About shows from its About section.
|
||
10. Known-issue check: HiDPI blur on a scaled display (seen on 1.18.1 — 2.x's
|
||
per-monitor DPI setup may have fixed it).
|