function renderProgress(p){ const progress=Number(p.progress||0); setText('progressPercent',`${progress}%`); const fill=document.getElementById('progressFill'); if(fill)fill.style.width=`${Math.max(0,Math.min(100,progress))}%`; setText('currentStep',p.current_step_label||'—'); setText('lastEvent',p.last_event||'—'); setText('elapsed',fmtSeconds(p.elapsed_seconds)); setText('progressTitle',p.current_step_label||'Live job progress'); setText('progressStatus',p.status||'running'); setText('lastPolled',new Date().toLocaleTimeString()); renderTimeline(p.timeline||[]); } function fmtSeconds(s){if(s==null)return"—";s=Number(s)||0;const h=Math.floor(s/3600),m=Math.floor((s%3600)/60),r=s%60;if(h)return`${h}h ${m}m`;return m?`${m}m ${r}s`:`${r}s`} function timelineIcon(status){if(status==='done')return'✓';if(status==='running')return'…';if(status==='failed')return'!';return'○'} function timelineStatus(status){if(status==='done')return'completed';if(status==='running')return'active';if(status==='failed')return'failed';return'pending'} function renderTimeline(items){const root=document.getElementById('timeline');if(!root)return;root.innerHTML=(items||[]).map((x,i)=>{const status=x.status||'pending';return `