feat(messages): per-user saved-messages list #74

Merged
nrupard merged 1 commit from feat/saved-messages into main 2026-05-11 21:02:17 +02:00
Owner

A user can now bookmark any message they can see (room or DM); the bookmark row is private to that user and rows are listed newest-first on a new /saved page reached from the sidebar footer. Save/Unsave appears in the existing message hover menu alongside Pin/Unpin, and the response re-renders the bubble so the button flips in place via the standard outerHTML swap. The Saved page's Unsave button removes its row with hx-swap=delete and discards the response body. No WS broadcast: bookmarks are per-viewer state.

The schema is a (user_id, message_id) composite primary key in chat.db; ON DELETE CASCADE removes rows when a message is hard-deleted, and the bookmarks_for_user query joins to messages so soft-deleted tombstones never surface. MessageView gains an is_bookmarked field and load_message_view_for_viewer takes a new is_bookmarked parameter; page-render call sites populate it from a per-room bulk lookup. The edit-broadcast path also now queries the canonical is_pinned set instead of hardcoding false, which silently fixed an existing bug where editing a pinned message would re-render the bubble as if the pin had been removed.

Tests in server/tests/db_bookmarks.rs cover the round-trip, idempotency, per-user isolation, and soft-delete/cascade behaviour. server/tests/routes_bookmarks.rs covers the POST/DELETE bubble re-render, anonymous and access-denied paths, and the /saved page ordering / empty / soft-delete cases. All other test setups gain the 0019 migration include so room and DM page renders continue to work in tests.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

A user can now bookmark any message they can see (room or DM); the bookmark row is private to that user and rows are listed newest-first on a new /saved page reached from the sidebar footer. Save/Unsave appears in the existing message hover menu alongside Pin/Unpin, and the response re-renders the bubble so the button flips in place via the standard outerHTML swap. The Saved page's Unsave button removes its row with hx-swap=delete and discards the response body. No WS broadcast: bookmarks are per-viewer state. The schema is a (user_id, message_id) composite primary key in chat.db; ON DELETE CASCADE removes rows when a message is hard-deleted, and the bookmarks_for_user query joins to messages so soft-deleted tombstones never surface. MessageView gains an is_bookmarked field and load_message_view_for_viewer takes a new is_bookmarked parameter; page-render call sites populate it from a per-room bulk lookup. The edit-broadcast path also now queries the canonical is_pinned set instead of hardcoding false, which silently fixed an existing bug where editing a pinned message would re-render the bubble as if the pin had been removed. Tests in server/tests/db_bookmarks.rs cover the round-trip, idempotency, per-user isolation, and soft-delete/cascade behaviour. server/tests/routes_bookmarks.rs covers the POST/DELETE bubble re-render, anonymous and access-denied paths, and the /saved page ordering / empty / soft-delete cases. All other test setups gain the 0019 migration include so room and DM page renders continue to work in tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(messages): per-user saved-messages list
Some checks failed
Check / clippy + fmt + tests (pull_request) Failing after 33s
c7814caad4
A user can now bookmark any message they can see (room or DM); the bookmark row is private to that user and rows are listed newest-first on a new /saved page reached from the sidebar footer. Save/Unsave appears in the existing message hover menu alongside Pin/Unpin, and the response re-renders the bubble so the button flips in place via the standard outerHTML swap. The Saved page's Unsave button removes its row with hx-swap=delete and discards the response body. No WS broadcast: bookmarks are per-viewer state.

The schema is a (user_id, message_id) composite primary key in chat.db; ON DELETE CASCADE removes rows when a message is hard-deleted, and the bookmarks_for_user query joins to messages so soft-deleted tombstones never surface. MessageView gains an is_bookmarked field and load_message_view_for_viewer takes a new is_bookmarked parameter; page-render call sites populate it from a per-room bulk lookup. The edit-broadcast path also now queries the canonical is_pinned set instead of hardcoding false, which silently fixed an existing bug where editing a pinned message would re-render the bubble as if the pin had been removed.

Tests in server/tests/db_bookmarks.rs cover the round-trip, idempotency, per-user isolation, and soft-delete/cascade behaviour. server/tests/routes_bookmarks.rs covers the POST/DELETE bubble re-render, anonymous and access-denied paths, and the /saved page ordering / empty / soft-delete cases. All other test setups gain the 0019 migration include so room and DM page renders continue to work in tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nrupard deleted branch feat/saved-messages 2026-05-11 21:02:17 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!74
No description provided.