From 9987da2c0747a8a7761a6c38353e1659361d85d6 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 27 Apr 2026 05:42:17 -0400 Subject: [PATCH] feat: cross-chat search deep-links to turn via memories.event_id (T111.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- chat/services/cross_chat_search.py | 26 ++++++++++++++++++-------- chat/templates/search.html | 9 ++++++++- chat/web/search.py | 7 +++++++ tests/test_phase4_integration.py | 14 ++++++++------ tests/test_search_ux.py | 30 ++++++++++++++++++++++++++---- 5 files changed, 67 insertions(+), 19 deletions(-) diff --git a/chat/services/cross_chat_search.py b/chat/services/cross_chat_search.py index 2e10f71..d582610 100644 --- a/chat/services/cross_chat_search.py +++ b/chat/services/cross_chat_search.py @@ -26,8 +26,17 @@ def search_all_memories( """Search FTS5 across all owners and chats. Returns rows with ``{memory_id, owner_id, chat_id, scene_id, - pov_summary, snippet, significance, ts, fts_rank}``, sorted by FTS5 - BM25 rank ascending (lower rank = stronger match, surfaced first). + event_id, pov_summary, snippet, significance, ts, fts_rank}``, + sorted by FTS5 BM25 rank ascending (lower rank = stronger match, + surfaced first). + + ``event_id`` (T111.2 / T109) is the id of the ``event_log`` row that + drove the projecting ``memory_written`` event. May be ``None`` for + memory rows projected before the 0014 schema migration ran (the + column is nullable on purpose; T109 did not backfill historical + rows). The search-results UI uses it to deep-link to the originating + turn anchor (Phase 3.5 T86 stamps ``id="turn-{event_id}"`` on each + turn DOM node) and falls back to a chat-level link when ``None``. The ``memories`` table has no ``ts`` column; we expose ``created_at`` (the projector-side row insertion timestamp) under that key so the @@ -60,7 +69,7 @@ def search_all_memories( # before/after match markers, so the only HTML in the output is the # ```` we injected — safe to render with ``|safe`` server-side. rows = conn.execute( - "SELECT m.id, m.owner_id, m.chat_id, m.scene_id, " + "SELECT m.id, m.owner_id, m.chat_id, m.scene_id, m.event_id, " " m.pov_summary, " " snippet(memories_fts, 0, '', '', '…', 32) " " AS snippet, " @@ -80,11 +89,12 @@ def search_all_memories( "owner_id": r[1], "chat_id": r[2], "scene_id": r[3], - "pov_summary": r[4], - "snippet": r[5], - "significance": r[6], - "ts": r[7], - "fts_rank": r[8], + "event_id": r[4], + "pov_summary": r[5], + "snippet": r[6], + "significance": r[7], + "ts": r[8], + "fts_rank": r[9], } for r in rows ] diff --git a/chat/templates/search.html b/chat/templates/search.html index 527ee86..ce0e8c7 100644 --- a/chat/templates/search.html +++ b/chat/templates/search.html @@ -21,7 +21,14 @@