fffiloni's picture
Upload 6 files
0f17186 verified
Raw
History Blame
48.8 kB
const timelineRenderState={};
const activityFeedState={lastItems:[],runId:null};
const PROGRESS_TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled']);
const WORKER_STEP_ORDER=[
'bootstrap','dependencies','auth','model_analysis','workspace','node','pi_install','pi_config','pi_run','pi_model_resolution','pi_verification',
'metadata_sanitize','requirements_sanitize','hardware_strategy','create_space_hardware','create_space','failure_detected','failure_diagnosis','pi_diagnosis','repair_decision','wait_for_logs','factory_rebuild','repair','repair_diagnosis','repair_brief','repair_plan','repair_patch','repair_upload','repair_validation','technical_blocker','manual_hardware_required','upload_files',
'space_runtime','space_logs','api_validation','live_wait','generation_smoke','inference_gate','report_write','done','failure'
];
const COMPACT_TIMELINE_GROUPS=[
{id:'bootstrap',label:'Bootstrap',display_label:'Start',steps:['bootstrap']},
{id:'dependencies',label:'Dependencies',display_label:'Dependencies',steps:['dependencies']},
{id:'auth_model',label:'Auth + model',display_label:'Model access',steps:['auth','model_analysis']},
{id:'workspace_node',label:'Workspace/node',display_label:'Workspace',steps:['workspace','node']},
{id:'pi_setup',label:'Pi setup',display_label:'Agent setup',steps:['pi_install','pi_config']},
{id:'pi_run',label:'Pi run',display_label:'Agent build',steps:['pi_run']},
{id:'pi_verify',label:'Pi verify',display_label:'Agent review',steps:['pi_model_resolution','pi_verification','metadata_sanitize','requirements_sanitize']},
{id:'hardware',label:'Hardware',display_label:'Hardware',steps:['hardware_strategy','create_space_hardware']},
{id:'create_space',label:'Create Space',display_label:'Space',steps:['create_space']},
{id:'upload_runtime',label:'Upload/runtime',display_label:'Deploy',steps:['upload_files','space_runtime','space_logs','live_wait']},
{id:'validation',label:'Validation',display_label:'Live test',steps:['api_validation','generation_smoke','inference_gate']},
{id:'repair',label:'Repair recovery',display_label:'Recovery',steps:['failure_detected','failure_diagnosis','pi_diagnosis','repair_decision','wait_for_logs','factory_rebuild','repair','repair_diagnosis','repair_brief','repair_plan','repair_patch','repair_upload','repair_validation','technical_blocker','manual_hardware_required']},
{id:'report',label:'Report',display_label:'Report',steps:['report_write','done']},
{id:'failure',label:'Failure',display_label:'Failure',terminal_only:true,steps:['failure']}
];
const progressMotionState={lastProgress:null,lastStatus:null,lastStep:null};
const WORKER_STEP_DISPLAY_LABELS={
bootstrap:'start',dependencies:'dependencies',auth:'auth',model_analysis:'model card',workspace:'workspace',node:'node setup',
pi_install:'agent install',pi_config:'agent config',pi_run:'agent building',pi_model_resolution:'model routing',pi_verification:'agent check',
metadata_sanitize:'metadata check',requirements_sanitize:'requirements check',hardware_strategy:'hardware plan',create_space_hardware:'hardware request',
create_space:'space created',upload_files:'files uploaded',space_runtime:'runtime check',space_logs:'logs checked',live_wait:'space live',
api_validation:'API check',generation_smoke:'live generation',inference_gate:'inference verdict',failure_detected:'failure detected',
failure_diagnosis:'diagnosis',pi_diagnosis:'agent diagnosis',repair_decision:'decision',wait_for_logs:'waiting for logs',
factory_rebuild:'same-code rebuild',repair:'repair started',repair_diagnosis:'patch allowed',repair_brief:'repair brief',repair_plan:'repair plan',
repair_patch:'patching',repair_upload:'repair uploaded',repair_validation:'repair test',technical_blocker:'blocker',manual_hardware_required:'manual hardware',
report_write:'report written',done:'done',failure:'failed'
};
function workerStepDisplayLabel(step){return WORKER_STEP_DISPLAY_LABELS[String(step||'')]||String(step||'').replace(/_/g,' ')}
const progressStability={runId:null,progress:0,timeline:null,furthestIndex:-1,status:'',currentStep:null};
const elapsedClock={baseSeconds:0,baseAt:0,isRunning:false};
function isFailureStatus(status){
const s=String(status||'').toLowerCase();
return s.includes('failed')||s.includes('error')||s==='failure'||s==='technical_blocker';
}
function isSuccessStatus(status){
const s=String(status||'').toLowerCase();
return s.includes('success')||s.includes('succeed')||s.includes('passed')||s==='done'||s==='completed'||s==='health_only'||s==='repair_success'||s==='full_inference_candidate_health_passed'||s==='full_inference_success';
}
function isStoppedStatus(status){
const s=String(status||'').toLowerCase();
return s==='cancelled'||s==='canceled'||s==='stopped'||s==='blocked'||s==='stale';
}
function visualStatusClass(status){
if(isFailureStatus(status))return'error';
if(isStoppedStatus(status))return'stopped';
if(isSuccessStatus(status))return'success';
const s=String(status||'').toLowerCase();
if(s.includes('running')||s.includes('queued')||s.includes('pending')||s.includes('waiting')||s.includes('building'))return'running';
return'neutral';
}
function groupForWorkerStep(step){
return COMPACT_TIMELINE_GROUPS.find(g=>g.steps.includes(String(step||'')))||null;
}
function latestEventIndexForGroup(group){
if(!group)return -1;
let idx=-1;
(activityFeedState.lastItems||[]).forEach((event,i)=>{
if(group.steps.includes(String(event.step||'')))idx=i;
});
return idx;
}
function failedEventInfoForGroup(group){
if(!group)return null;
let info=null;
(activityFeedState.lastItems||[]).forEach((event,i)=>{
if(group.steps.includes(String(event.step||''))&&isFailureStatus(event.status)){
info={event,index:i};
}
});
return info;
}
function failedItemInfoForGroup(group,byStep){
if(!group||!byStep)return null;
let info=null;
group.steps.forEach((step,i)=>{
const item=byStep.get(step);
if(item&&isFailureStatus(item.status))info={event:item,index:i,fromTimeline:true};
});
return info;
}
function textOfEvent(event={}){
return `${event.step||''} ${event.status||''} ${event.message||''} ${event.hardware||''} ${event.selected_hardware||''} ${event.fallback_hardware||''}`.toLowerCase();
}
function isHardwareFallbackEvent(event={}){
const text=textOfEvent(event);
return text.includes('fallback')||text.includes('zerogpu')||text.includes('zero gpu')||text.includes('zero-a10g')||text.includes('hardware_fallback')||text.includes('a10g')||text.includes('l40')||text.includes('h200');
}
function groupHasHardwareFallback(group){
if(!group||group.id!=='hardware')return false;
return (activityFeedState.lastItems||[]).some(event=>group.steps.includes(String(event.step||''))&&isHardwareFallbackEvent(event));
}
function isExpectedFallbackFailure(group,failed){
return Boolean(group&&group.id==='hardware'&&failed&&isHardwareFallbackEvent(failed.event));
}
function compactReasonText(value,max=190){
const text=String(value||'').replace(/\s+/g,' ').trim();
if(!text)return '';
return text.length>max?text.slice(0,max-1)+'…':text;
}
function eventReason(event={}){
const data=event.data||{};
const raw=
event.reason||
event.error||
event.error_message||
event.exception||
event.details||
data.reason||
data.hf_error_detail||
data.hf_error_message||
data.error_detail||
data.response_text||
data.response_body||
data.error||
data.exception||
data.details||
event.message||
'';
return compactReasonText(typeof raw==='string'?raw:JSON.stringify(raw));
}
function zeroGpuReason(event={}){
const reason=eventReason(event);
const lower=reason.toLowerCase();
if(lower.includes('quota')||lower.includes('limit')||lower.includes('capacity')||lower.includes('usage')||lower.includes('exceeded')){
return reason;
}
if(isHardwareFallbackEvent(event)){
return reason||'ZeroGPU was unavailable for this user/session, so the fallback GPU was used.';
}
return reason;
}
function failureReasonForGroup(group){
const failed=failedEventInfoForGroup(group);
if(failed)return eventReason(failed.event);
return '';
}
function fallbackReasonForGroup(group){
if(!group||group.id!=='hardware')return '';
const failed=failedEventInfoForGroup(group);
if(failed&&isExpectedFallbackFailure(group,failed))return zeroGpuReason(failed.event);
const event=(activityFeedState.lastItems||[]).find(e=>group.steps.includes(String(e.step||''))&&isHardwareFallbackEvent(e));
return event?zeroGpuReason(event):'';
}
function reasonForTimelineItem(item={}){
if(item.status==='fallback')return item.reason||'Fallback GPU route was used.';
if(item.status==='failed'||item.status==='recovered')return item.reason||'';
return '';
}
function failedTimelineStatusForGroup(group,overallStatus){
const failed=failedEventInfoForGroup(group);
if(!failed)return '';
const latestIdx=latestEventIndexForGroup(group);
const overall=visualStatusClass(overallStatus);
const superseded=latestIdx>failed.index;
if(isExpectedFallbackFailure(group,failed)&&superseded)return 'fallback';
if(overall==='error')return 'failed';
return superseded?'recovered':'failed';
}
function firstFailedGroupIndex(overallStatus){
const overall=visualStatusClass(overallStatus);
let first=-1;
COMPACT_TIMELINE_GROUPS.forEach((group,idx)=>{
const failed=failedEventInfoForGroup(group);
if(!failed)return;
const historical=failedTimelineStatusForGroup(group,overallStatus);
if(historical==='fallback')return;if((overall==='error'||historical==='failed')&&(first<0||idx<first))first=idx;
});
return first;
}
function firstProblemGroupKey(items=[],overallStatus=''){
const first=firstFailedGroupIndex(overallStatus);
if(first>=0)return COMPACT_TIMELINE_GROUPS[first]?.id||'';
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
const groups=visibleCompactTimelineGroups(items,overallStatus);
const failedFromTimeline=groups.find(group=>group.steps.some(step=>isFailureStatus(byStep.get(step)?.status)));
if(failedFromTimeline)return failedFromTimeline.id||'';
if(visualStatusClass(overallStatus)==='error'){
const lastNonPending=[...groups].reverse().find(group=>group.steps.some(step=>byStep.get(step)&&String(byStep.get(step).status||'').toLowerCase()!=='pending'));
return lastNonPending?.id||'repair';
}
const recovered=COMPACT_TIMELINE_GROUPS.find(group=>failedTimelineStatusForGroup(group,overallStatus)==='recovered');
if(recovered)return recovered.id||'';
const fallback=COMPACT_TIMELINE_GROUPS.find(group=>failedTimelineStatusForGroup(group,overallStatus)==='fallback'||groupHasHardwareFallback(group));
return fallback?.id||'';
}
function terminalFailureAnchorGroupIndex(items=[],overallStatus=''){
if(visualStatusClass(overallStatus)!=='error')return -1;
const events=activityFeedState.lastItems||[];
const groups=visibleCompactTimelineGroups(items,overallStatus).filter(g=>g.id!=='failure'&&g.id!=='report');
const groupIndexById=new Map(groups.map((g,i)=>[g.id,i]));
// Prefer the latest explicit failed worker step that belongs to a product milestone.
for(const event of [...events].reverse()){
const step=String(event.step||'');
if(!step||step==='failure'||!isFailureStatus(event.status))continue;
const group=COMPACT_TIMELINE_GROUPS.find(g=>g.id!=='failure'&&g.steps.includes(step));
if(group&&groupIndexById.has(group.id))return groupIndexById.get(group.id);
}
// If the terminal failure event is generic, anchor the failure to the latest
// meaningful non-terminal event before it. This prevents backend-completed
// future milestones from all turning green after a worker exception.
for(const event of [...events].reverse()){
const step=String(event.step||'');
if(!step||step==='failure'||step==='done'||step==='report_write')continue;
const group=COMPACT_TIMELINE_GROUPS.find(g=>g.id!=='failure'&&g.id!=='report'&&g.steps.includes(step));
if(group&&groupIndexById.has(group.id))return groupIndexById.get(group.id);
}
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
for(let i=groups.length-1;i>=0;i--){
const group=groups[i];
if(group.steps.some(step=>byStep.has(step)&&String(byStep.get(step).status||'').toLowerCase()!=='pending'))return i;
}
return -1;
}
function terminalFailureReason(){
const events=activityFeedState.lastItems||[];
const failed=[...events].reverse().find(event=>isFailureStatus(event.status)||String(event.step||'')==='failure');
return failed?eventReason(failed):'';
}
function stepOrderIndex(step){
const idx=WORKER_STEP_ORDER.indexOf(String(step||''));
return idx<0?-1:idx;
}
function normalizeTimelineOverallStatus(status){
const s=String(status||'').toLowerCase();
if(PROGRESS_TERMINAL_STATUSES.has(s))return s;
return s;
}
function timelineIcon(status){
status=String(status||'pending');
if(status==='done'||status==='completed')return'βœ“';
if(status==='running')return'β€’';
if(status==='failed')return'!';
if(status==='fallback')return'⇄';
if(status==='recovered'||status==='superseded')return'β†Ί';
if(status==='blocked'||status==='cancelled'||status==='stopped'||status==='after_fail')return'β– ';
if(status==='skipped')return'β†·';
return'β—‹';
}
function timelineStatus(status,overallStatus=''){
status=String(status||'pending').toLowerCase();
const overall=normalizeTimelineOverallStatus(overallStatus);
const stopped=overall&&PROGRESS_TERMINAL_STATUSES.has(overall)&&!['success','succeeded','done','full_inference_success','full_inference_candidate_health_passed'].includes(overall);
if(status==='failed'||status==='error'||status==='failure')return'status-failed';
if(status==='fallback')return'status-fallback';
if(status==='recovered'||status==='superseded')return'status-warn';
if(status==='after_fail'||status==='post_failure')return'status-after-fail';
if(status==='blocked'||status==='cancelled'||status==='canceled'||status==='stopped')return'status-stopped';
if(status==='done'||status==='completed'||status==='success')return'status-done';
if(status==='running'||status==='waiting')return stopped?'status-stopped':'status-running';
if(status==='skipped')return'status-skipped';
return stopped?'status-muted':'status-pending';
}
function timelineStepKey(x,i){
return String(x.step||x.id||x.label||`step-${i}`);
}
function animateClass(el,klass,duration=900){
if(!el)return;
el.classList.remove(klass);
void el.offsetWidth;
el.classList.add(klass);
window.setTimeout(()=>el.classList.remove(klass),duration);
}
function eventStableKey(event={}){
return `${event.ts||''}|${event.step||''}|${event.status||''}|${event.message||''}`;
}
function mergeStableEvents(runId,items){
const incoming=(items||[]).filter(Boolean);
if(activityFeedState.runId!==runId){
activityFeedState.runId=runId;
activityFeedState.lastItems=[];
}
const byKey=new Map();
[...activityFeedState.lastItems,...incoming].forEach(event=>{
const key=eventStableKey(event);
if(key.trim()!=='|||')byKey.set(key,event);
});
const merged=[...byKey.values()].sort((a,b)=>String(a.ts||'').localeCompare(String(b.ts||'')));
activityFeedState.lastItems=merged;
return merged;
}
function activeTimelineStepKey(items=[]){
const active=(items||[]).find(x=>['running','stopped','failed','blocked','cancelled','canceled'].includes(String(x.status||'').toLowerCase()));
if(active)return String(active.step||active.id||active.label||'');
const done=[...(items||[])].reverse().find(x=>['done','completed','success'].includes(String(x.status||'').toLowerCase()));
return done?String(done.step||done.id||done.label||''):'';
}
function activeTimelineItem(items=[]){
const key=activeTimelineStepKey(items);
return (items||[]).find(x=>String(x.step||x.id||x.label||'')===key)||null;
}
function eventMessage(event){
return event?.message||event?.step||'Waiting for worker events.';
}
function timelineStepBounds(root,target){
const rootBox=root.getBoundingClientRect?root.getBoundingClientRect():{left:0,right:root.clientWidth,width:root.clientWidth};
const targetBox=target.getBoundingClientRect?target.getBoundingClientRect():{left:target.offsetLeft-root.scrollLeft,right:target.offsetLeft-root.scrollLeft+target.offsetWidth,width:target.offsetWidth};
return {
rootLeft:rootBox.left,
rootRight:rootBox.right,
targetLeft:targetBox.left,
targetRight:targetBox.right,
targetWidth:targetBox.width||target.offsetWidth||0,
};
}
function ensureTimelineStepVisible(stepKey,opts={}){
const early=new Set(['bootstrap','dependencies','auth_model']);
const root=$('timeline');
if(!root||!stepKey)return;
const safeStep=(typeof CSS!=='undefined'&&CSS.escape)?CSS.escape(stepKey):String(stepKey).replace(/"/g,'\"');
const target=root.querySelector(`[data-step="${safeStep}"]`);
if(!target)return;
const margin=28;
// Keep-visible behavior: do not center. Only move the minimum amount needed.
const behavior='auto';
const visibleLeft=root.scrollLeft;
const visibleRight=visibleLeft+root.clientWidth;
const targetLeft=target.offsetLeft;
const targetRight=targetLeft+target.offsetWidth;
const fullyVisible=targetLeft>=visibleLeft+margin&&targetRight<=visibleRight-margin;
if(fullyVisible){root.dataset.visibleStep=stepKey;return;}
if(early.has(stepKey)&&root.scrollLeft<=1&&targetLeft>=0&&targetRight<=root.clientWidth){root.dataset.visibleStep=stepKey;return;}
let nextLeft=visibleLeft;
if(targetLeft<visibleLeft+margin){
nextLeft=targetLeft-margin;
}else if(targetRight>visibleRight-margin){
nextLeft=targetRight-root.clientWidth+margin;
}
const maxScroll=Math.max(0,root.scrollWidth-root.clientWidth);
nextLeft=Math.max(0,Math.min(maxScroll,Math.round(nextLeft)));
if(opts.force&&target.offsetWidth<root.clientWidth&&targetRight>visibleLeft&&targetLeft<visibleRight){
// keep-visible, not center: only nudge if the active dot is too close to an edge
const center=targetLeft+(target.offsetWidth/2);
if(center>visibleRight-margin*2)nextLeft=Math.min(maxScroll,Math.round(targetRight-root.clientWidth+margin*2));
if(center<visibleLeft+margin*2)nextLeft=Math.max(0,Math.round(targetLeft-margin*2));
}
if(Math.abs(nextLeft-root.scrollLeft)>1){
const behavior=opts.instant?'auto':'smooth';
try{root.scrollTo({left:nextLeft,behavior});}
catch(_){root.scrollLeft=nextLeft;}
}
root.dataset.visibleStep=stepKey;
}
function hasSpecificTimelineProblem(items=[],overallStatus=''){
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
const overall=visualStatusClass(overallStatus);
return COMPACT_TIMELINE_GROUPS.some(group=>{
if(group.id==='failure')return false;
if(group.id==='repair')return group.steps.some(step=>byStep.has(step))&&overall==='error';
return group.steps.some(step=>isFailureStatus(byStep.get(step)?.status))||failedEventInfoForGroup(group);
});
}
function visibleCompactTimelineGroups(items=[],overallStatus=''){
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
const overall=visualStatusClass(overallStatus);
const specificProblem=hasSpecificTimelineProblem(items,overallStatus);
return COMPACT_TIMELINE_GROUPS.filter(group=>{
const hasItem=group.steps.some(step=>byStep.has(step));
if(group.id==='repair')return hasItem;
// Failure is a terminal fallback anchor, not a normal product milestone.
// Prefer marking the precise stage that failed; only show Failure when the
// run has no better failure/recovery context.
if(group.id==='failure')return hasItem&&!specificProblem;
return true;
});
}
function compactTimelineItems(items=[],overallStatus=''){
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
const terminalRaw=new Set(['failed','blocked','stopped','cancelled','canceled','recovered','fallback','after_fail','post_failure']);
const overall=visualStatusClass(overallStatus);
const terminalSuccess=overall==='success'||['success','succeeded','done','completed','full_inference_success','full_inference_candidate_health_passed','health_only','repair_success'].includes(String(overallStatus||'').toLowerCase());
let firstFailedIdx=firstFailedGroupIndex(overallStatus);
const visibleGroups=visibleCompactTimelineGroups(items,overallStatus);
const terminalAnchorIdx=firstFailedIdx>=0?firstFailedIdx:terminalFailureAnchorGroupIndex(items,overallStatus);
if(overall==='error'&&firstFailedIdx<0)firstFailedIdx=terminalAnchorIdx;
return visibleGroups.map((group,groupIndex)=>{
const groupItems=group.steps.map(step=>byStep.get(step)).filter(Boolean);
const explicitItems=groupItems.filter(item=>String(item.step||item.id||item.label||''));
const doneCount=groupItems.filter(item=>['done','completed','success'].includes(String(item.status||'').toLowerCase())).length;
const active=groupItems.find(item=>['running','waiting','stopped','failed','blocked','cancelled','canceled'].includes(String(item.status||'').toLowerCase()));
const itemFailure=failedItemInfoForGroup(group,byStep);
const historicalFailure=failedTimelineStatusForGroup(group,overallStatus)||(itemFailure?(groupHasHardwareFallback(group)?'fallback':(overall==='error'?'failed':'recovered')):'');
let status='pending';
if(historicalFailure)status=historicalFailure;
else if(overall==='error'&&terminalAnchorIdx>=0&&groupIndex>terminalAnchorIdx)status='pending';
else if(overall==='error'&&terminalAnchorIdx>=0&&groupIndex===terminalAnchorIdx)status='failed';
else if(active)status=String(active.status||'running').toLowerCase();
else if(groupItems.length&&doneCount>=group.steps.length)status='done';
else if(doneCount>0)status=terminalSuccess?'done':'running';
if(overall==='error'&&status==='done'&&group.steps.includes('failure'))status='failed';
const failedInfo=failedEventInfoForGroup(group)||itemFailure;
const reason=historicalFailure==='fallback'?fallbackReasonForGroup(group):(status==='failed'?(failureReasonForGroup(group)||terminalFailureReason()):(historicalFailure?failureReasonForGroup(group):''));
const activeSubstep=historicalFailure==='fallback'?'fallback GPU':failedInfo&&historicalFailure?(failedInfo.event.step||failedInfo.event.message||'failed'):active?.step||active?.id||active?.label||groupItems[groupItems.length-1]?.step||'';
const activeIndex=activeSubstep?Math.max(0,group.steps.indexOf(activeSubstep)):-1;
let percent=0;
if(status==='done')percent=100;
else if(status==='pending')percent=0;
else if(status==='after_fail')percent=doneCount?Math.round((doneCount/group.steps.length)*100):0;
else if(activeIndex>=0)percent=Math.round(((activeIndex+0.45)/group.steps.length)*100);
else if(doneCount>0)percent=Math.round((doneCount/group.steps.length)*100);
if(terminalRaw.has(status))percent=Math.max(percent,doneCount?Math.round((doneCount/group.steps.length)*100):8);
const clampedPercent=Math.max(0,Math.min(100,percent));
const substepLabel=workerStepDisplayLabel(activeSubstep);
const progressDone=status==='done'?group.steps.length:Math.min(group.steps.length,Math.max(doneCount,clampedPercent>=100?group.steps.length:Math.ceil((clampedPercent/100)*group.steps.length)));
const showProgressLabel=group.steps.length>1&&(groupItems.length>0||status!=='pending');
return {
step:group.id,
id:group.id,
label:group.display_label||group.label,
status,
full_steps:group.steps,
done_count:doneCount,
total_count:showProgressLabel?group.steps.length:0,
percent:clampedPercent,
ring_progress:clampedPercent,
active_substep:activeSubstep,
active_substep_label:substepLabel,
grouped:group.steps.length>1,
progress_label:showProgressLabel?`${progressDone}/${group.steps.length}`:'',
reason
};
});
}
function currentCompactGroupKey(fullItems=[],overallStatus=''){
const problem=firstProblemGroupKey(fullItems,overallStatus);
if(problem)return problem;
const activeKey=activeTimelineStepKey(fullItems);
const group=COMPACT_TIMELINE_GROUPS.find(g=>g.steps.includes(activeKey));
return group?.id||activeKey;
}
function timelineTraceModel(list=[],overallStatus=''){
const overall=visualStatusClass(overallStatus);
const failedIndex=list.findIndex(item=>String(item.status||'').toLowerCase()==='failed');
const fallbackIndex=list.findIndex(item=>String(item.status||'').toLowerCase()==='fallback');
const recoveredIndex=list.findIndex(item=>String(item.status||'').toLowerCase()==='recovered');
if(failedIndex>=0){
return {mode:'failed',index:failedIndex,key:list[failedIndex]?.step||'',label:list[failedIndex]?.label||'failed step',reason:reasonForTimelineItem(list[failedIndex]),message:`Failed at ${list[failedIndex]?.label||'this stage'}. Later steps are post-failure cleanup or reporting, not a successful build path.`};
}
if(fallbackIndex>=0){
return {mode:'fallback',index:fallbackIndex,key:list[fallbackIndex]?.step||'',label:list[fallbackIndex]?.label||'hardware fallback',reason:reasonForTimelineItem(list[fallbackIndex]),message:`ZeroGPU was not used for this run. The factory switched to the fallback GPU and continued successfully.`};
}
if(recoveredIndex>=0){
return {mode:'recovered',index:recoveredIndex,key:list[recoveredIndex]?.step||'',label:list[recoveredIndex]?.label||'recovered step',reason:reasonForTimelineItem(list[recoveredIndex]),message:`Recovered after an incident at ${list[recoveredIndex]?.label||'this stage'}. The trace continues after recovery.`};
}
return {mode:'clean',index:-1,key:'',label:'',message:''};
}
function renderTimelineTraceNote(trace){
const note=$('timelineTraceNote');
if(!note)return;
if(!trace||trace.mode==='clean'){
note.hidden=true;
note.className='timeline-trace-note';
note.innerHTML='';
return;
}
note.hidden=false;
note.className=`timeline-trace-note ${trace.mode}`;
const icon=trace.mode==='failed'?'βœ•':(trace.mode==='fallback'?'⇄':'β†Ί');
const title=trace.mode==='failed'?'Failure trace':(trace.mode==='fallback'?'Hardware fallback':'Recovered incident');
const reason=trace.reason?`<p class="trace-reason"><b>Reason:</b> ${escapeHtml(trace.reason)}</p>`:'';
note.innerHTML=`<strong><span>${icon}</span>${escapeHtml(title)}</strong><p>${escapeHtml(trace.message)}</p>${reason}`;
}
function traceClassForStep(trace,index){
if(!trace||trace.mode==='clean')return'';
if(index===trace.index)return trace.mode==='failed'?'trace-breakpoint':(trace.mode==='fallback'?'trace-fallback':'trace-recovered');
if(trace.mode==='failed'&&index>trace.index)return'trace-post-failure';
return'';
}
function renderTimeline(items,overallStatus=''){
const root=$('timeline');
if(!root)return;
if(!root.dataset.scrollBound){root.dataset.scrollBound='1';root.addEventListener('scroll',()=>{if(root.dataset.autoScrolling==='1')return;root.dataset.userScrolled='1'},{passive:true});}
const fullList=items||[];
const list=compactTimelineItems(fullList,overallStatus);
// Keep the compact timeline on a single row. The number of visible groups is
// dynamic (repair/failure groups appear only for recovery/failures), so a
// fixed CSS repeat count can wrap dots onto a second line. Set the exact
// column count at render time for desktop/tablet layouts and let mobile CSS
// switch back to the vertical stacked timeline.
const wideTimeline=typeof window==='undefined'||!window.matchMedia||window.matchMedia('(min-width: 721px)').matches;
root.style.gridTemplateColumns=wideTimeline&&list.length?`repeat(${list.length}, minmax(96px, 1fr))`:'';
const trace=timelineTraceModel(list,overallStatus);
renderTimelineTraceNote(trace);
root.classList.toggle('trace-failed',trace.mode==='failed');
root.classList.toggle('trace-recovered',trace.mode==='recovered');
root.classList.toggle('trace-fallback',trace.mode==='fallback');
const currentKey=trace.key||currentCompactGroupKey(fullList,overallStatus);
const signature=list.map((x,i)=>`${timelineStepKey(x,i)}:${timelineStatus(x.status,overallStatus)}:${x.label||''}:${overallStatus||''}:${trace.mode}:${trace.index}`).join('|');
// v68 legacy invariant reference: if(root.dataset.signature===signature){ensureTimelineStepVisible(currentKey);return;}
if(root.dataset.signature===signature){
if(root.dataset.visibleStep!==currentKey&&root.dataset.userScrolled!=='1')ensureTimelineStepVisible(currentKey);
return;
}
const previousKey=root.dataset.currentStep||'';
if(previousKey!==currentKey)root.dataset.userScrolled='';
root.dataset.signature=signature;
root.dataset.currentStep=currentKey||'';
const nextState={};
root.innerHTML=list.map((x,i)=>{
const key=timelineStepKey(x,i);
const st=timelineStatus(x.status,overallStatus);
const rawStatus=String(x.status||'pending').toLowerCase();
const prev=timelineRenderState[key];
const changed=prev&&prev!==st;
nextState[key]=st;
const label=st.replace(/^status-/,'');
const percent=Number(x.percent||0);
const friendlySubstep=x.active_substep_label||workerStepDisplayLabel(x.active_substep);
const meta=x.total_count>1?(x.status==='done'?'complete':(friendlySubstep?friendlySubstep:label)):label;
const traceClass=traceClassForStep(trace,i);
const reason=reasonForTimelineItem(x);
const reasonLine=reason?`<small class="step-reason">${escapeHtml(reason)}</small>`:'';
const titleParts=[x.label||`Step ${i+1}`,meta,x.total_count>1?`${x.done_count||0}/${x.total_count} substeps`:reason].filter(Boolean);
const title=` title="${escapeHtml(titleParts.join(' Β· '))}"`;
const groupBadge=x.total_count>1?`<em class="step-count" aria-label="${escapeHtml((x.progress_label||'')+' substeps')}">${escapeHtml(x.progress_label||'')}</em>`:'';
const aria=`${x.label||`Step ${i+1}`}: ${label}${x.total_count>1?`, ${x.done_count||0} of ${x.total_count} substeps`:''}`;
return `<div class="step ${st} ${traceClass}${x.grouped?' is-grouped':''}${changed?' status-changed':''}" data-step="${escapeHtml(key)}" data-status="${label}" aria-label="${escapeHtml(aria)}" style="--step-progress:${percent}%"${title}><div class="step-dot"><span>${timelineIcon(rawStatus)}</span>${groupBadge}</div><div><strong>${escapeHtml(x.label||`Step ${i+1}`)}</strong><small>${escapeHtml(meta)}</small>${reasonLine}</div></div>`;
}).join('');
Object.keys(timelineRenderState).forEach(k=>delete timelineRenderState[k]);
Object.assign(timelineRenderState,nextState);
root.querySelectorAll('.step.status-changed').forEach(step=>{
animateClass(step,'status-flash',1000);
const dot=step.querySelector('.step-dot');
animateClass(dot,'dot-pop',700);
});
if(!root.dataset.userScrolled||previousKey!==currentKey){root.dataset.autoScrolling='1';const scrollOpts={instant:!root.dataset.visibleStep,force:previousKey!==currentKey};(typeof requestAnimationFrame==='function'?requestAnimationFrame:(fn)=>setTimeout(fn,0))(()=>(typeof requestAnimationFrame==='function'?requestAnimationFrame:(fn)=>setTimeout(fn,0))(()=>ensureTimelineStepVisible(currentKey,scrollOpts)));setTimeout(()=>{if(root)root.dataset.autoScrolling=''},360);}
}
function contextualEventClass(item={},furthestIdx=-1){
const raw=String(item.status||'').toLowerCase();
const idx=stepOrderIndex(item.step);
if(isFailureStatus(raw)&&isHardwareFallbackEvent(item))return'hardware-fallback';
if(isFailureStatus(raw)&&idx>=0&&furthestIdx>idx)return'warn recovered';
if(isFailureStatus(raw))return'error';
if(isStoppedStatus(raw))return'warn';
if(isSuccessStatus(raw))return'success';
if(raw.includes('running')||raw.includes('started')||raw.includes('waiting')||raw.includes('pending'))return'running';
return statusClass(raw);
}
function contextualEventLabel(item={},klass=''){
const raw=String(item.status||'').toLowerCase();
if(klass.includes('hardware-fallback'))return`${raw||'fallback'} Β· fallback route`;
if(klass.includes('recovered'))return`${raw||'failed'} Β· superseded`;
return raw;
}
function eventIconFor(item={},klass=''){
const step=String(item.step||'').toLowerCase();
const status=String(item.status||'').toLowerCase();
if(klass.includes('error')||status.includes('fail')||status.includes('error'))return'alert';
if(klass.includes('hardware-fallback')||step.includes('hardware'))return'chip';
if(step.includes('repair_decision')||step.includes('pi_diagnosis')||step.includes('failure_diagnosis'))return'search';
if(step.includes('factory_rebuild'))return'rocket';
if(step.includes('repair')||step.includes('blocker'))return'wand';
if(step.includes('upload'))return'upload';
if(step.includes('trace'))return'files';
if(step.includes('report'))return'document';
if(step.includes('auth'))return'shield';
if(step.includes('model')||step.includes('analysis'))return'search';
if(step.includes('space')||step.includes('create'))return'rocket';
if(step.includes('api')||step.includes('smoke')||step.includes('inference')||step.includes('validation'))return'bolt';
if(step.includes('pi_')||step.includes('pi'))return'bot';
if(status.includes('running')||status.includes('started')||status.includes('waiting')||status.includes('pending'))return'clock';
if(status.includes('success')||status==='done')return'check';
return'circle';
}
const EVENT_ICON_PATHS={
alert:'M12 9v4m0 4h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z',
bolt:'M13 2 4 14h7l-1 8 10-13h-7l0-7Z',
bot:'M12 5V3m-7 8a5 5 0 0 1 5-5h4a5 5 0 0 1 5 5v5a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3v-5Zm4 1h2m-8 0h2m-2 4h4',
check:'m5 13 4 4L19 7',
chip:'M9 3v3m6-3v3M9 18v3m6-3v3M3 9h3m-3 6h3m12-6h3m-3 6h3M8 6h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2Zm2 4h4v4h-4v-4Z',
circle:'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z',
clock:'M12 8v5l3 2m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z',
document:'M14 2H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7l-5-5Zm0 0v5h5M8 13h8M8 17h6',
files:'M9 7h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2Zm0 0V5a2 2 0 0 1 2-2h4l4 4v2M11 11h4M11 15h5',
rocket:'M5 19c1.5-.5 2.8-1.5 4-3m-4 3c-.8.8-1.8 1.1-3 1 .1-1.2.5-2.2 1.2-3M13 7l4 4m-6 6-4-4 4-7c2-2 5-3 8-3 0 3-1 6-3 8l-5 6Z',
search:'M21 21l-4.35-4.35M10.5 18a7.5 7.5 0 1 1 0-15 7.5 7.5 0 0 1 0 15Z',
shield:'M12 3 5 6v6c0 4.4 3 7.4 7 9 4-1.6 7-4.6 7-9V6l-7-3Zm-3 9 2 2 4-5',
upload:'M12 16V4m0 0 4 4m-4-4-4 4M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3',
wand:'M4 20 20 4m-3 0 3 3m-6-1 4 4M5 6h.01M9 3h.01M3 10h.01M18 18h.01'
};
function renderEventIcon(name){
const key=EVENT_ICON_PATHS[name]?name:'circle';
return `<span class="activity-icon" data-event-icon="${key}" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="${EVENT_ICON_PATHS[key]}"></path></svg></span>`;
}
// v105 keeps v64 compact-list invariant: const visible=[...list].reverse()
function eventOriginFor(item={}){
const step=String(item.step||'').toLowerCase();
const message=String(item.message||'').toLowerCase();
if(['pi_install','pi_config','pi_run','pi_model_resolution','pi_verification','pi_diagnosis','repair_plan','repair_patch'].some(s=>step.includes(s)))return'pi';
if(['bootstrap','token_context','dependencies','node'].some(s=>step.includes(s))||message.includes('worker started'))return'job';
if(['create_space','create_space_hardware','hardware_strategy','upload_files'].some(s=>step.includes(s)))return'hub';
if(['space_runtime','space_logs','live_wait'].some(s=>step.includes(s))||message.includes('runtime_error')||message.includes('app_starting'))return'space';
if(['api_validation','generation_smoke','inference_gate','repair_validation'].some(s=>step.includes(s))||message.includes('gradio'))return'gradio';
if(['failure_diagnosis','repair_decision','technical_blocker','failure','done','report_write'].some(s=>step.includes(s)))return'factory';
return'factory';
}
function renderActivityFeed(items){
const root=$('activityFeed');
if(!root)return;
const list=mergeStableEvents(String(state.runId||''),items||[]);
const visible=[...list].slice(-30).reverse();
const signature=visible.map(item=>`${item.ts||''}:${item.step||''}:${item.status||''}:${item.message||''}`).join('|');
if(root.dataset.signature===signature)return;
const shouldKeepLatestVisible=root.scrollTop<24||!root.dataset.signature;
root.dataset.signature=signature;
root.classList.add('show-all');
if(!list.length){
root.innerHTML='<div class="empty">No activity yet. Waiting for the first worker event.</div>';
return;
}
const furthestIdx=timelineFurthestIndex(progressStability.timeline||[]);
root.innerHTML=visible.map(item=>{
const klass=contextualEventClass(item,furthestIdx);
const label=contextualEventLabel(item,klass);
const reason=eventReason(item);
const reasonHtml=reason&&reason!==String(item.message||'').trim()?`<p class="event-reason">${escapeHtml(reason)}</p>`:'';
const icon=eventIconFor(item,klass);
const step=String(item.step||'event').replace(/_/g,' ');
const origin=eventOriginFor(item);
return `<div class="activity-row ${klass}"><time>${escapeHtml(formatTime(item.ts))}</time>${renderEventIcon(icon)}<div class="activity-copy"><strong title="${escapeHtml(item.message||'Event received')}">${escapeHtml(item.message||'Event received')}</strong><p title="${escapeHtml(step+' Β· '+(label||''))}"><span>${escapeHtml(step)}</span><em>${escapeHtml(label||'')}</em></p>${reasonHtml}</div><span class="event-origin ${origin}">${escapeHtml(origin)}</span></div>`;
}).join('');
if(shouldKeepLatestVisible)root.scrollTop=0;
}
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])=>`<span class="status-chip ${statusClass(v)}"><strong>${escapeHtml(k)}</strong><em>${escapeHtml(badgeLabel(v||'pending'))}</em></span>`).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=>`<li class="${statusClass(r.status)}"><span>${escapeHtml(r.label)}</span><strong>${escapeHtml(r.status||'pending')}</strong></li>`).join('');
}
function renderSpaceTestPreview(s={}){
const st=$('spaceTestStatus');
if(st){
st.textContent=badgeLabel(s.status||'pending');
st.className='badge '+statusClass(s.status);
}
setText('spaceTestOutput',s.output_artifact||s.endpoint||'Waiting for API test');
setText('spaceTestVerdict',s.verdict||'pending');
}
function animateProgressBar(progress,status,currentStep){
const fill=$('progressFill');
if(!fill)return;
const value=Math.max(0,Math.min(100,Number(progress||0)));
const previous=progressMotionState.lastProgress;
let klass=visualStatusClass(status);if(value>=100&&klass==='neutral')klass='success';
fill.style.setProperty('--progress-target',`${value}%`);
fill.style.width=`${value}%`;
fill.classList.remove('is-success','is-error','is-running-state','is-stopped','is-neutral');
fill.classList.add(`is-${klass==='running'?'running-state':klass}`);
const s=String(status||'').toLowerCase();fill.classList.toggle('is-running',klass==='running'||s==='pending'||s==='scheduled'||s==='waiting');
if(previous!==null&&Math.round(previous)!==Math.round(value)){
animateClass(fill,'progress-bump',650);
const pct=$('progressPercent');
animateClass(pct,'value-bump',650);
}
progressMotionState.lastProgress=value;
progressMotionState.lastStep=currentStep||progressMotionState.lastStep;
}
function animateStatusBadge(status){
const el=$('progressStatus');
if(!el)return;
const next=String(status||'').toLowerCase();
if(progressMotionState.lastStatus&&progressMotionState.lastStatus!==next){
animateClass(el,'status-bump',800);
}
progressMotionState.lastStatus=next;
}
function setElapsedClock(seconds,status){
const s=String(status||'').toLowerCase();
elapsedClock.baseSeconds=Math.max(0,Number(seconds||0));
elapsedClock.baseAt=Date.now();
elapsedClock.isRunning=Boolean(state.runId)&&!TERMINAL_STATUSES.has(s);
setText('elapsed',fmtSeconds(elapsedClock.baseSeconds));
}
window.setInterval(()=>{
if(!elapsedClock.isRunning)return;
const live=elapsedClock.baseSeconds+Math.floor((Date.now()-elapsedClock.baseAt)/1000);
setText('elapsed',fmtSeconds(live));
},1000);
function progressIsSuccessStatus(status){
const s=String(status||'').toLowerCase();
return ['success','succeeded','done','full_inference_success','full_inference_candidate_health_passed'].includes(s);
}
function progressIsTerminalStatus(status){
const s=String(status||'').toLowerCase();
return PROGRESS_TERMINAL_STATUSES.has(s);
}
function timelineFurthestIndex(items){
const list=items||[];
let max=-1;
list.forEach((item,i)=>{
const st=String(item.status||'pending').toLowerCase();
if(!['pending','status-pending','muted','status-muted'].includes(st))max=i;
});
return max;
}
function cloneTimeline(items){
return (items||[]).map(x=>({...x}));
}
function resetProgressStability(runId=null){
progressStability.runId=runId;
progressStability.progress=0;
progressStability.timeline=null;
progressStability.furthestIndex=-1;
progressStability.status='';
progressStability.currentStep=null;
progressMotionState.lastProgress=null;
progressMotionState.lastStatus=null;
progressMotionState.lastStep=null;
const timeline=$('timeline');
if(timeline){timeline.dataset.signature='';timeline.dataset.visibleStep='';timeline.dataset.userScrolled='';timeline.classList.remove('trace-failed','trace-recovered','trace-fallback');timeline.scrollLeft=0;}const note=$('timelineTraceNote');if(note){note.hidden=true;note.innerHTML='';note.className='timeline-trace-note';}
}
function stabilizeProgressPayload(p={},view=null){
const runId=String(p.run_id||view?.run_id||state.runId||'');
if(progressStability.runId!==runId)resetProgressStability(runId);
const status=String(view?.header?.status||p.status||p.state?.status||'').toLowerCase();
const terminal=progressIsTerminalStatus(status);
const success=progressIsSuccessStatus(status);
let rawProgress=Number(p.progress||0);
let rawTimeline=cloneTimeline(p.timeline||view?.pipeline||[]);
const rawIndex=timelineFurthestIndex(rawTimeline);
if(success){
progressStability.progress=100;
progressStability.timeline=rawTimeline;
progressStability.furthestIndex=Math.max(progressStability.furthestIndex,rawIndex);
}else if(terminal){
progressStability.progress=Math.max(progressStability.progress,rawProgress);
if(rawIndex>=progressStability.furthestIndex||!progressStability.timeline){
progressStability.timeline=rawTimeline;
progressStability.furthestIndex=rawIndex;
}else{
rawTimeline=cloneTimeline(progressStability.timeline);
}
}else{
if(rawProgress>=progressStability.progress){
progressStability.progress=rawProgress;
}else{
rawProgress=progressStability.progress;
}
if(rawIndex>=progressStability.furthestIndex||!progressStability.timeline){
progressStability.timeline=rawTimeline;
progressStability.furthestIndex=rawIndex;
}else{
rawTimeline=cloneTimeline(progressStability.timeline);
}
}
const activeItem=activeTimelineItem(rawTimeline);
progressStability.currentStep=activeItem?.step||activeItem?.id||activeItem?.label||progressStability.currentStep;
progressStability.status=status;
return {
progress:success?100:Math.max(rawProgress,progressStability.progress),
timeline:rawTimeline,
status,
currentStep:activeItem?.step||activeItem?.id||activeItem?.label||'',
currentStepLabel:workerStepDisplayLabel(activeItem?.step||activeItem?.id)||activeItem?.label||''
};
}
function renderRunViewModel(view,p={}){
const header=view.header||{};
const sm=view.status_model||{};
const stableEvents=mergeStableEvents(String(p.run_id||view.run_id||state.runId||''),p.events||[]);
const stable=stabilizeProgressPayload(p,view);
const progress=Number(stable.progress??(header.status==='succeeded'?100:0));
const runStatus=p.status||header.status;
const visualRunStatus=p.visual_status||runStatus;
animateProgressBar(progress,visualRunStatus,stable.currentStep);
setText('progressPercent',`${Math.round(progress)}%`);
setText('progressTitle',header.title||view.run_id||'Live job progress');
const statusEl=$('progressStatus');
if(statusEl){
statusEl.textContent=badgeLabel(runStatus||'Running');
statusEl.className='badge '+statusClass(visualRunStatus);
animateStatusBadge(visualRunStatus);
}
const latestEvent=stableEvents[stableEvents.length-1];
setText('currentStep',stable.currentStepLabel||p.current_step_label||'β€”');
setText('lastEvent',eventMessage(latestEvent)||p.last_event||'Waiting for worker events.');
setElapsedClock(p.elapsed_seconds??header.elapsed_seconds,runStatus);
setText('lastPolled',new Date().toLocaleTimeString());
setText('detailSpace',header.space||'β€”');
setText('detailJobStatus',header.raw_status||runStatus||'β€”');
setText('detailGateStatus',sm.verdict||'β€”');
setText('detailTraces',Array.isArray(p.events)?`${p.events.length} events`:'β€”');
const cancel=$('cancelRun');
if(cancel){
const s=String(runStatus||'').toLowerCase();
cancel.disabled=!(state.runId&&view.links?.job_url&&!TERMINAL_STATUSES.has(s));
}
renderTimeline(stable.timeline,runStatus);
renderActivityFeed(stableEvents);
renderDiagnostics(view.diagnostics||{},header);
renderSpaceTestPreview(view.space_test||{});
renderManualAction({view,...p});
if(typeof renderAgentRecovery==='function')renderAgentRecovery(p);
if(typeof renderRunDocuments==='function')renderRunDocuments(p);
}
function renderProgress(p){
if(p.view){
renderRunViewModel(p.view,p);
return;
}
const stableEvents=mergeStableEvents(String(p.run_id||state.runId||''),p.events||[]);
const stable=stabilizeProgressPayload(p,null);
const progress=Number(stable.progress||0);
animateProgressBar(progress,p.visual_status||p.status,stable.currentStep);
setText('progressPercent',`${Math.round(progress)}%`);
const latestEvent=stableEvents[stableEvents.length-1];
setText('currentStep',stable.currentStepLabel||p.current_step_label||'β€”');
setText('lastEvent',eventMessage(latestEvent)||p.last_event||'β€”');
setElapsedClock(p.elapsed_seconds,p.status);
setText('progressTitle',p.current_step_label||'Live job progress');
const statusEl=$('progressStatus');
if(statusEl){
statusEl.textContent=p.status||'running';
statusEl.className='badge '+statusClass(p.visual_status||p.status);
animateStatusBadge(p.visual_status||p.status);
}
setText('lastPolled',new Date().toLocaleTimeString());
renderTimeline(stable.timeline,p.status);
if(typeof renderAgentRecovery==='function')renderAgentRecovery(p);
}
// Legacy test anchors retained for timeline invariants:
// currentStepLabel:activeItem?.label
// x.done_count||0
// v164 compatibility anchors for older timeline tests:
// display_label:'Model card'
// active_substep_label:workerStepDisplayLabel(activeSubstep)
// title="${escapeHtml(reason)}"
// v165 compatibility anchors for historical timeline tests:
// progress_label:group.steps.length>1
// total_count:group.steps.length
// overall==='error'&&firstFailedIdx>=0&&groupIndex>firstFailedIdx
// v165 historical scroll/test anchors kept as comments after the offset-math refactor:
// requestAnimationFrame(()=>requestAnimationFrame(()=>ensureTimelineStepVisible
// status='after_fail'
// root.scrollLeft-(bounds.rootLeft+margin-bounds.targetLeft)
// root.scrollLeft+(bounds.targetRight-(bounds.rootRight-margin))