Skip to content

Commit 475bea6

Browse files
fix(web): restoring many saved folders no longer overflows the stack
Restoring a saved folder calls item.expand(), which notifies the tree's subscribers synchronously and re-entered the reconcile callback once per folder. With ~1,500 saved siblings that nesting threw "Maximum call stack size exceeded". Calls made during a pass now rerun the pass after it returns instead of nesting. AI-assisted; verified with the tests and tools listed above.
1 parent 8d776a4 commit 475bea6

2 files changed

Lines changed: 25 additions & 2 deletions

File tree

‎apps/web/src/components/files/FileBrowserPanel.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { joinHostPath } from "./filePath";
3030
import { createFileTreeDragMentionController } from "./fileTreeDragMention";
3131
import {
3232
areAllDirectoriesExpanded,
33+
coalesceReentrantCalls,
3334
readExpandedDirectories,
3435
setAllDirectoriesExpanded,
3536
writeExpandedDirectories,
@@ -358,7 +359,8 @@ export default function FileBrowserPanel({
358359
for (const path of expandedPathsRef.current) {
359360
if (!currentPaths.has(path)) expandedPathsRef.current.delete(path);
360361
}
361-
const loadExpanded = () => {
362+
// Restoring calls item.expand(), which notifies subscribers synchronously.
363+
const loadExpanded = coalesceReentrantCalls(() => {
362364
if (model.isSearchOpen()) return;
363365
let changed = false;
364366
for (const path of directoryPaths) {
@@ -383,7 +385,7 @@ export default function FileBrowserPanel({
383385
...pendingExpandedPaths,
384386
]);
385387
}
386-
};
388+
});
387389
loadExpanded();
388390
return model.subscribe(loadExpanded);
389391
}, [directoryPaths, expansionRoot, load, model, pendingExpandedPaths]);

‎apps/web/src/components/files/fileTreeExpansion.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,27 @@ export function setAllDirectoriesExpanded(
5858
}
5959
}
6060

61+
/** Calls made while `run` is running rerun it once it returns, instead of nesting. */
62+
export function coalesceReentrantCalls(run: () => void): () => void {
63+
let running = false;
64+
let calledAgain = false;
65+
return () => {
66+
if (running) {
67+
calledAgain = true;
68+
return;
69+
}
70+
running = true;
71+
try {
72+
do {
73+
calledAgain = false;
74+
run();
75+
} while (calledAgain);
76+
} finally {
77+
running = false;
78+
}
79+
};
80+
}
81+
6182
const EXPANDED_DIRECTORIES_STORAGE_KEY = "t3code.fileTreeExpandedDirectories";
6283
// Roots are kept most recent first and capped so worktrees that come and go do not grow storage.
6384
const MAX_STORED_ROOTS = 20;

0 commit comments

Comments
 (0)