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
+17 -1
View File
@@ -314,16 +314,31 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
didRestoreDraft.current = true;
if (msgDraft.length > 0) {
Transforms.insertFragment(editor, msgDraft);
} else {
// Jotai draft is empty (page reload) — try localStorage fallback
try {
const stored = localStorage.getItem(`draft-msg-${roomId}`);
if (stored) {
const nodes = JSON.parse(stored);
if (Array.isArray(nodes) && nodes.length > 0) {
Transforms.insertFragment(editor, nodes);
}
}
} catch {
// Ignore malformed stored draft
}
}
}, [editor, msgDraft]);
}, [editor, msgDraft, roomId]);
useEffect(
() => () => {
if (!isEmptyEditor(editor)) {
const parsedDraft = JSON.parse(JSON.stringify(editor.children));
setMsgDraft(parsedDraft);
localStorage.setItem(`draft-msg-${roomId}`, JSON.stringify(parsedDraft));
} else {
setMsgDraft([]);
localStorage.removeItem(`draft-msg-${roomId}`);
}
resetEditor(editor);
resetEditorHistory(editor);
@@ -463,6 +478,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
mx.sendMessage(roomId, content as any);
resetEditor(editor);
resetEditorHistory(editor);
localStorage.removeItem(`draft-msg-${roomId}`);
setReplyDraft(undefined);
sendTypingStatus(false);
}, [mx, roomId, editor, replyDraft, sendTypingStatus, setReplyDraft, isMarkdown, commands]);