@@ -308,10 +308,33 @@ def set_peek_visible(self, visible: bool) -> None:
308308
309309 # ── Shared rendering ─────────────────────────────────────────────
310310
311+ def _build_title (self ) -> Text :
312+ """Title bar text: elapsed time. Subclasses may override."""
313+ elapsed = time .monotonic () - self ._start
314+ title = Text ()
315+ title .append (" ⏱ " , style = _brand .PURPLE )
316+ title .append (format_duration (elapsed ), style = f"bold { _brand .PURPLE } " )
317+ title .append (" " , style = "dim" )
318+ return title
319+
320+ def _build_subtitle (self ) -> Text | None :
321+ """Subtitle text. Returns ``None`` by default."""
322+ return None
323+
311324 def _build_footer (self ) -> Table :
312325 """Subclasses must override to provide the footer summary row."""
313326 raise NotImplementedError
314327
328+ def _footer_grid (self , summary : Text ) -> Table :
329+ """Three-column footer row: spinner | summary | peek hint."""
330+ hint = Text ("Shift+P full screen" , style = "dim" , no_wrap = True )
331+ grid = Table .grid (expand = True )
332+ grid .add_column (width = 2 , no_wrap = True )
333+ grid .add_column (ratio = 1 , no_wrap = True , overflow = "ellipsis" )
334+ grid .add_column (no_wrap = True , justify = "right" )
335+ grid .add_row (self ._spinner , summary , hint )
336+ return grid
337+
315338 def _build_body (self ) -> Group :
316339 """Body group: scroll lines (or peek message) + spacer + footer."""
317340 rows : list [Any ] = []
@@ -330,6 +353,21 @@ def _build_body(self) -> Group:
330353 rows .append (self ._build_footer ())
331354 return Group (* rows )
332355
356+ def __rich_console__ (
357+ self , console : Console , options : ConsoleOptions
358+ ) -> RenderResult :
359+ panel = Panel (
360+ self ._build_body (),
361+ box = box .ROUNDED ,
362+ title = self ._build_title (),
363+ title_align = "left" ,
364+ subtitle = self ._build_subtitle (),
365+ subtitle_align = "right" ,
366+ border_style = _brand .PURPLE ,
367+ padding = (0 , 2 ),
368+ )
369+ yield panel
370+
333371
334372class _IterationPanel (_LivePanelBase ):
335373 """Rich renderable for the live peek panel.
@@ -490,15 +528,12 @@ def _format_categories(self) -> str:
490528
491529 def _build_title (self ) -> Text :
492530 """Title bar text: elapsed time + token usage."""
493- elapsed = time .monotonic () - self ._start
494- title = Text ()
495- title .append (" ⏱ " , style = _brand .PURPLE )
496- title .append (format_duration (elapsed ), style = f"bold { _brand .PURPLE } " )
531+ title = super ()._build_title ()
497532 tokens = self ._format_tokens ()
498533 if tokens :
499- title .append (" " , style = "dim" )
534+ title .append (" " , style = "dim" )
500535 title .append (tokens , style = f"bold { _brand .LAVENDER } " )
501- title .append (" " , style = "dim" )
536+ title .append (" " , style = "dim" )
502537 return title
503538
504539 def _build_subtitle (self ) -> Text | None :
@@ -525,30 +560,7 @@ def _build_footer(self) -> Table:
525560 summary .append (cats , style = "dim" )
526561 else :
527562 summary .append ("waiting for first tool call…" , style = "dim italic" )
528-
529- hint = Text ("Shift+P full screen" , style = "dim" , no_wrap = True )
530-
531- grid = Table .grid (expand = True )
532- grid .add_column (width = 2 , no_wrap = True )
533- grid .add_column (ratio = 1 , no_wrap = True , overflow = "ellipsis" )
534- grid .add_column (no_wrap = True , justify = "right" )
535- grid .add_row (self ._spinner , summary , hint )
536- return grid
537-
538- def __rich_console__ (
539- self , console : Console , options : ConsoleOptions
540- ) -> RenderResult :
541- panel = Panel (
542- self ._build_body (),
543- box = box .ROUNDED ,
544- title = self ._build_title (),
545- title_align = "left" ,
546- subtitle = self ._build_subtitle (),
547- subtitle_align = "right" ,
548- border_style = _brand .PURPLE ,
549- padding = (0 , 2 ),
550- )
551- yield panel
563+ return self ._footer_grid (summary )
552564
553565
554566# ── Full-screen peek ─────────────────────────────────────────────────
@@ -1164,14 +1176,6 @@ class _IterationSpinner(_LivePanelBase):
11641176 lines vs. structured tool rows).
11651177 """
11661178
1167- def _build_title (self ) -> Text :
1168- elapsed = time .monotonic () - self ._start
1169- title = Text ()
1170- title .append (" ⏱ " , style = _brand .PURPLE )
1171- title .append (format_duration (elapsed ), style = f"bold { _brand .PURPLE } " )
1172- title .append (" " , style = "dim" )
1173- return title
1174-
11751179 def _build_footer (self ) -> Table :
11761180 line_count = len (self ._scroll_lines )
11771181 summary = Text (no_wrap = True , overflow = "ellipsis" )
@@ -1183,25 +1187,4 @@ def _build_footer(self) -> Table:
11831187 summary .append (" of agent output" , style = "dim" )
11841188 else :
11851189 summary .append ("waiting for agent output…" , style = "dim italic" )
1186-
1187- hint = Text ("Shift+P full screen" , style = "dim" , no_wrap = True )
1188-
1189- grid = Table .grid (expand = True )
1190- grid .add_column (width = 2 , no_wrap = True )
1191- grid .add_column (ratio = 1 , no_wrap = True , overflow = "ellipsis" )
1192- grid .add_column (no_wrap = True , justify = "right" )
1193- grid .add_row (self ._spinner , summary , hint )
1194- return grid
1195-
1196- def __rich_console__ (
1197- self , console : Console , options : ConsoleOptions
1198- ) -> RenderResult :
1199- panel = Panel (
1200- self ._build_body (),
1201- box = box .ROUNDED ,
1202- title = self ._build_title (),
1203- title_align = "left" ,
1204- border_style = _brand .PURPLE ,
1205- padding = (0 , 2 ),
1206- )
1207- yield panel
1190+ return self ._footer_grid (summary )
0 commit comments