feat: persistent chat history with clear button

- Add dashboard/store.py: MessageLog dataclass singleton tracking
  user/agent/error messages for the lifetime of the server process
- agents.py: write each chat turn to MessageLog; add GET and DELETE
  /agents/timmy/history routes returning the history.html partial
- partials/history.html: render stored messages by role (YOU / TIMMY /
  SYSTEM); falls back to the Mission Control init message when empty
- index.html: chat-log loads history via hx-get on page start; new
  CLEAR button in panel header sends hx-delete to reset the log
- style.css: add .mc-btn-clear (muted, red-on-hover for the header)
- tests: autouse reset_message_log fixture in conftest; 5 new history
  tests covering empty state, recording, offline errors, clear, and
  post-clear state → 32 tests total, all passing

https://claude.ai/code/session_01KZMfwBpLuiv6x9GbzTqbys
This commit is contained in:
Claude
2026-02-20 14:00:16 +00:00
parent c9ac2d9d17
commit 0d14be291a
7 changed files with 173 additions and 7 deletions

View File

@@ -18,6 +18,15 @@ for _mod in [
sys.modules.setdefault(_mod, MagicMock())
@pytest.fixture(autouse=True)
def reset_message_log():
"""Clear the in-memory chat log before and after every test."""
from dashboard.store import message_log
message_log.clear()
yield
message_log.clear()
@pytest.fixture
def client():
from dashboard.app import app

View File

@@ -108,3 +108,57 @@ def test_chat_timmy_ollama_offline(client):
def test_chat_timmy_requires_message(client):
response = client.post("/agents/timmy/chat", data={})
assert response.status_code == 422
# ── History ────────────────────────────────────────────────────────────────────
def test_history_empty_shows_init_message(client):
response = client.get("/agents/timmy/history")
assert response.status_code == 200
assert "Mission Control initialized" in response.text
def test_history_records_user_and_agent_messages(client):
mock_agent = MagicMock()
mock_agent.run.return_value = MagicMock(content="I am operational.")
with patch("dashboard.routes.agents.create_timmy", return_value=mock_agent):
client.post("/agents/timmy/chat", data={"message": "status check"})
response = client.get("/agents/timmy/history")
assert "status check" in response.text
assert "I am operational." in response.text
def test_history_records_error_when_offline(client):
with patch("dashboard.routes.agents.create_timmy", side_effect=Exception("refused")):
client.post("/agents/timmy/chat", data={"message": "ping"})
response = client.get("/agents/timmy/history")
assert "ping" in response.text
assert "Timmy is offline" in response.text
def test_history_clear_resets_to_init_message(client):
mock_agent = MagicMock()
mock_agent.run.return_value = MagicMock(content="Acknowledged.")
with patch("dashboard.routes.agents.create_timmy", return_value=mock_agent):
client.post("/agents/timmy/chat", data={"message": "hello"})
response = client.delete("/agents/timmy/history")
assert response.status_code == 200
assert "Mission Control initialized" in response.text
def test_history_empty_after_clear(client):
mock_agent = MagicMock()
mock_agent.run.return_value = MagicMock(content="OK.")
with patch("dashboard.routes.agents.create_timmy", return_value=mock_agent):
client.post("/agents/timmy/chat", data={"message": "test"})
client.delete("/agents/timmy/history")
response = client.get("/agents/timmy/history")
assert "test" not in response.text
assert "Mission Control initialized" in response.text