fix(webui): stop the loader double-loading scripts, and the sidebar noise

Two recurring console errors, each masking real ones.

"Identifier 'BackupAppCard' has already been declared": the app has two
script loaders that could not see each other's work. The boot-time
system loader injects tags with NO id and dedupes by script[src]; the
SPA's loadScript injects with an id and deduped ONLY by that id. So a
file both of them load — backup-app-card.js is on the boot list AND in
the overview Backups tab's asset chain — loaded twice whenever the SPA
loaded second: its getElementById check cannot see an id-less tag. The
class redeclared with an uncaught SyntaxError. The blindness was
one-directional, which is why it looked sporadic. spaClean.loadScript
now also matches by src, closing the asymmetry for every shared file,
not just this one.

"ConfigSidebar: config-categories-list element not found", three to
four times per navigation: renderConfig populated the admin sidebar
unconditionally, but the backup center's Configuration tab EMBEDS that
renderer inside its own pane (renderConfig('backup', <target>)), where
the sidebar element legitimately does not exist — and the refresh
coordinator re-renders that tab on every task event. Populating is now
skipped when a target is passed: an embedded render does not own the
admin shell. The error stays for target-less renders, where a missing
sidebar genuinely is a broken shell.

Verified in a live session: repeated sweeps across /tasks, the overview
Backups tab (Configuration sub-tab included) and a per-app Backups page
produce zero new occurrences of either error — confirmed against a
live console buffer via a sentinel, since the buffer retains the
pre-fix history and re-reading it can look like recurrence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-21 00:28:36 +01:00
parent 429e750266
commit b3b9f9a18b
2 changed files with 17 additions and 2 deletions

View File

@ -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 = '<div class="no-config"><h3>No Configuration Available</h3><p>No configuration items found for this category.</p></div>';

View File

@ -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');