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>
This commit is contained in:
Jokin
2026-07-11 14:50:19 +02:00
co-authored by Claude Fable 5
parent 76b9b621a2
commit 9eae6c58dc
12 changed files with 410 additions and 158 deletions
+4 -62
View File
@@ -1,19 +1,4 @@
<div class="flex h-screen flex-col bg-background text-foreground">
<header class="flex items-center gap-2 border-b border-border px-3 py-1.5">
<ng-icon name="mynaArrowLeftRight" class="text-muted-foreground" size="0.9rem" />
<h1 class="text-[13px] font-semibold tracking-tight">{{ appName }}</h1>
<div class="flex-1"></div>
@if (rpc.available) {
<button
(click)="dialogs.manager.set(true)"
title="Manage connections"
class="grid size-6 place-items-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="mynaCog" size="0.95rem" />
</button>
}
</header>
@if (!rpc.available) {
<div class="flex flex-1 items-center justify-center">
<div class="max-w-sm rounded-lg border border-border bg-card p-6 text-center shadow-sm">
@@ -25,58 +10,15 @@
</div>
</div>
} @else {
<app-tab-bar />
<main class="flex min-h-0 flex-1 divide-x divide-border">
<app-file-panel side="left" />
<app-file-panel side="right" />
</main>
<app-transfer-bar />
<!-- function bar: acts on the active panel -->
<footer class="flex items-center gap-1 border-t border-border px-2 py-1">
<button
(click)="transfers.copyToOtherPanel()"
title="Copy to other panel (F5)"
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="mynaArrowLeftRight" size="0.9rem" /> Copy
</button>
<button
(click)="actions.rename()"
title="Rename (F2)"
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="mynaEdit" size="0.9rem" /> Rename
</button>
<button
(click)="actions.mkdir()"
title="New folder (F7)"
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="mynaFolderPlus" size="0.9rem" /> Folder
</button>
<button
(click)="actions.createFile()"
title="New file"
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="mynaFilePlus" size="0.9rem" /> File
</button>
<button
(click)="actions.chmod()"
title="Permissions"
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="mynaLock" size="0.9rem" /> Permissions
</button>
<button
(click)="actions.delete()"
title="Delete (F8 / Del)"
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-destructive"
>
<ng-icon name="mynaTrash" size="0.9rem" /> Delete
</button>
</footer>
<app-status-bar />
}
<!-- dialogs -->
@@ -94,7 +36,7 @@
}
<!-- toasts -->
<div class="pointer-events-none fixed right-3 bottom-3 z-50 flex w-80 flex-col gap-2">
<div class="pointer-events-none fixed right-3 bottom-8 z-50 flex w-80 flex-col gap-2">
@for (t of toast.toasts(); track t.id) {
<div
class="pointer-events-auto flex items-start gap-2 rounded-lg border border-border bg-card p-3 text-xs text-card-foreground shadow-lg"