Skip to content

Commit df0419c

Browse files
Kasper JungeRalphify
authored andcommitted
refactor: replace dual _iteration_panel/_iteration_spinner fields with single _active_renderable
The two fields were always set mutually exclusively — one held the active panel/spinner while the other was None. Collapsing them into a single _LivePanelBase field eliminates the redundant state and removes the @Property that merged them back together at every access. Co-authored-by: Ralphify <noreply@ralphify.co>
1 parent ae98c0d commit df0419c

2 files changed

Lines changed: 27 additions & 40 deletions

File tree

src/ralphify/_console_emitter.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -971,8 +971,7 @@ def __init__(self, console: Console) -> None:
971971
self._peek_enabled = _interactive_default_peek(console)
972972
self._structured_agent: bool = False
973973
self._peek_broken: bool = False
974-
self._iteration_panel: _IterationPanel | None = None
975-
self._iteration_spinner: _IterationSpinner | None = None
974+
self._active_renderable: _LivePanelBase | None = None
976975
# Iteration number of the currently-active panel (the one
977976
# receiving events). ``None`` between iterations.
978977
self._current_iteration: int | None = None
@@ -1014,11 +1013,6 @@ def __init__(self, console: Console) -> None:
10141013
EventType.AGENT_ACTIVITY: self._on_agent_activity,
10151014
}
10161015

1017-
@property
1018-
def _active_renderable(self) -> _LivePanelBase | None:
1019-
"""The panel or spinner for the current iteration, or ``None``."""
1020-
return self._iteration_panel or self._iteration_spinner
1021-
10221016
# ── _IterationNavigator implementation ───────────────────────────
10231017
#
10241018
# These three methods expose the emitter's iteration state to
@@ -1130,8 +1124,7 @@ def _archive_current_iteration_unlocked(self, outcome: str) -> None:
11301124
break
11311125
self._iteration_order.remove(candidate)
11321126
self._iteration_history.pop(candidate, None)
1133-
self._iteration_panel = None
1134-
self._iteration_spinner = None
1127+
self._active_renderable = None
11351128
self._current_iteration = None
11361129

11371130
def _refresh_live_unlocked(self, renderable: _LivePanelBase) -> None:
@@ -1271,15 +1264,10 @@ def _create_panel_unlocked(self) -> _LivePanelBase:
12711264
Caller must hold ``_console_lock``.
12721265
"""
12731266
if self._structured_agent:
1274-
panel = _IterationPanel()
1275-
self._iteration_panel = panel
1276-
self._iteration_spinner = None
1277-
renderable: _LivePanelBase = panel
1267+
renderable: _LivePanelBase = _IterationPanel()
12781268
else:
1279-
spinner = _IterationSpinner()
1280-
self._iteration_panel = None
1281-
self._iteration_spinner = spinner
1282-
renderable = spinner
1269+
renderable = _IterationSpinner()
1270+
self._active_renderable = renderable
12831271
# Carry the current peek visibility into the new renderable so
12841272
# an iteration that starts with peek already off doesn't flash
12851273
# the empty scroll feed before the first event lands.
@@ -1317,8 +1305,7 @@ def _stop_live_unlocked(self) -> None:
13171305
if self._live is not None:
13181306
self._live.stop()
13191307
self._live = None
1320-
self._iteration_panel = None
1321-
self._iteration_spinner = None
1308+
self._active_renderable = None
13221309
self._current_iteration = None
13231310

13241311
def _stop_live(self) -> None:

tests/test_console_emitter.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def test_toggle_peek_enables_rendering(self):
6969
iteration=1,
7070
)
7171
)
72-
spinner = emitter._iteration_spinner
72+
spinner = emitter._active_renderable
7373
assert spinner is not None
7474
assert any("visible line" in line.plain for line in spinner._scroll_lines)
7575
emitter._stop_live()
@@ -94,7 +94,7 @@ def test_toggle_peek_off_keeps_buffering_but_hides_lines(self):
9494
iteration=1,
9595
)
9696
)
97-
spinner = emitter._iteration_spinner
97+
spinner = emitter._active_renderable
9898
assert spinner is not None
9999
# Buffer keeps recording even with peek off…
100100
assert any(
@@ -146,7 +146,7 @@ def worker(line: str) -> None:
146146
for t in threads:
147147
t.join()
148148

149-
spinner = emitter._iteration_spinner
149+
spinner = emitter._active_renderable
150150
assert spinner is not None
151151
# Each worker adds ``iterations`` whole copies of its line to the
152152
# scroll buffer. If the lock is working, both substrings appear
@@ -186,7 +186,7 @@ def test_peek_line_escapes_rich_markup(self):
186186
iteration=1,
187187
)
188188
)
189-
spinner = emitter._iteration_spinner
189+
spinner = emitter._active_renderable
190190
assert spinner is not None
191191
assert any(
192192
"[bold red]not markup[/]" in line.plain for line in spinner._scroll_lines
@@ -425,7 +425,7 @@ def _emit_tool(name: str, arg: str) -> None:
425425
# Toggle back on — both the original and the catch-up event are
426426
# in the buffer, so the user sees current state, not stale state.
427427
emitter.toggle_peek()
428-
panel = emitter._iteration_panel
428+
panel = emitter._active_renderable
429429
assert panel is not None
430430
plains = [line.plain for line in panel._scroll_lines]
431431
assert any("seen-while-on.py" in p for p in plains)
@@ -461,7 +461,7 @@ def test_toggle_peek_off_preserves_scroll_buffer(self):
461461
iteration=1,
462462
)
463463
)
464-
panel = emitter._iteration_panel
464+
panel = emitter._active_renderable
465465
assert panel is not None
466466
assert len(panel._scroll_lines) == 1
467467
before = list(panel._scroll_lines)
@@ -482,7 +482,7 @@ def test_toggle_peek_in_live_sets_peek_message(self):
482482
emitter.emit(_make_event(EventType.ITERATION_STARTED, iteration=1))
483483
# Toggle peek off — should set peek message on the spinner
484484
emitter.toggle_peek()
485-
spinner = emitter._iteration_spinner
485+
spinner = emitter._active_renderable
486486
assert spinner is not None
487487
assert spinner._peek_message is not None
488488
assert "peek off" in spinner._peek_message.plain
@@ -535,7 +535,7 @@ def test_non_claude_agent_keeps_raw_line_rendering(self):
535535
iteration=1,
536536
)
537537
)
538-
spinner = emitter._iteration_spinner
538+
spinner = emitter._active_renderable
539539
assert spinner is not None
540540
assert any("raw agent output" in line.plain for line in spinner._scroll_lines)
541541
emitter._stop_live()
@@ -563,7 +563,7 @@ def test_tool_use_scroll_line(self):
563563
iteration=1,
564564
)
565565
)
566-
panel = emitter._iteration_panel
566+
panel = emitter._active_renderable
567567
assert panel is not None
568568
assert len(panel._scroll_lines) == 1
569569
assert "Bash" in panel._scroll_lines[0].plain
@@ -586,7 +586,7 @@ def test_assistant_text_scroll_line(self):
586586
iteration=1,
587587
)
588588
)
589-
panel = emitter._iteration_panel
589+
panel = emitter._active_renderable
590590
assert panel is not None
591591
assert any("fix the bug" in line.plain for line in panel._scroll_lines)
592592
emitter._stop_live()
@@ -607,7 +607,7 @@ def test_thinking_produces_scroll_lines(self):
607607
iteration=1,
608608
)
609609
)
610-
panel = emitter._iteration_panel
610+
panel = emitter._active_renderable
611611
assert panel is not None
612612
assert len(panel._scroll_lines) == 1
613613
assert "let me think..." in panel._scroll_lines[0].plain
@@ -629,7 +629,7 @@ def test_rate_limit_scroll_line(self):
629629
iteration=1,
630630
)
631631
)
632-
panel = emitter._iteration_panel
632+
panel = emitter._active_renderable
633633
assert panel is not None
634634
assert any("rate limit" in line.plain for line in panel._scroll_lines)
635635
assert any("rate_limited" in line.plain for line in panel._scroll_lines)
@@ -659,7 +659,7 @@ def test_parse_exception_caught_and_logged_once(self):
659659

660660
# Monkeypatch the panel to raise
661661
with patch.object(
662-
emitter._iteration_panel, "apply", side_effect=ValueError("parse boom")
662+
emitter._active_renderable, "apply", side_effect=ValueError("parse boom")
663663
):
664664
emitter.emit(
665665
_make_event(
@@ -716,7 +716,7 @@ def test_peek_off_still_buffers_activity(self):
716716
iteration=1,
717717
)
718718
)
719-
panel = emitter._iteration_panel
719+
panel = emitter._active_renderable
720720
assert panel is not None
721721
# Buffer captures activity even with peek off…
722722
assert len(panel._scroll_lines) == 1
@@ -749,7 +749,7 @@ def test_tool_error_scroll_line(self):
749749
iteration=1,
750750
)
751751
)
752-
panel = emitter._iteration_panel
752+
panel = emitter._active_renderable
753753
assert panel is not None
754754
assert any("tool error" in line.plain for line in panel._scroll_lines)
755755
assert any("File not found" in line.plain for line in panel._scroll_lines)
@@ -1693,7 +1693,7 @@ def test_enter_with_structured_iteration_creates_view(self):
16931693
assert emitter.enter_fullscreen() is True
16941694
assert emitter._fullscreen_view is not None
16951695
assert emitter._fullscreen_live is not None
1696-
assert emitter._fullscreen_view._source is emitter._iteration_panel
1696+
assert emitter._fullscreen_view._source is emitter._active_renderable
16971697
assert emitter._fullscreen_view.iteration_id == 1
16981698
finally:
16991699
emitter._stop_live()
@@ -1703,7 +1703,7 @@ def test_enter_with_raw_iteration_uses_spinner(self):
17031703
try:
17041704
assert emitter.enter_fullscreen() is True
17051705
assert emitter._fullscreen_view is not None
1706-
assert emitter._fullscreen_view._source is emitter._iteration_spinner
1706+
assert emitter._fullscreen_view._source is emitter._active_renderable
17071707
finally:
17081708
emitter._stop_live()
17091709

@@ -1727,7 +1727,7 @@ def test_exit_restores_compact_live(self):
17271727
assert emitter._fullscreen_live is None
17281728
# Compact Live is back, still pointing at the same panel.
17291729
assert emitter._live is not None
1730-
assert emitter._iteration_panel is not None
1730+
assert emitter._active_renderable is not None
17311731
finally:
17321732
emitter._stop_live()
17331733

@@ -1744,7 +1744,7 @@ def test_iteration_end_keeps_fullscreen_and_archives_panel(self):
17441744
emitter, _ = self._make_emitter_with_iteration(structured=True)
17451745
try:
17461746
emitter.enter_fullscreen()
1747-
panel_before = emitter._iteration_panel
1747+
panel_before = emitter._active_renderable
17481748
assert panel_before is not None
17491749
emitter.emit(
17501750
_make_event(
@@ -1759,7 +1759,7 @@ def test_iteration_end_keeps_fullscreen_and_archives_panel(self):
17591759
assert emitter._fullscreen_view is not None
17601760
assert emitter._fullscreen_live is not None
17611761
# Active iteration cleared, but the panel lives on in history.
1762-
assert emitter._iteration_panel is None
1762+
assert emitter._active_renderable is None
17631763
assert emitter._current_iteration is None
17641764
assert emitter._iteration_history.get(1) is panel_before
17651765
# Frozen with the right outcome — surfaced in the header.
@@ -1931,7 +1931,7 @@ def test_handle_key_q_exits_fullscreen(self):
19311931
def test_handle_key_scroll_keys_move_offset(self):
19321932
emitter, _ = self._make_emitter_with_iteration(structured=True)
19331933
try:
1934-
panel = emitter._iteration_panel
1934+
panel = emitter._active_renderable
19351935
assert panel is not None
19361936
_populate_buffer(panel, 500)
19371937
emitter.enter_fullscreen()

0 commit comments

Comments
 (0)