From bd8c1a6d15ad738cfbfa1116c958e8fa39091427 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Fri, 28 Aug 2026 08:41:48 +0300 Subject: [PATCH] Render a citation whose pictures no database claims Chat asked the covering client for a picture with the citation's source, which raises when there is none, losing the answer to one figure. It resolves the reader first and omits the bytes when the database cannot be placed, as the rich formatter already did. --- haiku_rag_slim/haiku/rag/chat/app.py | 10 ++++-- tests/chat/test_chat_app.py | 46 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/haiku_rag_slim/haiku/rag/chat/app.py b/haiku_rag_slim/haiku/rag/chat/app.py index 149d3f14..a9b8a1fb 100644 --- a/haiku_rag_slim/haiku/rag/chat/app.py +++ b/haiku_rag_slim/haiku/rag/chat/app.py @@ -300,11 +300,15 @@ class ChatApp(App): refs = list(citation.picture_refs or []) if not refs: continue + # Evidence recorded before databases could be named carries no + # source, and across databases nothing places its pictures. The + # citation still renders, with its figure markers. + owner = await self.client.reader_for(citation.source) + if owner is None: + continue blobs: list[bytes] = [] for ref in refs: - data = await self.client.get_picture_bytes( - citation.document_id, ref, citation.source - ) + data = await owner.get_picture_bytes(citation.document_id, ref) if data: blobs.append(data) if blobs: diff --git a/tests/chat/test_chat_app.py b/tests/chat/test_chat_app.py index 28b82427..06a90bd8 100644 --- a/tests/chat/test_chat_app.py +++ b/tests/chat/test_chat_app.py @@ -687,6 +687,52 @@ class TestDocumentSelectionIdentity: assert modal._selected == {"id-one"} +class TestRenderingUnattributedPictures: + @pytest.mark.asyncio + async def test_a_sourceless_picture_citation_does_not_fail_the_answer( + self, temp_db_path: Path + ): + """Evidence recorded before databases could be named carries no source, + and across databases nothing places its pictures. The citation still + renders, with its figure markers.""" + from haiku.rag.chat.app import RAG_STATE_NAMESPACE + from haiku.rag.store.models.citation import Citation + + covering = _make_mock_client() + covering.covers_multiple = True + covering.source_names = ("alpha", "beta") + covering.reader_for = AsyncMock(return_value=None) + covering.get_picture_bytes = AsyncMock( + side_effect=AssertionError("asked a set for a picture it cannot place") + ) + + citation = Citation( + document_id="d1", + chunk_id="c1", + content="body", + document_uri="test://doc", + picture_refs=["#/pictures/0"], + ) + + app, _ = _make_app(temp_db_path, covering) + with ( + patch("haiku.rag.chat.app.HaikuRAG") as stub, + _covering_returns(stub, covering), + ): + async with app.run_test() as pilot: + app._state[RAG_STATE_NAMESPACE] = { + "citations": ["c1"], + "citation_index": {"c1": citation.model_dump(mode="json")}, + } + from haiku.rag.chat.widgets.chat_history import ChatHistory + + await app._show_citations_and_programs(app.query_one(ChatHistory)) + await pilot.pause() + + covering.reader_for.assert_awaited_once_with(None) + covering.get_picture_bytes.assert_not_awaited() + + class TestKeepingSelectionsReachable: """A selection applies whether or not the page shows it, and a checkbox is the only way to remove one."""