diff --git a/LOTUS_FEATURES.md b/LOTUS_FEATURES.md
index 434daca85..19de1fef8 100644
--- a/LOTUS_FEATURES.md
+++ b/LOTUS_FEATURES.md
@@ -733,6 +733,7 @@ Redacted events display "This message has been deleted" along with the redaction
- Maximum of 500 bookmarked entries
- `BookmarksPanel.tsx` is a sidebar panel accessible from the navigation rail
- Live-renders edits/redactions, text search, jump-to-message, and remove
+- **Author attribution**: each saved-message card shows who wrote it (`{sender} · {time ago}`). The author is snapshotted at save time (`senderId`/`senderName` on the bookmark, optional for backward compatibility) and re-resolved live from the event when the room is joined; search also matches the author name.
- **Sort & group**: a Newest / Oldest / By-room segmented control sorts the list; "By room" renders collapsible per-room sections (groups ordered by most-recent save). The chosen sort persists across panel opens (`cinny_bookmarks_sort_v1`). Ordering/grouping logic is pure and unit-tested in `src/app/utils/bookmarks.ts` (`bookmarks.test.ts`).
- Hook: `src/app/hooks/useBookmarks.ts`
diff --git a/src/app/features/bookmarks/BookmarksPanel.tsx b/src/app/features/bookmarks/BookmarksPanel.tsx
index 67fc77009..279f7b2f4 100644
--- a/src/app/features/bookmarks/BookmarksPanel.tsx
+++ b/src/app/features/bookmarks/BookmarksPanel.tsx
@@ -37,7 +37,7 @@ import { MessageDeletedContent } from '../../components/message/content/Fallback
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { RoomAvatar } from '../../components/room-avatar';
-import { getRoomAvatarUrl } from '../../utils/room';
+import { getRoomAvatarUrl, getMemberName } from '../../utils/room';
import { nameInitials } from '../../utils/common';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { stopPropagation } from '../../utils/keyboard';
@@ -102,9 +102,11 @@ type BookmarkItemProps = {
onRemove: (eventId: string) => void;
// Optional live-rendered preview node; falls back to the stored snapshot when absent.
preview?: ReactNode;
+ // Live-resolved author name; falls back to the stored snapshot when absent.
+ senderName?: string;
};
-function BookmarkItem({ bookmark, onJump, onRemove, preview }: BookmarkItemProps) {
+function BookmarkItem({ bookmark, onJump, onRemove, preview, senderName }: BookmarkItemProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const room = mx.getRoom(bookmark.roomId) ?? undefined;
@@ -112,6 +114,9 @@ function BookmarkItem({ bookmark, onJump, onRemove, preview }: BookmarkItemProps
const avatarUrl = room
? (getRoomAvatarUrl(mx, room, 96, useAuthentication) ?? undefined)
: undefined;
+ // Prefer a live-resolved author name, then the stored snapshot.
+ const author = senderName ?? bookmark.senderName;
+ const timeAgo = formatTimeAgo(bookmark.savedAt);
return (
{displayRoomName}
-
- {formatTimeAgo(bookmark.savedAt)}
+
+ {author ? `${author} · ${timeAgo}` : timeAgo}
;
+ // Resolve the author's current display name from the live event when available.
+ const liveSender = liveEvent?.getSender();
+ const senderName = liveSender ? getMemberName(room, liveSender) : undefined;
+
+ return (
+
+ );
}
type RoomGroupHeaderProps = {
@@ -320,7 +337,8 @@ export function BookmarksPanel({ onClose }: BookmarksPanelProps) {
: bookmarks.filter(
(bk) =>
bk.previewText.toLowerCase().includes(query) ||
- bk.roomName.toLowerCase().includes(query),
+ bk.roomName.toLowerCase().includes(query) ||
+ (bk.senderName?.toLowerCase().includes(query) ?? false),
),
[bookmarks, query],
);
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index 3ce5645cb..49925cfdb 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -1190,6 +1190,8 @@ export const Message = React.memo(
savedAt: Date.now(),
previewText: body.slice(0, 120),
roomName: room.name,
+ senderId,
+ senderName: senderDisplayName,
});
}
closeMenu();
diff --git a/src/app/hooks/useBookmarks.ts b/src/app/hooks/useBookmarks.ts
index 3c8c4341d..db8689687 100644
--- a/src/app/hooks/useBookmarks.ts
+++ b/src/app/hooks/useBookmarks.ts
@@ -8,6 +8,11 @@ export type Bookmark = {
savedAt: number;
previewText: string;
roomName: string;
+ // Author of the saved message. Optional for backward compatibility with
+ // bookmarks stored before attribution was added; the panel resolves a live
+ // name when the room is joined and falls back to these snapshots otherwise.
+ senderId?: string;
+ senderName?: string;
};
const BOOKMARKS_KEY = 'io.lotus.bookmarks';