function timelineIcon(status){status=String(status||'pending');if(status==='done'||status==='completed')return'✓';if(status==='running')return'•';if(status==='failed'||status==='blocked')return'!';if(status==='skipped')return'↷';return'○'} function timelineStatus(status){status=String(status||'pending');if(status==='done'||status==='completed')return'completed';if(status==='running')return'running';if(status==='failed')return'failed';if(status==='blocked')return'blocked';return'pending'} function renderTimeline(items){const root=$('timeline');if(!root)return;root.innerHTML=(items||[]).map((x,i)=>{const st=timelineStatus(x.status);return `
${timelineIcon(x.status)}
${escapeHtml(x.label||`Step ${i+1}`)}${st}
`}).join('')} function renderActivityFeed(items){const root=$('activityFeed');if(!root)return;if(!items.length){root.innerHTML='
No activity yet. Waiting for the first worker event.
';return}root.innerHTML=items.slice(-8).reverse().map(item=>`
${escapeHtml(item.message||'Event received')}

${escapeHtml(item.step||'event')} · ${escapeHtml(item.status||'')}

${escapeHtml(item.agent||'Factory Agent')}
`).join('')} function renderDiagnostics(d={},header={}){setText('diagBuildStatus',badgeLabel(d.build_status||'pending'));setText('diagBuildStep',header.current_phase_label||'Waiting for run data');const chips=$('statusChips');if(chips){const data=[['Build',d.build_status],['API',d.api_status],['Tests',d.tests_status],['Verdict',d.verdict_status]];chips.innerHTML=data.map(([k,v])=>`${escapeHtml(k)}${escapeHtml(badgeLabel(v||'pending'))}`).join('')}const issue=$('issueCard');if(issue){const has=Boolean(d.issue&&d.issue.title);issue.hidden=!has;if(has){issue.className='issue-card '+statusClass(d.issue.status||d.issue.title);setText('issueTitle',d.issue.title);setText('issueDetail',d.issue.detail||d.issue.status||'Review logs.')}}const rules=$('zeroGpuChecklist');if(rules)rules.innerHTML=(d.zerogpu_rules||[]).map(r=>`
  • ${escapeHtml(r.label)}${escapeHtml(r.status||'pending')}
  • `).join('')} function renderSpaceTestPreview(s={}){const st=$('spaceTestStatus');if(st){st.textContent=badgeLabel(s.status||'Pending');st.className='badge '+statusClass(s.status)}setText('spaceTestVerdict',badgeLabel(s.verdict||'pending'));setText('spaceTestOutput',s.output_artifact?'Output artifact ready':'Waiting for API test')} function renderRunViewModel(view,p={}){const header=view.header||{};const sm=view.status_model||{};const progress=Number(sm.global_status==='succeeded'?100:(p.progress||0));const fill=$('progressFill');if(fill)fill.style.width=`${Math.max(0,Math.min(100,progress))}%`;setText('progressPercent',`${Math.round(progress)}%`);setText('progressTitle',header.title||view.run_id||'Live job progress');const statusEl=$('progressStatus');if(statusEl){statusEl.textContent=header.status_label||badgeLabel(header.status||'Running');statusEl.className='badge '+statusClass(header.status)}setText('currentStep',header.current_phase_label||'—');setText('lastEvent',(view.activity||[]).slice(-1)[0]?.message||p.last_event||'Waiting for worker events.');setText('elapsed',fmtSeconds(header.elapsed_seconds||p.elapsed_seconds));setText('lastPolled',new Date().toLocaleTimeString());setText('detailSpace',header.space||'—');setText('detailJobStatus',header.raw_status||header.status||'—');setText('detailGateStatus',sm.verdict||'—');setText('detailTraces',Array.isArray(p.events)?`${p.events.length} events`:'—');setLink('openJob',view.links?.job_url);setLink('openSpace',view.links?.target_space_url);setLink('openSettings',view.links?.target_space_settings_url);setLink('openArtifacts',view.links?.artifacts_url);renderTimeline(view.pipeline||[]);renderActivityFeed(view.activity||[]);renderDiagnostics(view.diagnostics||{},header);renderSpaceTestPreview(view.space_test||{});renderManualAction({view,...p})} function renderProgress(p){if(p.view){renderRunViewModel(p.view,p);return}const progress=Number(p.progress||0);const fill=$('progressFill');if(fill)fill.style.width=`${Math.max(0,Math.min(100,progress))}%`;setText('progressPercent',`${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');const statusEl=$('progressStatus');if(statusEl){statusEl.textContent=p.status||'running';statusEl.className='badge '+statusClass(p.status)}setText('lastPolled',new Date().toLocaleTimeString());renderTimeline(p.timeline||[])}