diff --git a/check_installed.html b/check_installed.html new file mode 100644 index 00000000..960b6842 --- /dev/null +++ b/check_installed.html @@ -0,0 +1,23 @@ + + +Check localStorage + +

Checking installed photo packs...

+

+
+
+
diff --git a/src/features/background/api/backgroundLoader.js b/src/features/background/api/backgroundLoader.js
index bcd217c1..38cdebbc 100644
--- a/src/features/background/api/backgroundLoader.js
+++ b/src/features/background/api/backgroundLoader.js
@@ -4,6 +4,7 @@ import { getOfflineImage } from './offlineImage';
 import { randomColourStyleBuilder } from './randomColour';
 import videoCheck from './videoCheck';
 import { getAllBackgrounds, getAllBackgroundsWithMetadata } from 'utils/customBackgroundDB';
+import { BackgroundQueueManager } from 'utils/backgroundQueue';
 
 const parseJSON = (key, fallback = null) => {
   const item = localStorage.getItem(key);
@@ -126,42 +127,29 @@ function getColourBackground() {
 async function getAPIBackground(isOffline) {
   if (isOffline) return getOfflineImage('api');
 
-  // Use cached next image if available
-  const cachedQueue = parseJSON('imageQueue', []);
+  const queueManager = new BackgroundQueueManager('imageQueue', 3);
   let data;
 
+  // Use cached next image if available
+  const cachedQueue = queueManager.getQueue();
   if (cachedQueue.length > 0) {
-    data = cachedQueue.shift();
-    localStorage.setItem('imageQueue', JSON.stringify(cachedQueue));
+    data = queueManager.shift();
   } else {
     data = await fetchAPIImageData();
   }
 
   if (!data) return getOfflineImage('api');
 
-  localStorage.setItem('currentBackground', JSON.stringify(data));
+  try {
+    localStorage.setItem('currentBackground', JSON.stringify(data));
+  } catch (e) {
+    console.warn('Could not save currentBackground to localStorage:', e);
+  }
 
-  // Pre-fetch next 3 images in the background
-  const targetQueueSize = 3;
-  const currentQueueSize = cachedQueue.length;
-
-  if (currentQueueSize < targetQueueSize) {
-    const excludedPuns = [
-      data.photoInfo.pun,
-      ...cachedQueue.map((img) => img.photoInfo?.pun).filter(Boolean),
-    ];
-
-    // Prefetch remaining images asynchronously
-    Promise.all(
-      Array.from({ length: targetQueueSize - currentQueueSize }, (_, i) =>
-        fetchAPIImageData(excludedPuns[i] || data.photoInfo.pun),
-      ),
-    ).then((newImages) => {
-      const validImages = newImages.filter(Boolean);
-      if (validImages.length > 0) {
-        const updatedQueue = [...cachedQueue, ...validImages];
-        localStorage.setItem('imageQueue', JSON.stringify(updatedQueue));
-      }
+  // Pre-fetch next images in the background
+  if (queueManager.needsPrefetch()) {
+    prefetchAPIImages(queueManager, data, cachedQueue).catch((error) => {
+      console.error('Failed to prefetch API images:', error);
     });
   }
 
@@ -169,7 +157,33 @@ async function getAPIBackground(isOffline) {
 }
 
 /**
- * Gets custom background
+ * Prefetch API images in the background
+ * @param {BackgroundQueueManager} queueManager - The queue manager
+ * @param {Object} currentImage - The current image data
+ * @param {Array} currentQueue - The current queue state
+ */
+async function prefetchAPIImages(queueManager, currentImage, currentQueue) {
+  const count = queueManager.getSpaceNeeded();
+  const excludedPuns = [
+    currentImage.photoInfo.pun,
+    ...currentQueue.map((img) => img.photoInfo?.pun).filter(Boolean),
+  ];
+
+  // Prefetch remaining images asynchronously
+  const newImages = await Promise.all(
+    Array.from({ length: count }, (_, i) =>
+      fetchAPIImageData(excludedPuns[i] || currentImage.photoInfo.pun),
+    ),
+  );
+
+  const validImages = newImages.filter(Boolean);
+  if (validImages.length > 0) {
+    queueManager.push(validImages);
+  }
+}
+
+/**
+ * Gets custom background with prefetching
  */
 async function getCustomBackground(isOffline) {
   // Get full metadata from IndexedDB
@@ -186,18 +200,33 @@ async function getCustomBackground(isOffline) {
 
   if (!backgrounds || backgrounds.length === 0) return null;
 
-  const selected = backgrounds[Math.floor(Math.random() * backgrounds.length)];
+  const queueManager = new BackgroundQueueManager('customQueue', 3);
+  let selected;
+
+  // Use cached next background ID if available
+  const cachedQueue = queueManager.getQueue();
+  if (cachedQueue.length > 0) {
+    // Queue contains IDs only, not full data
+    const queuedId = queueManager.shift();
+    // Look up the full background data by ID
+    selected = backgrounds.find((bg) => bg.id === queuedId);
+
+    // If not found (maybe deleted), pick random
+    if (!selected) {
+      selected = backgrounds[Math.floor(Math.random() * backgrounds.length)];
+    }
+  } else {
+    // Pick random background
+    selected = backgrounds[Math.floor(Math.random() * backgrounds.length)];
+  }
 
   // Check if selected is valid before using it
   if (!selected) return null;
 
   const url = selected.url || selected;
 
-  if (isOffline && !url.startsWith('data:')) {
-    return getOfflineImage('custom');
-  }
-
   const data = {
+    id: selected.id,
     url,
     type: 'custom',
     video: videoCheck(url),
@@ -207,6 +236,10 @@ async function getCustomBackground(isOffline) {
     },
   };
 
+  if (isOffline && !data.url.startsWith('data:')) {
+    return getOfflineImage('custom');
+  }
+
   // Don't store full image data in localStorage to avoid quota errors
   // Just store metadata
   try {
@@ -223,11 +256,51 @@ async function getCustomBackground(isOffline) {
     console.warn('Could not save currentBackground to localStorage:', e);
   }
 
+  // Prefetch more backgrounds in the background (skip videos)
+  if (queueManager.needsPrefetch() && !data.video && selected.id) {
+    prefetchCustomBackgrounds(queueManager, backgrounds, selected.id, cachedQueue).catch((error) => {
+      console.error('Failed to prefetch custom backgrounds:', error);
+    });
+  }
+
   return data;
 }
 
 /**
- * Gets photo pack background
+ * Prefetch custom backgrounds in the background
+ * Store only IDs in queue to avoid localStorage quota issues with large data URLs
+ * @param {BackgroundQueueManager} queueManager - The queue manager
+ * @param {Array} allBackgrounds - All available custom backgrounds
+ * @param {number} currentId - The current background ID
+ * @param {Array} currentQueue - The current queue state (array of IDs)
+ */
+async function prefetchCustomBackgrounds(queueManager, allBackgrounds, currentId, currentQueue) {
+  const count = queueManager.getSpaceNeeded();
+
+  // Get already used IDs (queue now contains IDs only)
+  const usedIds = [currentId, ...currentQueue.filter(Boolean)];
+
+  // Filter available (exclude videos from prefetch and already used)
+  const available = allBackgrounds.filter(
+    (bg) => bg.id && !usedIds.includes(bg.id) && !videoCheck(bg.url || bg),
+  );
+
+  if (available.length === 0) return;
+
+  // Shuffle and take N
+  const shuffled = available.sort(() => Math.random() - 0.5);
+  const selected = shuffled.slice(0, count);
+
+  // Store only IDs to avoid quota issues (custom backgrounds use large data URLs)
+  const ids = selected.map((bg) => bg.id).filter(Boolean);
+
+  if (ids.length > 0) {
+    queueManager.push(ids);
+  }
+}
+
+/**
+ * Gets photo pack background with prefetching and blurhash
  */
 function getPhotoPackBackground(isOffline) {
   if (isOffline) return getOfflineImage('photo_pack');
@@ -238,26 +311,82 @@ function getPhotoPackBackground(isOffline) {
 
   if (photos.length === 0) return null;
 
-  const interval = localStorage.getItem('backgroundchange');
-  const startTime = Number(localStorage.getItem('backgroundStartTime'));
-  const shouldRefresh =
-    !interval || interval === 'refresh' || startTime + Number(interval) < Date.now();
+  const queueManager = new BackgroundQueueManager('photoPackQueue', 3);
+  let photoData;
 
-  let index;
-  if (shouldRefresh) {
-    index = Math.floor(Math.random() * photos.length);
-    localStorage.setItem('marketplaceNumber', index);
+  // Use cached next photo if available
+  const cachedQueue = queueManager.getQueue();
+  if (cachedQueue.length > 0) {
+    photoData = queueManager.shift();
   } else {
-    index = Number(localStorage.getItem('marketplaceNumber')) || 0;
+    // Pick random photo
+    const index = Math.floor(Math.random() * photos.length);
+    const selected = photos[index];
+
+    photoData = {
+      url: selected.url.default,
+      type: 'photo_pack',
+      photoInfo: {
+        hidden: false,
+        credit: selected.photographer,
+        location: selected.location,
+        blur_hash: selected.blur_hash || null,
+      },
+    };
   }
 
-  const photo = photos[index];
+  try {
+    localStorage.setItem('currentBackground', JSON.stringify(photoData));
+  } catch (e) {
+    console.warn('Could not save currentBackground to localStorage:', e);
+  }
 
-  return photo
-    ? {
-        url: photo.url.default,
-        type: 'photo_pack',
-        photoInfo: { hidden: false, credit: photo.photographer, location: photo.location },
-      }
-    : null;
+  // Prefetch more photos in the background
+  if (queueManager.needsPrefetch()) {
+    prefetchPhotoPackImages(queueManager, photos, photoData, cachedQueue).catch((error) => {
+      console.error('Failed to prefetch photo pack images:', error);
+    });
+  }
+
+  return photoData;
+}
+
+/**
+ * Prefetch photo pack images in the background
+ * @param {BackgroundQueueManager} queueManager - The queue manager
+ * @param {Array} allPhotos - All available photos from installed packs
+ * @param {Object} currentPhoto - The current photo data
+ * @param {Array} currentQueue - The current queue state
+ */
+async function prefetchPhotoPackImages(queueManager, allPhotos, currentPhoto, currentQueue) {
+  const count = queueManager.getSpaceNeeded();
+
+  // Get already used URLs
+  const usedUrls = [
+    currentPhoto.url,
+    ...currentQueue.map((p) => p.url),
+  ];
+
+  // Filter available photos
+  const available = allPhotos.filter((p) => !usedUrls.includes(p.url.default));
+
+  if (available.length === 0) return;
+
+  // Shuffle and take N
+  const shuffled = available.sort(() => Math.random() - 0.5);
+  const selected = shuffled.slice(0, count);
+
+  // Normalize metadata
+  const normalized = selected.map((photo) => ({
+    url: photo.url.default,
+    type: 'photo_pack',
+    photoInfo: {
+      hidden: false,
+      credit: photo.photographer,
+      location: photo.location,
+      blur_hash: photo.blur_hash || null,
+    },
+  }));
+
+  queueManager.push(normalized);
 }
diff --git a/src/features/background/options/BackgroundOptions.jsx b/src/features/background/options/BackgroundOptions.jsx
index e1a1564f..9fd4269d 100644
--- a/src/features/background/options/BackgroundOptions.jsx
+++ b/src/features/background/options/BackgroundOptions.jsx
@@ -2,6 +2,7 @@ import variables from 'config/variables';
 import { memo, useState, useEffect, useCallback, useRef } from 'react';
 import { MdSource, MdOutlineAutoAwesome } from 'react-icons/md';
 import EventBus from 'utils/eventbus';
+import { clearQueuesOnSettingChange } from 'utils/queueOperations';
 
 import { Header } from 'components/Layout/Settings';
 import { Dropdown } from 'components/Form/Settings';
@@ -67,7 +68,7 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section
   const updateAPI = useCallback((e) => {
     localStorage.setItem('nextImage', null);
     // Clear prefetch queue when API changes to prevent showing cached images from old API
-    localStorage.removeItem('imageQueue');
+    clearQueuesOnSettingChange('backgroundAPI');
     if (e === 'mue') {
       setBackgroundCategories(backgroundCategoriesOG);
       setBackgroundAPI('mue');
@@ -190,7 +191,7 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section
                 name="backgroundType"
                 onChange={(value) => {
                   // Clear prefetch queue when changing background type
-                  localStorage.removeItem('imageQueue');
+                  clearQueuesOnSettingChange('backgroundType');
                   setBackgroundType(value);
                   // Automatically refresh background when switching to custom images
                   if (value === 'custom') {
@@ -229,7 +230,7 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section
             marketplaceEnabled={marketplaceEnabled}
             onTypeChange={(value) => {
               // Clear prefetch queue when changing background type
-              localStorage.removeItem('imageQueue');
+              clearQueuesOnSettingChange('backgroundType');
               setBackgroundType(value);
             }}
           />
diff --git a/src/features/background/options/sections/APISettings.jsx b/src/features/background/options/sections/APISettings.jsx
index 95e8b555..4f1779e7 100644
--- a/src/features/background/options/sections/APISettings.jsx
+++ b/src/features/background/options/sections/APISettings.jsx
@@ -2,6 +2,7 @@ import variables from 'config/variables';
 import { Dropdown, Radio, Text, ChipSelect } from 'components/Form/Settings';
 import { Row, Content, Action } from 'components/Layout/Settings/Item';
 import { APIQualityOptions } from '../optionTypes';
+import { clearQueuesOnSettingChange } from 'utils/queueOperations';
 
 const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
   return (
@@ -36,7 +37,7 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
               name="apiCategories"
               onChange={() => {
                 // Clear prefetch queue when categories change
-                localStorage.removeItem('imageQueue');
+                clearQueuesOnSettingChange('apiCategories');
               }}
             />
           )}
@@ -49,7 +50,7 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
             items={APIQualityOptions}
             onChange={() => {
               // Clear prefetch queue when quality changes
-              localStorage.removeItem('imageQueue');
+              clearQueuesOnSettingChange('apiQuality');
             }}
           />
            {
               element="#backgroundImage"
               onChange={() => {
                 // Clear prefetch queue when Unsplash collections change
-                localStorage.removeItem('imageQueue');
+                clearQueuesOnSettingChange('unsplashCollections');
               }}
             />
           
diff --git a/src/features/marketplace/components/Items/Items.jsx b/src/features/marketplace/components/Items/Items.jsx
index 7e9f9e07..cc573618 100644
--- a/src/features/marketplace/components/Items/Items.jsx
+++ b/src/features/marketplace/components/Items/Items.jsx
@@ -129,7 +129,7 @@ function ItemCard({ item, toggleFunction, type, onCollection, isCurator, isInsta
             
           )}
           {item.in_collections && item.in_collections.length > 0 && !onCollection && (
-            {item.in_collections[0]}
+            {item.in_collections[0].display_name || item.in_collections[0].name}
           )}
         
       
diff --git a/src/features/misc/views/Discover.jsx b/src/features/misc/views/Discover.jsx
index 9328aff0..30bee5a8 100644
--- a/src/features/misc/views/Discover.jsx
+++ b/src/features/misc/views/Discover.jsx
@@ -154,17 +154,33 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) {
       switch (type) {
         case 'marketplace:item:install':
           if (payload?.item) {
-            installItem(payload.item.type, payload.item);
-            // Send confirmation back to iframe
-            if (iframeRef.current?.contentWindow) {
-              iframeRef.current.contentWindow.postMessage(
-                {
-                  type: 'marketplace:item:installed',
-                  payload: { id: payload.item.id || payload.item.name, installed: true },
-                },
-                MARKETPLACE_URL
-              );
-            }
+            // Fetch fresh data from API to ensure we get latest version with blur_hash
+            const itemId = payload.item.id || payload.item.name;
+            const itemType = payload.item.type;
+
+            fetch(`${variables.constants.API_URL}/marketplace/item/${itemId}`)
+              .then(res => res.json())
+              .then(({ data }) => {
+                // Install with fresh data from API
+                installItem(data.type, data);
+              })
+              .catch(error => {
+                console.error('Failed to fetch item from API, using iframe data:', error);
+                // Fallback to iframe data if API fetch fails
+                installItem(itemType, payload.item);
+              })
+              .finally(() => {
+                // Send confirmation back to iframe
+                if (iframeRef.current?.contentWindow) {
+                  iframeRef.current.contentWindow.postMessage(
+                    {
+                      type: 'marketplace:item:installed',
+                      payload: { id: itemId, installed: true },
+                    },
+                    MARKETPLACE_URL
+                  );
+                }
+              });
           }
           break;
 
diff --git a/src/utils/backgroundQueue.js b/src/utils/backgroundQueue.js
new file mode 100644
index 00000000..0c856d97
--- /dev/null
+++ b/src/utils/backgroundQueue.js
@@ -0,0 +1,152 @@
+/**
+ * Background Queue Manager
+ * Manages prefetch queues for background images across all background types
+ */
+
+/**
+ * Helper to safely parse JSON from localStorage
+ * @param {string} key - localStorage key
+ * @param {*} fallback - Fallback value if parsing fails
+ * @returns {*} Parsed value or fallback
+ */
+function parseJSON(key, fallback = null) {
+  const item = localStorage.getItem(key);
+  if (item === null || item === 'null') {
+    return fallback;
+  }
+  try {
+    const parsed = JSON.parse(item);
+    return parsed !== null ? parsed : fallback;
+  } catch {
+    return fallback;
+  }
+}
+
+/**
+ * Manages a prefetch queue for background images
+ *
+ * @class BackgroundQueueManager
+ * @example
+ * const queueManager = new BackgroundQueueManager('imageQueue', 3);
+ * const queue = queueManager.getQueue();
+ * if (queue.length > 0) {
+ *   const nextImage = queueManager.shift();
+ * }
+ */
+export class BackgroundQueueManager {
+  /**
+   * Creates a new queue manager
+   * @param {string} storageKey - localStorage key for this queue
+   * @param {number} targetSize - Target number of items to keep in queue (default: 3)
+   */
+  constructor(storageKey, targetSize = 3) {
+    this.storageKey = storageKey;
+    this.targetSize = targetSize;
+  }
+
+  /**
+   * Get the current queue from localStorage
+   * @returns {Array} The queue array
+   */
+  getQueue() {
+    try {
+      return parseJSON(this.storageKey, []);
+    } catch (error) {
+      console.warn(`Failed to get queue from ${this.storageKey}:`, error);
+      return [];
+    }
+  }
+
+  /**
+   * Set the queue in localStorage
+   * @param {Array} queue - The queue array to save
+   */
+  setQueue(queue) {
+    try {
+      localStorage.setItem(this.storageKey, JSON.stringify(queue));
+    } catch (error) {
+      console.error(`Failed to save queue to ${this.storageKey}:`, error);
+      // If quota exceeded, clear the queue and try again with empty array
+      if (error.name === 'QuotaExceededError') {
+        try {
+          localStorage.removeItem(this.storageKey);
+        } catch (e) {
+          console.error('Failed to clear queue after quota error:', e);
+        }
+      }
+    }
+  }
+
+  /**
+   * Remove and return the first item from the queue
+   * @returns {*} The first item in the queue, or undefined if empty
+   */
+  shift() {
+    try {
+      const queue = this.getQueue();
+      if (queue.length === 0) {
+        return undefined;
+      }
+      const item = queue.shift();
+      this.setQueue(queue);
+      return item;
+    } catch (error) {
+      console.error(`Failed to shift queue ${this.storageKey}:`, error);
+      return undefined;
+    }
+  }
+
+  /**
+   * Add items to the end of the queue
+   * @param {Array} items - Items to add to the queue
+   */
+  push(items) {
+    try {
+      const queue = this.getQueue();
+      const itemsArray = Array.isArray(items) ? items : [items];
+      queue.push(...itemsArray);
+      this.setQueue(queue);
+    } catch (error) {
+      console.error(`Failed to push to queue ${this.storageKey}:`, error);
+    }
+  }
+
+  /**
+   * Clear the queue from localStorage
+   */
+  clear() {
+    try {
+      localStorage.removeItem(this.storageKey);
+    } catch (error) {
+      console.error(`Failed to clear queue ${this.storageKey}:`, error);
+    }
+  }
+
+  /**
+   * Check if the queue needs prefetching (below target size)
+   * @returns {boolean} True if queue size is below target
+   */
+  needsPrefetch() {
+    const queue = this.getQueue();
+    return queue.length < this.targetSize;
+  }
+
+  /**
+   * Get the number of items needed to reach target size
+   * @returns {number} Number of items to prefetch
+   */
+  getSpaceNeeded() {
+    const queue = this.getQueue();
+    return Math.max(0, this.targetSize - queue.length);
+  }
+
+  /**
+   * Get the current queue size
+   * @returns {number} Number of items in queue
+   */
+  size() {
+    return this.getQueue().length;
+  }
+}
+
+export default BackgroundQueueManager;
diff --git a/src/utils/marketplace/install.js b/src/utils/marketplace/install.js
index fbdd9b6a..d4dead7c 100644
--- a/src/utils/marketplace/install.js
+++ b/src/utils/marketplace/install.js
@@ -1,4 +1,5 @@
 import EventBus from 'utils/eventbus';
+import { clearQueuesOnSettingChange } from 'utils/queueOperations';
 
 // todo: relocate this function
 function showReminder() {
@@ -39,7 +40,7 @@ export function install(type, input, sideload, collection) {
       localStorage.setItem('backgroundType', 'photo_pack');
       localStorage.removeItem('backgroundchange');
       // Clear image queue to ensure fresh background loads
-      localStorage.removeItem('imageQueue');
+      clearQueuesOnSettingChange('packInstall');
       // Set refresh event to emit after installed data is saved
       refreshEvent = 'backgroundrefresh';
       break;
diff --git a/src/utils/marketplace/uninstall.js b/src/utils/marketplace/uninstall.js
index cf73f954..5f6cde0f 100644
--- a/src/utils/marketplace/uninstall.js
+++ b/src/utils/marketplace/uninstall.js
@@ -1,4 +1,5 @@
 import EventBus from 'utils/eventbus';
+import { clearQueuesOnSettingChange } from 'utils/queueOperations';
 
 // todo: relocate this function
 function showReminder() {
@@ -67,7 +68,7 @@ export function uninstall(type, name) {
       }
       localStorage.removeItem('backgroundchange');
       // Clear image queue to ensure fresh background loads
-      localStorage.removeItem('imageQueue');
+      clearQueuesOnSettingChange('packUninstall');
       // Set refresh event to emit after installed data is saved
       refreshEvent = 'marketplacebackgrounduninstall';
       break;
diff --git a/src/utils/queueOperations.js b/src/utils/queueOperations.js
new file mode 100644
index 00000000..ea86aea5
--- /dev/null
+++ b/src/utils/queueOperations.js
@@ -0,0 +1,102 @@
+/**
+ * Queue Operations
+ * Centralized queue clearing logic for background prefetch queues
+ */
+
+/**
+ * Queue key mapping for each background type
+ */
+const QUEUE_KEYS = {
+  api: 'imageQueue',
+  photo_pack: 'photoPackQueue',
+  custom: 'customQueue',
+};
+
+/**
+ * Clear background prefetch queues
+ * @param {string} type - Queue type to clear: 'all', 'api', 'photo_pack', or 'custom'
+ *
+ * @example
+ * // Clear all queues
+ * clearBackgroundQueues('all');
+ *
+ * // Clear only API queue
+ * clearBackgroundQueues('api');
+ */
+export function clearBackgroundQueues(type = 'all') {
+  try {
+    if (type === 'all') {
+      // Clear all queue types
+      Object.values(QUEUE_KEYS).forEach((key) => {
+        localStorage.removeItem(key);
+      });
+    } else if (QUEUE_KEYS[type]) {
+      // Clear specific queue type
+      localStorage.removeItem(QUEUE_KEYS[type]);
+    } else {
+      console.warn(`Unknown queue type: ${type}`);
+    }
+  } catch (error) {
+    console.error('Failed to clear background queues:', error);
+  }
+}
+
+/**
+ * Clear queues based on setting changes
+ * Maps specific setting changes to the appropriate queue clearing strategy
+ *
+ * @param {string} settingName - The name of the setting that changed
+ *
+ * @example
+ * // User changed background type
+ * clearQueuesOnSettingChange('backgroundType');
+ *
+ * // User changed API categories
+ * clearQueuesOnSettingChange('apiCategories');
+ *
+ * // User installed a photo pack
+ * clearQueuesOnSettingChange('packInstall');
+ */
+export function clearQueuesOnSettingChange(settingName) {
+  const clearTriggers = {
+    // Clear all queues
+    backgroundType: 'all', // Background type changed
+
+    // Clear API queue only
+    backgroundAPI: 'api', // API provider changed (Mue <-> Unsplash)
+    apiCategories: 'api', // Categories changed
+    apiQuality: 'api', // Quality changed
+    unsplashCollections: 'api', // Unsplash collections changed
+    backgroundExclude: 'api', // Exclude list changed
+
+    // Clear photo pack queue only
+    packInstall: 'photo_pack', // Photo pack installed
+    packUninstall: 'photo_pack', // Photo pack uninstalled
+
+    // Clear custom queue only
+    customModified: 'custom', // Custom background modified
+    customDeleted: 'custom', // Custom background deleted
+  };
+
+  const queueType = clearTriggers[settingName];
+  if (queueType) {
+    clearBackgroundQueues(queueType);
+  } else {
+    console.warn(`No queue clearing mapped for setting: ${settingName}`);
+  }
+}
+
+/**
+ * Get the queue key for a specific background type
+ * @param {string} type - Background type: 'api', 'photo_pack', or 'custom'
+ * @returns {string} The localStorage key for that queue
+ */
+export function getQueueKey(type) {
+  return QUEUE_KEYS[type] || null;
+}
+
+export default {
+  clearBackgroundQueues,
+  clearQueuesOnSettingChange,
+  getQueueKey,
+};