perf(web): gate verbose console logging behind the existing pluginDebug switch

plugins_manager.js, base.html's inline scripts, and app.js emitted 198
console.log calls in production - including per-interaction [DEBUG] dumps -
costing main-thread time and drowning real errors in noise.

- New window.debugLog() gate defined in base.html's first inline script
  (before any other script runs): forwards to console.log only when
  localStorage.pluginDebug === 'true' - the SAME switch plugins_manager.js
  already used for its _PLUGIN_DEBUG_EARLY logs, so existing debug workflow
  docs stay valid. Exposed as window.LEDMATRIX_DEBUG for other scripts.
- Mechanically rewrote console.log( -> debugLog( in plugins_manager.js
  (127), base.html (64), app.js (7). Verified no occurrences lived inside
  string literals before rewriting; console.error/console.warn untouched.
- app.js's no-Alpine showNotification fallback restored to console.info -
  it's a user-facing last resort, not debug output.

Both load paths are safe: the gate is the first inline <script> in <head>,
and every rewritten file loads deferred after it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
This commit is contained in:
ChuckBuilds
2026-07-16 10:49:21 -04:00
co-authored by Claude Sonnet 5
parent 8578d83f19
commit 35a8fbb5be
3 changed files with 211 additions and 199 deletions
+8 -8
View File
@@ -12,8 +12,8 @@ window.showNotification = function(message, type = 'info') {
});
document.dispatchEvent(event);
} else {
// Fallback notification
console.log(`${type}: ${message}`);
// Fallback notification — user-facing last resort, so never gated
console.info(`${type}: ${message}`);
}
};
@@ -246,13 +246,13 @@ window.performanceMonitor = {
logMetrics: function() {
const metrics = this.getMetrics();
console.group('Performance Metrics');
console.log('DOM Content Loaded:', metrics.domContentLoaded?.toFixed(2) || 'N/A', 'ms');
console.log('Load Complete:', metrics.loadComplete?.toFixed(2) || 'N/A', 'ms');
console.log('First Paint:', metrics.firstPaint?.toFixed(2) || 'N/A', 'ms');
console.log('First Contentful Paint:', metrics.firstContentfulPaint?.toFixed(2) || 'N/A', 'ms');
console.log('Resources:', metrics.resourceCount || 0, 'files,', (metrics.totalResourceSize / 1024).toFixed(2) || '0', 'KB');
debugLog('DOM Content Loaded:', metrics.domContentLoaded?.toFixed(2) || 'N/A', 'ms');
debugLog('Load Complete:', metrics.loadComplete?.toFixed(2) || 'N/A', 'ms');
debugLog('First Paint:', metrics.firstPaint?.toFixed(2) || 'N/A', 'ms');
debugLog('First Contentful Paint:', metrics.firstContentfulPaint?.toFixed(2) || 'N/A', 'ms');
debugLog('Resources:', metrics.resourceCount || 0, 'files,', (metrics.totalResourceSize / 1024).toFixed(2) || '0', 'KB');
if (Object.keys(metrics.measures || {}).length > 0) {
console.log('Custom Measures:', metrics.measures);
debugLog('Custom Measures:', metrics.measures);
}
console.groupEnd();
}
+127 -127
View File
@@ -38,7 +38,7 @@ const safeLocalStorage = {
// Define critical functions immediately so they're available before any HTML is rendered
// Debug logging controlled by safeLocalStorage.setItem('pluginDebug', 'true')
const _PLUGIN_DEBUG_EARLY = safeLocalStorage.getItem('pluginDebug') === 'true';
if (_PLUGIN_DEBUG_EARLY) console.log('[PLUGINS SCRIPT] Defining configurePlugin and togglePlugin at top level...');
if (_PLUGIN_DEBUG_EARLY) debugLog('[PLUGINS SCRIPT] Defining configurePlugin and togglePlugin at top level...');
// Expose on-demand functions early as stubs (will be replaced when IIFE runs)
window.openOnDemandModal = function(pluginId) {
@@ -85,7 +85,7 @@ window.requestOnDemandStop = function({ stopService = false } = {}) {
// Define updatePlugin early as a stub to ensure it's always available
window.updatePlugin = window.updatePlugin || function(pluginId) {
if (_PLUGIN_DEBUG_EARLY) console.log('[PLUGINS STUB] updatePlugin called for', pluginId);
if (_PLUGIN_DEBUG_EARLY) debugLog('[PLUGINS STUB] updatePlugin called for', pluginId);
// Validate pluginId
if (!pluginId || typeof pluginId !== 'string') {
@@ -105,7 +105,7 @@ window.updatePlugin = window.updatePlugin || function(pluginId) {
const requestBody = { plugin_id: pluginId };
const requestBodyJson = JSON.stringify(requestBody);
console.log('[UPDATE] Sending request:', { url: '/api/v3/plugins/update', body: requestBodyJson });
debugLog('[UPDATE] Sending request:', { url: '/api/v3/plugins/update', body: requestBodyJson });
// Make the API call directly
return fetch('/api/v3/plugins/update', {
@@ -161,7 +161,7 @@ window.updatePlugin = window.updatePlugin || function(pluginId) {
// Define uninstallPlugin early as a stub
window.uninstallPlugin = window.uninstallPlugin || function(pluginId) {
if (_PLUGIN_DEBUG_EARLY) console.log('[PLUGINS STUB] uninstallPlugin called for', pluginId);
if (_PLUGIN_DEBUG_EARLY) debugLog('[PLUGINS STUB] uninstallPlugin called for', pluginId);
if (!confirm(`Are you sure you want to uninstall ${pluginId}?`)) {
return Promise.resolve({ cancelled: true });
@@ -200,7 +200,7 @@ window.uninstallPlugin = window.uninstallPlugin || function(pluginId) {
// Define configurePlugin early to ensure it's always available
window.configurePlugin = window.configurePlugin || async function(pluginId) {
if (_PLUGIN_DEBUG_EARLY) console.log('[PLUGINS STUB] configurePlugin called for', pluginId);
if (_PLUGIN_DEBUG_EARLY) debugLog('[PLUGINS STUB] configurePlugin called for', pluginId);
// Switch to the plugin's configuration tab instead of opening a modal
// This matches the behavior of clicking the plugin tab at the top
@@ -218,7 +218,7 @@ window.configurePlugin = window.configurePlugin || async function(pluginId) {
if (appComponent) {
// Set the active tab to the plugin ID
appComponent.activeTab = pluginId;
if (_PLUGIN_DEBUG_EARLY) console.log('[PLUGINS STUB] Switched to plugin tab:', pluginId);
if (_PLUGIN_DEBUG_EARLY) debugLog('[PLUGINS STUB] Switched to plugin tab:', pluginId);
// Scroll to top of page to ensure the tab is visible
window.scrollTo({ top: 0, behavior: 'smooth' });
@@ -237,7 +237,7 @@ if (!window._pluginToggleRequests) {
// Define togglePlugin early to ensure it's always available
window.togglePlugin = window.togglePlugin || function(pluginId, enabled) {
if (_PLUGIN_DEBUG_EARLY) console.log('[PLUGINS STUB] togglePlugin called for', pluginId, 'enabled:', enabled);
if (_PLUGIN_DEBUG_EARLY) debugLog('[PLUGINS STUB] togglePlugin called for', pluginId, 'enabled:', enabled);
const plugin = (window.installedPlugins || []).find(p => p.id === pluginId);
const pluginName = plugin ? (plugin.name || pluginId) : pluginId;
@@ -325,7 +325,7 @@ window.togglePlugin = window.togglePlugin || function(pluginId, enabled) {
.then(data => {
// Verify this response is for the latest request (prevent race conditions)
if (window._pluginToggleRequests[pluginId] !== requestToken) {
console.log(`[togglePlugin] Ignoring out-of-order response for ${pluginId}`);
debugLog(`[togglePlugin] Ignoring out-of-order response for ${pluginId}`);
return;
}
@@ -364,7 +364,7 @@ window.togglePlugin = window.togglePlugin || function(pluginId, enabled) {
.catch(error => {
// Verify this error is for the latest request (prevent race conditions)
if (window._pluginToggleRequests[pluginId] !== requestToken) {
console.log(`[togglePlugin] Ignoring out-of-order error for ${pluginId}`);
debugLog(`[togglePlugin] Ignoring out-of-order error for ${pluginId}`);
return;
}
@@ -491,7 +491,7 @@ window.__pluginDomReady = window.__pluginDomReady || false;
event.preventDefault();
event.stopPropagation();
console.log('[DEBUG toggle fallback] Plugin:', pluginId, 'Current enabled (from data):', currentEnabled, 'New state:', isChecked);
debugLog('[DEBUG toggle fallback] Plugin:', pluginId, 'Current enabled (from data):', currentEnabled, 'New state:', isChecked);
window.togglePlugin(pluginId, isChecked);
} else if (action === 'configure' && window.configurePlugin) {
@@ -501,12 +501,12 @@ window.__pluginDomReady = window.__pluginDomReady || false;
} else if (action === 'update' && window.updatePlugin) {
event.preventDefault();
event.stopPropagation();
console.log('[DEBUG update fallback] Updating plugin:', pluginId);
debugLog('[DEBUG update fallback] Updating plugin:', pluginId);
window.updatePlugin(pluginId);
} else if (action === 'uninstall' && window.uninstallPlugin) {
event.preventDefault();
event.stopPropagation();
console.log('[DEBUG uninstall fallback] Uninstalling plugin:', pluginId);
debugLog('[DEBUG uninstall fallback] Uninstalling plugin:', pluginId);
if (confirm(`Are you sure you want to uninstall ${pluginId}?`)) {
window.uninstallPlugin(pluginId);
}
@@ -517,7 +517,7 @@ window.__pluginDomReady = window.__pluginDomReady || false;
// Set up delegation on document (capture phase for better reliability)
document.addEventListener('click', handleGlobalPluginAction, true);
document.addEventListener('change', handleGlobalPluginAction, true);
console.log('[PLUGINS SCRIPT] Global event delegation set up');
debugLog('[PLUGINS SCRIPT] Global event delegation set up');
})();
// Note: configurePlugin and togglePlugin are now defined at the top of the file (after uninstallPlugin)
@@ -525,30 +525,30 @@ window.__pluginDomReady = window.__pluginDomReady || false;
// Verify functions are defined (debug only)
if (_PLUGIN_DEBUG_EARLY) {
console.log('[PLUGINS SCRIPT] Functions defined:', {
debugLog('[PLUGINS SCRIPT] Functions defined:', {
configurePlugin: typeof window.configurePlugin,
togglePlugin: typeof window.togglePlugin
});
if (typeof window.configurePlugin === 'function') {
console.log('[PLUGINS SCRIPT] ✓ configurePlugin ready');
debugLog('[PLUGINS SCRIPT] ✓ configurePlugin ready');
}
if (typeof window.togglePlugin === 'function') {
console.log('[PLUGINS SCRIPT] ✓ togglePlugin ready');
debugLog('[PLUGINS SCRIPT] ✓ togglePlugin ready');
}
}
// GitHub Token Collapse Handler - Define early so it's available before IIFE
console.log('[DEFINE] Defining attachGithubTokenCollapseHandler function...');
debugLog('[DEFINE] Defining attachGithubTokenCollapseHandler function...');
window.attachGithubTokenCollapseHandler = function() {
console.log('[attachGithubTokenCollapseHandler] Starting...');
debugLog('[attachGithubTokenCollapseHandler] Starting...');
const toggleTokenCollapseBtn = document.getElementById('toggle-github-token-collapse');
console.log('[attachGithubTokenCollapseHandler] Button found:', !!toggleTokenCollapseBtn);
debugLog('[attachGithubTokenCollapseHandler] Button found:', !!toggleTokenCollapseBtn);
if (!toggleTokenCollapseBtn) {
console.warn('[attachGithubTokenCollapseHandler] GitHub token collapse button not found');
return;
}
console.log('[attachGithubTokenCollapseHandler] Checking toggleGithubTokenContent...', {
debugLog('[attachGithubTokenCollapseHandler] Checking toggleGithubTokenContent...', {
exists: typeof window.toggleGithubTokenContent
});
if (!window.toggleGithubTokenContent) {
@@ -568,17 +568,17 @@ window.attachGithubTokenCollapseHandler = function() {
// Attach listener to the new button
newBtn.addEventListener('click', function(e) {
console.log('[attachGithubTokenCollapseHandler] Button clicked, calling toggleGithubTokenContent');
debugLog('[attachGithubTokenCollapseHandler] Button clicked, calling toggleGithubTokenContent');
window.toggleGithubTokenContent(e);
});
console.log('[attachGithubTokenCollapseHandler] Handler attached to button:', newBtn.id);
debugLog('[attachGithubTokenCollapseHandler] Handler attached to button:', newBtn.id);
};
// Toggle GitHub Token Settings section
console.log('[DEFINE] Defining toggleGithubTokenContent function...');
debugLog('[DEFINE] Defining toggleGithubTokenContent function...');
window.toggleGithubTokenContent = function(e) {
console.log('[toggleGithubTokenContent] called', e);
debugLog('[toggleGithubTokenContent] called', e);
if (e) {
e.stopPropagation();
@@ -589,7 +589,7 @@ window.toggleGithubTokenContent = function(e) {
const tokenIconCollapse = document.getElementById('github-token-icon-collapse');
const toggleTokenCollapseBtn = document.getElementById('toggle-github-token-collapse');
console.log('[toggleGithubTokenContent] Elements found:', {
debugLog('[toggleGithubTokenContent] Elements found:', {
tokenContent: !!tokenContent,
tokenIconCollapse: !!tokenIconCollapse,
toggleTokenCollapseBtn: !!toggleTokenCollapseBtn
@@ -603,7 +603,7 @@ window.toggleGithubTokenContent = function(e) {
const hasHiddenClass = tokenContent.classList.contains('hidden');
const computedDisplay = window.getComputedStyle(tokenContent).display;
console.log('[toggleGithubTokenContent] Current state:', {
debugLog('[toggleGithubTokenContent] Current state:', {
hasHiddenClass,
computedDisplay,
buttonText: toggleTokenCollapseBtn.querySelector('span')?.textContent
@@ -620,7 +620,7 @@ window.toggleGithubTokenContent = function(e) {
}
const span = toggleTokenCollapseBtn.querySelector('span');
if (span) span.textContent = 'Collapse';
console.log('[toggleGithubTokenContent] Content shown - removed hidden, added block');
debugLog('[toggleGithubTokenContent] Content shown - removed hidden, added block');
} else {
// Hide content - add hidden class, remove block class, ensure display is none
tokenContent.classList.add('hidden');
@@ -632,15 +632,15 @@ window.toggleGithubTokenContent = function(e) {
}
const span = toggleTokenCollapseBtn.querySelector('span');
if (span) span.textContent = 'Expand';
console.log('[toggleGithubTokenContent] Content hidden - added hidden, removed block, set display:none');
debugLog('[toggleGithubTokenContent] Content hidden - added hidden, removed block, set display:none');
}
};
// Simple standalone handler for GitHub plugin installation
// Defined early and globally to ensure it's always available
console.log('[DEFINE] Defining handleGitHubPluginInstall function...');
debugLog('[DEFINE] Defining handleGitHubPluginInstall function...');
window.handleGitHubPluginInstall = function() {
console.log('[handleGitHubPluginInstall] Function called!');
debugLog('[handleGitHubPluginInstall] Function called!');
const urlInput = document.getElementById('github-plugin-url');
const statusDiv = document.getElementById('github-plugin-status');
@@ -654,7 +654,7 @@ window.handleGitHubPluginInstall = function() {
}
const repoUrl = urlInput.value.trim();
console.log('[handleGitHubPluginInstall] Repo URL:', repoUrl);
debugLog('[handleGitHubPluginInstall] Repo URL:', repoUrl);
if (!repoUrl) {
if (statusDiv) {
@@ -685,7 +685,7 @@ window.handleGitHubPluginInstall = function() {
requestBody.branch = branch;
}
console.log('[handleGitHubPluginInstall] Sending request:', requestBody);
debugLog('[handleGitHubPluginInstall] Sending request:', requestBody);
fetch('/api/v3/plugins/install-from-url', {
method: 'POST',
@@ -695,11 +695,11 @@ window.handleGitHubPluginInstall = function() {
body: JSON.stringify(requestBody)
})
.then(response => {
console.log('[handleGitHubPluginInstall] Response status:', response.status);
debugLog('[handleGitHubPluginInstall] Response status:', response.status);
return response.json();
})
.then(data => {
console.log('[handleGitHubPluginInstall] Response data:', data);
debugLog('[handleGitHubPluginInstall] Response data:', data);
if (data.status === 'success') {
if (statusDiv) {
statusDiv.innerHTML = `<span class="text-green-600"><i class="fas fa-check-circle mr-1"></i>Successfully installed: ${data.plugin_id}</span>`;
@@ -744,30 +744,30 @@ window.handleGitHubPluginInstall = function() {
}
});
};
console.log('[DEFINE] handleGitHubPluginInstall defined and ready');
debugLog('[DEFINE] handleGitHubPluginInstall defined and ready');
// GitHub Authentication Status - Define early so it's available in IIFE
// Shows warning banner only when token is missing or invalid
// The token itself is never exposed to the frontend for security
// Returns a Promise so it can be awaited
console.log('[DEFINE] Defining checkGitHubAuthStatus function...');
debugLog('[DEFINE] Defining checkGitHubAuthStatus function...');
window.checkGitHubAuthStatus = function checkGitHubAuthStatus() {
console.log('[checkGitHubAuthStatus] Starting...');
debugLog('[checkGitHubAuthStatus] Starting...');
return fetch('/api/v3/plugins/store/github-status')
.then(response => {
console.log('checkGitHubAuthStatus: Response status:', response.status);
debugLog('checkGitHubAuthStatus: Response status:', response.status);
return response.json();
})
.then(data => {
console.log('checkGitHubAuthStatus: Data received:', data);
debugLog('checkGitHubAuthStatus: Data received:', data);
if (data.status === 'success') {
const authData = data.data;
const tokenStatus = authData.token_status || (authData.authenticated ? 'valid' : 'none');
console.log('checkGitHubAuthStatus: Token status:', tokenStatus);
debugLog('checkGitHubAuthStatus: Token status:', tokenStatus);
const warning = document.getElementById('github-auth-warning');
const settings = document.getElementById('github-token-settings');
const rateLimit = document.getElementById('rate-limit-count');
console.log('checkGitHubAuthStatus: Elements found:', {
debugLog('checkGitHubAuthStatus: Elements found:', {
warning: !!warning,
settings: !!settings,
rateLimit: !!rateLimit
@@ -808,7 +808,7 @@ window.checkGitHubAuthStatus = function checkGitHubAuthStatus() {
// Show warning using both classList and style.display
warning.classList.remove('hidden');
warning.style.display = '';
console.log(`GitHub token status: ${tokenStatus} - showing API limit warning`);
debugLog(`GitHub token status: ${tokenStatus} - showing API limit warning`);
}
}
@@ -821,7 +821,7 @@ window.checkGitHubAuthStatus = function checkGitHubAuthStatus() {
// Hide warning using both classList and style.display
warning.classList.add('hidden');
warning.style.display = 'none';
console.log('GitHub token is valid - hiding API limit warning');
debugLog('GitHub token is valid - hiding API limit warning');
}
// Make settings panel visible but collapsed (accessible for token management)
@@ -872,7 +872,7 @@ window.checkGitHubAuthStatus = function checkGitHubAuthStatus() {
(function() {
'use strict';
if (_PLUGIN_DEBUG_EARLY) console.log('Plugin manager script starting...');
if (_PLUGIN_DEBUG_EARLY) debugLog('Plugin manager script starting...');
// Local variables for this instance
let installedPlugins = [];
@@ -1007,14 +1007,14 @@ window.pluginManager.initializing = false; // Track if initialization is in prog
window.initPluginsPage = function() {
// Prevent duplicate initialization
if (window.pluginManager.initialized || window.pluginManager.initializing) {
console.log('Plugin page already initialized or initializing, skipping...');
debugLog('Plugin page already initialized or initializing, skipping...');
return;
}
// Check if required elements exist
const installedGrid = document.getElementById('installed-plugins-grid');
if (!installedGrid) {
console.log('Plugin elements not ready yet');
debugLog('Plugin elements not ready yet');
return false;
}
@@ -1024,18 +1024,18 @@ window.initPluginsPage = function() {
// Check GitHub auth status immediately (don't wait for full initialization)
// This can run in parallel with other initialization
if (window.checkGitHubAuthStatus) {
console.log('[INIT] Checking GitHub auth status immediately...');
debugLog('[INIT] Checking GitHub auth status immediately...');
window.checkGitHubAuthStatus();
}
// If we fetched data before the DOM existed, render it now
if (window.__pendingInstalledPlugins) {
console.log('[RENDER] Applying pending installed plugins data');
debugLog('[RENDER] Applying pending installed plugins data');
renderInstalledPlugins(window.__pendingInstalledPlugins);
window.__pendingInstalledPlugins = null;
}
if (window.__pendingStorePlugins) {
console.log('[RENDER] Applying pending plugin store data');
debugLog('[RENDER] Applying pending plugin store data');
pluginStoreCache = window.__pendingStorePlugins;
cacheTimestamp = Date.now();
window.__pendingStorePlugins = null;
@@ -1137,7 +1137,7 @@ function initializePluginPageWhenReady() {
// Check if plugins content was swapped in (only match direct plugins content targets)
if (target.id === 'plugins-content' ||
target.querySelector('#installed-plugins-grid')) {
console.log('HTMX swap detected for plugins, initializing...');
debugLog('HTMX swap detected for plugins, initializing...');
// Reset all initialization flags so the fresh empty DOM gets populated
window.pluginManager.initialized = false;
window.pluginManager.initializing = false;
@@ -1152,12 +1152,12 @@ function initializePluginPageWhenReady() {
let pluginsInitialized = false;
function initializePlugins() {
console.log('[initializePlugins] FUNCTION CALLED, pluginsInitialized:', pluginsInitialized);
debugLog('[initializePlugins] FUNCTION CALLED, pluginsInitialized:', pluginsInitialized);
// Guard against multiple initializations
if (pluginsInitialized) {
console.log('[initializePlugins] Already initialized, skipping (but still setting up handlers)');
debugLog('[initializePlugins] Already initialized, skipping (but still setting up handlers)');
// Still set up handlers even if already initialized (in case page was HTMX swapped)
console.log('[initializePlugins] Force setting up GitHub handlers anyway...');
debugLog('[initializePlugins] Force setting up GitHub handlers anyway...');
if (typeof setupGitHubInstallHandlers === 'function') {
setupGitHubInstallHandlers();
} else {
@@ -1167,16 +1167,16 @@ function initializePlugins() {
}
pluginsInitialized = true;
console.log('[initializePlugins] Starting initialization...');
debugLog('[initializePlugins] Starting initialization...');
pluginLog('[INIT] Initializing plugins...');
// Check GitHub authentication status
console.log('[INIT] Checking for checkGitHubAuthStatus function...', {
debugLog('[INIT] Checking for checkGitHubAuthStatus function...', {
exists: typeof window.checkGitHubAuthStatus,
type: typeof window.checkGitHubAuthStatus
});
if (window.checkGitHubAuthStatus) {
console.log('[INIT] Calling checkGitHubAuthStatus...');
debugLog('[INIT] Calling checkGitHubAuthStatus...');
try {
window.checkGitHubAuthStatus();
} catch (error) {
@@ -1222,11 +1222,11 @@ function initializePlugins() {
}
// Setup GitHub installation handlers
console.log('[initializePlugins] About to call setupGitHubInstallHandlers...');
debugLog('[initializePlugins] About to call setupGitHubInstallHandlers...');
if (typeof setupGitHubInstallHandlers === 'function') {
console.log('[initializePlugins] setupGitHubInstallHandlers is a function, calling it...');
debugLog('[initializePlugins] setupGitHubInstallHandlers is a function, calling it...');
setupGitHubInstallHandlers();
console.log('[initializePlugins] setupGitHubInstallHandlers called');
debugLog('[initializePlugins] setupGitHubInstallHandlers called');
} else {
console.error('[initializePlugins] ERROR: setupGitHubInstallHandlers is not a function! Type:', typeof setupGitHubInstallHandlers);
}
@@ -1260,7 +1260,7 @@ const pluginLoadCache = {
// Debug flag - set via safeLocalStorage.setItem('pluginDebug', 'true')
const PLUGIN_DEBUG = typeof localStorage !== 'undefined' && safeLocalStorage.getItem('pluginDebug') === 'true';
function pluginLog(...args) {
if (PLUGIN_DEBUG) console.log(...args);
if (PLUGIN_DEBUG) debugLog(...args);
}
function loadInstalledPlugins(forceRefresh = false) {
@@ -1320,7 +1320,7 @@ function loadInstalledPlugins(forceRefresh = false) {
// Debug logging only when enabled
if (PLUGIN_DEBUG) {
installedPlugins.forEach(plugin => {
console.log(`[DEBUG] Plugin ${plugin.id}: enabled=${plugin.enabled}`);
debugLog(`[DEBUG] Plugin ${plugin.id}: enabled=${plugin.enabled}`);
});
}
@@ -1433,7 +1433,7 @@ function renderInstalledPlugins(plugins) {
// Debug: Log enabled status during rendering (only when debug enabled)
if (PLUGIN_DEBUG) {
console.log(`[DEBUG RENDER] Plugin ${plugin.id}: enabled=${enabledBool}`);
debugLog(`[DEBUG RENDER] Plugin ${plugin.id}: enabled=${enabledBool}`);
}
// Escape plugin ID for use in HTML attributes and JavaScript
@@ -1560,7 +1560,7 @@ function handlePluginAction(event) {
event.preventDefault();
event.stopPropagation();
console.log('[EVENT DELEGATION] Plugin action:', action, 'Plugin ID:', pluginId);
debugLog('[EVENT DELEGATION] Plugin action:', action, 'Plugin ID:', pluginId);
// Helper function to wait for a function to be available
const waitForFunction = (funcName, maxAttempts = 10, delay = 50) => {
@@ -1613,7 +1613,7 @@ function handlePluginAction(event) {
// Toggle the state - we want the opposite of current state
const isChecked = !currentEnabled;
console.log('[DEBUG toggle] Plugin:', pluginId, 'Current enabled (from data):', currentEnabled, 'New state:', isChecked, 'Event type:', event.type);
debugLog('[DEBUG toggle] Plugin:', pluginId, 'Current enabled (from data):', currentEnabled, 'New state:', isChecked, 'Event type:', event.type);
waitForFunction('togglePlugin', 10, 50)
.then(toggleFunc => {
@@ -1852,9 +1852,9 @@ function initializeOnDemandModal() {
// Store the real implementation and replace the stub
window.__openOnDemandModalImpl = function(pluginId) {
console.log('[__openOnDemandModalImpl] Called with pluginId:', pluginId);
debugLog('[__openOnDemandModalImpl] Called with pluginId:', pluginId);
const plugin = findInstalledPlugin(pluginId);
console.log('[__openOnDemandModalImpl] Found plugin:', plugin ? plugin.id : 'NOT FOUND');
debugLog('[__openOnDemandModalImpl] Found plugin:', plugin ? plugin.id : 'NOT FOUND');
if (!plugin) {
console.warn('[__openOnDemandModalImpl] Plugin not found, installedPlugins:', window.installedPlugins?.length || 0);
if (typeof showNotification === 'function') {
@@ -1866,14 +1866,14 @@ window.__openOnDemandModalImpl = function(pluginId) {
// Note: On-demand can work with disabled plugins - the backend will temporarily enable them
// We still log it for debugging but don't block the modal
if (!plugin.enabled) {
console.log('[__openOnDemandModalImpl] Plugin is disabled, but on-demand will temporarily enable it');
debugLog('[__openOnDemandModalImpl] Plugin is disabled, but on-demand will temporarily enable it');
}
currentOnDemandPluginId = pluginId;
console.log('[__openOnDemandModalImpl] Setting currentOnDemandPluginId to:', pluginId);
debugLog('[__openOnDemandModalImpl] Setting currentOnDemandPluginId to:', pluginId);
// Ensure modal is initialized
console.log('[__openOnDemandModalImpl] Initializing modal...');
debugLog('[__openOnDemandModalImpl] Initializing modal...');
initializeOnDemandModal();
const modal = document.getElementById('on-demand-modal');
@@ -1884,7 +1884,7 @@ window.__openOnDemandModalImpl = function(pluginId) {
const startServiceCheckbox = document.getElementById('on-demand-start-service');
const modalTitle = document.getElementById('on-demand-modal-title');
console.log('[__openOnDemandModalImpl] Modal elements check:', {
debugLog('[__openOnDemandModalImpl] Modal elements check:', {
modal: !!modal,
modeSelect: !!modeSelect,
modeHint: !!modeHint,
@@ -1907,7 +1907,7 @@ window.__openOnDemandModalImpl = function(pluginId) {
return;
}
console.log('[__openOnDemandModalImpl] All elements found, opening modal...');
debugLog('[__openOnDemandModalImpl] All elements found, opening modal...');
modalTitle.textContent = `Run ${resolvePluginDisplayName(pluginId)} On-Demand`;
modeSelect.innerHTML = '';
@@ -1954,7 +1954,7 @@ window.__openOnDemandModalImpl = function(pluginId) {
console.error('Error checking service status:', error);
});
console.log('[__openOnDemandModalImpl] Setting modal display to flex');
debugLog('[__openOnDemandModalImpl] Setting modal display to flex');
// Force modal to be visible and properly positioned
// Remove all inline styles that might interfere
modal.removeAttribute('style');
@@ -1974,8 +1974,8 @@ window.__openOnDemandModalImpl = function(pluginId) {
// Force a reflow to ensure styles are applied
modal.offsetHeight;
console.log('[__openOnDemandModalImpl] Modal display set, should be visible now. Modal element:', modal);
console.log('[__openOnDemandModalImpl] Modal computed styles:', {
debugLog('[__openOnDemandModalImpl] Modal display set, should be visible now. Modal element:', modal);
debugLog('[__openOnDemandModalImpl] Modal computed styles:', {
display: window.getComputedStyle(modal).display,
visibility: window.getComputedStyle(modal).visibility,
opacity: window.getComputedStyle(modal).opacity,
@@ -1984,7 +1984,7 @@ window.__openOnDemandModalImpl = function(pluginId) {
});
// Also check if modal is actually in the viewport
const rect = modal.getBoundingClientRect();
console.log('[__openOnDemandModalImpl] Modal bounding rect:', {
debugLog('[__openOnDemandModalImpl] Modal bounding rect:', {
top: rect.top,
left: rect.left,
width: rect.width,
@@ -2006,7 +2006,7 @@ function closeOnDemandModal() {
function submitOnDemandRequest(event) {
event.preventDefault();
console.log('[submitOnDemandRequest] Form submitted, currentOnDemandPluginId:', currentOnDemandPluginId);
debugLog('[submitOnDemandRequest] Form submitted, currentOnDemandPluginId:', currentOnDemandPluginId);
if (!currentOnDemandPluginId) {
console.error('[submitOnDemandRequest] No plugin ID set');
@@ -2022,7 +2022,7 @@ function submitOnDemandRequest(event) {
return;
}
console.log('[submitOnDemandRequest] Form found, processing...');
debugLog('[submitOnDemandRequest] Form found, processing...');
const formData = new FormData(form);
const mode = formData.get('mode');
@@ -2044,7 +2044,7 @@ function submitOnDemandRequest(event) {
}
}
console.log('[submitOnDemandRequest] Payload:', payload);
debugLog('[submitOnDemandRequest] Payload:', payload);
markOnDemandLoading();
fetch('/api/v3/display/on-demand/start', {
@@ -2055,11 +2055,11 @@ function submitOnDemandRequest(event) {
body: JSON.stringify(payload)
})
.then(response => {
console.log('[submitOnDemandRequest] Response status:', response.status);
debugLog('[submitOnDemandRequest] Response status:', response.status);
return response.json();
})
.then(result => {
console.log('[submitOnDemandRequest] Response data:', result);
debugLog('[submitOnDemandRequest] Response data:', result);
if (result.status === 'success') {
if (typeof showNotification === 'function') {
const pluginName = resolvePluginDisplayName(currentOnDemandPluginId);
@@ -2923,7 +2923,7 @@ window.toggleNestedSection = function(sectionId, event) {
// Generic Plugin Action Handler
window.executePluginAction = function(actionId, actionIndex, pluginIdParam = null) {
console.log('[DEBUG] executePluginAction called - actionId:', actionId, 'actionIndex:', actionIndex, 'pluginIdParam:', pluginIdParam);
debugLog('[DEBUG] executePluginAction called - actionId:', actionId, 'actionIndex:', actionIndex, 'pluginIdParam:', pluginIdParam);
// Construct button ID first (we have actionId and actionIndex)
const actionIdFull = `action-${actionId}-${actionIndex}`;
@@ -2938,7 +2938,7 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
if (!pluginId && btn) {
pluginId = btn.getAttribute('data-plugin-id');
if (pluginId) {
console.log('[DEBUG] Got pluginId from button data attribute:', pluginId);
debugLog('[DEBUG] Got pluginId from button data attribute:', pluginId);
}
}
@@ -2948,7 +2948,7 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
if (parentWithPluginId) {
pluginId = parentWithPluginId.getAttribute('data-plugin-id');
if (pluginId) {
console.log('[DEBUG] Got pluginId from parent element:', pluginId);
debugLog('[DEBUG] Got pluginId from parent element:', pluginId);
}
}
}
@@ -2967,7 +2967,7 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
}
}
if (pluginId) {
console.log('[DEBUG] Got pluginId from container:', pluginId);
debugLog('[DEBUG] Got pluginId from container:', pluginId);
}
}
}
@@ -2976,7 +2976,7 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
if (!pluginId) {
pluginId = currentPluginConfig?.pluginId;
if (pluginId) {
console.log('[DEBUG] Got pluginId from currentPluginConfig:', pluginId);
debugLog('[DEBUG] Got pluginId from currentPluginConfig:', pluginId);
}
}
@@ -2988,7 +2988,7 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
const appData = appElement._x_dataStack[0];
if (appData.activeTab && appData.activeTab !== 'overview' && appData.activeTab !== 'plugins' && appData.activeTab !== 'wifi') {
pluginId = appData.activeTab;
console.log('[DEBUG] Got pluginId from Alpine activeTab:', pluginId);
debugLog('[DEBUG] Got pluginId from Alpine activeTab:', pluginId);
}
}
} catch (e) {
@@ -3009,7 +3009,7 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
if (pluginData && pluginData.plugin) {
pluginId = pluginData.plugin.id;
if (pluginId) {
console.log('[DEBUG] Got pluginId from Alpine plugin data (scoped to button context):', pluginId);
debugLog('[DEBUG] Got pluginId from Alpine plugin data (scoped to button context):', pluginId);
}
}
} catch (e) {
@@ -3028,7 +3028,7 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
if (containerData && containerData.plugin) {
pluginId = containerData.plugin.id;
if (pluginId) {
console.log('[DEBUG] Got pluginId from Alpine plugin data (scoped to container):', pluginId);
debugLog('[DEBUG] Got pluginId from Alpine plugin data (scoped to container):', pluginId);
}
}
} catch (e) {
@@ -3053,7 +3053,7 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
return;
}
console.log('[DEBUG] executePluginAction - Final pluginId:', pluginId, 'actionId:', actionId, 'actionIndex:', actionIndex);
debugLog('[DEBUG] executePluginAction - Final pluginId:', pluginId, 'actionId:', actionId, 'actionIndex:', actionIndex);
if (!btn || !statusDiv) {
console.error(`Action elements not found: ${actionIdFull}`);
@@ -3075,15 +3075,15 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
if (!action) {
console.error(`Action not found: ${actionId} for plugin ${pluginId}`);
console.log('[DEBUG] currentPluginConfig:', currentPluginConfig);
console.log('[DEBUG] installedPlugins:', window.installedPlugins);
debugLog('[DEBUG] currentPluginConfig:', currentPluginConfig);
debugLog('[DEBUG] installedPlugins:', window.installedPlugins);
if (typeof showNotification === 'function') {
showNotification(`Action ${actionId} not found. Please refresh the page.`, 'error');
}
return;
}
console.log('[DEBUG] Found action:', action);
debugLog('[DEBUG] Found action:', action);
// Check if we're in step 2 (completing OAuth flow)
if (btn.dataset.step === '2') {
@@ -3237,7 +3237,7 @@ if (!window.updatePlugin || window.updatePlugin.toString().includes('[UPDATE]'))
const requestBody = { plugin_id: pluginId };
const requestBodyJson = JSON.stringify(requestBody);
console.log('[UPDATE] Sending request:', { url: '/api/v3/plugins/update', body: requestBodyJson });
debugLog('[UPDATE] Sending request:', { url: '/api/v3/plugins/update', body: requestBodyJson });
return fetch('/api/v3/plugins/update', {
method: 'POST',
@@ -3308,7 +3308,7 @@ window.uninstallPlugin = function(pluginId) {
})
.then(response => response.json())
.then(data => {
console.log('Uninstall response:', data);
debugLog('Uninstall response:', data);
// Check if operation was queued
if (data.status === 'success' && data.data && data.data.operation_id) {
@@ -3404,7 +3404,7 @@ function handleUninstallSuccess(pluginId) {
}
function refreshPlugins() {
console.log('[refreshPlugins] Button clicked, refreshing plugins...');
debugLog('[refreshPlugins] Button clicked, refreshing plugins...');
// Clear cache to force fresh data
pluginStoreCache = null;
cacheTimestamp = null;
@@ -3419,7 +3419,7 @@ function refreshPlugins() {
}
function restartDisplay() {
console.log('[restartDisplay] Button clicked, restarting display service...');
debugLog('[restartDisplay] Button clicked, restarting display service...');
showNotification('Restarting display service...', 'info');
fetch('/api/v3/system/action', {
@@ -3444,7 +3444,7 @@ function searchPluginStore(fetchCommitInfo = true) {
// If cache is valid and we don't need fresh commit info, just re-filter
if (isCacheValid && !fetchCommitInfo) {
console.log('Using cached plugin store data');
debugLog('Using cached plugin store data');
const storeGrid = document.getElementById('plugin-store-grid');
if (storeGrid) {
applyStoreFiltersAndSort();
@@ -3464,7 +3464,7 @@ function searchPluginStore(fetchCommitInfo = true) {
url += '?fetch_commit_info=false';
}
console.log('Store URL:', url);
debugLog('Store URL:', url);
fetch(url)
.then(response => response.json())
@@ -3473,7 +3473,7 @@ function searchPluginStore(fetchCommitInfo = true) {
if (data.status === 'success') {
const plugins = data.data.plugins || [];
console.log('Store plugins count:', plugins.length);
debugLog('Store plugins count:', plugins.length);
pluginStoreCache = plugins;
cacheTimestamp = Date.now();
@@ -3954,7 +3954,7 @@ window.installFromCustomRegistry = function(pluginId, registryUrl, pluginPath, b
}
function setupCollapsibleSections() {
console.log('[setupCollapsibleSections] Setting up collapsible sections...');
debugLog('[setupCollapsibleSections] Setting up collapsible sections...');
// Installed Plugins and Plugin Store sections no longer have collapse buttons
// They are always visible
@@ -3964,7 +3964,7 @@ function setupCollapsibleSections() {
window.attachGithubTokenCollapseHandler();
}
console.log('[setupCollapsibleSections] Collapsible sections setup complete');
debugLog('[setupCollapsibleSections] Collapsible sections setup complete');
}
function loadSavedRepositories() {
@@ -4052,12 +4052,12 @@ window.removeSavedRepository = function(repoUrl) {
// Separate function to attach install button handler (can be called multiple times)
function attachInstallButtonHandler() {
console.log('[attachInstallButtonHandler] ===== FUNCTION CALLED =====');
debugLog('[attachInstallButtonHandler] ===== FUNCTION CALLED =====');
const installBtn = document.getElementById('install-plugin-from-url');
const pluginUrlInput = document.getElementById('github-plugin-url');
const pluginStatusDiv = document.getElementById('github-plugin-status');
console.log('[attachInstallButtonHandler] Looking for install button elements:', {
debugLog('[attachInstallButtonHandler] Looking for install button elements:', {
installBtn: !!installBtn,
pluginUrlInput: !!pluginUrlInput,
pluginStatusDiv: !!pluginStatusDiv
@@ -4066,7 +4066,7 @@ function attachInstallButtonHandler() {
if (installBtn && pluginUrlInput) {
// Check if handler already attached (prevent duplicates)
if (installBtn.hasAttribute('data-handler-attached')) {
console.log('[attachInstallButtonHandler] Handler already attached, skipping');
debugLog('[attachInstallButtonHandler] Handler already attached, skipping');
return;
}
@@ -4080,12 +4080,12 @@ function attachInstallButtonHandler() {
newBtn.setAttribute('data-handler-attached', 'true');
parent.replaceChild(newBtn, installBtn);
console.log('[attachInstallButtonHandler] Install button cloned and replaced, type:', newBtn.type);
debugLog('[attachInstallButtonHandler] Install button cloned and replaced, type:', newBtn.type);
newBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('[attachInstallButtonHandler] Install button clicked!');
debugLog('[attachInstallButtonHandler] Install button clicked!');
const repoUrl = pluginUrlInput.value.trim();
if (!repoUrl) {
@@ -4114,7 +4114,7 @@ function attachInstallButtonHandler() {
requestBody.branch = branch;
}
console.log('[attachInstallButtonHandler] Sending install request:', requestBody);
debugLog('[attachInstallButtonHandler] Sending install request:', requestBody);
fetch('/api/v3/plugins/install-from-url', {
method: 'POST',
@@ -4124,11 +4124,11 @@ function attachInstallButtonHandler() {
body: JSON.stringify(requestBody)
})
.then(response => {
console.log('[attachInstallButtonHandler] Response status:', response.status);
debugLog('[attachInstallButtonHandler] Response status:', response.status);
return response.json();
})
.then(data => {
console.log('[attachInstallButtonHandler] Response data:', data);
debugLog('[attachInstallButtonHandler] Response data:', data);
if (data.status === 'success') {
if (pluginStatusDiv) {
pluginStatusDiv.innerHTML = `<span class="text-green-600"><i class="fas fa-check-circle mr-1"></i>Successfully installed: ${data.plugin_id}</span>`;
@@ -4161,12 +4161,12 @@ function attachInstallButtonHandler() {
pluginUrlInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
console.log('[attachInstallButtonHandler] Enter key pressed, triggering install');
debugLog('[attachInstallButtonHandler] Enter key pressed, triggering install');
newBtn.click();
}
});
console.log('[attachInstallButtonHandler] Install button handler attached successfully');
debugLog('[attachInstallButtonHandler] Install button handler attached successfully');
} else {
console.error('[attachInstallButtonHandler] Install button parent not found!');
}
@@ -4179,14 +4179,14 @@ function attachInstallButtonHandler() {
}
function setupGitHubInstallHandlers() {
console.log('[setupGitHubInstallHandlers] ===== FUNCTION CALLED ===== Setting up GitHub install handlers...');
debugLog('[setupGitHubInstallHandlers] ===== FUNCTION CALLED ===== Setting up GitHub install handlers...');
// Toggle GitHub install section visibility
const toggleBtn = document.getElementById('toggle-github-install');
const installSection = document.getElementById('github-install-section');
const icon = document.getElementById('github-install-icon');
console.log('[setupGitHubInstallHandlers] Elements found:', {
debugLog('[setupGitHubInstallHandlers] Elements found:', {
button: !!toggleBtn,
section: !!installSection,
icon: !!icon
@@ -4202,7 +4202,7 @@ function setupGitHubInstallHandlers() {
newBtn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
console.log('[setupGitHubInstallHandlers] GitHub install toggle clicked');
debugLog('[setupGitHubInstallHandlers] GitHub install toggle clicked');
const section = document.getElementById('github-install-section');
const iconEl = document.getElementById('github-install-icon');
@@ -4225,9 +4225,9 @@ function setupGitHubInstallHandlers() {
if (span) span.textContent = 'Hide';
// Re-attach install button handler when section is shown (in case elements weren't ready before)
console.log('[setupGitHubInstallHandlers] Section shown, will re-attach install button handler in 100ms');
debugLog('[setupGitHubInstallHandlers] Section shown, will re-attach install button handler in 100ms');
setTimeout(() => {
console.log('[setupGitHubInstallHandlers] Re-attaching install button handler now');
debugLog('[setupGitHubInstallHandlers] Re-attaching install button handler now');
attachInstallButtonHandler();
}, 100);
} else {
@@ -4242,16 +4242,16 @@ function setupGitHubInstallHandlers() {
if (span) span.textContent = 'Show';
}
});
console.log('[setupGitHubInstallHandlers] Handler attached');
debugLog('[setupGitHubInstallHandlers] Handler attached');
}
} else {
console.warn('[setupGitHubInstallHandlers] Required elements not found');
}
// Install single plugin from URL - use separate function so we can re-call it
console.log('[setupGitHubInstallHandlers] About to call attachInstallButtonHandler...');
debugLog('[setupGitHubInstallHandlers] About to call attachInstallButtonHandler...');
attachInstallButtonHandler();
console.log('[setupGitHubInstallHandlers] Called attachInstallButtonHandler');
debugLog('[setupGitHubInstallHandlers] Called attachInstallButtonHandler');
// Load registry from URL
const loadRegistryBtn = document.getElementById('load-registry-from-url');
@@ -4436,7 +4436,7 @@ function showSuccess(message) {
if (typeof showNotification === 'function') {
showNotification(message, 'success');
} else {
console.log('Success: ' + message);
debugLog('Success: ' + message);
// Show a temporary success message
const statusDiv = document.getElementById('github-plugin-status') || document.getElementById('registry-status');
if (statusDiv) {
@@ -5547,7 +5547,7 @@ if (typeof window !== 'undefined') {
// Debug logging (only if pluginDebug is enabled)
if (_PLUGIN_DEBUG_EARLY) {
console.log('[ARRAY-OBJECTS] Functions defined on window:', {
debugLog('[ARRAY-OBJECTS] Functions defined on window:', {
addArrayObjectItem: typeof window.addArrayObjectItem,
removeArrayObjectItem: typeof window.removeArrayObjectItem,
updateArrayObjectData: typeof window.updateArrayObjectData,
@@ -5561,7 +5561,7 @@ if (typeof window !== 'undefined') {
window.currentPluginConfig = null;
// Force initialization immediately when script loads (for HTMX swapped content)
console.log('Plugins script loaded, checking for elements...');
debugLog('Plugins script loaded, checking for elements...');
// Ensure all functions are globally available (in case IIFE didn't expose them properly)
// These should already be set inside the IIFE, but this ensures they're available
@@ -5582,7 +5582,7 @@ if (typeof renderInstalledPlugins !== 'undefined') {
// Verify critical functions are available
if (_PLUGIN_DEBUG_EARLY) {
console.log('Plugin functions available:', {
debugLog('Plugin functions available:', {
configurePlugin: typeof window.configurePlugin,
togglePlugin: typeof window.togglePlugin,
initializePlugins: typeof window.initializePlugins,
@@ -5593,7 +5593,7 @@ if (_PLUGIN_DEBUG_EARLY) {
// Check GitHub auth status immediately if elements exist (don't wait for full initialization)
if (window.checkGitHubAuthStatus && document.getElementById('github-auth-warning')) {
console.log('[EARLY] Checking GitHub auth status immediately on script load...');
debugLog('[EARLY] Checking GitHub auth status immediately on script load...');
window.checkGitHubAuthStatus();
}
@@ -5612,7 +5612,7 @@ if (typeof initializeOnDemandModal === 'function') {
setTimeout(function() {
const installedGrid = document.getElementById('installed-plugins-grid');
if (installedGrid) {
console.log('Found installed-plugins-grid, forcing initialization...');
debugLog('Found installed-plugins-grid, forcing initialization...');
window.pluginManager.initialized = false;
if (typeof initializePluginPageWhenReady === 'function') {
initializePluginPageWhenReady();
@@ -5620,7 +5620,7 @@ setTimeout(function() {
window.initPluginsPage();
}
} else {
console.log('installed-plugins-grid not found yet, will retry via event listeners');
debugLog('installed-plugins-grid not found yet, will retry via event listeners');
}
// Also try to attach install button handler after a delay (fallback).
@@ -5819,7 +5819,7 @@ document.addEventListener('htmx:afterSettle', function() {
if (countEl) countEl.textContent = `${data.count} apps`;
if (data.rate_limit) {
console.log(`[Starlark] GitHub rate limit: ${data.rate_limit.remaining}/${data.rate_limit.limit} remaining` + (data.cached ? ' (cached)' : ''));
debugLog(`[Starlark] GitHub rate limit: ${data.rate_limit.remaining}/${data.rate_limit.limit} remaining` + (data.cached ? ' (cached)' : ''));
}
applyStarlarkFiltersAndSort();
+76 -64
View File
@@ -5,6 +5,18 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LED Matrix Control Panel - v3</title>
<!-- Debug logging gate: verbose console output is suppressed unless
localStorage.pluginDebug === 'true' (the same switch the plugin
scripts already use). console.error/warn are never gated. -->
<script>
window.LEDMATRIX_DEBUG = (function() {
try { return localStorage.getItem('pluginDebug') === 'true'; } catch (e) { return false; }
})();
window.debugLog = function() {
if (window.LEDMATRIX_DEBUG) console.log.apply(console, arguments);
};
</script>
<!-- Theme initialization (must run before CSS to prevent flash) -->
<script>
(function() {
@@ -135,7 +147,7 @@
loadScript(fallbackSrc, null, function() {
setTimeout(function() {
if (typeof htmx !== 'undefined') {
console.log('HTMX loaded from fallback');
debugLog('HTMX loaded from fallback');
window.dispatchEvent(new Event('htmx:ready'));
// Load extensions after core loads
loadScript(sseSrc, isAPMode ? 'https://unpkg.com/htmx.org/dist/ext/sse.js' : '/static/v3/js/htmx-sse.js');
@@ -152,7 +164,7 @@
window.dispatchEvent(new Event('htmx-load-failed'));
}
} else {
console.log('HTMX loaded successfully');
debugLog('HTMX loaded successfully');
window.dispatchEvent(new Event('htmx:ready'));
// Load extensions after core loads
loadScript(sseSrc, isAPMode ? 'https://unpkg.com/htmx.org/dist/ext/sse.js' : '/static/v3/js/htmx-sse.js');
@@ -415,7 +427,7 @@
const content = document.getElementById('plugins-content');
if (content && !content.hasAttribute('data-loaded')) {
content.setAttribute('data-loaded', 'true');
console.log('Loading plugins directly via fetch (HTMX fallback)...');
debugLog('Loading plugins directly via fetch (HTMX fallback)...');
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
fetch('/v3/partials/plugins', { signal: controller.signal })
@@ -482,7 +494,7 @@
// Global event listener for pluginsUpdated - works even if Alpine isn't ready yet
// This ensures tabs update when plugins_manager.js loads plugins
document.addEventListener('pluginsUpdated', function(event) {
console.log('[GLOBAL] Received pluginsUpdated event:', event.detail?.plugins?.length || 0, 'plugins');
debugLog('[GLOBAL] Received pluginsUpdated event:', event.detail?.plugins?.length || 0, 'plugins');
const plugins = event.detail?.plugins || [];
// Update window.installedPlugins
@@ -497,7 +509,7 @@
// Only call updatePluginTabs if it's the full implementation (has _doUpdatePluginTabs)
if (typeof appComponent.updatePluginTabs === 'function' &&
appComponent.updatePluginTabs.toString().includes('_doUpdatePluginTabs')) {
console.log('[GLOBAL] Updating plugin tabs via Alpine component (full implementation)');
debugLog('[GLOBAL] Updating plugin tabs via Alpine component (full implementation)');
appComponent.updatePluginTabs();
return; // Full implementation handles it, don't do direct update
}
@@ -535,7 +547,7 @@
tabButton.innerHTML = `<i class="${iconClass}"></i>${(plugin.name || plugin.id).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}`;
pluginTabsNav.appendChild(tabButton);
});
console.log('[GLOBAL] Updated plugin tabs directly:', plugins.length, 'tabs added');
debugLog('[GLOBAL] Updated plugin tabs directly:', plugins.length, 'tabs added');
}
});
@@ -594,7 +606,7 @@
// The full implementation will have its own listener, so we only need this for the stub
if (!this._pluginsUpdatedListenerSet) {
const handlePluginsUpdated = (event) => {
console.log('[STUB] Received pluginsUpdated event:', event.detail?.plugins?.length || 0, 'plugins');
debugLog('[STUB] Received pluginsUpdated event:', event.detail?.plugins?.length || 0, 'plugins');
const plugins = event.detail?.plugins || [];
// Only update if we're still in stub mode (not enhanced yet)
if (typeof this.updatePluginTabs === 'function' && !this.updatePluginTabs.toString().includes('_doUpdatePluginTabs')) {
@@ -612,7 +624,7 @@
};
document.addEventListener('pluginsUpdated', handlePluginsUpdated);
this._pluginsUpdatedListenerSet = true;
console.log('[STUB] init: Set up pluginsUpdated event listener');
debugLog('[STUB] init: Set up pluginsUpdated event listener');
}
// Try immediately - if full implementation is already loaded, use it right away
@@ -629,7 +641,7 @@
if (retryCount >= maxRetries) {
// Fallback: if plugins_manager.js hasn't loaded after 2 seconds, fetch directly
if (!window.installedPlugins || window.installedPlugins.length === 0) {
console.log('[STUB] checkAndUpdateTabs: Fallback - fetching plugins directly after timeout');
debugLog('[STUB] checkAndUpdateTabs: Fallback - fetching plugins directly after timeout');
this.loadInstalledPluginsDirectly();
}
return;
@@ -638,7 +650,7 @@
// Check if plugins are available (either from window or component)
const plugins = window.installedPlugins || this.installedPlugins || [];
if (plugins.length > 0) {
console.log('[STUB] checkAndUpdateTabs: Found', plugins.length, 'plugins, updating tabs');
debugLog('[STUB] checkAndUpdateTabs: Found', plugins.length, 'plugins, updating tabs');
this.installedPlugins = plugins;
if (typeof this.updatePluginTabs === 'function') {
this.updatePluginTabs();
@@ -654,7 +666,7 @@
// Full implementation loaded, but still set up fallback timer
setTimeout(() => {
if (!window.installedPlugins || window.installedPlugins.length === 0) {
console.log('[STUB] init: Fallback timer - fetching plugins directly');
debugLog('[STUB] init: Fallback timer - fetching plugins directly');
this.loadInstalledPluginsDirectly();
}
}, 2000);
@@ -664,7 +676,7 @@
// Direct plugin loading for stub (before full implementation loads)
async loadInstalledPluginsDirectly() {
try {
console.log('[STUB] loadInstalledPluginsDirectly: Starting...');
debugLog('[STUB] loadInstalledPluginsDirectly: Starting...');
// Ensure DOM is ready
const ensureDOMReady = () => {
return new Promise((resolve) => {
@@ -688,7 +700,7 @@
const data = await getInstalledPluginsSafe();
if (data.status === 'success') {
const plugins = data.data.plugins || [];
console.log('[STUB] loadInstalledPluginsDirectly: Loaded', plugins.length, 'plugins');
debugLog('[STUB] loadInstalledPluginsDirectly: Loaded', plugins.length, 'plugins');
// Update both component and window
this.installedPlugins = plugins;
@@ -698,7 +710,7 @@
document.dispatchEvent(new CustomEvent('pluginsUpdated', {
detail: { plugins: plugins }
}));
console.log('[STUB] loadInstalledPluginsDirectly: Dispatched pluginsUpdated event');
debugLog('[STUB] loadInstalledPluginsDirectly: Dispatched pluginsUpdated event');
// Update tabs if we have the method - use $nextTick if available
if (typeof this.updatePluginTabs === 'function') {
@@ -752,7 +764,7 @@
}
this._updatePluginTabsTimeout = setTimeout(() => {
console.log('[STUB] updatePluginTabs: Executing with', this.installedPlugins?.length || 0, 'plugins');
debugLog('[STUB] updatePluginTabs: Executing with', this.installedPlugins?.length || 0, 'plugins');
const pluginTabsRow = document.getElementById('plugin-tabs-row');
const pluginTabsNav = pluginTabsRow?.querySelector('nav');
if (!pluginTabsRow || !pluginTabsNav) {
@@ -760,7 +772,7 @@
return;
}
if (!this.installedPlugins || this.installedPlugins.length === 0) {
console.log('[STUB] updatePluginTabs: No plugins to display');
debugLog('[STUB] updatePluginTabs: No plugins to display');
return;
}
@@ -770,13 +782,13 @@
const currentIds = this.installedPlugins.map(p => p.id).sort().join(',');
if (existingIds === currentIds && existingTabs.length === this.installedPlugins.length) {
console.log('[STUB] updatePluginTabs: Tabs already match, skipping update');
debugLog('[STUB] updatePluginTabs: Tabs already match, skipping update');
return;
}
// Clear existing plugin tabs (except Plugin Manager)
existingTabs.forEach(tab => tab.remove());
console.log('[STUB] updatePluginTabs: Cleared', existingTabs.length, 'existing tabs');
debugLog('[STUB] updatePluginTabs: Cleared', existingTabs.length, 'existing tabs');
// Add tabs for each installed plugin
this.installedPlugins.forEach(plugin => {
@@ -796,7 +808,7 @@
tabButton.innerHTML = `<i class="${iconClass}"></i>${div.innerHTML}`;
pluginTabsNav.appendChild(tabButton);
});
console.log('[STUB] updatePluginTabs: Added', this.installedPlugins.length, 'plugin tabs');
debugLog('[STUB] updatePluginTabs: Added', this.installedPlugins.length, 'plugin tabs');
}, 100);
},
showNotification: function(message, type) {},
@@ -1735,7 +1747,7 @@
// First check if plugins are already in window.installedPlugins (from plugins_manager.js)
if (typeof window.installedPlugins !== 'undefined' && Array.isArray(window.installedPlugins) && window.installedPlugins.length > 0) {
this.installedPlugins = window.installedPlugins;
console.log('Initialized installedPlugins from global:', this.installedPlugins.length);
debugLog('Initialized installedPlugins from global:', this.installedPlugins.length);
// Ensure tabs are updated immediately
this.$nextTick(() => {
this.updatePluginTabs();
@@ -1780,7 +1792,7 @@
// Listen for plugin updates from pluginManager
document.addEventListener('pluginsUpdated', (event) => {
console.log('Received pluginsUpdated event:', event.detail.plugins.length, 'plugins');
debugLog('Received pluginsUpdated event:', event.detail.plugins.length, 'plugins');
this.installedPlugins = event.detail.plugins;
this.updatePluginTabs();
});
@@ -1805,7 +1817,7 @@
// Only update if plugin list actually changed
if (oldIds !== newIds) {
console.log('window.installedPlugins changed:', newPlugins.length, 'plugins');
debugLog('window.installedPlugins changed:', newPlugins.length, 'plugins');
_installedPluginsValue = newPlugins;
this.installedPlugins = newPlugins;
this.updatePluginTabs();
@@ -1892,12 +1904,12 @@
async loadInstalledPlugins() {
// If pluginManager exists (plugins.html is loaded), delegate to it
if (window.pluginManager) {
console.log('[FULL] Delegating plugin loading to pluginManager...');
debugLog('[FULL] Delegating plugin loading to pluginManager...');
await window.pluginManager.loadInstalledPlugins();
// pluginManager should set window.installedPlugins, so update our component
if (window.installedPlugins && Array.isArray(window.installedPlugins)) {
this.installedPlugins = window.installedPlugins;
console.log('[FULL] Updated component plugins from window.installedPlugins:', this.installedPlugins.length);
debugLog('[FULL] Updated component plugins from window.installedPlugins:', this.installedPlugins.length);
}
this.updatePluginTabs();
return;
@@ -1905,18 +1917,18 @@
// Otherwise, load plugins directly (fallback for when plugins.html isn't loaded)
try {
console.log('[FULL] Loading installed plugins directly...');
debugLog('[FULL] Loading installed plugins directly...');
const data = await getInstalledPluginsSafe();
if (data.status === 'success') {
this.installedPlugins = data.data.plugins || [];
// Also update window.installedPlugins for consistency
window.installedPlugins = this.installedPlugins;
console.log(`[FULL] Loaded ${this.installedPlugins.length} plugins:`, this.installedPlugins.map(p => p.id));
debugLog(`[FULL] Loaded ${this.installedPlugins.length} plugins:`, this.installedPlugins.map(p => p.id));
// Debug: Log enabled status for each plugin
this.installedPlugins.forEach(plugin => {
console.log(`[DEBUG Alpine] Plugin ${plugin.id}: enabled=${plugin.enabled} (type: ${typeof plugin.enabled})`);
debugLog(`[DEBUG Alpine] Plugin ${plugin.id}: enabled=${plugin.enabled} (type: ${typeof plugin.enabled})`);
});
this.updatePluginTabs();
@@ -1929,7 +1941,7 @@
},
updatePluginTabs(retryCount = 0) {
console.log('[FULL] updatePluginTabs called (retryCount:', retryCount, ')');
debugLog('[FULL] updatePluginTabs called (retryCount:', retryCount, ')');
const maxRetries = 5;
// Debounce: Clear any pending update
@@ -1955,8 +1967,8 @@
? this.installedPlugins
: (window.installedPlugins || []);
console.log('[FULL] _doUpdatePluginTabs called with:', pluginsToShow.length, 'plugins (attempt', retryCount + 1, ')');
console.log('[FULL] Plugin sources:', {
debugLog('[FULL] _doUpdatePluginTabs called with:', pluginsToShow.length, 'plugins (attempt', retryCount + 1, ')');
debugLog('[FULL] Plugin sources:', {
componentPlugins: this.installedPlugins?.length || 0,
windowPlugins: window.installedPlugins?.length || 0,
using: pluginsToShow.length > 0 ? (this.installedPlugins?.length > 0 ? 'component' : 'window') : 'none'
@@ -1969,13 +1981,13 @@
// Only skip if we have plugins and they match (don't skip if both are empty)
if (currentPluginIds === lastRenderedIds && retryCount === 0 && currentPluginIds.length > 0) {
// Plugin list hasn't changed, skip update
console.log('[FULL] Plugin list unchanged, skipping update');
debugLog('[FULL] Plugin list unchanged, skipping update');
return;
}
// If we have no plugins and haven't rendered anything yet, still try to render (might be first load)
if (pluginsToShow.length === 0 && retryCount === 0) {
console.log('[FULL] No plugins to show, but will retry in case they load...');
debugLog('[FULL] No plugins to show, but will retry in case they load...');
if (retryCount < maxRetries) {
setTimeout(() => {
this._doUpdatePluginTabs(retryCount + 1);
@@ -1990,7 +2002,7 @@
const pluginTabsRow = document.getElementById('plugin-tabs-row');
const pluginTabsNav = pluginTabsRow?.querySelector('nav');
console.log('[FULL] Plugin tabs elements:', {
debugLog('[FULL] Plugin tabs elements:', {
pluginTabsRow: !!pluginTabsRow,
pluginTabsNav: !!pluginTabsNav,
bodyExists: !!document.body,
@@ -2014,19 +2026,19 @@
return;
}
console.log(`[FULL] Updating plugin tabs for ${pluginsToShow.length} plugins`);
debugLog(`[FULL] Updating plugin tabs for ${pluginsToShow.length} plugins`);
// Always show the plugin tabs row (Plugin Manager should always be available)
console.log('[FULL] Ensuring plugin tabs row is visible');
debugLog('[FULL] Ensuring plugin tabs row is visible');
pluginTabsRow.style.display = 'block';
// Clear existing plugin tabs (except the Plugin Manager tab)
const existingTabs = pluginTabsNav.querySelectorAll('.plugin-tab');
console.log(`[FULL] Removing ${existingTabs.length} existing plugin tabs`);
debugLog(`[FULL] Removing ${existingTabs.length} existing plugin tabs`);
existingTabs.forEach(tab => tab.remove());
// Add tabs for each installed plugin
console.log('[FULL] Adding tabs for plugins:', pluginsToShow.map(p => p.id));
debugLog('[FULL] Adding tabs for plugins:', pluginsToShow.map(p => p.id));
pluginsToShow.forEach(plugin => {
const tabButton = document.createElement('button');
tabButton.type = 'button';
@@ -2050,10 +2062,10 @@
// Insert before the closing </nav> tag
pluginTabsNav.appendChild(tabButton);
console.log('[FULL] Added tab for plugin:', plugin.id);
debugLog('[FULL] Added tab for plugin:', plugin.id);
});
console.log('[FULL] Plugin tabs update completed. Total tabs:', pluginTabsNav.querySelectorAll('.plugin-tab').length);
debugLog('[FULL] Plugin tabs update completed. Total tabs:', pluginTabsNav.querySelectorAll('.plugin-tab').length);
},
updatePluginTabStates() {
@@ -2077,7 +2089,7 @@
if (typeof window.showNotification === 'function') {
window.showNotification(message, type);
} else {
console.log(`[${type.toUpperCase()}]`, message);
debugLog(`[${type.toUpperCase()}]`, message);
}
},
@@ -2096,7 +2108,7 @@
async loadPluginConfig(pluginId) {
console.log('Loading config for plugin:', pluginId);
debugLog('Loading config for plugin:', pluginId);
this.loading = true;
try {
@@ -2163,8 +2175,8 @@
const pluginInfo = pluginsData.data.plugins.find(p => p.id === pluginId);
this.webUiActions = pluginInfo ? (pluginInfo.web_ui_actions || []) : [];
console.log('[DEBUG] Loaded web_ui_actions for', pluginId, ':', this.webUiActions.length, 'actions');
console.log('[DEBUG] Updated plugin data with commit info:', pluginInfo ? {
debugLog('[DEBUG] Loaded web_ui_actions for', pluginId, ':', this.webUiActions.length, 'actions');
debugLog('[DEBUG] Updated plugin data with commit info:', pluginInfo ? {
last_commit: pluginInfo.last_commit,
branch: pluginInfo.branch,
last_updated: pluginInfo.last_updated
@@ -2174,7 +2186,7 @@
this.webUiActions = [];
}
console.log('Loaded config, schema, and actions for', pluginId);
debugLog('Loaded config, schema, and actions for', pluginId);
} catch (error) {
console.error('Error loading plugin config:', error);
this.config = { enabled: true, display_duration: 30 };
@@ -2193,13 +2205,13 @@
// Only log once per plugin to avoid spam (Alpine.js may call this multiple times during rendering)
if (!this._configFormLogged || this._configFormLogged !== pluginId) {
console.log('[DEBUG] generateConfigForm called for', pluginId, 'with', webUiActions?.length || 0, 'actions');
debugLog('[DEBUG] generateConfigForm called for', pluginId, 'with', webUiActions?.length || 0, 'actions');
// Debug: Check if image_config.images has x-widget in schema
if (schema && schema.properties && schema.properties.image_config) {
const imgConfig = schema.properties.image_config;
if (imgConfig.properties && imgConfig.properties.images) {
const imagesProp = imgConfig.properties.images;
console.log('[DEBUG] Schema check - image_config.images:', {
debugLog('[DEBUG] Schema check - image_config.images:', {
type: imagesProp.type,
'x-widget': imagesProp['x-widget'],
'has x-widget': 'x-widget' in imagesProp,
@@ -2253,7 +2265,7 @@
const schemaProp = getSchemaProperty(schema, fullKey);
const xWidgetFromSchema = schemaProp ? (schemaProp['x-widget'] || schemaProp['x_widget']) : null;
console.log('[DEBUG generateFieldHtml] Array property:', fullKey, {
debugLog('[DEBUG generateFieldHtml] Array property:', fullKey, {
'prop.x-widget': prop['x-widget'],
'prop.x_widget': prop['x_widget'],
'schema.x-widget': xWidgetFromSchema,
@@ -2321,7 +2333,7 @@
// Debug logging for file-upload widgets
if (nestedProp.type === 'array' && (nestedProp['x-widget'] === 'file-upload' || nestedProp['x_widget'] === 'file-upload')) {
console.log('[DEBUG] Found file-upload widget in nested property:', nestedKey, 'fullKey:', fullKey + '.' + nestedKey, 'prop:', nestedProp);
debugLog('[DEBUG] Found file-upload widget in nested property:', nestedKey, 'fullKey:', fullKey + '.' + nestedKey, 'prop:', nestedProp);
}
html += generateFieldHtml(nestedKey, nestedProp, nestedValue, fullKey);
@@ -2411,7 +2423,7 @@
if (imagesSchema['x-widget'] === 'file-upload' || imagesSchema['x_widget'] === 'file-upload') {
isFileUpload = true;
uploadConfig = imagesSchema['x-upload-config'] || imagesSchema['x_upload_config'] || {};
console.log('[DEBUG] ✅ Direct detection: images field has file-upload widget', uploadConfig);
debugLog('[DEBUG] ✅ Direct detection: images field has file-upload widget', uploadConfig);
}
}
@@ -2421,7 +2433,7 @@
if (xWidgetFromProp === 'file-upload') {
isFileUpload = true;
uploadConfig = prop['x-upload-config'] || prop['x_upload_config'] || {};
console.log('[DEBUG] ✅ Detection via prop object');
debugLog('[DEBUG] ✅ Detection via prop object');
}
}
@@ -2435,12 +2447,12 @@
if (xWidgetFromSchema === 'file-upload') {
isFileUpload = true;
uploadConfig = schemaProp['x-upload-config'] || schemaProp['x_upload_config'] || {};
console.log('[DEBUG] ✅ Detection via schema lookup');
debugLog('[DEBUG] ✅ Detection via schema lookup');
}
}
// Debug logging for ALL array fields to diagnose
console.log('[DEBUG] Array field check:', fullKey, {
debugLog('[DEBUG] Array field check:', fullKey, {
'isFileUpload': isFileUpload,
'prop keys': Object.keys(prop),
'prop.x-widget': prop['x-widget'],
@@ -2450,7 +2462,7 @@
});
if (isFileUpload) {
console.log('[DEBUG] ✅ Rendering file-upload widget for', fullKey, 'with config:', uploadConfig);
debugLog('[DEBUG] ✅ Rendering file-upload widget for', fullKey, 'with config:', uploadConfig);
// Use the file upload widget from plugins.html
// We'll need to call a function that exists in the global scope
const maxFiles = uploadConfig.max_files || 10;
@@ -2645,7 +2657,7 @@
// Add web UI actions section if plugin defines any
if (webUiActions && webUiActions.length > 0) {
console.log('[DEBUG] Rendering', webUiActions.length, 'actions in tab form');
debugLog('[DEBUG] Rendering', webUiActions.length, 'actions in tab form');
// Map color names to explicit Tailwind classes
const colorMap = {
@@ -2945,7 +2957,7 @@
const jsonValue = JSON.parse(unescapedValue);
if (Array.isArray(jsonValue)) {
flatConfig[key] = jsonValue;
console.log(`File upload array field ${key}: parsed JSON array with ${jsonValue.length} items`);
debugLog(`File upload array field ${key}: parsed JSON array with ${jsonValue.length} items`);
} else {
// Fallback to empty array
flatConfig[key] = [];
@@ -3081,8 +3093,8 @@
throw new Error(`Failed to parse server response: ${response.status} ${response.statusText}`);
}
console.log('Response status:', response.status, 'Response OK:', response.ok);
console.log('Response data:', JSON.stringify(data, null, 2));
debugLog('Response status:', response.status, 'Response OK:', response.ok);
debugLog('Response data:', JSON.stringify(data, null, 2));
if (!response.ok || data.status !== 'success') {
let errorMessage = data.message || 'Failed to save configuration';
@@ -3594,7 +3606,7 @@
async function savePluginConfig(pluginId) {
try {
console.log('Saving config for plugin:', pluginId);
debugLog('Saving config for plugin:', pluginId);
// Load schema for type detection
let schema = {};
@@ -3611,14 +3623,14 @@
// Find the form in the active plugin tab
// Alpine.js hides/shows elements with display:none, so we look for the currently visible one
const allForms = document.querySelectorAll('form[x-on\\:submit\\.prevent]');
console.log('Found forms:', allForms.length);
debugLog('Found forms:', allForms.length);
let form = null;
for (const f of allForms) {
const parent = f.closest('[x-show]');
if (parent && parent.style.display !== 'none' && parent.offsetParent !== null) {
form = f;
console.log('Found visible form');
debugLog('Found visible form');
break;
}
}
@@ -3712,7 +3724,7 @@
const jsonValue = JSON.parse(unescapedValue);
if (Array.isArray(jsonValue)) {
flatConfig[key] = jsonValue;
console.log(`File upload array field ${key}: parsed JSON array with ${jsonValue.length} items`);
debugLog(`File upload array field ${key}: parsed JSON array with ${jsonValue.length} items`);
} else {
// Fallback to empty array
flatConfig[key] = [];
@@ -3805,8 +3817,8 @@
const config = dotToNested(flatConfig);
console.log('Saving config for', pluginId, ':', config);
console.log('Flat config before nesting:', flatConfig);
debugLog('Saving config for', pluginId, ':', config);
debugLog('Flat config before nesting:', flatConfig);
// Save to backend
const response = await fetch('/api/v3/plugins/config', {
@@ -3863,7 +3875,7 @@
// This fallback is only used if the widget hasn't loaded yet
if (typeof window.showNotification !== 'function') {
window.showNotification = function(message, type = 'info') {
console.log(`[${type.toUpperCase()}]`, message);
debugLog(`[${type.toUpperCase()}]`, message);
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 ${
type === 'success' ? 'bg-green-500' : type === 'error' ? 'bg-red-500' : 'bg-blue-500'