P1: typed RPC bridge + FileSystemProvider + dual-panel local browsing

- shared/types + shared/rpc: full wire contract (Result envelope, no exceptions cross the bridge)
- FileSystemProvider interface, LocalProvider, SessionRegistry with built-in 'local'
- walk.ts: provider-generic recursive walk + removeRecursive
- rpc-handlers wired into BrowserWindow; 10min request timeout for slow SFTP ops
- Angular: RpcService (Electroview bridge + plain-browser guard), PanelsService with
  signals-based PanelState (sort/cursor/selection), FilePanel component with
  keyboard nav (arrows/Space/Enter/Backspace/Tab), path bar, toasts
- verified: both panels listDir home over the bridge in the running app

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jokin
2026-07-11 13:35:02 +02:00
co-authored by Claude Fable 5
parent 8092cb0c08
commit a150c5ee2e
17 changed files with 1062 additions and 15 deletions
+40 -12
View File
@@ -1,17 +1,45 @@
<div class="flex h-screen flex-col">
<header class="flex items-center gap-2 border-b border-border px-4 py-2">
<ng-icon name="mynaArrowLeftRight" class="text-muted-foreground" />
<h1 class="text-sm font-semibold">{{ appName }}</h1>
<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>
</header>
<main class="flex flex-1 items-center justify-center">
<div class="rounded-lg border border-border bg-card p-6 text-card-foreground shadow-sm">
<div class="flex items-center gap-3">
<ng-icon name="mynaHardDrive" size="1.5rem" class="text-primary" />
<ng-icon name="mynaArrowLeftRight" size="1.25rem" class="text-muted-foreground" />
<ng-icon name="mynaServers" size="1.5rem" class="text-primary" />
@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">
<ng-icon name="mynaXCircle" size="1.5rem" class="text-destructive" />
<p class="mt-2 text-sm font-medium">Not connected to the app</p>
<p class="mt-1 text-xs text-muted-foreground">
This page is running in a plain browser. Launch the desktop app to use it.
</p>
</div>
<p class="mt-3 text-sm text-muted-foreground">P0 skeleton — panels arrive in P1.</p>
</div>
</main>
} @else {
<main class="flex min-h-0 flex-1 divide-x divide-border">
<app-file-panel side="left" />
<app-file-panel side="right" />
</main>
}
<!-- toasts -->
<div class="pointer-events-none fixed right-3 bottom-3 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"
>
<ng-icon
[name]="toastIcon(t.kind)"
size="1rem"
[class]="t.kind === 'error' ? 'shrink-0 text-destructive' : 'shrink-0 text-success'"
/>
<span class="min-w-0 flex-1 wrap-break-word">{{ t.text }}</span>
<button
(click)="toast.dismiss(t.id)"
class="shrink-0 text-muted-foreground hover:text-foreground"
>
<ng-icon name="mynaX" size="0.9rem" />
</button>
</div>
}
</div>
</div>