@@ -27,6 +27,15 @@ public class LiveDisplay
2727 private string ? _filter ;
2828 private string ? _assemblyName ;
2929 private int _workerCount = 1 ;
30+ private int _timeoutSeconds = 30 ;
31+ private readonly Dictionary < int , WorkerState > _workerStates = new ( ) ;
32+
33+ private class WorkerState
34+ {
35+ public DateTime LastActivity { get ; set ; } = DateTime . UtcNow ;
36+ public bool IsRestarting { get ; set ; }
37+ public bool IsComplete { get ; set ; }
38+ }
3039
3140 public void SetTotal ( int total )
3241 {
@@ -45,7 +54,47 @@ public void SetAssembly(string assemblyPath)
4554
4655 public void SetWorkerCount ( int count )
4756 {
48- lock ( _lock ) _workerCount = count ;
57+ lock ( _lock )
58+ {
59+ _workerCount = count ;
60+ for ( var i = 0 ; i < count ; i ++ )
61+ _workerStates [ i ] = new WorkerState ( ) ;
62+ }
63+ }
64+
65+ public void SetTimeout ( int seconds )
66+ {
67+ lock ( _lock ) _timeoutSeconds = seconds ;
68+ }
69+
70+ public void WorkerActivity ( int workerIndex )
71+ {
72+ lock ( _lock )
73+ {
74+ if ( _workerStates . TryGetValue ( workerIndex , out var state ) )
75+ {
76+ state . LastActivity = DateTime . UtcNow ;
77+ state . IsRestarting = false ;
78+ }
79+ }
80+ }
81+
82+ public void WorkerRestarting ( int workerIndex )
83+ {
84+ lock ( _lock )
85+ {
86+ if ( _workerStates . TryGetValue ( workerIndex , out var state ) )
87+ state . IsRestarting = true ;
88+ }
89+ }
90+
91+ public void WorkerComplete ( int workerIndex )
92+ {
93+ lock ( _lock )
94+ {
95+ if ( _workerStates . TryGetValue ( workerIndex , out var state ) )
96+ state . IsComplete = true ;
97+ }
4998 }
5099
51100 public void TestStarted ( string displayName )
@@ -108,13 +157,6 @@ public void TestCrashed(string displayName)
108157 }
109158 }
110159
111- public void WorkerRestarted ( int remaining )
112- {
113- lock ( _lock )
114- {
115- _running . Clear ( ) ;
116- }
117- }
118160
119161 public IRenderable Render ( )
120162 {
@@ -142,20 +184,30 @@ public IRenderable Render()
142184 new Markup ( $ "[dim]{ elapsed : mm\\:ss} [/] [dim]({ rate : F1} /s)[/]")
143185 ) ;
144186
145- var layout = new Rows (
187+ var layoutItems = new List < IRenderable >
188+ {
146189 grid ,
147190 new Text ( "" ) ,
148191 CreateProgressBar ( completed , _total ) ,
149192 new Text ( "" ) ,
150193 CreateRunningSection ( )
151- ) ;
194+ } ;
195+
196+ // Add worker status bar if multiple workers
197+ if ( _workerCount > 1 )
198+ {
199+ layoutItems . Add ( new Text ( "" ) ) ;
200+ layoutItems . Add ( CreateWorkerStatusBar ( ) ) ;
201+ }
202+
203+ var layout = new Rows ( layoutItems ) ;
152204
153205 // Build header: show filter if set, otherwise assembly name
154206 var headerText = ! string . IsNullOrEmpty ( _filter )
155207 ? $ "[blue]filter[/] [green]\" { _filter } \" [/]"
156208 : _assemblyName ?? "Test Progress" ;
157209
158- var workerText = _workerCount > 1 ? $ " [dim]( { _workerCount } workers)[/] " : "" ;
210+ var workerText = _workerCount > 1 ? " " : "" ; // Removed from header, shown in bar now
159211
160212 var panel = new Panel ( layout )
161213 . Header ( $ "{ headerText } { workerText } [blue]({ completed } /{ _total } )[/]")
@@ -225,6 +277,49 @@ private IRenderable CreateRunningSection()
225277 return new Rows ( lines ) ;
226278 }
227279
280+ private IRenderable CreateWorkerStatusBar ( )
281+ {
282+ var parts = new List < string > { "[dim]workers:[/]" } ;
283+
284+ for ( var i = 0 ; i < _workerCount ; i ++ )
285+ {
286+ if ( ! _workerStates . TryGetValue ( i , out var state ) )
287+ {
288+ parts . Add ( "[dim]○[/]" ) ;
289+ continue ;
290+ }
291+
292+ if ( state . IsComplete )
293+ {
294+ parts . Add ( "[green]✓[/]" ) ;
295+ continue ;
296+ }
297+
298+ if ( state . IsRestarting )
299+ {
300+ parts . Add ( "[yellow]⏳[/]" ) ;
301+ continue ;
302+ }
303+
304+ // Calculate health based on time since last activity
305+ var elapsed = ( DateTime . UtcNow - state . LastActivity ) . TotalSeconds ;
306+ var ratio = Math . Min ( 1.0 , elapsed / _timeoutSeconds ) ;
307+
308+ // Color gradient: green → yellow → orange → red
309+ var color = ratio switch
310+ {
311+ < 0.25 => "green" ,
312+ < 0.5 => "yellow" ,
313+ < 0.75 => "orange3" ,
314+ _ => "red"
315+ } ;
316+
317+ parts . Add ( $ "[{ color } ]●[/]") ;
318+ }
319+
320+ return new Markup ( string . Join ( " " , parts ) ) ;
321+ }
322+
228323 private static string Truncate ( string text , int maxLength )
229324 {
230325 if ( text . Length <= maxLength ) return text ;
0 commit comments