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:
@@ -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
|
||||
# ``<mark>`` 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, '<mark>', '</mark>', '…', 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
|
||||
]
|
||||
|
||||
@@ -21,7 +21,14 @@
|
||||
<ul class="search-results">
|
||||
{% for r in results %}
|
||||
<li class="search-result">
|
||||
<a class="search-result-link" href="/chats/{{ r.chat_id }}">
|
||||
{# T111.2: deep-link to the originating turn via the
|
||||
``id="turn-{event_id}"`` anchor stamped by Phase 3.5 T86.
|
||||
``event_id`` may be NULL for memory rows projected before the
|
||||
0014 migration ran (T109 did not backfill historical rows); in
|
||||
that case fall back to a chat-level link with no anchor so we
|
||||
never emit ``#turn-None``. #}
|
||||
<a class="search-result-link"
|
||||
href="/chats/{{ r.chat_id }}{% if r.event_id %}#turn-{{ r.event_id }}{% endif %}">
|
||||
<div class="search-result-meta muted">
|
||||
<strong>{{ r.owner_name }}</strong>
|
||||
<span>· {{ r.chat_id }}</span>
|
||||
|
||||
@@ -193,6 +193,13 @@ async def search(request: Request, q: str = "", conn=Depends(get_conn)):
|
||||
chat.get("narrative_anchor") if chat else None
|
||||
),
|
||||
"scene_id": row["scene_id"],
|
||||
# T111.2: event_id deep-links to the originating turn
|
||||
# via the ``id="turn-{event_id}"`` anchor that Phase 3.5
|
||||
# T86 stamps on each turn DOM node. May be ``None`` for
|
||||
# memory rows projected before the 0014 migration ran
|
||||
# (T109 did not backfill historical rows); the template
|
||||
# falls back to a chat-level link in that case.
|
||||
"event_id": row["event_id"],
|
||||
# Scenes have no ``title`` column today; surface the
|
||||
# ``started_at`` timestamp as a human-friendly label
|
||||
# when a scene is set, otherwise leave it blank.
|
||||
|
||||
Reference in New Issue
Block a user