bun run release -- --target mac-arm64|mac-x64|win-x64: gen version/icons,
build UI, electrobun build --env=stable, collect artifacts as
release/<target>/miniSCP-Build<n>-<target>.*. mac-x64 builds on the
arm64 Mac by running electrobun's x64 CLI under Rosetta 2 (verified:
x86_64 launcher in the produced app). Windows also zips a portable
build next to the Setup installer. Tag-triggered Gitea Actions matrix
included, inert until runners exist. legacyNames emptied — the BunSCP
migration has served its purpose.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
External editor via cmd/start + exe picker; Permissions hidden for
Windows-local (chmod only flips read-only there); Explorer/Recycle Bin
wording; Ctrl vs Cmd shortcut labels; separator-aware path joins +
drive-root detection; drives list in the panel source picker; icon.ico
+ colored tray icon; icacls ACL on connections.json; README Windows
(beta) section with upstream known issues and a smoke-test checklist.
Runtime paths pending first boot on a real Windows machine.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Build n = commit count, baked at build time into a gitignored
version.gen.ts (postinstall + all build scripts regenerate it). About
lives in the macOS app menu and as a footer row in the options popover
(the cross-platform path), showing build, commit, date and engines.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reload/Inspect Element no longer appear on uncontrolled right-clicks
in any window; text fields keep the system text menu.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Native checkable menu on the column headers toggles Size, Modified,
Kind, Rights, Owner and Group (persisted, both panels). Owner/group
come from the ls-style SFTP longname remotely and cached uid/gid
lookups locally (POSIX; undefined on Windows). All columns sort.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Local left, remote right — the commander convention. Panel-dropdown
connects still target the panel they were opened from.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Import moves to the window header; sidebar widens with a muted tint;
the stack row's count and action buttons swap on hover instead of
sharing the row, so long names can't collide with them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Single window centered over the main app. Electrobun has no native
modality, so it's approximated: bun bounces the main window's focus back
to the manager, the main webview shows a click-swallowing overlay, and
main-targeted menu/keyboard actions are dropped while open. Editor
windows stay fully usable. Connect/open-stack execute in the main
window via the existing connect: action channel; store edits push a
connectionsChanged refresh. Trigger moved from the panel dropdown to a
tab-bar button.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dialogs only needed refocus(); injecting PanelsService boots a whole
workspace, which secondary windows must not do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Real BrowserWindow per edited file with its own RPC instance, deduped by
sessionId+path. The same Angular bundle boots an editor-only root via
preload globals. Native close isn't cancellable in electrobun, so the
editor mirrors its draft to bun; traffic-light closes get a native
Save/Discard prompt after the fact, and failed saves park the draft in
the edit-leftovers dir. In-window closes (Cmd+W, Close) use a styled
prompt; the quit guard now also counts unsaved editors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Folders become Stacks (connections.json v2, no v1 migration): collapsible
stack-first list, open-stack-in-tabs (first reuses a pristine tab), drag
connections between stacks. More... reveals a per-connection Local
directory applied to the other panel when local and untouched. Import
reads a WinSCP.ini export incl. deobfuscated passwords.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Connecting blanks the list to a fading spinner with the target name and
a Cancel button (token discards late results and orphan sessions).
Listings >300ms dim the list under a spinner. All panel input is
blocked while busy; the source picker stays usable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tone row appears under Theme when the effective theme is dark.
Grey palette uses macOS-like #26262a background.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Action buttons, server picker, and its dropdown now follow the size
setting via new --action-h/--action-font/--action-icon variables.
Default tier matches the old fixed sizes; tab bar stays fixed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Read-only group below saved connections; hosts resolve HostName/User/
Port/IdentityFile (Include, wildcards, %-tokens) and connect on click.
New ssh-agent auth kind covers hosts without a usable identity file;
ProxyJump/ProxyCommand hosts are shown disabled.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Choose... button beside the private-key field opens the native file dialog
starting in ~/.ssh (Utils.openFileDialog via a pickKeyFile RPC).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- before-quit is blocked while jobs are queued/scanning/running; a native
message box asks 'N transfers are still running - quit anyway?' (Cancel
default) and only an explicit Quit Anyway proceeds via Utils.quit()
- when the window is unfocused, finished transfers raise a native
notification (silent for completions, sound + error message for failures)
- engine.hasActiveJobs()/activeJobCount() (truth table verified headless)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Native menus (electrobun ContextMenu) on rows and empty panel space. Rows:
Open (dirs navigate; local files open with the default app via Utils.openPath),
Edit, Copy to Other Panel, Rename, Permissions, Copy Path (works for remote
paths too), Reveal in Finder (local, Utils.showItemInFolder), Move to Trash
(local, Utils.moveToTrash — tooltip notes Finder Restore doesn't apply) and
Delete. Empty space / '..': New Folder, New File, Refresh, Reveal in Finder.
Right-click parks the cursor on the row (keeping the selection when clicking
inside it). Local shell actions run bun-side directly; everything else routes
through the existing menuAction channel back to the stored click target.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Typing while the list is focused builds a filter query: a bar appears under
the column headers (search icon, query, live match count, Esc hint, clear
button) and the listing narrows to substring matches — '..' always stays.
Backspace edits the query, Escape clears it, cursor parks on the first match,
arrows/Enter keep working (the list never loses focus), and Space keeps its
select meaning until a query exists. Filter resets on directory change and
survives refresh; operations act only on visible (filtered) selection.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1.8 -> 1.5 stroke weight and the lock body switches from solid fill to an
outline — the filled version read as a blob at menu-bar size.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The setImage-swapped progress frames lost the template treatment on macOS and
rendered black-on-dark once transfers started. Dial back per user decision:
the tray is now just the glyph (template image set once at construction, never
swapped) and clicking it activates the window — no menu, no progress effects.
Removes everything that only served the fancy version: progress frames +
generation, engine.aggregateProgress, the workspacesSync webview->bun channel
(schema, send proxy, panels effect, handlers) and the activate-tab action.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- AppTray: template menu-bar icon (22pt, adapts to light/dark). While
transfers run it swaps to pre-rendered frames — faded arrows + a fill bar
showing aggregate doneBytes/totalBytes of the queue (nearest 5%, throttled
500ms); back to the glyph when idle. No runtime image encoding.
- Menu: one entry per tab (checkmark on the active one) with live per-tab
transfer count and percentage (jobs mapped via session ids), an overall
progress row while transferring, Show <app>, Quit. Selecting a tab
activates the window and switches to it.
- First use of webview->bun messages: PanelsService pushes workspacesSync
(tab names, sessions, active id) via a new ready-gated send proxy; verified
end-to-end in the running app.
- engine.aggregateProgress(): totals + per-session breakdown over non-terminal
jobs (unit-verified idle/mid-transfer/idle).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Glyph composed from mynaui arrow-diagonal-one (both diagonals) with the
mini-lock of lock-square centered between them — the macOS squircle plays the
role of lock-square's outer square. Indigo gradient + white strokes + amber
lock face for the app icon; a pure black+alpha variant for the macOS template
tray icon, plus 21 pre-rendered tray progress frames (faded arrows + fill bar,
0-100% in 5% steps) for the upcoming tray feature.
Pipeline: scripts/gen-icons.ts renders every size directly from the SVGs with
@resvg/resvg-js (dev-only dep, prebuilt binaries, no postinstall); replaces
make-icon.py + gen-iconset.sh and the sips/iconutil path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two panels on the same server hold two SSH sessions by design, and every
self-copy guard compared sessionId — so same-server same-path copies slipped
through all of them, and createWriteStream truncated the destination before
the read began: the file (or every file in a folder) became 0 bytes, with the
overwrite dialog making it look like a legitimate replace.
Fix, defense in depth, keyed on FILESYSTEM identity instead of session:
- fsKey on every provider: 'local', or resolvedIP:port for SFTP (host is
DNS-resolved at connect so hostname-vs-IP aliases compare equal; username
excluded — same host:port is the same filesystem regardless of login)
- engine guard blocks equal/nested paths when fsKeys match; per-file
belt-and-suspenders check right before streams open
- probe layer for hidden aliases (different addresses, same machine): when
fsKeys differ but paths collide, a token file written via dst and read via
src decides; visible => same machine => blocked. SFTP exposes no dev/inode
(what cp/rsync use), so probing is the only deterministic test; fails open
and cleans up after itself
- UI: same-folder toast now fires across two sessions to one server, and the
overwrite dialog auto-skips (never offers Replace) when the colliding item
IS the source file
Known residual, documented in README 'Same-file protection': one filesystem
mounted at different paths on two connections is undetectable client-side.
Regression suite against the real box: file-onto-itself and folder-onto-itself
blocked with contents byte-intact, folder-into-descendant blocked, legit
same-server different-dir copies still work, local same-path still blocked,
simulated alias caught by the probe with cleanup verified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Container query on the panel (Tailwind v4 @container): Copy, Edit, Rename and
Delete grow labels at >=30rem panel width; New folder, New file and
Permissions stay icon-only (self-explanatory icons). Tooltips unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Built-in editor (default): large modal with monospace textarea, Cmd+S save,
dirty guard; reads/writes through the provider streams so it works on local
and remote files identically (2MB cap with a friendly error)
- External editor: remote files download to appDataDir/edits/<id>, open via
macOS `open -a`, the temp DIR is watched (survives atomic rename-saves) and
every save enqueues a normal upload job (progress/errors in the transfer
list); watchers + temp dirs are cleaned when the session disconnects
- Editor setting: Built-in | Custom with a NATIVE app picker (electrobun
openFileDialog, /Applications, .app filter) and reset-to-default
- Leftover temp files from a crash prompt for cleanup at boot
- F4, toolbar button (mynaFileText), and File > Edit File (Cmd+E) menu entry
- README: feature notes + Portability notes (open/start/xdg-open seam
documented in code and README per user request)
Verified headless against the box: sftp read/write roundtrip incl. UTF-8,
size cap, watch->save->auto-upload->remote-updated, session cleanup, orphan
listing and clearing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The concurrency setting now caps total concurrent file STREAMS, not just jobs:
a resizable counting semaphore is shared engine-wide, and each job copies its
files through a small worker pool drawing from it. One folder job can use the
whole budget; several jobs share it fairly; Single stays strictly serial.
Cancel/session-death tear down every in-flight stream of the job.
Verified headless: 6-file folder at budget 3 -> exactly 3 parallel streams;
budget 1 -> serial; two folder jobs never exceed the combined budget; cancel
mid-parallel leaves no straggler streams. Also demoed a real remote error
surfacing in a failed job (upload into /proc -> server message on the row).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Options popover: 'Finished transfers' Auto-clear | Keep (default auto-clear,
the previous behavior); persisted like the rest
- Transfer list header: 'N active · M finished' + Clear finished button
(dismisses completed/failed/cancelled only, never running/queued)
- Failed rows now name the file that failed alongside the error message
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Folder select showed '(none)' regardless of the actual folder (Angular sets
[value] on the select before its options render), so 'move out of folder'
silently saved the old folderId. Per-option [selected] bindings fix both the
display and the save.
- Folder rows gain 'Open all in tabs': each connection opens in its own new
tab with the left panel connecting (right stays local); manager closes.
- Folder-row action buttons no longer shift the row on hover (opacity toggle
instead of display toggle); also canonicalized accent-primary classes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Standard menus (App: hide/quit; Edit: clipboard roles enabling Cmd+C/V/X/Z;
View: Refresh Cmd+R + fullscreen; Window: minimize/zoom/cycle) plus File with
New/Close Tab (Cmd+T/W), New Folder/File, and a Connect submenu built from
saved connections (folders as nested submenus, Connection Manager entry).
Custom actions forward to the webview as menuAction messages; the menu rebuilds
whenever connections change. macOS-only per electrobun support.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Synthetic cursor-navigable first row (hidden at /) — Enter/double-click goes to
the parent like the toolbar button. Excluded from selection, rename, delete,
chmod, and transfers; shown with an up-arrow icon.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tabs live in their own min-w-0 overflow-x-auto container (scrollbar hidden,
trackpad scrolls); the new-tab button and the options gear sit outside it and
stay pinned. The active tab scrolls into view when it changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>