feat: persistent model caching, premium prompt templates, and UI selectors#1
feat: persistent model caching, premium prompt templates, and UI selectors#1Mr-Dark-debug wants to merge 1 commit into
Conversation
…nd UI selectors - Cache fetched provider models in local storage to prevent redundant API queries - Add 4 premium LinkedIn post system prompts (Viral Staircase, Storyteller, How-to Playbook, Contrarian) - Seed prompts into settings via dynamic migration on install/upgrade - Add right-click context menus on the 'Post' button on Reddit to select templates on the fly - Add 'Post' generation button to History list items with an inline template selector - Implement premium dark mode, export/import, search debouncing, and modal escape key handling Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the ReddJSON extension to version 2.1, introducing a dark mode theme, export/import capabilities for history and AI posts, keyboard shortcuts, JSON filtering options (Full Thread vs. Post Only), and storage size optimizations. The review feedback identifies several critical and medium-severity issues, including a bug where settings can be overwritten with null during history import, a logic flaw in the cache eviction policy for processed post IDs, a portability issue with detached DOM nodes during export, a missing safety guard for uninitialized settings providers, and unhandled promise rejections when communicating with disconnected content scripts.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const merged = [...newEntries, ...existing].slice(0, 50); | ||
| await chrome.runtime.sendMessage({ | ||
| action: 'saveSettings', | ||
| settings: currentSettings | ||
| }); | ||
| await chrome.storage.local.set({ reddjson_history: merged }); |
There was a problem hiding this comment.
Critical Bug: Settings Overwritten with null on History Import
When importing history, the code attempts to save settings by calling chrome.runtime.sendMessage with settings: currentSettings.
However, currentSettings is initialized to null and is only populated when the user visits the Settings tab. If a user imports history immediately after opening the sidebar (without visiting the Settings tab first), currentSettings will be null. This will send null to the background script, which completely overwrites and wipes out all of the user's configured API keys, default models, and custom prompts in chrome.storage.local.
Since saving settings is completely unnecessary during a history import, this call should be removed entirely.
const merged = [...newEntries, ...existing].slice(0, 50);
await chrome.storage.local.set({ reddjson_history: merged });| function exportAsJSON(data, filename) { | ||
| const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); | ||
| const url = URL.createObjectURL(blob); | ||
| const a = document.createElement('a'); | ||
| a.href = url; | ||
| a.download = `${filename}-${new Date().toISOString().slice(0, 10)}.json`; | ||
| a.click(); | ||
| URL.revokeObjectURL(url); | ||
| } |
There was a problem hiding this comment.
Portability Issue: Detached DOM Node Click
Calling .click() on a detached <a> element (one that is not appended to the document DOM) fails to trigger a download in several browsers (such as Firefox) and under strict security policies in Chrome.
To ensure cross-browser compatibility and robust download behavior, temporarily append the element to document.body before clicking it, and then remove it immediately afterward.
| function exportAsJSON(data, filename) { | |
| const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `${filename}-${new Date().toISOString().slice(0, 10)}.json`; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| } | |
| function exportAsJSON(data, filename) { | |
| const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `${filename}-${new Date().toISOString().slice(0, 10)}.json`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } |
| if (!currentSettings.providers[pid]) currentSettings.providers[pid] = {}; | ||
| currentSettings.providers[pid].selectedModel = modelId; |
There was a problem hiding this comment.
Missing Safety Guard for currentSettings.providers
If currentSettings.providers is undefined (e.g., if settings are uninitialized or cleared), accessing currentSettings.providers[pid] will throw a TypeError and crash the click handler.
Add a defensive check to ensure currentSettings.providers is initialized as an object before accessing its properties, similar to how it is handled in other parts of the file.
| if (!currentSettings.providers[pid]) currentSettings.providers[pid] = {}; | |
| currentSettings.providers[pid].selectedModel = modelId; | |
| if (!currentSettings.providers) currentSettings.providers = {}; | |
| if (!currentSettings.providers[pid]) currentSettings.providers[pid] = {}; | |
| currentSettings.providers[pid].selectedModel = modelId; |
| if (processedPostIds.size >= CONFIG.maxProcessedIds) { | ||
| const first = processedPostIds.values().next().value; | ||
| processedPostIds.delete(first); | ||
| } | ||
| processedPostIds.add(id); |
There was a problem hiding this comment.
Logic Bug in Cache Eviction Policy
In the current implementation of trackPostId, if the added id is already present in the processedPostIds Set and the Set size is at the limit (CONFIG.maxProcessedIds), the code will evict the first element from the Set and then add the id. Since the id is already in the Set, adding it is a no-op, which incorrectly shrinks the Set size by 1.
To fix this and implement a proper FIFO/LRU update, check if the Set already has the id first. If it does, delete it so that re-adding it moves it to the end (refreshing its freshness) without evicting any other elements.
if (processedPostIds.has(id)) {
processedPostIds.delete(id);
} else if (processedPostIds.size >= CONFIG.maxProcessedIds) {
const first = processedPostIds.values().next().value;
processedPostIds.delete(first);
}
processedPostIds.add(id);| if (!result.success) { | ||
| chrome.tabs.sendMessage(tab.id, { action: 'showToast', message: result.error, type: 'error' }); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Unhandled Promise Rejection in chrome.tabs.sendMessage
If the content script is not active, has been unloaded, or is disconnected (e.g., on special Reddit pages or immediately after an extension update), calling chrome.tabs.sendMessage will throw an error: "Could not establish connection. Receiving end does not exist."
Since this is an asynchronous call, leaving it unhandled will trigger an unhandled promise rejection. Add a .catch() block to handle this gracefully.
| if (!result.success) { | |
| chrome.tabs.sendMessage(tab.id, { action: 'showToast', message: result.error, type: 'error' }); | |
| return; | |
| } | |
| if (!result.success) { | |
| chrome.tabs.sendMessage(tab.id, { action: 'showToast', message: result.error, type: 'error' }).catch(() => {}); | |
| return; | |
| } |
| const pretty = JSON.stringify(result.data, null, 2); | ||
| chrome.tabs.sendMessage(tab.id, { action: 'copyToClipboard', text: pretty }); |
There was a problem hiding this comment.
Unhandled Promise Rejection in chrome.tabs.sendMessage
Similar to other message sending calls, if the content script is disconnected or not loaded, chrome.tabs.sendMessage will throw an error. Add a .catch() block to prevent unhandled promise rejections.
| const pretty = JSON.stringify(result.data, null, 2); | |
| chrome.tabs.sendMessage(tab.id, { action: 'copyToClipboard', text: pretty }); | |
| const pretty = JSON.stringify(result.data, null, 2); | |
| chrome.tabs.sendMessage(tab.id, { action: 'copyToClipboard', text: pretty }).catch(() => {}); |
Description
This PR introduces several significant features and optimizations to ReddJSON:
Persistent Model Caching:
Premium LinkedIn Post Templates:
Context-Specific Template Switchers (UI/UX):
General Quality-of-Life Improvements:
🤖 Generated with Claude Code