feat(realtime): live /saved list on bookmark/unbookmark (LC-178, saved surface) #223

Merged
nrupard merged 1 commit from feat/lc-178-live-saved into main 2026-05-26 20:17:10 +02:00
Owner

First surface of LC-178. The /saved page was static-on-load: saving or unsaving a message from a room (or another tab) did not update an open /saved list until reload.

Changes

  • New SavedChanged { user_id } event. post_bookmark / delete_bookmark broadcast it via broadcast_to_user, fanning to every connection of the viewer (per-user fan-out like LC-161/173; no topic needed since #lc-saved-list only exists on /saved).
  • The saved list body is extracted into saved/items.html (shared by saved/page.html and the OOB fragment) and wrapped in <div id="lc-saved-list">. The WS send task gates SavedChanged on the recipient being the owner and swaps #lc-saved-list via ws/saved_live.html; tabs not on /saved have no matching id and drop it.
  • build_saved_rows is factored out of get_saved (author-label bulk lookup + per-row context path) and shared with the WS renderer so the live list matches a fresh page load exactly.

Out of scope (spun out)

/inbox (unread page list) and /activity (mentions/reminders page list) - different, higher-churn mutation sources (the message/read paths and the Mentioned/Reminder paths respectively), filed as a follow-up.

Verification

just test and just test-saas both green (106 test result: ok each, zero failures); cargo clippy / cargo fmt clean. Regression test (routes_bookmarks::saved_page_renders_live_region_with_the_bookmarked_message) asserts the /saved page renders the #lc-saved-list region with the bookmarked message, pinning the region + the get_saved refactor.

First surface of LC-178. The /saved page was static-on-load: saving or unsaving a message from a room (or another tab) did not update an open /saved list until reload. ## Changes - New `SavedChanged { user_id }` event. `post_bookmark` / `delete_bookmark` broadcast it via `broadcast_to_user`, fanning to every connection of the viewer (per-user fan-out like LC-161/173; no topic needed since `#lc-saved-list` only exists on /saved). - The saved list body is extracted into `saved/items.html` (shared by `saved/page.html` and the OOB fragment) and wrapped in `<div id="lc-saved-list">`. The WS send task gates `SavedChanged` on the recipient being the owner and swaps `#lc-saved-list` via `ws/saved_live.html`; tabs not on /saved have no matching id and drop it. - `build_saved_rows` is factored out of `get_saved` (author-label bulk lookup + per-row context path) and shared with the WS renderer so the live list matches a fresh page load exactly. ## Out of scope (spun out) `/inbox` (unread page list) and `/activity` (mentions/reminders page list) - different, higher-churn mutation sources (the message/read paths and the Mentioned/Reminder paths respectively), filed as a follow-up. ## Verification `just test` and `just test-saas` both green (106 `test result: ok` each, zero failures); `cargo clippy` / `cargo fmt` clean. Regression test (`routes_bookmarks::saved_page_renders_live_region_with_the_bookmarked_message`) asserts the /saved page renders the `#lc-saved-list` region with the bookmarked message, pinning the region + the `get_saved` refactor.
feat(realtime): live /saved list on bookmark/unbookmark (LC-178, saved surface)
All checks were successful
check-secrets / TruffleHog (push) Successful in 4s
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / Kingfisher (push) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 6s
check-secrets / Nosey parker (pull_request) Successful in 7s
check-secrets / Kingfisher (pull_request) Successful in 8s
Check / clippy + fmt + tests (pull_request) Successful in 3m8s
Create release / Create release from merged PR (pull_request) Has been skipped
e8e366dbcd
First surface of LC-178. The /saved page was static-on-load: saving or unsaving a message from a room (or another tab) did not update an open /saved list until reload.

- New SavedChanged { user_id } event. post_bookmark / delete_bookmark broadcast it via broadcast_to_user, fanning to every connection of the viewer (per-user fan-out like LC-161/173; no topic needed since #lc-saved-list only exists on /saved).
- The saved list body is extracted into saved/items.html (shared by saved/page.html and the OOB fragment) and wrapped in <div id="lc-saved-list">. The WS send task gates SavedChanged on the recipient being the owner and swaps #lc-saved-list via ws/saved_live.html; tabs not on /saved have no matching id and drop it.
- build_saved_rows is factored out of get_saved (author-label bulk lookup + per-row context path) and shared with the WS renderer so the live list matches a fresh page load exactly.

Out of scope (spun out): /inbox (unread page list) and /activity (mentions/reminders page list) - different, higher-churn mutation sources (the message/read paths and the Mentioned/Reminder paths respectively).

Regression test asserts the /saved page renders the #lc-saved-list region with the bookmarked message, pinning the region + the get_saved refactor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nrupard deleted branch feat/lc-178-live-saved 2026-05-26 20:17:11 +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!223
No description provided.