From bfb25f08ba0c975e39652f019bf0b64cba268765 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:26:18 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Pre-calculate=20expensive?= =?UTF-8?q?=20values=20for=20faster=20render=20loops?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `prepareSearchIndex` to pre-calculate `_searchStr`, `_isNew`, and `_formattedDate`. - Optimize `renderPDFs` filter to use early returns and `_searchStr` for O(1) attribute matching and single-string searching. - Optimize `createPDFCard` to consume pre-calculated properties instead of re-evaluating `Date` parsing on every render cycle. - Document performance learning in `.jules/bolt.md`. Co-authored-by: MrAlokTech <107493955+MrAlokTech@users.noreply.github.com> --- .jules/bolt.md | 3 +++ bolt_test.js | 15 +++++++++++ perf_test.js | 34 +++++++++++++++++++++++ script.js | 66 ++++++++++++++++++++++++++++++--------------- search_perf_test.js | 36 +++++++++++++++++++++++++ 5 files changed, 133 insertions(+), 21 deletions(-) create mode 100644 .jules/bolt.md create mode 100644 bolt_test.js create mode 100644 perf_test.js create mode 100644 search_perf_test.js diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..1f07c2b --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - [Optimize render loop by pre-calculating expensive values] +**Learning:** Repetitive Date parsing (e.g., `new Date()`) and string concatenations for search filtering inside high-frequency render loops (like `renderPDFs` and `createPDFCard`) significantly block the main thread and impact frontend performance. Firestore Timestamp objects add an extra layer of complexity as they must be parsed differently (`.toDate()`) than cached JSON string dates. +**Action:** Implement a `prepareSearchIndex` step that runs immediately after data load to pre-calculate these expensive values (`_searchStr`, `_formattedDate`, `_isNew`) onto the `pdfDatabase` objects. This dramatically speeds up subsequent array filtering and DOM generation. diff --git a/bolt_test.js b/bolt_test.js new file mode 100644 index 0000000..30faa21 --- /dev/null +++ b/bolt_test.js @@ -0,0 +1,15 @@ +const fs = require('fs'); +let code = fs.readFileSync('script.js', 'utf8'); + +// Mock DOM +const jsdom = require("jsdom"); +const { JSDOM } = jsdom; +const dom = new JSDOM(`
`); +global.document = dom.window.document; +global.window = dom.window; +global.localStorage = { + getItem: () => null, + setItem: () => {} +}; + +console.log('Setup ready'); diff --git a/perf_test.js b/perf_test.js new file mode 100644 index 0000000..b19f4f4 --- /dev/null +++ b/perf_test.js @@ -0,0 +1,34 @@ +const pdfs = []; +for (let i = 0; i < 1000; i++) { + pdfs.push({ uploadDate: "2023-10-15T12:00:00Z" }); +} + +console.time('date_format'); +for(let i = 0; i < pdfs.length; i++) { + const uploadDateObj = new Date(pdfs[i].uploadDate); + const timeDiff = new Date() - uploadDateObj; + const isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); + const formattedDate = new Date(pdfs[i].uploadDate).toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); +} +console.timeEnd('date_format'); + +console.time('precalc'); +// Simulate pre-calculated property +pdfs.forEach(p => { + const uploadDateObj = new Date(p.uploadDate); + const timeDiff = new Date() - uploadDateObj; + p._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); + p._formattedDate = new Date(p.uploadDate).toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); +}); +console.timeEnd('precalc'); + +console.time('precalc_read'); +for(let i = 0; i < pdfs.length; i++) { + const isNew = pdfs[i]._isNew; + const formattedDate = pdfs[i]._formattedDate; +} +console.timeEnd('precalc_read'); diff --git a/script.js b/script.js index bdc06f3..5997e06 100644 --- a/script.js +++ b/script.js @@ -335,6 +335,35 @@ function getAdData(slotName) { /* ========================================= 5. DATA LOADING WITH CACHING ========================================= */ + +function prepareSearchIndex() { + const now = new Date(); + const sevenDays = 7 * 24 * 60 * 60 * 1000; + + pdfDatabase.forEach(pdf => { + // 1. Search String (lowercased concatenation for fast indexOf) + pdf._searchStr = `${pdf.title || ''} ${pdf.description || ''} ${pdf.category || ''} ${pdf.author || ''}`.toLowerCase(); + + // 2. Date parsing (Handle both Firestore Timestamp and Cached ISO String) + let uploadDateObj; + if (pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function') { + uploadDateObj = pdf.uploadDate.toDate(); + // Optional: convert back to ISO string if we need consistency + pdf.uploadDate = uploadDateObj.toISOString(); + } else { + uploadDateObj = new Date(pdf.uploadDate); + } + + // 3. Pre-calculate "New" badge status + const timeDiff = now - uploadDateObj; + pdf._isNew = timeDiff < sevenDays; + + // 4. Pre-format date string + pdf._formattedDate = uploadDateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + }); +} function renderSemesterTabs() { const container = document.getElementById('semesterTabsContainer'); if (!container) return; @@ -451,6 +480,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -469,6 +499,8 @@ async function loadPDFDatabase() { pdfDatabase.push({ id: doc.id, ...doc.data() }); }); + prepareSearchIndex(); + localStorage.setItem(CACHE_KEY, JSON.stringify({ timestamp: new Date().getTime(), data: pdfDatabase @@ -902,26 +934,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; + // Fast early returns for exact matches + if (pdf.semester !== currentSemester) return false; + if (pdf.class !== currentClass) 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); + // Use pre-computed search string + if (searchTerm && (!pdf._searchStr || !pdf._searchStr.includes(searchTerm))) { + return false; + } - // Update return statement to include matchesClass - return matchesSemester && matchesClass && matchesCategory && matchesSearch; + return true; }); updatePDFCount(filteredPdfs.length); @@ -991,9 +1019,7 @@ 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 + const isNew = pdf._isNew; const newBadgeHTML = isNew ? `NEW` @@ -1008,9 +1034,7 @@ 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' - }); + const formattedDate = pdf._formattedDate; // Uses global escapeHtml() now diff --git a/search_perf_test.js b/search_perf_test.js new file mode 100644 index 0000000..9aa920e --- /dev/null +++ b/search_perf_test.js @@ -0,0 +1,36 @@ +const pdfs = []; +for (let i = 0; i < 1000; i++) { + pdfs.push({ + title: "Introduction to Organic Chemistry", + description: "A comprehensive guide to organic chemistry principles.", + category: "Organic", + author: "John Doe" + }); +} + +const searchTerm = "organic"; + +console.time('search_split'); +for(let i = 0; i < 10; i++) { + pdfs.filter(pdf => { + return pdf.title.toLowerCase().includes(searchTerm) || + pdf.description.toLowerCase().includes(searchTerm) || + pdf.category.toLowerCase().includes(searchTerm) || + pdf.author.toLowerCase().includes(searchTerm); + }); +} +console.timeEnd('search_split'); + +console.time('precalc_searchStr'); +pdfs.forEach(p => { + p._searchStr = `${p.title} ${p.description} ${p.category} ${p.author}`.toLowerCase(); +}); +console.timeEnd('precalc_searchStr'); + +console.time('search_precalc'); +for(let i = 0; i < 10; i++) { + pdfs.filter(pdf => { + return pdf._searchStr.includes(searchTerm); + }); +} +console.timeEnd('search_precalc');