fix(search): apply the date range to server results client-side

from_ts/to_ts are not Matrix filter fields; the server dropped them, so
the range only worked for the local encrypted-room search. Stop sending
them and post-filter server results by origin_server_ts with the same
inclusive predicate. Unit-tested; docs corrected.

Fixes #13

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-12 14:48:35 -04:00
co-authored by Claude Opus 5
parent 6bd2903de1
commit 02592ed43c
4 changed files with 87 additions and 12 deletions
@@ -71,6 +71,31 @@ export const filterGroupsByPinned = (
.filter((group) => group.items.length > 0);
};
/** Inclusive-range predicate, mirrored from `inRange` in useLocalMessageSearch.ts. */
export const inTsRange = (ts: number, fromTs?: number, toTs?: number): boolean =>
(fromTs === undefined || ts >= fromTs) && (toTs === undefined || ts <= toTs);
/**
* Filter result groups to items whose `origin_server_ts` falls within
* [fromTs, toTs] (inclusive, either bound optional). The Matrix search API
* has no timestamp filter fields, so server results must be post-filtered
* here — the same predicate the local/encrypted search already applies.
* Now-empty groups are dropped.
*/
export const filterGroupsByDateRange = (
groups: ResultGroup[],
fromTs?: number,
toTs?: number,
): ResultGroup[] => {
if (fromTs === undefined && toTs === undefined) return groups;
return groups
.map((group) => ({
...group,
items: group.items.filter((item) => inTsRange(item.event.origin_server_ts, fromTs, toTs)),
}))
.filter((group) => group.items.length > 0);
};
const groupSearchResult = (results: ISearchResult[]): ResultGroup[] => {
const groups: ResultGroup[] = [];
@@ -119,7 +144,9 @@ export type MessageSearchParams = {
};
export const useMessageSearch = (params: MessageSearchParams) => {
const mx = useMatrixClient();
const { term, order, rooms, senders, fromTs, toTs, containsUrl } = params;
// fromTs/toTs are intentionally not sent to the server (see comment below) —
// callers post-filter results with filterGroupsByDateRange instead.
const { term, order, rooms, senders, containsUrl } = params;
const searchMessages = useCallback(
async (nextBatch?: string) => {
@@ -142,9 +169,10 @@ export const useMessageSearch = (params: MessageSearchParams) => {
limit,
rooms,
senders,
// from_ts / to_ts and contains_url are valid Matrix spec fields not yet in SDK types
...(fromTs !== undefined && { from_ts: fromTs }),
...(toTs !== undefined && { to_ts: toTs }),
// `RoomEventFilter` has no timestamp bounds — from_ts/to_ts are not
// Matrix filter fields and the homeserver silently drops them, so the
// date range is instead enforced client-side (see filterGroupsByDateRange).
// contains_url is a valid spec field not yet in SDK types.
...(containsUrl !== undefined && { contains_url: containsUrl }),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
@@ -161,7 +189,7 @@ export const useMessageSearch = (params: MessageSearchParams) => {
});
return parseSearchResult(r);
},
[mx, term, order, rooms, senders, fromTs, toTs, containsUrl],
[mx, term, order, rooms, senders, containsUrl],
);
return searchMessages;