fix(core): use updated_at for recent_activity filter and ordering (#812)

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: phernandez <paul@basicmachines.co>
This commit is contained in:
Drew Cain
2026-05-11 09:14:06 -05:00
committed by GitHub
parent df5e8d805f
commit 9862ef5411
3 changed files with 76 additions and 9 deletions
@@ -774,7 +774,8 @@ class PostgresSearchRepository(SearchRepositoryBase):
# Handle date filter
if after_date:
params["after_date"] = after_date
conditions.append("search_index.created_at > :after_date")
# Filter on updated_at so recently-edited notes are included even when created_at is old
conditions.append("search_index.updated_at > :after_date")
# order by most recent first
order_by_clause = ", search_index.updated_at DESC"
@@ -945,7 +946,7 @@ class PostgresSearchRepository(SearchRepositoryBase):
{score_expr} as score
FROM {from_clause}
WHERE {where_clause}
ORDER BY score DESC, search_index.id ASC {order_by_clause}
ORDER BY score DESC {order_by_clause}, search_index.id ASC
LIMIT :limit
OFFSET :offset
"""
@@ -789,7 +789,8 @@ class SQLiteSearchRepository(SearchRepositoryBase):
# Handle date filter using datetime() for proper comparison
if after_date:
params["after_date"] = after_date
conditions.append("datetime(search_index.created_at) > datetime(:after_date)")
# Filter on updated_at so recently-edited notes are included even when created_at is old
conditions.append("datetime(search_index.updated_at) > datetime(:after_date)")
# order by most recent first
order_by_clause = ", search_index.updated_at DESC"
+71 -6
View File
@@ -1,6 +1,6 @@
"""Tests for search service."""
from datetime import datetime
from datetime import datetime, timezone
import pytest
from sqlalchemy import text
@@ -174,12 +174,12 @@ async def test_after_date(search_service, test_graph):
)
for r in results:
# Handle both string (SQLite) and datetime (Postgres) formats
created_at = (
r.created_at
if isinstance(r.created_at, datetime)
else datetime.fromisoformat(r.created_at)
updated_at = (
r.updated_at
if isinstance(r.updated_at, datetime)
else datetime.fromisoformat(r.updated_at)
)
assert created_at > past_date
assert updated_at > past_date
# Should not find with future date
future_date = datetime(2030, 1, 1).astimezone()
@@ -192,6 +192,71 @@ async def test_after_date(search_service, test_graph):
assert len(results) == 0
@pytest.mark.asyncio
async def test_after_date_uses_updated_at(search_service):
"""Regression: after_date should filter on updated_at, not created_at.
An entity created before the timeframe but updated within it must appear
in recent-activity results. A stale entity (updated_at also old) must not.
"""
cutoff = datetime(2020, 1, 1, tzinfo=timezone.utc)
old_created = datetime(2015, 6, 1, tzinfo=timezone.utc)
recently_updated = datetime(2023, 3, 15, tzinfo=timezone.utc)
stale_updated = datetime(2018, 6, 1, tzinfo=timezone.utc)
project_id = search_service.repository.project_id
# Leave metadata at its None default — SearchIndexRow.to_insert only
# JSON-serializes truthy metadata, so passing {} would slip an
# un-serialized dict into the SQLite bind and raise ProgrammingError.
recently_updated_row = SearchIndexRow(
project_id=project_id,
id=99001,
type="entity",
file_path="test/recently_updated.md",
title="Recently Updated Entity",
content_snippet="recently updated content",
permalink="test/recently-updated-entity",
created_at=old_created,
updated_at=recently_updated,
)
stale_row = SearchIndexRow(
project_id=project_id,
id=99002,
type="entity",
file_path="test/stale.md",
title="Stale Entity",
content_snippet="stale content",
permalink="test/stale-entity",
created_at=old_created,
updated_at=stale_updated,
)
await search_service.repository.index_item(recently_updated_row)
await search_service.repository.index_item(stale_row)
results = await search_service.search(
SearchQuery(after_date=cutoff.isoformat())
)
permalinks = {r.permalink for r in results}
# recently-updated entity must appear despite old created_at
assert "test/recently-updated-entity" in permalinks
# stale entity must not appear (updated_at is before cutoff)
assert "test/stale-entity" not in permalinks
# results should be ordered newest updated_at first
updated_ats = []
for r in results:
ua = (
r.updated_at
if isinstance(r.updated_at, datetime)
else datetime.fromisoformat(r.updated_at)
)
updated_ats.append(ua.replace(tzinfo=timezone.utc) if ua.tzinfo is None else ua)
assert updated_ats == sorted(updated_ats, reverse=True)
@pytest.mark.asyncio
async def test_search_type(search_service, test_graph):
"""Test search filters."""