Skip to content

Commit 8085508

Browse files
fix: implement batch Nanostores updates in FilesStore
Batches file system updates to occur every 500ms instead of per-file, dramatically reducing React re-render cascades during rapid file creation. This reduces UI freezing when AI generates many files simultaneously. Part of Phase 2 fix for issue #59.
1 parent e29fe10 commit 8085508

1 file changed

Lines changed: 30 additions & 7 deletions

File tree

app/lib/stores/files.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ export class FilesStore {
5454

5555
files: MapStore<FileMap> = import.meta.hot?.data.files ?? map({});
5656

57+
#updateBatchTimeout: number | null = null;
58+
59+
#pendingUpdates: FileMap = {};
60+
5761
get filesCount() {
5862
return this.#size;
5963
}
@@ -640,6 +644,23 @@ export class FilesStore {
640644
}
641645
}
642646

647+
#scheduleBatchUpdate() {
648+
if (this.#updateBatchTimeout !== null) {
649+
return;
650+
}
651+
652+
this.#updateBatchTimeout = self.setTimeout(() => {
653+
const updates = this.#pendingUpdates;
654+
this.#pendingUpdates = {};
655+
this.#updateBatchTimeout = null;
656+
657+
if (Object.keys(updates).length > 0) {
658+
const currentFiles = this.files.get();
659+
this.files.set({ ...currentFiles, ...updates });
660+
}
661+
}, 500);
662+
}
663+
643664
#processEventBuffer(events: Array<[events: PathWatcherEvent[]]>) {
644665
const watchEvents = events.flat(2);
645666

@@ -668,15 +689,15 @@ export class FilesStore {
668689
switch (type) {
669690
case 'add_dir': {
670691
// we intentionally add a trailing slash so we can distinguish files from folders in the file tree
671-
this.files.setKey(sanitizedPath, { type: 'folder' });
692+
this.#pendingUpdates[sanitizedPath] = { type: 'folder' };
672693
break;
673694
}
674695
case 'remove_dir': {
675-
this.files.setKey(sanitizedPath, undefined);
696+
this.#pendingUpdates[sanitizedPath] = undefined;
676697

677-
for (const [direntPath] of Object.entries(this.files)) {
698+
for (const [direntPath] of Object.entries(this.files.get())) {
678699
if (direntPath.startsWith(sanitizedPath)) {
679-
this.files.setKey(direntPath, undefined);
700+
this.#pendingUpdates[direntPath] = undefined;
680701
}
681702
}
682703

@@ -715,17 +736,17 @@ export class FilesStore {
715736
// Preserve lock state if the file already exists
716737
const isLocked = existingFile?.type === 'file' ? existingFile.isLocked : false;
717738

718-
this.files.setKey(sanitizedPath, {
739+
this.#pendingUpdates[sanitizedPath] = {
719740
type: 'file',
720741
content,
721742
isBinary,
722743
isLocked,
723-
});
744+
};
724745
break;
725746
}
726747
case 'remove_file': {
727748
this.#size--;
728-
this.files.setKey(sanitizedPath, undefined);
749+
this.#pendingUpdates[sanitizedPath] = undefined;
729750
break;
730751
}
731752
case 'update_directory': {
@@ -734,6 +755,8 @@ export class FilesStore {
734755
}
735756
}
736757
}
758+
759+
this.#scheduleBatchUpdate();
737760
}
738761

739762
#decodeFileContent(buffer?: Uint8Array) {

0 commit comments

Comments
 (0)