From ff95ac0ce40298e0590d0dd5708fb8db6ecf26b7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:51:03 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvement]?= =?UTF-8?q?=20Pre-index=20search=20strings=20and=20dates=20for=20O(1)=20fi?= =?UTF-8?q?ltering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: MrAlokTech <107493955+MrAlokTech@users.noreply.github.com> --- .jules/bolt.md | 3 ++ script.js | 87 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..0b266b8 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - [Optimize List Filtering and Card Rendering] +**Learning:** Instantiating `Date` objects and concatenating strings inside a filter/render loop for large datasets creates significant CPU overhead and garbage collection pauses. +**Action:** Always pre-index derived strings (like lowercased text for search) and pre-calculate expensive derivations like `Intl.DateTimeFormat` into the initial data loading pipeline (`prepareSearchIndex`) rather than calculating them inline in high-frequency rendering functions. diff --git a/script.js b/script.js index ef463ba..96e14a1 100644 --- a/script.js +++ b/script.js @@ -374,6 +374,38 @@ function getAdData(slotName) { /* ========================================= 5. DATA LOADING WITH CACHING ========================================= */ + +/** + * Pre-calculates derived properties for search and UI rendering to avoid expensive + * runtime recalculations during list filtering and rendering loops. + */ +function prepareSearchIndex() { + const now = new Date(); + const dateFormatter = new Intl.DateTimeFormat('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + + pdfDatabase.forEach(pdf => { + // Pre-calculate lowercased search string once + pdf._searchStr = `${pdf.title || ''} ${pdf.description || ''} ${pdf.category || ''} ${pdf.author || ''}`.toLowerCase(); + + // Pre-calculate date-based UI states + if (pdf.uploadDate) { + const uploadDateObj = new Date(pdf.uploadDate); + if (!isNaN(uploadDateObj)) { + pdf._isNew = (now - uploadDateObj) < (7 * 24 * 60 * 60 * 1000); + pdf._formattedDate = dateFormatter.format(uploadDateObj); + } else { + pdf._isNew = false; + pdf._formattedDate = 'Unknown Date'; + } + } else { + pdf._isNew = false; + pdf._formattedDate = 'Unknown Date'; + } + }); +} + function renderSemesterTabs() { const container = document.getElementById('semesterTabsContainer'); if (!container) return; @@ -490,6 +522,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(); // Bolt: Pre-calculate index for cached data // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -508,13 +541,18 @@ async function loadPDFDatabase() { pdfDatabase.push({ id: doc.id, ...doc.data() }); }); + // Bolt: Save to cache FIRST before adding non-serializable derived properties localStorage.setItem(CACHE_KEY, JSON.stringify({ timestamp: new Date().getTime(), data: pdfDatabase })); + prepareSearchIndex(); // Bolt: Pre-calculate index for fresh data + // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); + renderSemesterTabs(); // Fix missing call in fresh fetch block + renderCategoryFilters(); // Fix missing call in fresh fetch block renderPDFs(); hidePreloader(); @@ -948,26 +986,23 @@ 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: Optimize filtering with early returns and pre-calculated properties + if (pdf.semester !== currentSemester) return false; + if (pdf.class !== currentClass) return false; - let matchesCategory = false; if (currentCategory === 'favorites') { - matchesCategory = favorites.includes(pdf.id); + if (!favorites.includes(pdf.id)) return false; } else { - matchesCategory = currentCategory === 'all' || pdf.category === currentCategory; + if (currentCategory !== 'all' && 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) { + // Truthiness guard before calling .includes + if (!pdf._searchStr) return false; + if (!pdf._searchStr.includes(searchTerm)) return false; + } - // Update return statement to include matchesClass - return matchesSemester && matchesClass && matchesCategory && matchesSearch; + return true; }); updatePDFCount(filteredPdfs.length); @@ -1037,9 +1072,14 @@ function createPDFCard(pdf, favoritesList, index = 0, highlightRegex = null) { const heartIconClass = isFav ? 'fas' : 'far'; const btnActiveClass = isFav ? 'active' : ''; - const uploadDateObj = new Date(pdf.uploadDate); - const timeDiff = new Date() - uploadDateObj; - const isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days + // Bolt: Use pre-calculated _isNew with fallback for unindexed items + let isNew = pdf._isNew; + if (isNew === undefined && pdf.uploadDate) { + const uploadDateObj = new Date(pdf.uploadDate); + isNew = (new Date() - uploadDateObj) < (7 * 24 * 60 * 60 * 1000); // 7 days + } else if (isNew === undefined) { + isNew = false; + } const newBadgeHTML = isNew ? `NEW` @@ -1053,10 +1093,15 @@ function createPDFCard(pdf, favoritesList, index = 0, highlightRegex = null) { }; const categoryIcon = categoryIcons[pdf.category] || 'fa-file-pdf'; - // Formatting Date - const formattedDate = new Date(pdf.uploadDate).toLocaleDateString('en-US', { - year: 'numeric', month: 'short', day: 'numeric' - }); + // Bolt: Use pre-calculated _formattedDate with fallback + let formattedDate = pdf._formattedDate; + if (!formattedDate && pdf.uploadDate) { + formattedDate = new Date(pdf.uploadDate).toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + } else if (!formattedDate) { + formattedDate = 'Unknown Date'; + } // Uses global escapeHtml() now