diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..ade3548 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,4 @@ + +## 2025-02-17 - Pre-calculating Expensive Runtime Properties for Render Loops +**Learning:** Instantiating `new Date()`, date arithmetic, and multiple `toLowerCase()` string concatenations inside a hot loop (`renderPDFs` filter and `createPDFCard` mapping) creates a significant CPU bottleneck on the main thread, especially as the size of `pdfDatabase` grows. +**Action:** Move all static and predictable calculations (search string indexing, date formatting, and boolean states like `_isNew`) to an initialization step (`prepareSearchIndex`) executed exactly once per document during data loading. The render loop can then fast-path using cheap boolean/string property lookups. diff --git a/bolt_test.js b/bolt_test.js new file mode 100644 index 0000000..c043b4d --- /dev/null +++ b/bolt_test.js @@ -0,0 +1,10 @@ +const fs = require('fs'); +const script = fs.readFileSync('script.js', 'utf8'); + +// We just do syntax checking +try { + new Function(script); + console.log("Syntax OK"); +} catch (e) { + console.error("Syntax Error:", e); +} diff --git a/patch1.py b/patch1.py new file mode 100644 index 0000000..28f0e6f --- /dev/null +++ b/patch1.py @@ -0,0 +1,49 @@ +import sys + +def apply_patch(): + with open('script.js', 'r') as f: + content = f.read() + + prepare_func = """ +/* ========================================= + NEW: PERFORMANCE OPTIMIZATION (BOLT) + ========================================= */ +// ⚡ Bolt: Pre-calculating expensive runtime properties to speed up the render loop. +function prepareSearchIndex(pdf) { + // 1. Handle dates efficiently + let uploadDateObj; + if (pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function') { + uploadDateObj = pdf.uploadDate.toDate(); + } else { + uploadDateObj = new Date(pdf.uploadDate); + } + + // 2. Pre-calculate "New" badge (7 days) + const timeDiff = new Date() - uploadDateObj; + pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); + + // 3. Pre-format date string + pdf._formattedDate = uploadDateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + + // 4. Create a single combined lowercase string for O(1) search operations + const safeStr = (str) => (str || '').toString().toLowerCase(); + pdf._searchStr = `${safeStr(pdf.title)} ${safeStr(pdf.description)} ${safeStr(pdf.category)} ${safeStr(pdf.author)}`; + + return pdf; +} +""" + # Insert before loadPDFDatabase + target_str = "async function syncClassSwitcher() {" + + if target_str in content: + content = content.replace(target_str, prepare_func + "\n" + target_str) + with open('script.js', 'w') as f: + f.write(content) + print("Patch applied successfully.") + else: + print("Could not find target string.") + +if __name__ == "__main__": + apply_patch() diff --git a/patch2.py b/patch2.py new file mode 100644 index 0000000..c64e155 --- /dev/null +++ b/patch2.py @@ -0,0 +1,41 @@ +import sys + +def apply_patch(): + with open('script.js', 'r') as f: + content = f.read() + + # Apply to Cached Data + search_str1 = """ if (shouldUseCache) { + pdfDatabase = cachedData;""" + replace_str1 = """ if (shouldUseCache) { + // ⚡ Bolt: Apply pre-calculations to cached data + pdfDatabase = cachedData.map(pdf => prepareSearchIndex(pdf));""" + + if search_str1 in content: + content = content.replace(search_str1, replace_str1) + else: + print("Could not find cache block.") + + # Apply to Fresh Fetch + search_str2 = """ pdfDatabase = []; + snapshot.forEach(doc => { + pdfDatabase.push({ id: doc.id, ...doc.data() }); + });""" + replace_str2 = """ pdfDatabase = []; + snapshot.forEach(doc => { + // ⚡ Bolt: Prepare search index immediately on fetch + let pdf = { id: doc.id, ...doc.data() }; + pdfDatabase.push(prepareSearchIndex(pdf)); + });""" + + if search_str2 in content: + content = content.replace(search_str2, replace_str2) + else: + print("Could not find fresh fetch block.") + + with open('script.js', 'w') as f: + f.write(content) + print("Patch 2 applied successfully.") + +if __name__ == "__main__": + apply_patch() diff --git a/patch3.py b/patch3.py new file mode 100644 index 0000000..076f52b --- /dev/null +++ b/patch3.py @@ -0,0 +1,60 @@ +import sys + +def apply_patch(): + with open('script.js', 'r') as f: + content = f.read() + + search_str = """ 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; + + let matchesCategory = false; + if (currentCategory === 'favorites') { + matchesCategory = favorites.includes(pdf.id); + } else { + matchesCategory = currentCategory === 'all' || pdf.category === currentCategory; + } + + const matchesSearch = pdf.title.toLowerCase().includes(searchTerm) || + pdf.description.toLowerCase().includes(searchTerm) || + pdf.category.toLowerCase().includes(searchTerm) || + pdf.author.toLowerCase().includes(searchTerm); + + // Update return statement to include matchesClass + return matchesSemester && matchesClass && matchesCategory && matchesSearch; + });""" + + replace_str = """ // ⚡ Bolt: Fast-path filtering using early returns and pre-calculated index + const filteredPdfs = pdfDatabase.filter(pdf => { + // 1. Cheap Equality Checks First + if (pdf.class !== currentClass) return false; + if (pdf.semester !== currentSemester) return false; + + // 2. Category Check + if (currentCategory === 'favorites') { + if (!favorites.includes(pdf.id)) return false; + } else if (currentCategory !== 'all') { + if (pdf.category !== currentCategory) return false; + } + + // 3. Expensive String Match Last (Using pre-calculated string) + if (searchTerm) { + return pdf._searchStr.includes(searchTerm); + } + + return true; + });""" + + if search_str in content: + content = content.replace(search_str, replace_str) + with open('script.js', 'w') as f: + f.write(content) + print("Patch 3 applied successfully.") + else: + print("Could not find renderPDFs filter block.") + +if __name__ == "__main__": + apply_patch() diff --git a/patch4.py b/patch4.py new file mode 100644 index 0000000..9caabd7 --- /dev/null +++ b/patch4.py @@ -0,0 +1,41 @@ +import sys + +def apply_patch(): + with open('script.js', 'r') as f: + content = f.read() + + search_str = """ const uploadDateObj = new Date(pdf.uploadDate); + const timeDiff = new Date() - uploadDateObj; + const isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days""" + + replace_str = """ // ⚡ Bolt: Use pre-calculated values + const isNew = pdf._isNew;""" + + if search_str in content: + content = content.replace(search_str, replace_str) + print("Patch 4 applied successfully (part 1).") + else: + print("Could not find new badge block.") + + search_str2 = """ // Formatting Date + const formattedDate = new Date(pdf.uploadDate).toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + });""" + + replace_str2 = """ // Formatting Date + // ⚡ Bolt: Use pre-calculated date string + const formattedDate = pdf._formattedDate || new Date(pdf.uploadDate).toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + });""" + + if search_str2 in content: + content = content.replace(search_str2, replace_str2) + print("Patch 4 applied successfully (part 2).") + else: + print("Could not find formatted date block.") + + with open('script.js', 'w') as f: + f.write(content) + +if __name__ == "__main__": + apply_patch() diff --git a/script.js b/script.js index bdc06f3..acf7062 100644 --- a/script.js +++ b/script.js @@ -382,6 +382,36 @@ window.changeSemester = function (sem) { renderPDFs(); }; + +/* ========================================= + NEW: PERFORMANCE OPTIMIZATION (BOLT) + ========================================= */ +// ⚡ Bolt: Pre-calculating expensive runtime properties to speed up the render loop. +function prepareSearchIndex(pdf) { + // 1. Handle dates efficiently + let uploadDateObj; + if (pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function') { + uploadDateObj = pdf.uploadDate.toDate(); + } else { + uploadDateObj = new Date(pdf.uploadDate); + } + + // 2. Pre-calculate "New" badge (7 days) + const timeDiff = new Date() - uploadDateObj; + pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); + + // 3. Pre-format date string + pdf._formattedDate = uploadDateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + + // 4. Create a single combined lowercase string for O(1) search operations + const safeStr = (str) => (str || '').toString().toLowerCase(); + pdf._searchStr = `${safeStr(pdf.title)} ${safeStr(pdf.description)} ${safeStr(pdf.category)} ${safeStr(pdf.author)}`; + + return pdf; +} + async function syncClassSwitcher() { const classSelect = document.getElementById('classSelect'); if (!classSelect) return; @@ -450,7 +480,8 @@ async function loadPDFDatabase() { } if (shouldUseCache) { - pdfDatabase = cachedData; + // ⚡ Bolt: Apply pre-calculations to cached data + pdfDatabase = cachedData.map(pdf => prepareSearchIndex(pdf)); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -466,7 +497,9 @@ async function loadPDFDatabase() { pdfDatabase = []; snapshot.forEach(doc => { - pdfDatabase.push({ id: doc.id, ...doc.data() }); + // ⚡ Bolt: Prepare search index immediately on fetch + let pdf = { id: doc.id, ...doc.data() }; + pdfDatabase.push(prepareSearchIndex(pdf)); }); localStorage.setItem(CACHE_KEY, JSON.stringify({ @@ -901,27 +934,25 @@ function renderPDFs() { } // Locate renderPDFs() in script.js and update the filter section + // ⚡ Bolt: Fast-path filtering using early returns and pre-calculated index 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; + // 1. Cheap Equality Checks First + if (pdf.class !== currentClass) return false; + if (pdf.semester !== currentSemester) return false; - let matchesCategory = false; + // 2. Category Check 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); + // 3. Expensive String Match Last (Using pre-calculated string) + if (searchTerm) { + return pdf._searchStr.includes(searchTerm); + } - // Update return statement to include matchesClass - return matchesSemester && matchesClass && matchesCategory && matchesSearch; + return true; }); updatePDFCount(filteredPdfs.length); @@ -991,9 +1022,8 @@ 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 values + const isNew = pdf._isNew; const newBadgeHTML = isNew ? `NEW` @@ -1008,7 +1038,8 @@ 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', { + // ⚡ Bolt: Use pre-calculated date string + const formattedDate = pdf._formattedDate || new Date(pdf.uploadDate).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });