Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

⚡ Bolt: Remove JSON serialization overhead & memoize lookups#12

Open
mentalblank wants to merge 1 commit into
mainfrom
bolt-optimization-idb-memoization-13872188841744034412
Open

⚡ Bolt: Remove JSON serialization overhead & memoize lookups#12
mentalblank wants to merge 1 commit into
mainfrom
bolt-optimization-idb-memoization-13872188841744034412

Conversation

@mentalblank
Copy link
Copy Markdown
Owner

@mentalblank mentalblank commented Jan 27, 2026

User description

💡 What:

  • Changed IndexedDB storage for collectionROMList to store the raw JavaScript object instead of a JSON string.
  • Added migration logic to parse and upgrade existing string data.
  • Implemented memoization for the gameHashesMap generation in injectGames, reusing the map if the game ID and data reference haven't changed.

🎯 Why:

  • Serialization Overhead: hashlinks.json is ~6.4MB. JSON.parse and JSON.stringify on this data take significant time (~90ms blocking the main thread) every time data is loaded or saved. Storing as an object leverages IndexedDB's structured cloning, which is generally more efficient and doesn't block the script execution flow as heavily (or at least avoids the explicit double-handling).
  • Redundant Computation: handleRA is triggered by a MutationObserver on the body. While it has some safeguards, it can still trigger injectGames frequently. Rebuilding the gameHashesMap (iterating over game data) on every call is wasteful.

📊 Impact:

  • Faster Load: Eliminates the JSON parsing/stringifying overhead.
  • Efficient Updates: Reduces CPU usage during DOM mutations by reusing the pre-calculated hash map.
  • Safety: Includes migration logic so existing users don't lose data.

🔬 Measurement:

  • Verified using a custom benchmark script showing ~90ms savings on JSON operations.
  • Verified logic correctness (migration and memoization) using a mocked environment script verify_changes.js.

PR created automatically by Jules for task 13872188841744034412 started by @mentalblank


PR Type

Enhancement


Description

  • Remove JSON serialization overhead by storing raw objects in IndexedDB instead of stringified JSON

  • Implement memoization for gameHashesMap to prevent redundant hash map reconstruction on DOM mutations

  • Add migration logic to handle legacy stringified data and upgrade to object storage

  • Reduce blocking time (~90ms) on large data loads and CPU usage during frequent DOM updates


Diagram Walkthrough

flowchart LR
  A["IndexedDB Storage"] -->|"Store raw Object"| B["Eliminate JSON Serialization"]
  C["DOM Mutations"] -->|"Check memoization"| D["Reuse gameHashesMap"]
  E["Legacy Data"] -->|"Lazy Migration"| F["Parse & Upgrade to Object"]
  B --> G["Performance Gain ~90ms"]
  D --> G
Loading

File Walkthrough

Relevant files
Enhancement
TamperMonkeyRetroachievements.js
Optimize storage and memoize hash map generation                 

TamperMonkeyRetroachievements.js

  • Added memoization variables (lastGameData, lastGameId,
    lastGameHashesMap) to cache hash map generation
  • Modified injectGames function to reuse cached hash map when game data
    and ID haven't changed
  • Updated fetchData function to store raw objects instead of JSON
    strings in IndexedDB
  • Implemented lazy migration logic to parse legacy stringified data and
    upgrade storage format
  • Added error handling for JSON parsing failures during data retrieval
+49/-12 

- Switch `collectionROMList` storage in IndexedDB from JSON string to raw Object, removing ~90ms blocking time on large data loads.
- Add lazy migration logic to handle legacy stringified data.
- Memoize `gameHashesMap` in `injectGames` to prevent redundant hash map construction on every DOM mutation.

Co-authored-by: mentalblank <12580160+mentalblank@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@qodo-code-review
Copy link
Copy Markdown

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Swallowed parse error: Legacy collectionROMList JSON parse failures are silently swallowed in one path, setting
data to null without any logging or actionable context.

Referred Code
if (!cachedGameData) {
    let data = await idbGet('collectionROMList');
    if (typeof data === 'string') {
        try {
            data = JSON.parse(data);
            await idbSet('collectionROMList', data);
        } catch (e) {
            data = null;
        }
    }

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Console error details: The new console.error call logs the raw exception object which may include stack traces
and internal details visible to end-users in the browser console.

Referred Code
} catch (e) {
    console.error("Error parsing stored data", e);
    data = null;
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured console logs: The added error logging uses unstructured console.error(...) and may output verbose
exception details that are hard to audit and could expose sensitive context depending on
runtime data.

Referred Code
} catch (e) {
    console.error("Error parsing stored data", e);
    data = null;
}

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Preserve cache on data fetch failure

Instead of clearing the cache on fetch failure, preserve the stale cache and
attempt to use it.

TamperMonkeyRetroachievements.js [168-174]

 } catch (err) {
     console.error("Error fetching hash list:", err);
-    injectGames(null, true, "Error fetching hash list");
-    await idbSet('collectionLastModified', null);
-    await idbSet('collectionLastUpdated', null);
-    await idbSet('collectionROMList', null);
+    // Attempt to use stale cache if fetching fails, instead of clearing it.
+    if (!cachedGameData) {
+        let data = await idbGet('collectionROMList');
+        if (typeof data === 'string') {
+            try {
+                data = JSON.parse(data);
+            } catch (e) {
+                console.error("Error parsing stored data", e);
+                data = null;
+            }
+        }
+        cachedGameData = data;
+    }
+    injectGames(cachedGameData, true, "Error fetching hash list");
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This is a valuable suggestion that improves user experience by preserving the cache on fetch failures, providing graceful degradation. It's a significant enhancement to the error handling logic.

Medium
DRY legacy data migration

Refactor the duplicated legacy data migration logic into a single reusable
helper function to avoid code duplication.

TamperMonkeyRetroachievements.js [146-156]

-let data = await idbGet('collectionROMList');
-if (typeof data === 'string') {
+// new helper:
+async function loadROMList() {
+  let data = await idbGet('collectionROMList');
+  if (typeof data === 'string') {
     try {
-        data = JSON.parse(data);
-        await idbSet('collectionROMList', data);
+      data = JSON.parse(data);
+      await idbSet('collectionROMList', data);
     } catch (e) {
-        console.error("Error parsing stored data", e);
-        data = null;
+      console.error("Error parsing stored data", e);
+      data = null;
     }
+  }
+  return data;
 }
-cachedGameData = data;
 
+// in fetchData and cacheValid:
+cachedGameData = await loadROMList();
+
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: This suggestion correctly identifies duplicated code introduced in the PR and proposes a good refactoring by extracting the logic into a reusable helper function, improving code maintainability and adhering to the DRY principle.

Low
Add missing parse error logging

Add error logging to the catch block in the cacheValid branch to ensure JSON
parsing failures are not silent.

TamperMonkeyRetroachievements.js [185-187]

 } catch (e) {
+    console.error("Error parsing stored data during cache‐valid check", e);
     data = null;
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out that one of the new try...catch blocks lacks error logging, unlike its counterpart. Adding logging improves consistency and debuggability for the new data migration logic.

Low
  • More

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant