Render database names and failures verbatim in the inspector

A database name, a location and an exception message all reach the info modal
as configuration-derived text, and Rich reads `[...]` in any of them as markup:
a name like `beta [prod]` disappeared, and a message carrying `[Errno 2]` or a
stray closing tag could break the line it sat in.
This commit is contained in:
Yiorgis Gozadinos 2026-08-28 14:02:29 +03:00
parent 7dc853606f
commit 1de1b662ae
No known key found for this signature in database
2 changed files with 54 additions and 12 deletions

View file

@ -1,6 +1,7 @@
import asyncio
from typing import TYPE_CHECKING
from rich.markup import escape
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical, VerticalScroll
@ -35,12 +36,11 @@ async def database_lines(client: "HaikuRAG") -> list[str]:
db = client.store.db
stats = await get_database_stats(db)
except Exception as e:
return [f"[red]Failed to open database: {e}[/red]"]
return [f"[red]Failed to open database: {escape(str(e))}[/red]"]
# The store read these on open; a second query would re-read and re-parse
# the same blob.
# The store read and parsed these on open.
settings = client.store.stored_settings
stored_version = str(settings.get("version", "unknown"))
stored_version = escape(str(settings.get("version", "unknown")))
embed_model_obj = settings.get("embeddings", {}).get("model", {})
embed_provider = embed_model_obj.get("provider")
embed_model = embed_model_obj.get("name")
@ -56,9 +56,9 @@ async def database_lines(client: "HaikuRAG") -> list[str]:
)
if embed_provider or embed_model or vector_dim:
provider_part = embed_provider or "unknown"
model_part = embed_model or "unknown"
dim_part = f"{vector_dim}" if vector_dim is not None else "unknown"
provider_part = escape(str(embed_provider or "unknown"))
model_part = escape(str(embed_model or "unknown"))
dim_part = escape(str(vector_dim)) if vector_dim is not None else "unknown"
lines.append(
f"[bold $accent]embeddings[/bold $accent]: "
f"{provider_part}/{model_part} (dim: {dim_part})"
@ -172,11 +172,15 @@ class InfoModal(ModalScreen):
for block in blocks:
lines.extend(block)
else:
lines.append(f"[bold $accent]location[/bold $accent]: {location}")
lines.append(
f"[bold $accent]location[/bold $accent]: {escape(str(location))}"
)
lines.extend(await database_lines(self.client))
lines.append("[bold]Versions[/bold]")
versions = get_package_versions()
versions = {
key: escape(str(value)) for key, value in get_package_versions().items()
}
lines.append(f"[bold $accent]haiku.rag[/bold $accent]: {versions['haiku_rag']}")
lines.append(f"[bold $accent]lancedb[/bold $accent]: {versions['lancedb']}")
lines.append(f"[bold $accent]docling[/bold $accent]: {versions['docling']}")
@ -192,13 +196,13 @@ class InfoModal(ModalScreen):
async def _report(self, name: str) -> list[str]:
"""One database's block, including its own failure to open."""
lines = [f"[bold]{name}[/bold]"]
lines = [f"[bold]{escape(name)}[/bold]"]
try:
(owner,) = await self.client.clients_for([name])
except Exception as e:
# The client names a configured database by name and never by
# location, so its message is safe to show.
return [*lines, f"[red]{e}[/red]", ""]
return [*lines, f"[red]{escape(str(e))}[/red]", ""]
return [*lines, *await database_lines(owner)]
async def action_dismiss(self, result=None) -> None:

View file

@ -446,9 +446,47 @@ class TestReportingEachDatabase:
"database 'beta' could not be opened: OSError"
)
modal.client = client
modal.db_path = None
lines = await modal._report("beta")
assert lines[0] == "[bold]beta[/bold]"
assert "could not be opened" in lines[1]
@pytest.mark.asyncio
async def test_a_failure_with_bracketed_text_renders_literally(self):
"""Error text and database names render verbatim, not as Rich markup."""
from rich.text import Text
from haiku.rag.inspector.widgets.info_modal import InfoModal
from haiku.rag.store.exceptions import SourceUnavailableError
modal = InfoModal.__new__(InfoModal)
client = AsyncMock()
message = "database 'beta [prod]' could not be opened: [/red] [Errno 2]"
client.clients_for.side_effect = SourceUnavailableError(message)
modal.client = client
lines = await modal._report("beta [prod]")
assert Text.from_markup(lines[0]).plain == "beta [prod]"
assert message in Text.from_markup(lines[1]).plain
@pytest.mark.asyncio
async def test_an_open_failure_with_bracketed_text_renders_literally(
self, tmp_path
):
"""A stats-read failure renders its message verbatim, not as markup."""
from rich.text import Text
from haiku.rag.inspector.widgets.info_modal import database_lines
from haiku.rag.store.engine import ConnectionMode
client = MagicMock()
client.store.db_path = tmp_path
client.store._connection_mode = ConnectionMode.LOCAL
message = "Schema error: no field named [vector, text] [/red]"
type(client.store).db = property(MagicMock(side_effect=RuntimeError(message)))
lines = await database_lines(client)
assert message in Text.from_markup(lines[0]).plain