diff --git a/containers/libreportal/frontend/components/admin/config/js/config-manager.js b/containers/libreportal/frontend/components/admin/config/js/config-manager.js index 22cd309..24c87b3 100755 --- a/containers/libreportal/frontend/components/admin/config/js/config-manager.js +++ b/containers/libreportal/frontend/components/admin/config/js/config-manager.js @@ -162,8 +162,13 @@ if (typeof window.ConfigManager === 'undefined') { // Load configuration data const configData = await this.core.loadConfig(category); - // Populate sidebar with categories - this.sidebar.populateSidebar(); + // Populate sidebar with categories — but only when rendering into the + // admin shell. A `target` means an EMBEDDED render (the backup center's + // Configuration tab reuses this renderer inside its own pane), where + // config-categories-list legitimately does not exist; populating there + // logged "element not found" as an error on every repaint, and the + // refresh coordinator repaints on every task event. + if (!target) this.sidebar.populateSidebar(); if (Object.keys(configData).length === 0) { configSection.innerHTML = '

No Configuration Available

No configuration items found for this category.

'; diff --git a/containers/libreportal/frontend/core/kernel/js/spa.js b/containers/libreportal/frontend/core/kernel/js/spa.js index a5deaab..a54e3a1 100755 --- a/containers/libreportal/frontend/core/kernel/js/spa.js +++ b/containers/libreportal/frontend/core/kernel/js/spa.js @@ -528,6 +528,16 @@ class LibrePortalSPAClean { if (document.getElementById(scriptId)) { return; // Already loaded } + // Also recognise tags the boot-time system loader injected: it dedupes by + // src and adds NO id, so the id check above cannot see its scripts. That + // one-way blindness is how backup-app-card.js loaded twice — boot loads it + // id-less, the overview's Backups tab loads it again through here, and + // `class BackupAppCard` redeclares with an uncaught SyntaxError. The system + // loader's own attribute check already covers scripts WE inject, so + // matching by src here closes the asymmetry for every shared file. + if (document.querySelector(`script[src="${src}"]`)) { + return; + } return new Promise((resolve, reject) => { const script = document.createElement('script');