From d2bd65ff9b9621dbbe1e90a4adda2e27fd2c3800 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Fri, 27 Mar 2026 13:27:47 +0000
Subject: [PATCH] Perf: Pre-calculate derived data for search and rendering
Co-authored-by: MrAlokTech <107493955+MrAlokTech@users.noreply.github.com>
---
.jules/bolt.md | 3 +
patch2.js | 152 +++++++++++++++++++++++++++++++++++++++++++++++
script.js | 62 ++++++++++++-------
test_frontend.py | 49 +++++++++++++++
4 files changed, 244 insertions(+), 22 deletions(-)
create mode 100644 .jules/bolt.md
create mode 100644 patch2.js
create mode 100644 test_frontend.py
diff --git a/.jules/bolt.md b/.jules/bolt.md
new file mode 100644
index 0000000..313c18c
--- /dev/null
+++ b/.jules/bolt.md
@@ -0,0 +1,3 @@
+## 2024-05-24 - Pre-calculate Derived Data to Optimize List Rendering
+**Learning:** Instantiating `Date` objects and repeatedly lowercasing strings per item on every render loop causes significant and observable UI slowdowns in apps dealing with thousands of objects.
+**Action:** When filtering or mapping data items, calculate derived variables (e.g., lowercased text (`_searchStr`), boolean checks (`_isNew`), preformatted dates (`_formattedDate`)) once during initialization/cache-load via a centralized `prepareSearchIndex` utility instead of recalcuating per-render. Additionally, skip redundant computations by utilizing early returns (`if (!condition) return false;`) within filter methods instead of combined boolean returns.
\ No newline at end of file
diff --git a/patch2.js b/patch2.js
new file mode 100644
index 0000000..2120dbd
--- /dev/null
+++ b/patch2.js
@@ -0,0 +1,152 @@
+const fs = require('fs');
+let code = fs.readFileSync('script.js', 'utf8');
+
+code = code.replace(
+` 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;
+ });`,
+` const filteredPdfs = pdfDatabase.filter(pdf => {
+ if (pdf.semester !== currentSemester) return false;
+
+ // 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.
+ if (pdf.class !== currentClass) return false;
+
+ if (currentCategory === 'favorites') {
+ if (!favorites.includes(pdf.id)) return false;
+ } else if (currentCategory !== 'all') {
+ if (pdf.category !== currentCategory) return false;
+ }
+
+ if (searchTerm && pdf._searchStr) {
+ if (!pdf._searchStr.includes(searchTerm)) return false;
+ }
+
+ return true;
+ });`
+);
+
+code = code.replace(
+` const uploadDateObj = new Date(pdf.uploadDate);
+ const timeDiff = new Date() - uploadDateObj;
+ const isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days
+
+ const newBadgeHTML = isNew
+ ? \`NEW\`
+ : '';
+
+ const categoryIcons = {
+ 'Organic': 'fa-flask',
+ 'Inorganic': 'fa-atom',
+ 'Physical': 'fa-calculator',
+ 'Physics': 'fa-infinity' // Ensure Physics icon is mapped if used
+ };
+ 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 newBadgeHTML = pdf._isNew
+ ? \`NEW\`
+ : '';
+
+ const categoryIcons = {
+ 'Organic': 'fa-flask',
+ 'Inorganic': 'fa-atom',
+ 'Physical': 'fa-calculator',
+ 'Physics': 'fa-infinity' // Ensure Physics icon is mapped if used
+ };
+ const categoryIcon = categoryIcons[pdf.category] || 'fa-file-pdf';
+ const formattedDate = pdf._formattedDate || '';`
+);
+
+// Add prepareSearchIndex only once!
+if (!code.includes('function prepareSearchIndex(data)')) {
+code = code.replace(
+`/* =========================================
+ 6. MAINTENANCE & HOLIDAYS
+ ========================================= */`,
+`function prepareSearchIndex(data) {
+ const now = new Date();
+ data.forEach(pdf => {
+ const title = pdf.title || '';
+ const desc = pdf.description || '';
+ const cat = pdf.category || '';
+ const author = pdf.author || '';
+ pdf._searchStr = \`\${title} \${desc} \${cat} \${author}\`.toLowerCase();
+
+ let uploadDateObj;
+ if (pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function') {
+ uploadDateObj = pdf.uploadDate.toDate();
+ } else if (pdf.uploadDate) {
+ uploadDateObj = new Date(pdf.uploadDate);
+ } else {
+ uploadDateObj = new Date(0);
+ }
+
+ const timeDiff = now - uploadDateObj;
+ pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000);
+ pdf._formattedDate = uploadDateObj.toLocaleDateString('en-US', {
+ year: 'numeric', month: 'short', day: 'numeric'
+ });
+ });
+}
+
+/* =========================================
+ 6. MAINTENANCE & HOLIDAYS
+ ========================================= */`
+);
+}
+
+code = code.replace(
+` if (shouldUseCache) {
+ pdfDatabase = cachedData;
+ // --- FIX: CALL THIS TO POPULATE UI ---
+ syncClassSwitcher();`,
+` if (shouldUseCache) {
+ pdfDatabase = cachedData;
+ prepareSearchIndex(pdfDatabase);
+ // --- FIX: CALL THIS TO POPULATE UI ---
+ syncClassSwitcher();`
+);
+
+code = code.replace(
+` localStorage.setItem(CACHE_KEY, JSON.stringify({
+ timestamp: new Date().getTime(),
+ data: pdfDatabase
+ }));
+
+ // --- FIX: CALL THIS TO POPULATE UI ---
+ syncClassSwitcher();`,
+` localStorage.setItem(CACHE_KEY, JSON.stringify({
+ timestamp: new Date().getTime(),
+ data: pdfDatabase
+ }));
+
+ prepareSearchIndex(pdfDatabase);
+
+ // --- FIX: CALL THIS TO POPULATE UI ---
+ syncClassSwitcher();`
+);
+
+fs.writeFileSync('script.js', code);
diff --git a/script.js b/script.js
index 22f399d..8decbdb 100644
--- a/script.js
+++ b/script.js
@@ -454,6 +454,7 @@ async function loadPDFDatabase() {
if (shouldUseCache) {
pdfDatabase = cachedData;
+ prepareSearchIndex(pdfDatabase);
// --- FIX: CALL THIS TO POPULATE UI ---
syncClassSwitcher();
renderSemesterTabs();
@@ -477,6 +478,8 @@ async function loadPDFDatabase() {
data: pdfDatabase
}));
+ prepareSearchIndex(pdfDatabase);
+
// --- FIX: CALL THIS TO POPULATE UI ---
syncClassSwitcher();
renderPDFs();
@@ -503,6 +506,32 @@ function hidePreloader() {
}
}
+function prepareSearchIndex(data) {
+ const now = new Date();
+ data.forEach(pdf => {
+ const title = pdf.title || '';
+ const desc = pdf.description || '';
+ const cat = pdf.category || '';
+ const author = pdf.author || '';
+ pdf._searchStr = `${title} ${desc} ${cat} ${author}`.toLowerCase();
+
+ let uploadDateObj;
+ if (pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function') {
+ uploadDateObj = pdf.uploadDate.toDate();
+ } else if (pdf.uploadDate) {
+ uploadDateObj = new Date(pdf.uploadDate);
+ } else {
+ uploadDateObj = new Date(0);
+ }
+
+ const timeDiff = now - uploadDateObj;
+ pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000);
+ pdf._formattedDate = uploadDateObj.toLocaleDateString('en-US', {
+ year: 'numeric', month: 'short', day: 'numeric'
+ });
+ });
+}
+
/* =========================================
6. MAINTENANCE & HOLIDAYS
========================================= */
@@ -905,26 +934,23 @@ function renderPDFs() {
// Locate renderPDFs() in script.js and update the filter section
const filteredPdfs = pdfDatabase.filter(pdf => {
- const matchesSemester = pdf.semester === currentSemester;
+ if (pdf.semester !== currentSemester) return false;
// 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;
+ 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);
+ if (searchTerm && pdf._searchStr) {
+ if (!pdf._searchStr.includes(searchTerm)) return false;
+ }
- // Update return statement to include matchesClass
- return matchesSemester && matchesClass && matchesCategory && matchesSearch;
+ return true;
});
updatePDFCount(filteredPdfs.length);
@@ -994,11 +1020,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 newBadgeHTML = isNew
+ const newBadgeHTML = pdf._isNew
? `NEW`
: '';
@@ -1009,11 +1031,7 @@ function createPDFCard(pdf, favoritesList, index = 0, highlightRegex = null) {
'Physics': 'fa-infinity' // Ensure Physics icon is mapped if used
};
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/test_frontend.py b/test_frontend.py
new file mode 100644
index 0000000..33f036b
--- /dev/null
+++ b/test_frontend.py
@@ -0,0 +1,49 @@
+import asyncio
+from playwright.async_api import async_playwright
+import time
+import subprocess
+
+async def run():
+ # Start the HTTP server
+ server_process = subprocess.Popen(["python3", "-m", "http.server", "8000"])
+
+ # Wait for the server to start
+ time.sleep(2)
+
+ async with async_playwright() as p:
+ browser = await p.chromium.launch(headless=True)
+ page = await browser.new_page()
+
+ try:
+ # Go to the local server
+ await page.goto("http://localhost:8000")
+ print("Page loaded successfully.")
+
+ # Wait for any critical content to load, ensuring it runs
+ await page.wait_for_timeout(3000)
+
+ # Check if pdf grid exists
+ pdf_grid = await page.query_selector("#pdfGrid")
+ if pdf_grid:
+ print("PDF grid found.")
+ else:
+ print("Error: PDF grid not found.")
+
+ # Perform a search
+ search_input = await page.query_selector("#searchInput")
+ if search_input:
+ await search_input.fill("physics")
+ await page.wait_for_timeout(1000) # Wait for debounce and rendering
+ print("Search input filled and processed.")
+ else:
+ print("Error: Search input not found.")
+
+ except Exception as e:
+ print(f"Test failed: {e}")
+
+ finally:
+ await browser.close()
+ # Terminate the server
+ server_process.terminate()
+
+asyncio.run(run())