Skip to content

v2.15.0

Choose a tag to compare

@github-actions github-actions released this 26 Jun 23:17
· 6 commits to main since this release
0251e82

v2.15.0 — 2026-06-26

Added — keyword document search + click-to-read content overlay

The dashboard gains a keyword search box: type a term and see the matching
documents (FTS5-ranked over the federated index, with highlighted snippets and a
LIKE fallback). Click a result — or click a node in the 3D graph — to read
that document's full content in a shared overlay.

build_search(q) searches documents_fts with a prefix-tokenized, bound MATCH
(no FTS5 query-syntax injection) and an escaped LIKE fallback. build_doc(id)
returns a document's full record plus best-effort content fetched from its
source store, joined by index_id (the universal join key; falls back to
id=row_id, then to the indexed searchable text), capped at 500k characters
with a content_truncated flag. Two new read-only routes back these:
GET /api/search?q= and GET /api/doc?id=. The shared _send_json helper now
returns a generic {"error":"internal error"} and logs detail to stderr only, so
a --allow-non-local bind can't echo internal paths to the network.

The content overlay (CSS/markup/JS) is injected into both the dashboard and
the 3D graph. openDoc(id) renders title/meta/body via textContent only — an
ingested document title or body containing markup can never become script — with
focus management, Tab/Escape handling, and a truncation note. The dashboard adds
a debounced search box + results list (rendered via el()); the 3D graph opens
the same overlay on node click. Deep links: ?q=<keyword> prefills and runs the
search, ?doc=<index_id> opens a document. Read-only throughout — the search and
viewer write nothing, so they are not a Librarian/M3 write-path bypass.

21 new/updated tests in tests/test_dashboard.py cover search matching,
wildcard-escaping and the forced LIKE fallback, injection-safety, content
fetch via index_id-join / id-join fallback / multi-column assembly /
searchable fallback / no-content / the size cap, the two routes, and a
regression guard that the overlay markup precedes the inline script on both pages.