Commit Graph
6 Commits
Author SHA1 Message Date
JokinandClaude Fable 5 de75fb1480 Fix data loss: copying a file onto itself via two sessions truncated it to 0
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>
2026-07-11 21:01:20 +02:00
JokinandClaude Fable 5 eaa2d00115 Finished-transfers setting + transfer-list header with Clear finished
- 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>
2026-07-11 19:56:24 +02:00
JokinandClaude Fable 5 20e5703d29 '..' parent row in the file list
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>
2026-07-11 19:14:24 +02:00
JokinandClaude Fable 5 6048d5c814 v1.2: overwrite confirm, transfer speed/ETA, settings + themes, fused macOS titlebar
- Overwrite confirmation: name-collision detection before enqueue; dialog shows
  source vs destination (size, mtime, 'newer' badge) with per-item Skip/Replace
  and batch Skip all / Replace all; type mismatches auto-skip with a toast;
  cancel aborts the whole operation
- Transfer speed + ETA: UI computes smoothed bytes/sec from progress deltas
  (callback bus makes deltas meaningful); shown in the expanded queue rows and
  the status-bar summary; unit follows the speed-unit setting
- Settings: bun-side SettingsStore (settings.json, 0600, atomic) + get/save RPC;
  SettingsService; gear popover on the tab row (theme segmented control, speed
  unit); persisted across restarts
- Light theme: data-theme on <html> drives token overrides; absent = follow OS
- macOS fused titlebar: hiddenInset + trafficLightOffset, tab row becomes the
  draggable titlebar (app-region drag/no-drag classes); other platforms keep a
  separate titlebar (gated by a platform value injected via window preload)
- README: known-issues note for the upstream Bun/Electrobun FFI crash

Verified: tsc + ui build clean; SettingsStore roundtrip + 0600 headless;
app boots and the UI fetches settings over RPC at startup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 17:14:50 +02:00
JokinandClaude Fable 5 9eae6c58dc v1.1: fix transfer freeze, session tabs, per-panel toolbars, status bar
Freeze root cause: TransfersService consumed transferUpdate through an effect
that both read and wrote the jobMap signal — the first progress message spun
the effect into an infinite re-run loop, pegging the webview thread while the
copy completed fine bun-side. Push messages now flow through plain callback
registries on RpcService (also stops signal coalescing from dropping progress
snapshots). Verified live: a 60MB boot-time self-test copy streamed progress
into the running UI and the post-completion panel refreshes arrived.

- Tabs: workspaces (left+right PanelState pair each) in PanelsService; tab bar
  replaces the header; sessions persist in background tabs; Cmd+T/Cmd+W/Ctrl+Tab;
  closing a tab disconnects its sessions; last tab can't close
- Per-panel toolbar (top, icon-only): direction-explicit copy (-> / <-),
  rename, new folder/file, chmod, delete; panel footers removed
- VSCode-style status bar: transfer summary (spinner/percent/queued/failures,
  click to expand the job list) + active panel info; function bar removed
- transfer details list is collapsible, auto-expands on failure

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:50:19 +02:00
JokinandClaude Fable 5 88af44acfc P3: transfer engine with queue, progress, cancel + transfer bar UI
- TransferEngine: FIFO queue, provider-generic stream pipeline (covers all four
  panel combos), throttled transferUpdate push, cancel via stream destroy,
  self-copy guards, failSession hook for dead SSH sessions
- TransfersService + TransferBar: per-job rows, progress bars, cancel/dismiss,
  auto-dismiss completed, dst panel refresh
- F5 + Copy button wired to active-panel selection
- verified: 30MB nested tree copy (UTF-8 names, empty dirs), self-copy rejection,
  mid-transfer cancel leaving partial file

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 13:46:34 +02:00