From 20fb56424661da5f62c507e0afa475ca7bd6b21d Mon Sep 17 00:00:00 2001
From: mwiegand
Date: Sun, 17 May 2026 21:07:15 +0200
Subject: [PATCH] feat(live-state): compact 4-col current + 5-col recent chips
+ N Recent trigger
Co-Authored-By: Claude Sonnet 4.6
---
l4d2web/l4d2web/templates/_live_state.html | 44 ++++++++++++--------
l4d2web/tests/test_status_and_server_logs.py | 31 +++++++++++++-
2 files changed, 56 insertions(+), 19 deletions(-)
diff --git a/l4d2web/l4d2web/templates/_live_state.html b/l4d2web/l4d2web/templates/_live_state.html
index cc302ab..6695226 100644
--- a/l4d2web/l4d2web/templates/_live_state.html
+++ b/l4d2web/l4d2web/templates/_live_state.html
@@ -1,4 +1,9 @@
-Live state
+{# Live-state partial — HTMX-polled into the state cluster on server_detail.html.
+ The parent .state-cluster section provides the heading context, so there is
+ no here. Current players have no sub-header; they sit directly under
+ the summary line. Recent players' header is "N Recent" and doubles as the
+ modal trigger when N > 10. #}
+
{% if not snapshot or not snapshot_fresh %}
No data — server is not currently reporting.
{% else %}
@@ -6,17 +11,14 @@
{{ snapshot.players }}/{{ snapshot.max_players }}
{% if snapshot.hibernating %}· idle{% endif %}
· {{ snapshot.map }}
-
- polled {{ snapshot.last_seen_at | timeago }}
-
+ polled {{ snapshot.last_seen_at | timeago }}
{% endif %}
{% if current_players %}
- Current players
-
+
{% endif %}
-{% if recent_players %}
- Recent players
+{% if recent_players_overview %}
+
diff --git a/l4d2web/tests/test_status_and_server_logs.py b/l4d2web/tests/test_status_and_server_logs.py
index 3201cbc..f7c2d2b 100644
--- a/l4d2web/tests/test_status_and_server_logs.py
+++ b/l4d2web/tests/test_status_and_server_logs.py
@@ -73,7 +73,6 @@ def test_status_precedence() -> None:
assert compute_display_state("start", "stopped") == "starting"
-@pytest.mark.xfail(reason="template change in Task 3 will satisfy this", strict=True)
def test_live_state_exposes_recent_overview_and_total_count(owner_client_with_server) -> None:
"""Route must expose recent_players_overview (≤10) and recent_players_total_count.
@@ -107,3 +106,33 @@ def test_live_state_exposes_recent_overview_and_total_count(owner_client_with_se
# These assertions depend on the Task 3 template changes.
assert "13 Recent" in html
assert html.count("recent-chip") <= 10
+
+
+def test_live_state_recent_header_not_clickable_when_le_10(owner_client_with_server) -> None:
+ """When recent player count is ≤ 10, the header must be plain text, not a modal trigger."""
+ from datetime import timedelta
+ from l4d2web.models import ServerPlayerSession
+
+ client, server_id = owner_client_with_server
+
+ now = datetime.now(UTC)
+ total = 4
+
+ with session_scope() as db:
+ for i in range(total):
+ db.add(ServerPlayerSession(
+ server_id=server_id,
+ steam_id_64=str(76561190000000000 + i),
+ joined_at=now - timedelta(hours=i + 2),
+ left_at=now - timedelta(hours=i + 1),
+ name_at_join=f"Player{i}",
+ min_ping=10,
+ max_ping=50,
+ ))
+
+ res = client.get(f"/servers/{server_id}/live-state")
+ assert res.status_code == 200
+ html = res.get_data(as_text=True)
+
+ assert "4 Recent" in html
+ assert 'data-inline-modal-open="recent-players-modal"' not in html