From 96b3fbb1bb60d28be9ba45727fa1580b4644f411 Mon Sep 17 00:00:00 2001 From: dlawler489 <104159223@student.swin.edu.au> Date: Wed, 16 Sep 2026 14:49:12 +1000 Subject: [PATCH] Draw the failure list for any run that has one, not just the newest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The failures now reach the API, but the page could still not show them: the AI panel fetches detail for one run — the running one, else the newest — and renderAiLive passes detail only to that run, so every other run is drawn with nothing. Run 21's single failure stayed invisible for exactly this reason, even after the previous fix. Details are now fetched for any listed run with failures and keyed by run id. Usually that is no extra request, and at most a handful. Co-Authored-By: Claude Opus 5 --- public/app.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/public/app.js b/public/app.js index 27979f9..119d4a6 100644 --- a/public/app.js +++ b/public/app.js @@ -1537,10 +1537,17 @@ async function refreshAiPanel() { let status; let runs; let detail = null; + let details = {}; try { [status, runs] = await Promise.all([getJson('/api/ai/status'), getJson('/api/ai/runs')]); const focus = runs.find((r) => r.status === 'running') || runs[0]; if (focus) detail = await getJson(`/api/ai/runs/${focus.id}`); + if (detail) details[detail.id] = detail; + // Runs that failed something need their detail too, or the list of failures + // has nothing to draw: without this only the newest run ever gets one. + for (const r of runs.slice(0, 5)) { + if (r.failed > 0 && !details[r.id]) details[r.id] = await getJson(`/api/ai/runs/${r.id}`); + } } catch { aiPoll = setTimeout(refreshAiPanel, 5000); return; @@ -1548,7 +1555,7 @@ async function refreshAiPanel() { if (selectedScope !== 'ai' || !document.getElementById('aiRuns')) return; const running = runs.some((r) => r.status === 'running'); const changed = aiState.expectChange || (aiState.runs || []).some((r) => r.status === 'running'); - aiState = { ...aiState, status, runs, detail }; + aiState = { ...aiState, status, runs, detail, details }; renderAiLive(); if (changed && !running) { aiState.expectChange = false; @@ -1561,7 +1568,7 @@ async function refreshAiPanel() { } function renderAiLive() { - const { status, runs = [], detail } = aiState; + const { status, runs = [], detail, details = {} } = aiState; if (!status || !document.getElementById('aiRuns')) return; const c = status.config; document.getElementById('aiSetup').innerHTML = status.problem @@ -1578,7 +1585,7 @@ function renderAiLive() { start.disabled = Boolean(status.problem) || running; start.title = running ? 'A run is going: wait for it, or stop it' : ''; document.getElementById('aiRuns').innerHTML = runs.length - ? runs.slice(0, 5).map((r) => runHtml(r, detail && detail.id === r.id ? detail : null)).join('') + ? runs.slice(0, 5).map((r) => runHtml(r, details[r.id] || (detail && detail.id === r.id ? detail : null))).join('') : '

No runs yet.

'; const s = status.search; document.getElementById('aiSearch').textContent = s.error