feat: cross-chat search deep-links to turn via memories.event_id (T111.2)

Add ``m.event_id`` (T109's nullable column from migration 0014) to
``search_all_memories``'s SELECT, propagate it through the route's
template context, and have ``search.html`` build result links as
``/chats/{chat_id}#turn-{event_id}`` — matching the ``id="turn-{event_id}"``
anchor that Phase 3.5 T86 stamps on each turn DOM node so the chat page
scrolls to the originating turn on load. Memory rows projected before
the 0014 migration ran read NULL ``event_id``; the template falls back
to a chat-level link in that case so we never emit ``#turn-None``.

Pre-existing tests that asserted on the bare ``href="/chats/{chat_id}"``
contract are updated to assert on the ``href="/chats/{chat_id}#turn-``
prefix to reflect the new deep-link.
This commit is contained in:
Joseph Doherty
2026-04-27 05:42:17 -04:00
parent fa87ab8c55
commit 9987da2c07
5 changed files with 67 additions and 19 deletions
+26 -4
View File
@@ -127,13 +127,19 @@ def test_empty_query_renders_placeholder_not_results(client, tmp_path):
def test_result_links_navigate_to_chat(client, tmp_path):
"""Each result links back to its originating chat so the user can
reopen the thread where the memory was first witnessed."""
reopen the thread where the memory was first witnessed.
Post-T111.2: the link now includes a turn anchor when the memory
row carries an ``event_id`` (T109's nullable column is populated for
rows projected after migration 0014 ran). We assert on the chat-id
portion of the href because the exact event id is autoincrement and
depends on seed order; the dedicated
``test_search_result_link_includes_turn_anchor`` test below pins the
anchor format itself."""
_seed_two_chats_with_memories(tmp_path / "test.db")
resp = client.get("/search?q=rabbit")
assert resp.status_code == 200
# The link target is chat-level (memories don't carry an event_id
# column today, so we don't deep-link to a specific turn).
assert 'href="/chats/chat_a"' in resp.text
assert 'href="/chats/chat_a' in resp.text
def test_search_results_include_fts_snippet_with_highlight(client, tmp_path):
@@ -152,6 +158,22 @@ def test_search_results_include_fts_snippet_with_highlight(client, tmp_path):
assert "<mark>rabbit</mark>" in resp.text
def test_search_result_link_includes_turn_anchor(client, tmp_path):
"""T111.2: result links deep-link to the originating turn via the
chat-page anchor stamped by Phase 3.5 T86 (``id="turn-{event_id}"``).
The seeded ``memory_written`` events are projected with
``memories.event_id`` populated (T109); the route exposes that id and
the template builds the link as ``/chats/{chat_id}#turn-{event_id}``.
We don't assert a specific event id (it's an autoincrement that
depends on seed order), only that *some* turn anchor is present for
the chat link the user is about to click."""
_seed_two_chats_with_memories(tmp_path / "test.db")
resp = client.get("/search?q=rabbit")
assert resp.status_code == 200
assert "/chats/chat_a#turn-" in resp.text
def test_search_results_use_batched_lookups(client, tmp_path):
"""T106: hydration must not fan out to per-row ``get_bot``/
``get_chat``/``get_scene`` calls.