Skip to content

Commit 1f786a1

Browse files
committed
fix: stabilize disconnecting faulty folders
1 parent 70f7dcd commit 1f786a1

3 files changed

Lines changed: 46 additions & 11 deletions

File tree

packages/core/src/components/filebrowser.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ const t = await i18n(import.meta.glob('./filebrowser*.json'));
3030

3131
const WORKSPACE_CHANGED_DEBOUNCE_MS = 250;
3232

33+
/** `wa-tree-item` exposes `.model` for the bound {@link TreeNode} (not in generated element types). */
34+
type WaTreeItemElement = HTMLElement & { model?: TreeNode };
35+
3336
@customElement('lyra-filebrowser')
3437
export class LyraFileBrowser extends LyraPart {
3538

@@ -259,11 +262,11 @@ export class LyraFileBrowser extends LyraPart {
259262
try {
260263
const info = await workspaceService.getFolderInfoForDirectory(resource);
261264
if (buildGen !== this.treeBuildGeneration) {
262-
(node as any).loaded = !node.leaf;
265+
node.loaded = !node.leaf;
263266
return node;
264267
}
265268
if (info?.backendName) {
266-
(node as any).workspaceTag = info.backendName;
269+
node.workspaceTag = info.backendName;
267270
}
268271
} catch (e) {
269272
logger.debug('Failed to get workspace info for directory', e);
@@ -274,7 +277,7 @@ export class LyraFileBrowser extends LyraPart {
274277
try {
275278
const children = await resource.listChildren(forceRefreshChildren);
276279
if (buildGen !== this.treeBuildGeneration) {
277-
(node as any).loaded = true;
280+
node.loaded = true;
278281
return node;
279282
}
280283
for (const childResource of children) {
@@ -283,15 +286,19 @@ export class LyraFileBrowser extends LyraPart {
283286
}
284287
const child = await this.resourceToTreeNode(childResource, true, forceRefreshChildren);
285288
if (buildGen !== this.treeBuildGeneration) {
286-
(node as any).loaded = true;
289+
node.loaded = true;
287290
return node;
288291
}
289292
node.children.push(child);
290293
}
291294
node.children.sort(treeNodeComparator);
292295
} catch (error) {
293296
if (buildGen !== this.treeBuildGeneration) {
294-
(node as any).loaded = true;
297+
node.loaded = true;
298+
return node;
299+
}
300+
if (!(await workspaceService.isResourceInCurrentWorkspace(resource))) {
301+
node.loaded = true;
295302
return node;
296303
}
297304
const detail = error instanceof Error ? error.message : String(error);
@@ -300,7 +307,7 @@ export class LyraFileBrowser extends LyraPart {
300307
}
301308
// Mark directory as loaded even if it has no children or loading failed,
302309
// so empty folders don't stay in a perpetual "loading" state.
303-
(node as any).loaded = true;
310+
node.loaded = true;
304311
}
305312

306313
if (resource instanceof UnavailableWorkspaceFolderDirectory) {
@@ -318,14 +325,14 @@ export class LyraFileBrowser extends LyraPart {
318325
// Treat a directory as "lazy" only if its children
319326
// have never been loaded. Once loaded (even if empty),
320327
// we clear the lazy flag so the progress ring disappears.
321-
const isLazy = !node.leaf && !(node as any).loaded;
328+
const isLazy = !node.leaf && !node.loaded;
322329
const resource = node.data as Resource;
323330
const isFile = resource instanceof File;
324331
const isDraggable = !!resource.getParent();
325332
const iconSpec = isFile
326333
? editorRegistry.getFileIcon(resource.getName())
327334
: (node.icon || "folder-open");
328-
const workspaceTag = (node as any).workspaceTag as string | undefined;
335+
const workspaceTag = node.workspaceTag;
329336
const loadError = node.loadError;
330337
const placeholderNotice = node.placeholderNotice;
331338
const issueText = loadError
@@ -447,12 +454,17 @@ export class LyraFileBrowser extends LyraPart {
447454
node.children.sort(treeNodeComparator);
448455
// Children have now been loaded at least once (even if none remain
449456
// after filtering), so mark the node as loaded to disable lazy mode.
450-
(node as any).loaded = true;
457+
node.loaded = true;
451458
this.requestUpdate();
452459
} catch (error) {
453460
if (buildGen !== this.treeBuildGeneration) {
454461
return;
455462
}
463+
if (!(await workspaceService.isResourceInCurrentWorkspace(resource))) {
464+
node.loaded = true;
465+
this.requestUpdate();
466+
return;
467+
}
456468
const detail = error instanceof Error ? error.message : String(error);
457469
node.loadError = detail;
458470
logger.error('Failed to load directory children:', error);
@@ -488,7 +500,8 @@ export class LyraFileBrowser extends LyraPart {
488500
const node: TreeNode = selection[0].model
489501
const data = node.data
490502
activeSelectionSignal.set(data)
491-
const path = (data as any)?.getWorkspacePath?.();
503+
const resource = data as Resource;
504+
const path = resource?.getWorkspacePath?.();
492505
if (typeof path === 'string') {
493506
this.persistSelectedPath(path);
494507
} else {
@@ -522,7 +535,7 @@ export class LyraFileBrowser extends LyraPart {
522535
return singleRootResource instanceof Directory ? singleRootResource : undefined;
523536
}
524537

525-
const node: TreeNode | undefined = (treeItem as any).model;
538+
const node: TreeNode | undefined = (treeItem as WaTreeItemElement).model;
526539
const resource = node?.data as Resource | undefined;
527540
if (resource instanceof Directory) {
528541
return resource;

packages/core/src/core/filesys/common.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,26 @@ export class WorkspaceService {
656656
return { name, type: folder.type, backendName };
657657
}
658658

659+
/**
660+
* Whether {@link resource} still belongs to the current workspace (walks up to the backend root).
661+
* After {@link disconnectFolder}, the root is no longer listed, so in-flight listChildren errors can be ignored.
662+
*/
663+
async isResourceInCurrentWorkspace(resource: Resource): Promise<boolean> {
664+
await this.initPromise;
665+
let current: Resource | undefined = resource;
666+
while (current) {
667+
const parent = current.getParent();
668+
if (!parent) {
669+
if (!(current instanceof Directory)) {
670+
return false;
671+
}
672+
return this.folders.some(f => f.directory === current);
673+
}
674+
current = parent;
675+
}
676+
return false;
677+
}
678+
659679
/**
660680
* Update persisted metadata for a workspace root directory.
661681
* Currently used to keep display names of roots (e.g. IndexedDB) in sync

packages/core/src/core/tree-utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export interface TreeNode {
1818
loadError?: string;
1919
/** Persisted workspace folder that could not be restored (see UnavailableWorkspaceFolderDirectory). */
2020
placeholderNotice?: string;
21+
/** Backend label for workspace roots (e.g. `webdav`, `indexeddb`). */
22+
workspaceTag?: string;
2123
}
2224

2325
export const treeNodeComparator = (c1: TreeNode, c2: TreeNode) => {

0 commit comments

Comments
 (0)