@@ -773,6 +773,7 @@ def __init__(self, root: Path) -> None:
773773 # Warm-starts from any existing entries so the panel populates
774774 # immediately, but only counts truly-new events as "live".
775775 self ._event_reader = EventTailReader (event_log_path_for (self .root ))
776+ self ._refresh_timer : Any | None = None
776777
777778 def compose (self ) -> ComposeResult :
778779 yield Header (show_clock = True )
@@ -789,7 +790,14 @@ def compose(self) -> ComposeResult:
789790
790791 def on_mount (self ) -> None :
791792 self ._refresh_panels ()
792- self .set_interval (REFRESH_INTERVAL_SECONDS , self ._refresh_panels )
793+ self ._refresh_timer = self .set_interval (REFRESH_INTERVAL_SECONDS , self ._refresh_panels )
794+
795+ def on_unmount (self ) -> None :
796+ timer = self ._refresh_timer
797+ self ._refresh_timer = None
798+ stop = getattr (timer , "stop" , None )
799+ if callable (stop ):
800+ stop ()
793801
794802 def action_refresh_now (self ) -> None :
795803 self ._refresh_panels ()
@@ -812,24 +820,58 @@ def action_open_drift(self) -> None:
812820 self .app .push_screen (DriftScreen (self .root ))
813821
814822 def _refresh_panels (self ) -> None :
815- data = build_status_data (self .root )
816- loop_nav_data = build_loop_navigator_data (self .root )
817- artifact_data = build_artifact_viewer_data (self .root , loop_nav_data .current_phase )
818- packet_data = build_packet_viewer_data (self .root )
819- diagnostics = self ._event_reader .poll ()
823+ try :
824+ data = build_status_data (self .root )
825+ status_text = _format_status_bar (data )
826+ footer_text = _format_footer_line (data )
827+ except Exception as exc : # noqa: BLE001 - TUI must degrade per panel
828+ status_text = _format_panel_error ("Status" , exc )
829+ footer_text = "Last refresh: unavailable"
830+
831+ try :
832+ loop_nav_data = build_loop_navigator_data (self .root )
833+ loop_text = _format_loop_navigator (loop_nav_data )
834+ current_phase = loop_nav_data .current_phase
835+ except Exception as exc : # noqa: BLE001 - TUI must degrade per panel
836+ loop_text = _format_panel_error ("Loop" , exc )
837+ current_phase = ""
838+
839+ try :
840+ artifact_data = build_artifact_viewer_data (self .root , current_phase )
841+ artifact_text = _format_artifact_viewer (artifact_data )
842+ artifact_phase = artifact_data .phase or "(none)"
843+ except Exception as exc : # noqa: BLE001 - TUI must degrade per panel
844+ artifact_text = _format_panel_error ("Artefacts" , exc )
845+ artifact_phase = current_phase or "(none)"
846+
847+ try :
848+ packet_data = build_packet_viewer_data (self .root )
849+ packet_text = _format_packet_viewer (packet_data )
850+ packet_title = f"Packet ({ packet_data .packet_id } )" if packet_data .packet_id else "Packet"
851+ except Exception as exc : # noqa: BLE001 - TUI must degrade per panel
852+ packet_text = _format_panel_error ("Packet" , exc )
853+ packet_title = "Packet"
854+
855+ try :
856+ diagnostics = self ._event_reader .poll ()
857+ diagnostics_text = _format_diagnostics_panel (diagnostics )
858+ except Exception as exc : # noqa: BLE001 - TUI must degrade per panel
859+ diagnostics_text = _format_panel_error ("Diagnostics" , exc )
860+
820861 self ._loop_nav_widget .border_title = "Loop"
821862 self ._events_widget .border_title = "Diagnostics"
822- artifact_phase = artifact_data .phase or "(none)"
823863 self ._artifact_widget .border_title = f"Artefacts ({ artifact_phase } )"
824- self ._packet_widget .border_title = (
825- f"Packet ({ packet_data .packet_id } )" if packet_data .packet_id else "Packet"
826- )
827- self ._loop_nav_widget .update (_format_loop_navigator (loop_nav_data ))
828- self ._events_widget .update (_format_diagnostics_panel (diagnostics ))
829- self ._artifact_widget .update (_format_artifact_viewer (artifact_data ))
830- self ._packet_widget .update (_format_packet_viewer (packet_data ))
831- self ._status_bar_widget .update (_format_status_bar (data ))
832- self ._footer_widget .update (_format_footer_line (data ))
864+ self ._packet_widget .border_title = packet_title
865+ self ._loop_nav_widget .update (loop_text )
866+ self ._events_widget .update (diagnostics_text )
867+ self ._artifact_widget .update (artifact_text )
868+ self ._packet_widget .update (packet_text )
869+ self ._status_bar_widget .update (status_text )
870+ self ._footer_widget .update (footer_text )
871+
872+
873+ def _format_panel_error (label : str , exc : BaseException ) -> str :
874+ return f"[yellow]{ label } unavailable[/yellow]\n [dim]{ type (exc ).__name__ } : { exc } [/dim]"
833875
834876
835877class MythicTuiApp (App ):
0 commit comments