1111
1212class CustomFooter (Horizontal ):
1313 """A custom footer with keyboard shortcuts and render ID."""
14-
14+
1515 def __init__ (self , render_id : str = "" , ** kwargs ):
1616 super ().__init__ (** kwargs )
1717 self .render_id = render_id
18-
18+
1919 def compose (self ):
2020 yield Static ("ctrl+c: quit * ctrl+l: toggle logs" , classes = "custom-footer-text" )
2121 if self .render_id :
2222 yield Static (f"render id: { self .render_id } " , classes = "custom-footer-render-id" )
2323
2424
2525class ScriptOutputType (str , Enum ):
26- UNIT_TEST_OUTPUT_TEXT = "Latest unit test script execution output : "
27- CONFORMANCE_TEST_OUTPUT_TEXT = "Latest conformance tests script execution output : "
28- TESTING_ENVIRONMENT_OUTPUT_TEXT = "Latest testing environment preparation script execution output: "
26+ UNIT_TEST_OUTPUT_TEXT = "Unit tests : "
27+ CONFORMANCE_TEST_OUTPUT_TEXT = "Conformance tests: "
28+ TESTING_ENVIRONMENT_OUTPUT_TEXT = "Testing environment preparation execution output: "
2929
3030 @staticmethod
3131 def get_max_label_width (active_types : list ["ScriptOutputType" ]) -> int :
@@ -42,16 +42,16 @@ def get_max_label_width(active_types: list["ScriptOutputType"]) -> int:
4242 return max (len (script_type .value ) for script_type in active_types )
4343
4444 def get_padded_label (self , active_types : list ["ScriptOutputType" ]) -> str :
45- """Get the label right-padded to align with other active labels .
45+ """Get the label left-aligned (no padding) .
4646
4747 Args:
4848 active_types: List of ScriptOutputType enum members that are currently active
4949
5050 Returns:
51- Right-aligned label padded to match the longest active label
51+ Label without padding (left-aligned)
5252 """
53- max_width = ScriptOutputType . get_max_label_width ( active_types )
54- return self .value . rjust ( max_width )
53+ # Return label as-is without padding for left alignment
54+ return self .value
5555
5656
5757class TUIComponents (str , Enum ):
@@ -189,6 +189,155 @@ async def clear_substates(self):
189189 pass
190190
191191
192+ class RenderingInfoBox (Vertical ):
193+ """Container with ASCII border for module and functionality information."""
194+
195+ def __init__ (self , ** kwargs ):
196+ super ().__init__ (** kwargs )
197+ self .module_text = ""
198+ self .functionality_text = ""
199+
200+ def update_module (self , text : str ) -> None :
201+ """Update the module name and refresh the display."""
202+ self .module_text = text
203+ self ._refresh_content ()
204+
205+ def update_functionality (self , text : str ) -> None :
206+ """Update the functionality text and refresh the display."""
207+ self .functionality_text = text
208+ self ._refresh_content ()
209+
210+ def _refresh_content (self ) -> None :
211+ """Refresh the content of the box."""
212+ try :
213+ widget = self .query_one ("#rendering-info-content" , Static )
214+ # Calculate the width needed for the border
215+ lines = []
216+ if self .module_text :
217+ lines .append (f" { self .module_text } " )
218+ if self .functionality_text :
219+ lines .append (f" { self .functionality_text } " )
220+
221+ if not lines :
222+ widget .update ("" )
223+ return
224+
225+ # Find the longest line to determine border width
226+ max_width = max (len (line ) for line in lines ) if lines else 40
227+ border_width = max (max_width + 2 , 42 ) # At least 42 chars wide
228+
229+ # Build the bordered box with title
230+ title = " Currently rendering "
231+ top_border = "┌[#FFFFFF]" + title + "[/#FFFFFF]" + "─" * (border_width - len (title )) + "┐"
232+ bottom_border = "└" + "─" * border_width + "┘"
233+
234+ content_lines = [top_border ]
235+ # Add top padding (empty line)
236+ content_lines .append (f"│{ ' ' * border_width } │" )
237+ for line in lines :
238+ padding = border_width - len (line )
239+ content_lines .append (f"│{ line } { ' ' * padding } │" )
240+ # Add bottom padding (empty line)
241+ content_lines .append (f"│{ ' ' * border_width } │" )
242+ content_lines .append (bottom_border )
243+
244+ widget .update ("\n " .join (content_lines ))
245+ except Exception :
246+ pass
247+
248+ def on_mount (self ) -> None :
249+ """Initialize the box with empty state on mount."""
250+ # Set default empty labels
251+ self .module_text = "Module: "
252+ self .functionality_text = "Functionality:"
253+ self ._refresh_content ()
254+
255+ def compose (self ):
256+ yield Static ("" , id = "rendering-info-content" , classes = "rendering-info-box" )
257+
258+
259+ class TestScriptsContainer (Vertical ):
260+ """Container with ASCII border for test script outputs."""
261+
262+ def __init__ (
263+ self ,
264+ show_unit_test : bool = True ,
265+ show_conformance_test : bool = True ,
266+ show_testing_env : bool = True ,
267+ ** kwargs ,
268+ ):
269+ super ().__init__ (** kwargs )
270+ self .show_unit_test = show_unit_test
271+ self .show_conformance_test = show_conformance_test
272+ self .show_testing_env = show_testing_env
273+ self .unit_test_text = ScriptOutputType .UNIT_TEST_OUTPUT_TEXT .value
274+ self .conformance_test_text = ScriptOutputType .CONFORMANCE_TEST_OUTPUT_TEXT .value
275+ self .testing_env_text = ScriptOutputType .TESTING_ENVIRONMENT_OUTPUT_TEXT .value
276+
277+ def update_unit_test (self , text : str ) -> None :
278+ """Update unit test output and refresh."""
279+ self .unit_test_text = text
280+ self ._refresh_content ()
281+
282+ def update_conformance_test (self , text : str ) -> None :
283+ """Update conformance test output and refresh."""
284+ self .conformance_test_text = text
285+ self ._refresh_content ()
286+
287+ def update_testing_env (self , text : str ) -> None :
288+ """Update testing env output and refresh."""
289+ self .testing_env_text = text
290+ self ._refresh_content ()
291+
292+ def _refresh_content (self ) -> None :
293+ """Refresh the bordered box content."""
294+ try :
295+ widget = self .query_one ("#test-scripts-content" , Static )
296+
297+ # Collect lines to display
298+ lines = []
299+ if self .show_unit_test :
300+ lines .append (f" { self .unit_test_text } " )
301+ if self .show_conformance_test :
302+ lines .append (f" { self .conformance_test_text } " )
303+ if self .show_testing_env :
304+ lines .append (f" { self .testing_env_text } " )
305+
306+ if not lines :
307+ widget .update ("" )
308+ return
309+
310+ # Find the longest line to determine border width
311+ max_width = max (len (line ) for line in lines )
312+ border_width = max (max_width + 2 , 80 ) # Minimum width of 80 chars
313+
314+ # Build the bordered box with title
315+ title = " Latest test scripts "
316+ top_border = "┌[#FFFFFF]" + title + "[/#FFFFFF]" + "─" * (border_width - len (title )) + "┐"
317+ bottom_border = "└" + "─" * border_width + "┘"
318+
319+ content_lines = [top_border ]
320+ # Add top padding (empty line)
321+ content_lines .append (f"│{ ' ' * border_width } │" )
322+ for line in lines :
323+ padding = border_width - len (line )
324+ content_lines .append (f"│{ line } { ' ' * padding } │" )
325+ # Add bottom padding (empty line)
326+ content_lines .append (f"│{ ' ' * border_width } │" )
327+ content_lines .append (bottom_border )
328+
329+ widget .update ("\n " .join (content_lines ))
330+ except Exception :
331+ pass
332+
333+ def on_mount (self ) -> None :
334+ """Initialize the box on mount."""
335+ self ._refresh_content ()
336+
337+ def compose (self ):
338+ yield Static ("" , id = "test-scripts-content" , classes = "test-scripts-box" )
339+
340+
192341class FRIDProgress (Vertical ):
193342 """A widget to display the status of subcomponent tasks."""
194343
@@ -198,8 +347,8 @@ class FRIDProgress(Vertical):
198347 REFACTORING_TEXT = "Refactoring"
199348 CONFORMANCE_TEST_VALIDATION_TEXT = "Conformance tests"
200349
201- RENDERING_MODULE_TEXT = "Rendering module : "
202- RENDERING_FUNCTIONALITY_TEXT = "Rendering functionality :"
350+ RENDERING_MODULE_TEXT = "Module : "
351+ RENDERING_FUNCTIONALITY_TEXT = "Functionality :"
203352
204353 def __init__ (
205354 self ,
@@ -213,8 +362,9 @@ def __init__(
213362
214363 def update_fr_text (self , text : str ) -> None :
215364 try :
216- widget = self .query_one (f"#{ TUIComponents .FRID_PROGRESS_HEADER .value } " , Static )
217- widget .update (text )
365+ # Update the rendering info box instead
366+ info_box = self .query_one (RenderingInfoBox )
367+ info_box .update_functionality (text )
218368 except Exception :
219369 pass
220370
@@ -229,8 +379,7 @@ def on_mount(self) -> None:
229379 self .border_title = "FRID Progress"
230380
231381 def compose (self ):
232- yield Static (self .RENDERING_MODULE_TEXT , id = TUIComponents .RENDER_MODULE_NAME_WIDGET .value )
233- yield Static (self .RENDERING_FUNCTIONALITY_TEXT , id = TUIComponents .FRID_PROGRESS_HEADER .value )
382+ yield RenderingInfoBox ()
234383 yield ProgressItem (
235384 self .IMPLEMENTING_FUNCTIONALITY_TEXT ,
236385 id = TUIComponents .FRID_PROGRESS_RENDER_FR .value ,
@@ -266,22 +415,23 @@ def compose(self):
266415 # Column 1: Offset time with brackets (just the time portion, not full timestamp)
267416 time_part = self .timestamp .split ()[- 1 ] if self .timestamp else ""
268417 yield Static (f"[{ time_part } ]" , classes = "log-col-time" )
269-
418+
270419 # Column 2: Level
271420 yield Static (self .level , classes = "log-col-level" )
272-
421+
273422 # Column 3: Location (logger name) - truncate if longer than 20 characters
274423 location = self .logger_name
275424 if len (location ) > 9 :
276425 location = location [:9 ] + "..."
277426 yield Static (location , classes = "log-col-location" )
278-
427+
279428 # Column 4: Message with green checkmark for successful outcomes
280429 message_text = self .message
281430 # Add green checkmark for messages indicating success
282- if any (keyword in message_text .lower () for keyword in [
283- "completed" , "success" , "successfully" , "passed" , "done" , "✓"
284- ]):
431+ if any (
432+ keyword in message_text .lower ()
433+ for keyword in ["completed" , "success" , "successfully" , "passed" , "done" , "✓" ]
434+ ):
285435 message_text = f"[green]✓[/green] { message_text } "
286436 yield Static (message_text , classes = "log-col-message" )
287437
@@ -310,15 +460,15 @@ def _should_show_log(self, level: str) -> bool:
310460 async def add_log (self , logger_name : str , level : str , message : str , timestamp : str = "" ):
311461 """Add a new log entry."""
312462 # Check if this is a success message that should have spacing before it
313- is_success_message = any (keyword in message . lower () for keyword in [
314- "completed" , "success" , "successfully" , "passed" , "done" , "✓"
315- ] )
316-
463+ is_success_message = any (
464+ keyword in message . lower () for keyword in [ "completed" , "success" , "successfully" , "passed" , "done" , "✓" ]
465+ )
466+
317467 # Add empty line before success messages
318468 if is_success_message :
319469 spacer = Static ("" , classes = "log-spacer" )
320470 await self .mount (spacer )
321-
471+
322472 entry = LogEntry (logger_name , level , message , timestamp )
323473
324474 # Only show if level is >= minimum level
@@ -350,7 +500,7 @@ class LogLevelFilter(Horizontal):
350500 """Filter logs by minimum level with buttons."""
351501
352502 LEVELS = ["debug" , "info" , "warning" , "error" ]
353-
503+
354504 # Make the widget focusable to receive keyboard events
355505 can_focus = True
356506
0 commit comments