Fix panel apps not rendering and Add button disappearing

Restructured loadPanelApps() to use per-operation try/catch instead of
one giant try/catch that silently swallowed errors. The Add button is now
appended unconditionally after the app list, so it always remains visible.
Each app row renders independently — a single bad app config won't break
the entire panel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
roberts 2026-03-15 00:24:16 -05:00
parent 14f5ce7cf1
commit e44ee2fe64

View file

@ -338,36 +338,54 @@
const container = $('#panelApps'); const container = $('#panelApps');
if (!container) return; if (!container) return;
let guiApps = [];
let runningApps = [];
try { try {
const cfgRes = await Atlus.apiFetch('/api/settings'); const cfgRes = await Atlus.apiFetch('/api/settings');
if (!cfgRes.ok) return; if (cfgRes && cfgRes.ok) {
const cfg = await cfgRes.json(); const cfg = await cfgRes.json();
const guiApps = cfg.gui_apps || []; guiApps = cfg.gui_apps || [];
}
} catch (e) {
console.warn('Panel apps: failed to load config', e);
}
// Get running apps (may 503 if display deps unavailable) try {
let runningApps = []; const runRes = await Atlus.apiFetch('/api/display/apps');
if (runRes && runRes.ok) runningApps = await runRes.json();
} catch (e) { /* display may be unavailable */ }
// Ensure all GUI apps are registered as Atlus app modules
for (const app of guiApps) {
try { try {
const runRes = await Atlus.apiFetch('/api/display/apps');
if (runRes.ok) runningApps = await runRes.json();
} catch (e) { /* ignore */ }
// Ensure all GUI apps are registered as Atlus app modules
for (const app of guiApps) {
const appId = 'gui-' + app.id; const appId = 'gui-' + app.id;
if (!Atlus.apps[appId] && window._atlusRegisterGuiApp) { if (!Atlus.apps[appId] && window._atlusRegisterGuiApp) {
window._atlusRegisterGuiApp(app); window._atlusRegisterGuiApp(app);
} }
} catch (e) {
console.warn('Panel apps: failed to register', app.id, e);
} }
}
container.innerHTML = ''; // Clear and rebuild — always append Add button at the end
container.innerHTML = '';
if (guiApps.length === 0) { if (guiApps.length === 0) {
container.innerHTML = '<div style="color:var(--text-muted);font-size:12px;font-family:var(--font-mono);padding:4px 0;">No apps configured</div>'; const empty = document.createElement('div');
} else { empty.style.cssText = 'color:var(--text-muted);font-size:12px;font-family:var(--font-mono);padding:4px 0;';
for (const app of guiApps) { empty.textContent = 'No apps configured';
const running = runningApps.find(r => r.command === app.command && r.alive); container.appendChild(empty);
} else {
for (const app of guiApps) {
try {
const running = Array.isArray(runningApps)
? runningApps.find(r => r.command === app.command && r.alive)
: null;
const row = document.createElement('div'); const row = document.createElement('div');
row.className = 'panel-app-row'; row.className = 'panel-app-row';
const args = Array.isArray(app.args) ? app.args.join(' ') : '';
row.innerHTML = ` row.innerHTML = `
<div class="panel-app-dot ${running ? 'active' : ''}"></div> <div class="panel-app-dot ${running ? 'active' : ''}"></div>
<span class="panel-app-icon">${app.icon || '🖥'}</span> <span class="panel-app-icon">${app.icon || '🖥'}</span>
@ -375,13 +393,13 @@
<button class="panel-app-action ${running ? 'stop' : 'start'}" <button class="panel-app-action ${running ? 'stop' : 'start'}"
data-command="${app.command}" data-app-id="${running ? running.app_id : ''}" data-command="${app.command}" data-app-id="${running ? running.app_id : ''}"
data-gui-id="${app.id}" data-title="${app.name || app.command}" data-gui-id="${app.id}" data-title="${app.name || app.command}"
data-args="${(app.args || []).join(' ')}" data-fps="${app.target_fps || 10}" data-args="${args}" data-fps="${app.target_fps || 10}"
title="${running ? 'Stop' : 'Launch'}"> title="${running ? 'Stop' : 'Launch'}">
${running ? '■' : '▶'} ${running ? '■' : '▶'}
</button> </button>
`; `;
// Click name to open in tab // Click name/icon to open in tab
row.querySelector('.panel-app-name').addEventListener('click', () => { row.querySelector('.panel-app-name').addEventListener('click', () => {
openApp('gui-' + app.id); openApp('gui-' + app.id);
}); });
@ -389,54 +407,54 @@
openApp('gui-' + app.id); openApp('gui-' + app.id);
}); });
// Action button (launch/stop)
const actionBtn = row.querySelector('.panel-app-action');
actionBtn.addEventListener('click', async (e) => {
e.stopPropagation();
const isRunning = actionBtn.classList.contains('stop');
actionBtn.disabled = true;
try {
if (isRunning) {
const id = actionBtn.dataset.appId;
if (id) await Atlus.apiFetch(`/api/display/apps/${id}`, { method: 'DELETE' });
} else {
const a = actionBtn.dataset.args ? actionBtn.dataset.args.split(' ').filter(Boolean) : [];
await Atlus.apiFetch('/api/display/apps', {
method: 'POST',
body: {
command: actionBtn.dataset.command,
title: actionBtn.dataset.title,
args: a,
target_fps: parseInt(actionBtn.dataset.fps) || 10,
},
});
}
} catch (err) {
console.warn('Panel app action failed', err);
}
setTimeout(loadPanelApps, 1500);
});
container.appendChild(row); container.appendChild(row);
} catch (e) {
console.warn('Panel apps: failed to render', app.id, e);
} }
} }
}
// Add App button // Add App button — ALWAYS appended, regardless of errors above
const addBtn = document.createElement('button'); const addBtn = document.createElement('button');
addBtn.className = 'panel-app-add'; addBtn.className = 'panel-app-add';
addBtn.textContent = '+ Add'; addBtn.textContent = '+ Add';
addBtn.addEventListener('click', () => { addBtn.addEventListener('click', () => {
openApp('settings'); openApp('settings');
// Navigate to Applications section after a brief delay setTimeout(() => {
setTimeout(() => { const navItem = document.querySelector('.settings-nav-item[data-section="applications"]');
const navItem = document.querySelector('.settings-nav-item[data-section="applications"]'); if (navItem) navItem.click();
if (navItem) navItem.click(); }, 200);
}, 200); });
}); container.appendChild(addBtn);
container.appendChild(addBtn);
// Action button handlers (launch/stop)
container.querySelectorAll('.panel-app-action').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const isRunning = btn.classList.contains('stop');
btn.disabled = true;
if (isRunning) {
// Stop the app
const appId = btn.dataset.appId;
if (appId) {
await Atlus.apiFetch(`/api/display/apps/${appId}`, { method: 'DELETE' });
}
} else {
// Launch the app
const args = btn.dataset.args ? btn.dataset.args.split(' ').filter(Boolean) : [];
await Atlus.apiFetch('/api/display/apps', {
method: 'POST',
body: {
command: btn.dataset.command,
title: btn.dataset.title,
args: args,
target_fps: parseInt(btn.dataset.fps) || 10,
},
});
}
setTimeout(loadPanelApps, 1500);
});
});
} catch (e) { /* ignore */ }
} }
// ===================================================================== // =====================================================================