From cb9360ef7b5e302b82614e9a4afadcf7e680730e Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Fri, 24 Apr 2026 13:48:18 +0300 Subject: [PATCH] fix chat TUI citation rendering after state flattening --- CHANGELOG.md | 4 ++++ haiku_rag_slim/haiku/rag/chat/app.py | 10 ++++---- tests/chat/test_chat_app.py | 35 ++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a972d3b4..bba86814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Fixed + +- **Chat TUI now renders citations again.** After the 0.42.1 flattening of skill state `citations` to `list[str]`, the TUI still indexed `citations[-1]` and iterated the resulting chunk-id string character-by-character, so no citations resolved through `citation_index` and the citation panel stayed empty. Fixed by iterating `state.citations` directly. + ## [0.42.1] - 2026-04-22 ### Changed diff --git a/haiku_rag_slim/haiku/rag/chat/app.py b/haiku_rag_slim/haiku/rag/chat/app.py index b0f1a1ee..a40f7e03 100644 --- a/haiku_rag_slim/haiku/rag/chat/app.py +++ b/haiku_rag_slim/haiku/rag/chat/app.py @@ -328,13 +328,11 @@ class ChatApp(App): state = self._toolset.get_namespace(namespace) if not state: continue - citation_turns = getattr(state, "citations", []) + cited_ids = getattr(state, "citations", []) citation_index = getattr(state, "citation_index", {}) - if citation_turns: - latest_ids = citation_turns[-1] - for cid in latest_ids: - if cid in citation_index: - citations.append(citation_index[cid]) + for cid in cited_ids: + if cid in citation_index: + citations.append(citation_index[cid]) if citations: await chat_history.add_citations(citations) diff --git a/tests/chat/test_chat_app.py b/tests/chat/test_chat_app.py index 84324bb1..9fcf80cd 100644 --- a/tests/chat/test_chat_app.py +++ b/tests/chat/test_chat_app.py @@ -276,6 +276,41 @@ async def test_citation_expand_collapse_with_enter(temp_db_path: Path): assert citation_widget.collapsed is True +@pytest.mark.asyncio +async def test_show_citations_renders_from_flat_state(temp_db_path: Path): + """Citations in state (flat list[str]) render into the chat history.""" + from haiku.rag.agents.research.models import Citation + from haiku.rag.chat.app import RAG_STATE_NAMESPACE + from haiku.rag.chat.widgets.chat_history import ChatHistory, CitationWidget + + app, mock_client = _make_app_with_state(temp_db_path) + + with patch("haiku.rag.chat.app.HaikuRAG", return_value=mock_client): + async with app.run_test() as pilot: + rag_state = app._toolset.get_namespace(RAG_STATE_NAMESPACE) + assert isinstance(rag_state, RAGState) + + citation = Citation( + index=1, + document_id="doc1", + chunk_id="chunk1", + document_uri="file:///test/doc1.pdf", + document_title="Test Document", + page_numbers=[1], + content="Cited content", + ) + rag_state.citation_index["chunk1"] = citation + rag_state.citations.append("chunk1") + + chat_history = app.query_one(ChatHistory) + await app._show_citations_and_programs(chat_history) + await pilot.pause() + + widgets = list(chat_history.query(CitationWidget)) + assert len(widgets) == 1 + assert widgets[0].citation.chunk_id == "chunk1" + + @pytest.mark.asyncio async def test_document_filter_updates_rag_state(temp_db_path: Path): """Test that selecting document filters updates RAGState.document_filter."""