Windows compatibility pass (beta target)

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>
This commit is contained in:
Jokin
2026-07-14 00:02:42 +02:00
co-authored by Claude Fable 5
parent d80d5c23dc
commit c7271fb7cf
23 changed files with 223 additions and 57 deletions
+20 -7
View File
@@ -63,13 +63,15 @@
>
<ng-icon name="mynaFilePlus" size="var(--action-icon)" />
</button>
<button
(click)="run('chmod')"
title="Permissions"
class="grid size-(--action-h) shrink-0 place-items-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="mynaLock" size="var(--action-icon)" />
</button>
@if (chmodAvailable()) {
<button
(click)="run('chmod')"
title="Permissions"
class="grid size-(--action-h) shrink-0 place-items-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
>
<ng-icon name="mynaLock" size="var(--action-icon)" />
</button>
}
<button
(click)="run('delete')"
title="Delete (F8 / Del)"
@@ -95,6 +97,17 @@
}
</button>
<!-- Windows drive roots (empty list elsewhere) -->
@for (drive of drives(); track drive) {
<button
(click)="pickDrive(drive)"
class="flex w-full items-center gap-2 py-1.5 pr-3 pl-6 text-left text-(length:--path-font) hover:bg-accent"
>
<ng-icon name="mynaHardDrive" size="var(--action-icon)" class="shrink-0 text-muted-foreground" />
<span class="flex-1 font-mono">{{ drive }}</span>
</button>
}
@if (connections.grouped().loose.length || connections.grouped().stacks.length) {
<div class="my-1 border-t border-border"></div>
}
+16
View File
@@ -16,6 +16,7 @@ import { FileActionsService } from '../file-actions.service';
import { fileGlyph, fileIconClass, fileKind } from '../file-icons';
import { formatDate, formatMode, formatSize } from '../format';
import { PanelsService, type Side, type SortKey } from '../panels.service';
import { IS_WINDOWS } from '../platform';
import { RpcService } from '../rpc.service';
import { SettingsService } from '../settings.service';
import { TransfersService } from '../transfers.service';
@@ -56,6 +57,10 @@ export class FilePanel {
readonly pathDraft = signal('');
readonly menuOpen = signal(false);
/** Windows drive roots for the source picker; always empty elsewhere. */
readonly drives = signal<string[]>([]);
/** chmod on Windows local files only flips the read-only bit — hide it. */
readonly chmodAvailable = computed(() => !(IS_WINDOWS && !this.isRemote()));
private readonly listEl = viewChild.required<ElementRef<HTMLDivElement>>('list');
private lastClickedIndex = 0;
@@ -79,7 +84,18 @@ export class FilePanel {
void this.rpc.request.showColumnsMenu({ visible: this.settings.columns() });
}
pickDrive(drive: string): void {
this.menuOpen.set(false);
this.activate();
void this.panel().load(drive);
}
constructor() {
if (IS_WINDOWS && this.rpc.available) {
void this.rpc.request.listDrives({}).then((res) => {
if (res.ok) this.drives.set(res.drives);
});
}
// keep the editable path bar in sync with the actual cwd
effect(() => this.pathDraft.set(this.panel().cwd()));
// focus follows the active side (Tab switches panels, dialogs refocus on close)