feat: document title unread count, draft persistence, search date range

E1 - Document title unread count: FaviconUpdater now also sets
     document.title to '(N) Lotus Chat' for mentions, '· Lotus Chat'
     for plain unreads, and 'Lotus Chat' when clear. Reuses the
     existing roomToUnread forEach loop.

E2 - Draft persistence across reloads: on room unmount, unsent message
     is written to localStorage as 'draft-msg-<roomId>'. On mount, if
     the Jotai atom is empty (page reload), the localStorage draft is
     restored. Cleared on send. Uses the existing Slate node JSON format.

E5 - Search date range filter: new DateRangeButton in SearchFilters
     with From/To date inputs in a PopOut. Dates stored as epoch ms in
     ?fromTs=&toTs= URL params. Passed to Matrix /search as from_ts /
     to_ts filter fields (valid spec fields, cast via 'as any' since
     SDK types don't include them yet).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 22:22:40 -04:00
parent 10f0093f8e
commit 582839fddb
6 changed files with 188 additions and 13 deletions
@@ -33,6 +33,8 @@ const useSearchPathSearchParams = (searchParams: URLSearchParams): _SearchPathSe
order: searchParams.get('order') ?? undefined,
rooms: searchParams.get('rooms') ?? undefined,
senders: searchParams.get('senders') ?? undefined,
fromTs: searchParams.get('fromTs') ?? undefined,
toTs: searchParams.get('toTs') ?? undefined,
}),
[searchParams],
);
@@ -193,6 +195,8 @@ export function MessageSearch({
order: searchPathSearchParams.order ?? SearchOrderBy.Recent,
rooms: searchParamRooms ?? defaultRooms,
senders: searchParamsSenders ?? senders,
fromTs: searchPathSearchParams.fromTs ? Number(searchPathSearchParams.fromTs) : undefined,
toTs: searchPathSearchParams.toTs ? Number(searchPathSearchParams.toTs) : undefined,
};
}, [searchPathSearchParams, searchParamRooms, searchParamsSenders, rooms, senders]);
@@ -235,6 +239,8 @@ export function MessageSearch({
msgSearchParams.order,
msgSearchParams.rooms,
msgSearchParams.senders,
msgSearchParams.fromTs,
msgSearchParams.toTs,
],
queryFn: ({ pageParam }) => searchMessages(pageParam),
initialPageParam: '',
@@ -329,6 +335,20 @@ export function MessageSearch({
[searchParamsSenders, handleSelectedSendersChange],
);
const handleDateRangeChange = useCallback(
(fromTs?: number, toTs?: number) => {
setSearchParams((prevParams) => {
const p = new URLSearchParams(prevParams);
p.delete('fromTs');
p.delete('toTs');
if (fromTs) p.append('fromTs', String(fromTs));
if (toTs) p.append('toTs', String(toTs));
return p;
});
},
[setSearchParams],
);
const lastVItem = vItems[vItems.length - 1];
const lastVItemIndex: number | undefined = lastVItem?.index;
const lastGroupIndex = groups.length - 1;
@@ -378,6 +398,9 @@ export function MessageSearch({
onOrderChange={handleOrderChange}
selectedSenders={searchParamsSenders}
onSelectedSendersChange={handleSelectedSendersChange}
fromTs={msgSearchParams.fromTs}
toTs={msgSearchParams.toTs}
onDateRangeChange={handleDateRangeChange}
/>
</Box>