From d3649235acec84e8a53d8f401d6106634fc773ea Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:02:44 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Cache=20workspace=20sta?= =?UTF-8?q?tic=20analysis=20scanner=20with=20WeakMap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimize workspace static analysis parsing in FileSystemContext and BottomPanel. By leveraging a WeakMap cache keyed on immutable FileSystemItem references, unmodified files are skipped during scanning. This avoids repetitive string splits and regex evaluations on keypresses and keeps the UI responsive in larger projects. Co-authored-by: beingniloy <235952944+beingniloy@users.noreply.github.com> --- .jules/bolt.md | 3 ++ package-lock.json | 8 +++-- src/components/BottomPanel.tsx | 58 ++++++++++++++++++++++-------- src/contexts/FileSystemContext.tsx | 50 ++++++++++++++++++-------- 4 files changed, 87 insertions(+), 32 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..24d688a --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2026-07-17 - [Static Analysis Scan Cache] +**Learning:** Workspace static analysis recursively crawls all project files, performing regex and string scanning for warnings (e.g. empty blocks, `console.log`, TODO/FIXME). When typing, React's state is updated and the entire file tree is re-scanned repeatedly on every keystroke, which causes significant performance lag and blocking UI in large workspaces. +**Action:** Use a `WeakMap` to cache computed static analysis results per-item based on the immutable `FileSystemItem` object references. When React does an immutable update, unchanged files retain their reference and bypass re-scanning by using the cache, while only modified files (with new references) are re-evaluated. Old references are automatically garbage collected. diff --git a/package-lock.json b/package-lock.json index 09a1b7d..0a15c4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "electron-updater": "^6.3.9", "jszip": "^3.10.1", "lucide-react": "^0.469.0", - "node-pty": "^1.1.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -27,6 +26,9 @@ "electron-builder": "^25.1.8", "typescript": "^5.6.3", "vite": "^5.4.10" + }, + "optionalDependencies": { + "node-pty": "^1.1.0" } }, "node_modules/@babel/code-frame": { @@ -5608,7 +5610,8 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/node-api-version": { "version": "0.2.1", @@ -5678,6 +5681,7 @@ "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { "node-addon-api": "^7.1.0" } diff --git a/src/components/BottomPanel.tsx b/src/components/BottomPanel.tsx index af89007..a53b8e2 100644 --- a/src/components/BottomPanel.tsx +++ b/src/components/BottomPanel.tsx @@ -9,6 +9,14 @@ import XTerminal from './XTerminal'; import { useContextMenu } from '../hooks/useContextMenu'; import ContextMenu from './ContextMenu'; +interface ProblemItem { + file: string; + path: string; + line: number; + message: string; + severity: 'error' | 'warning' | 'info'; +} + export default React.memo(function BottomPanel() { const { setBottomPanelOpen } = useLayout(); const onClose = useCallback(() => setBottomPanelOpen(false), [setBottomPanelOpen]); @@ -32,30 +40,50 @@ export default React.memo(function BottomPanel() { const { menu, menuRef, showMenu, hideMenu } = useContextMenu(); + // Performance Cache: Map each immutable FileSystemItem reference to its static analysis problem details + // Avoids re-scanning unmodified files, dramatically improving responsiveness. + const problemsCache = useMemo(() => new WeakMap(), []); + // Static analysis scanner const problems = useMemo(() => { - const list: Array<{ file: string; path: string; line: number; message: string; severity: 'error' | 'warning' | 'info' }> = []; + const list: ProblemItem[] = []; const scan = (items: FileSystemItem[]) => { for (const item of items) { - if (item.isFolder && item.children) { scan(item.children); continue; } - if (!item.isFolder && item.content) { - item.content.split('\n').forEach((lineText, idx) => { - if (lineText.includes('TODO')) { - list.push({ file: item.name, path: item.path, line: idx + 1, message: lineText.substring(lineText.indexOf('TODO')).replace(/^TODO:?\s*/, '') || 'TODO item', severity: 'info' }); - } - if (/\{\s*\}/.test(lineText) && !lineText.includes('=>') && !lineText.includes('const')) { - list.push({ file: item.name, path: item.path, line: idx + 1, message: 'Empty block detected', severity: 'warning' }); - } - if (lineText.includes('console.log')) { - list.push({ file: item.name, path: item.path, line: idx + 1, message: 'Remove console.log before production', severity: 'warning' }); - } - }); + if (item.isFolder && item.children) { + scan(item.children); + continue; + } + if (!item.isFolder) { + // Check WeakMap cache first + const cached = problemsCache.get(item); + if (cached) { + list.push(...cached); + continue; + } + + const fileProblems: ProblemItem[] = []; + if (item.content) { + item.content.split('\n').forEach((lineText, idx) => { + if (lineText.includes('TODO')) { + fileProblems.push({ file: item.name, path: item.path, line: idx + 1, message: lineText.substring(lineText.indexOf('TODO')).replace(/^TODO:?\s*/, '') || 'TODO item', severity: 'info' }); + } + if (/\{\s*\}/.test(lineText) && !lineText.includes('=>') && !lineText.includes('const')) { + fileProblems.push({ file: item.name, path: item.path, line: idx + 1, message: 'Empty block detected', severity: 'warning' }); + } + if (lineText.includes('console.log')) { + fileProblems.push({ file: item.name, path: item.path, line: idx + 1, message: 'Remove console.log before production', severity: 'warning' }); + } + }); + } + + problemsCache.set(item, fileProblems); + list.push(...fileProblems); } } }; scan(files); return list; - }, [files]); + }, [files, problemsCache]); useEffect(() => { if (window.electronAPI) { diff --git a/src/contexts/FileSystemContext.tsx b/src/contexts/FileSystemContext.tsx index 697afaa..f08e81e 100644 --- a/src/contexts/FileSystemContext.tsx +++ b/src/contexts/FileSystemContext.tsx @@ -86,6 +86,10 @@ export function FileSystemProvider({ children }: { children: ReactNode }) { const [clipboard, setClipboard] = useState(null); const [undoHistory, setUndoHistory] = useState({}); + // Performance Cache: Map each immutable FileSystemItem reference to its static analysis problem count + // Avoids re-computing empty block, console.log, TODO, and FIXME warnings on unmodified files + const problemsCountCache = useMemo(() => new WeakMap(), []); + const pushHistory = useCallback((path: string, oldContent: string) => { if (!path || path === 'welcome' || path.startsWith('docs/')) return; if (!oldContent) return; @@ -202,29 +206,45 @@ export function FileSystemProvider({ children }: { children: ReactNode }) { for (const item of items) { if (item.isFolder && item.children) { scan(item.children); - } else if (!item.isFolder && item.content) { - const lines = item.content.split('\n'); - for (const lineText of lines) { - if (/\{\s*\}/.test(lineText) && !lineText.includes('=>') && !lineText.includes('const')) { - warnings++; - } - if (lineText.includes('console.log')) { - warnings++; - } - if (lineText.includes('TODO')) { - warnings++; - } - if (lineText.includes('FIXME')) { - warnings++; + } else if (!item.isFolder) { + // Check WeakMap cache first + const cached = problemsCountCache.get(item); + if (cached) { + errors += cached.errors; + warnings += cached.warnings; + continue; + } + + let fileErrors = 0; + let fileWarnings = 0; + if (item.content) { + const lines = item.content.split('\n'); + for (const lineText of lines) { + if (/\{\s*\}/.test(lineText) && !lineText.includes('=>') && !lineText.includes('const')) { + fileWarnings++; + } + if (lineText.includes('console.log')) { + fileWarnings++; + } + if (lineText.includes('TODO')) { + fileWarnings++; + } + if (lineText.includes('FIXME')) { + fileWarnings++; + } } } + + problemsCountCache.set(item, { errors: fileErrors, warnings: fileWarnings }); + errors += fileErrors; + warnings += fileWarnings; } } }; scan(files); return { errors, warnings }; - }, [files]); + }, [files, problemsCountCache]); const handleFileSelect = useCallback(async (path: string) => { if (path === 'welcome') { From cbe02ec3f16721b940628d59d25d428c92cf9c9e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:04:25 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Cache=20workspace=20sta?= =?UTF-8?q?tic=20analysis=20scanner=20with=20WeakMap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimize workspace static analysis parsing in FileSystemContext and BottomPanel. By leveraging a WeakMap cache keyed on immutable FileSystemItem references, unmodified files are skipped during scanning. This avoids repetitive string splits and regex evaluations on keypresses and keeps the UI responsive in larger projects. Co-authored-by: beingniloy <235952944+beingniloy@users.noreply.github.com> From b939aced940f4aed8f1b1bdf4d3664bf2de8634f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:30:47 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Cache=20workspace=20sta?= =?UTF-8?q?tic=20analysis=20scanner=20with=20WeakMap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimize workspace static analysis parsing in FileSystemContext and BottomPanel. By leveraging a WeakMap cache keyed on immutable FileSystemItem references, unmodified files are skipped during scanning. This avoids repetitive string splits and regex evaluations on keypresses and keeps the UI responsive in larger projects. Co-authored-by: beingniloy <235952944+beingniloy@users.noreply.github.com>