diff --git a/src/copilot_usage/render_detail.py b/src/copilot_usage/render_detail.py index 2650b22..5e3434c 100644 --- a/src/copilot_usage/render_detail.py +++ b/src/copilot_usage/render_detail.py @@ -261,10 +261,16 @@ def _render_active_period( *, target_console: Console | None = None, ) -> None: - """Show model calls / messages / tokens since last shutdown (if active).""" + """Show model calls / messages / tokens since last shutdown (if active). + + The panel is only rendered when *summary* has prior shutdown data + (``has_shutdown_metrics=True``). For pure-active sessions the + numbers would duplicate the aggregate stats panel, and the "since + last shutdown" label would be factually wrong. + """ out = target_console or Console() - if not summary.is_active: + if not summary.is_active or not summary.has_shutdown_metrics: return content = ( diff --git a/src/copilot_usage/report.py b/src/copilot_usage/report.py index 809e214..be56df9 100644 --- a/src/copilot_usage/report.py +++ b/src/copilot_usage/report.py @@ -545,6 +545,9 @@ def _render_active_section_from( *active* must already contain only sessions where ``is_active`` is ``True``. No filtering is performed here. + + The table title includes "Since Last Shutdown" only when at least one + session has prior shutdown data (``has_shutdown_metrics=True``). """ if not active: console.print( @@ -554,9 +557,13 @@ def _render_active_section_from( ) return - table = Table( - title="🟢 Active Sessions (Since Last Shutdown)", border_style="green" + has_resumed = any(s.has_shutdown_metrics for s in active) + title = ( + "🟢 Active Sessions (Since Last Shutdown)" + if has_resumed + else "🟢 Active Sessions" ) + table = Table(title=title, border_style="green") table.add_column("Name", style="bold", max_width=40) table.add_column("Model") table.add_column("Model Calls", justify="right") diff --git a/tests/copilot_usage/test_render_detail.py b/tests/copilot_usage/test_render_detail.py index 8434d0f..57389fa 100644 --- a/tests/copilot_usage/test_render_detail.py +++ b/tests/copilot_usage/test_render_detail.py @@ -798,10 +798,11 @@ class TestRenderActivePeriod: """Direct unit tests for _render_active_period covering active / inactive.""" def test_active_session_renders_panel(self) -> None: - """Active session must render an 'Active Period' panel with stats.""" + """Active session with shutdown history must render an 'Active Period' panel.""" summary = SessionSummary( session_id="active-test", is_active=True, + has_shutdown_metrics=True, model_calls=3, user_messages=2, active_model_calls=3, @@ -822,18 +823,59 @@ def test_inactive_session_produces_no_output(self) -> None: _render_active_period(summary, target_console=console) assert buf.getvalue() == "" + def test_pure_active_session_no_active_period_panel(self) -> None: + """Pure-active session (no shutdown history) must not render the panel. + + When ``has_shutdown_metrics`` is ``False`` the active-period numbers + duplicate the aggregate stats and the "since last shutdown" label is + factually wrong (issue #1070). + """ + summary = SessionSummary( + session_id="pure-active", + is_active=True, + has_shutdown_metrics=False, + model_calls=4, + user_messages=2, + active_model_calls=4, + active_user_messages=2, + active_output_tokens=800, + ) + buf, console = _buf_console() + _render_active_period(summary, target_console=console) + assert buf.getvalue() == "" + + def test_resumed_session_shows_active_period_panel(self) -> None: + """Resumed session (has_shutdown_metrics=True) must render the panel.""" + summary = SessionSummary( + session_id="resumed", + is_active=True, + has_shutdown_metrics=True, + model_calls=10, + user_messages=5, + active_model_calls=3, + active_user_messages=1, + active_output_tokens=500, + ) + buf, console = _buf_console() + _render_active_period(summary, target_console=console) + output = _strip_ansi(buf.getvalue()) + assert "Active Period" in output + assert "3 model calls" in output + assert "1 user messages" in output + class TestRenderSessionDetailActivePeriod: """Integration test: render_session_detail with is_active=True must render the Active Period panel (issue #879).""" def test_active_session_shows_active_period_panel(self) -> None: - """render_session_detail with is_active=True must include the - Active Period panel in its output.""" + """render_session_detail with is_active=True and has_shutdown_metrics=True + must include the Active Period panel in its output.""" summary = SessionSummary( session_id="active-e2e", start_time=datetime(2026, 4, 1, 10, 0, 0, tzinfo=UTC), is_active=True, + has_shutdown_metrics=True, model_calls=5, user_messages=3, active_model_calls=5, diff --git a/tests/copilot_usage/test_report.py b/tests/copilot_usage/test_report.py index b5ecb7d..83e9c6d 100644 --- a/tests/copilot_usage/test_report.py +++ b/tests/copilot_usage/test_report.py @@ -6405,3 +6405,58 @@ def spy(session: SessionSummary) -> int: "total_output_tokens should not be called for resumed sessions " "with model_metrics" ) + + +# --------------------------------------------------------------------------- +# Issue #1070 — active section title reflects shutdown history +# --------------------------------------------------------------------------- + + +class TestActiveSectionTitle: + """The active-section table title must say 'Since Last Shutdown' only + when at least one active session has prior shutdown data.""" + + def test_pure_active_session_active_section_title(self) -> None: + """Pure-active sessions → title must NOT contain 'Since Last Shutdown'.""" + session = SessionSummary( + session_id="pure-active-title", + name="PureActive", + model="gpt-5-mini", + start_time=datetime(2026, 4, 1, 10, 0, tzinfo=UTC), + is_active=True, + has_shutdown_metrics=False, + model_calls=5, + user_messages=3, + active_model_calls=5, + active_user_messages=3, + active_output_tokens=1000, + ) + output = _capture_full_summary([session]) + assert "Active Sessions" in output + assert "Since Last Shutdown" not in output + + def test_resumed_session_active_section_title(self) -> None: + """Resumed session → title must contain 'Since Last Shutdown'.""" + session = SessionSummary( + session_id="resumed-title", + name="Resumed", + model="gpt-5-mini", + start_time=datetime(2026, 4, 1, 10, 0, tzinfo=UTC), + is_active=True, + has_shutdown_metrics=True, + total_premium_requests=5, + total_api_duration_ms=100, + model_calls=10, + user_messages=5, + active_model_calls=3, + active_user_messages=1, + active_output_tokens=500, + model_metrics={ + "gpt-5-mini": ModelMetrics( + requests=RequestMetrics(count=7, cost=0), + usage=TokenUsage(outputTokens=2000), + ) + }, + ) + output = _capture_full_summary([session]) + assert "Since Last Shutdown" in output