Compare commits

..

2 commits

Author SHA1 Message Date
dlawler489
4036139901 Merge failure list rendering for any run with failures
All checks were successful
build-and-push / docker (push) Successful in 23s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 14:52:40 +10:00
dlawler489
96b3fbb1bb Draw the failure list for any run that has one, not just the newest
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 <noreply@anthropic.com>
2026-09-16 14:49:12 +10:00

View file

@ -1537,10 +1537,17 @@ async function refreshAiPanel() {
let status; let status;
let runs; let runs;
let detail = null; let detail = null;
let details = {};
try { try {
[status, runs] = await Promise.all([getJson('/api/ai/status'), getJson('/api/ai/runs')]); [status, runs] = await Promise.all([getJson('/api/ai/status'), getJson('/api/ai/runs')]);
const focus = runs.find((r) => r.status === 'running') || runs[0]; const focus = runs.find((r) => r.status === 'running') || runs[0];
if (focus) detail = await getJson(`/api/ai/runs/${focus.id}`); 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 { } catch {
aiPoll = setTimeout(refreshAiPanel, 5000); aiPoll = setTimeout(refreshAiPanel, 5000);
return; return;
@ -1548,7 +1555,7 @@ async function refreshAiPanel() {
if (selectedScope !== 'ai' || !document.getElementById('aiRuns')) return; if (selectedScope !== 'ai' || !document.getElementById('aiRuns')) return;
const running = runs.some((r) => r.status === 'running'); const running = runs.some((r) => r.status === 'running');
const changed = aiState.expectChange || (aiState.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(); renderAiLive();
if (changed && !running) { if (changed && !running) {
aiState.expectChange = false; aiState.expectChange = false;
@ -1561,7 +1568,7 @@ async function refreshAiPanel() {
} }
function renderAiLive() { function renderAiLive() {
const { status, runs = [], detail } = aiState; const { status, runs = [], detail, details = {} } = aiState;
if (!status || !document.getElementById('aiRuns')) return; if (!status || !document.getElementById('aiRuns')) return;
const c = status.config; const c = status.config;
document.getElementById('aiSetup').innerHTML = status.problem document.getElementById('aiSetup').innerHTML = status.problem
@ -1578,7 +1585,7 @@ function renderAiLive() {
start.disabled = Boolean(status.problem) || running; start.disabled = Boolean(status.problem) || running;
start.title = running ? 'A run is going: wait for it, or stop it' : ''; start.title = running ? 'A run is going: wait for it, or stop it' : '';
document.getElementById('aiRuns').innerHTML = runs.length 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('')
: '<p class="form-hint">No runs yet.</p>'; : '<p class="form-hint">No runs yet.</p>';
const s = status.search; const s = status.search;
document.getElementById('aiSearch').textContent = s.error document.getElementById('aiSearch').textContent = s.error