Skip to content

Commit 1aecdab

Browse files
committed
fix: right panel sftp
1 parent 8d6f9dc commit 1aecdab

4 files changed

Lines changed: 44 additions & 9 deletions

File tree

‎ui/components/RightPanel/sftpPanel.vue‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,20 @@ const panesById = computed(() => {
4545
}
4646
return panes;
4747
});
48+
// Every live pane is listed, including ones with no resolvable identity yet, so
49+
// the cache can tell "pane closed" from "details not loaded on this tick".
4850
const liveIdentities = computed(() => {
4951
const identities = new Map<string, string>();
5052
for (const [paneId, surface] of panesById.value) {
51-
const identity = compactSftpCacheIdentity({
53+
identities.set(
5254
paneId,
53-
protocol: surface.protocol,
54-
assetId: surface.assetId,
55-
account: surface.account
56-
});
57-
if (identity) identities.set(paneId, identity);
55+
compactSftpCacheIdentity({
56+
paneId,
57+
protocol: surface.protocol,
58+
assetId: surface.assetId,
59+
account: surface.account
60+
})
61+
);
5862
}
5963
return identities;
6064
});

‎ui/components/RightPanel/sftpSessionCache.test.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,17 @@ describe("nextSftpSessionCache", () => {
102102
expect(next).toEqual([]);
103103
});
104104

105+
it("keeps the surface while a live pane has no resolved identity", () => {
106+
const next = nextSftpSessionCache({
107+
cached: [{ paneId: paneA, identity: identityA }],
108+
identities: new Map([[paneA, ""]]),
109+
activePaneId: paneA,
110+
activeReady: true,
111+
sftpTabVisible: true
112+
});
113+
expect(next).toEqual([{ paneId: paneA, identity: identityA }]);
114+
});
115+
105116
it("drops a closed pane", () => {
106117
const next = nextSftpSessionCache({
107118
cached: [
@@ -150,7 +161,7 @@ describe("nextSftpSessionCache", () => {
150161
expect(next).toEqual([]);
151162
});
152163

153-
it("does not cache a non-SSH or missing pane", () => {
164+
it("does not cache a non-SSH or unresolved pane", () => {
154165
expect(
155166
nextSftpSessionCache({
156167
cached: [],

‎ui/components/RightPanel/sftpSessionCache.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,22 @@ export function compactSftpCacheIdentity(input: {
2121
/** ponytail: no LRU; bound is SSH panes that opened SFTP. */
2222
export function nextSftpSessionCache(input: {
2323
cached: CachedSftpPane[];
24+
/** Every live pane, including ones whose identity is not resolved yet. */
2425
identities: Map<string, string>;
2526
activePaneId: string;
2627
/** The koko session must exist before a surface can be created, never to keep one. */
2728
activeReady: boolean;
2829
sftpTabVisible: boolean;
2930
}): CachedSftpPane[] {
30-
const retained = input.cached.filter((entry) => input.identities.get(entry.paneId) === entry.identity);
31+
const retained = input.cached.filter((entry) => {
32+
// Pane closed.
33+
if (!input.identities.has(entry.paneId)) return false;
34+
const identity = input.identities.get(entry.paneId) || "";
35+
// Identity not resolved on this tick; keep the surface rather than
36+
// rebuilding it, which would drop the websocket and any transfer.
37+
if (!identity) return true;
38+
return identity === entry.identity;
39+
});
3140
if (!input.sftpTabVisible || !input.activeReady) return retained;
3241
const identity = input.identities.get(input.activePaneId) || "";
3342
if (!identity || retained.some((entry) => entry.paneId === input.activePaneId)) return retained;

‎ui/layouts/default.vue‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ const {
3434
statusBarVisible
3535
} = useSettingManager();
3636
const { open: rightPanelOpen, toggle: toggleRightPanel } = useRightPanel();
37+
// Mount on first open, then keep mounted. Unmounting the panel tears down its
38+
// KeepAlive cache, which drops the SFTP file manager's websocket and restarts
39+
// any transfer running in it.
40+
const rightPanelMounted = ref(false);
41+
watch(
42+
rightPanelOpen,
43+
(open) => {
44+
if (open) rightPanelMounted.value = true;
45+
},
46+
{ immediate: true }
47+
);
3748
const { open: aiPanelOpen, setOpen: setAiPanelOpen, toggleAi } = useAiPanel();
3849
const localePath = useLocalePath();
3950
const { open: settingsOpen, activeSection: activeSettingsSection, openSettings, closeSettings } = useSettingsWindow();
@@ -407,7 +418,7 @@ onBeforeUnmount(() => {
407418

408419
<template #rightPanel>
409420
<div v-if="isVideoPlayerRoute" id="offline-playlist-host" class="h-full min-h-0" />
410-
<RightPanel v-else-if="rightPanelOpen" />
421+
<RightPanel v-else-if="rightPanelMounted" v-show="rightPanelOpen" />
411422
</template>
412423

413424
<template #bottomPanel>

0 commit comments

Comments
 (0)