From fb7c390698dfc81fa71f537ab38ffcd82a42d8a0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:38:46 +0000 Subject: [PATCH] perf: optimize search and filtering loop with pre-calculation and early returns - Added `prepareSearchIndex` to pre-calculate a lowercased `_searchStr` for each PDF during initial load. - Refactored `pdfDatabase.filter` in `renderPDFs` to use explicit early returns, breaking out of conditions instantly upon failure. - Replaced multiple per-keystroke `toLowerCase` calls inside the filter loop with a single pre-calculated `_searchStr` substring check. - Added `.jules/bolt.md` to log performance learnings. Co-authored-by: MrAlokTech <107493955+MrAlokTech@users.noreply.github.com> --- .jules/bolt.md | 3 +++ script.js | 66 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..9cb3caf --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-20 - Pre-calculate derived search strings and use early returns +**Learning:** In a pure vanilla JS client-side filtering loop, grouping multiple boolean conditions (like `matchesSemester && matchesClass && matchesCategory && matchesSearch`) forces all conditions to be evaluated for every item, even if the first condition fails. Furthermore, doing string manipulations (like `.toLowerCase()`) on multiple properties inside the `filter` callback on every keystroke causes significant CPU overhead for large lists. +**Action:** Always use explicit early returns (`if (!condition) return false;`) to short-circuit the loop as soon as possible. Move expensive string processing out of the render loop by calculating a derived `_searchStr` property once during the initial data load, reducing the per-keystroke filter to a simple, pre-computed substring check. diff --git a/script.js b/script.js index 22f399d..3e76cd2 100644 --- a/script.js +++ b/script.js @@ -338,6 +338,40 @@ function getAdData(slotName) { /* ========================================= 5. DATA LOADING WITH CACHING ========================================= */ + +// Optimize search by pre-calculating search strings and derived properties once during load +function prepareSearchIndex(dataList) { + const now = new Date(); + dataList.forEach(item => { + // 1. Lowercase search string concatenation for fast O(1) matching later + const title = item.title || ''; + const desc = item.description || ''; + const cat = item.category || ''; + const author = item.author || ''; + item._searchStr = `${title} ${desc} ${cat} ${author}`.toLowerCase(); + + // 2. Pre-calculate formatted date + if (item.uploadDate) { + let uploadDateObj; + if (item.uploadDate && typeof item.uploadDate.toDate === 'function') { + uploadDateObj = item.uploadDate.toDate(); + } else { + uploadDateObj = new Date(item.uploadDate); + } + item._formattedDate = uploadDateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + + // 3. Pre-calculate "isNew" flag (7 days) + const timeDiff = now - uploadDateObj; + item._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); + } else { + item._formattedDate = ''; + item._isNew = false; + } + }); +} + function renderSemesterTabs() { const container = document.getElementById('semesterTabsContainer'); if (!container) return; @@ -454,6 +488,8 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + // ⚡ Bolt Optimization: Prepare search index + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -477,6 +513,10 @@ async function loadPDFDatabase() { data: pdfDatabase })); + // ⚡ Bolt Optimization: Prepare search index AFTER saving to cache + // to avoid bloating localStorage with derived properties. + prepareSearchIndex(pdfDatabase); + // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderPDFs(); @@ -905,26 +945,22 @@ function renderPDFs() { // Locate renderPDFs() in script.js and update the filter section const filteredPdfs = pdfDatabase.filter(pdf => { - const matchesSemester = pdf.semester === currentSemester; - - // NEW: Check if the PDF class matches the UI's current class selection - // Note: If old documents don't have this field, they will be hidden. - const matchesClass = pdf.class === currentClass; + // ⚡ Bolt Optimization: Early returns and pre-calculated search string + if (pdf.class !== currentClass) return false; + if (pdf.semester !== currentSemester) return false; - let matchesCategory = false; if (currentCategory === 'favorites') { - matchesCategory = favorites.includes(pdf.id); - } else { - matchesCategory = currentCategory === 'all' || pdf.category === currentCategory; + if (!favorites.includes(pdf.id)) return false; + } else if (currentCategory !== 'all') { + if (pdf.category !== currentCategory) return false; } - const matchesSearch = pdf.title.toLowerCase().includes(searchTerm) || - pdf.description.toLowerCase().includes(searchTerm) || - pdf.category.toLowerCase().includes(searchTerm) || - pdf.author.toLowerCase().includes(searchTerm); + if (searchTerm) { + // Use pre-calculated lowercase search string + if (pdf._searchStr && !pdf._searchStr.includes(searchTerm)) return false; + } - // Update return statement to include matchesClass - return matchesSemester && matchesClass && matchesCategory && matchesSearch; + return true; }); updatePDFCount(filteredPdfs.length);