feat(ui): mark all as read - clear unread across every conversation (LC-250) #302

Merged
longjacksonle merged 1 commit from feat/lc-250-mark-all-read into main 2026-06-09 05:55:54 +02:00

Summary

Implements LC-250 (subtask LC-251). A "Mark all as read" control in the sidebar footer clears the viewer's unread message badges (rooms + DMs) and the paired mention chips in one action, refreshing the sidebar - the acting tab via the swap response, other tabs live.

Implementation

  • Route POST /read-all (routes::read_all): collects the distinct room ids the viewer has unread in (union of list_room_unread_counts admin-aware, list_dm_unread_counts, count_unread_mentions_per_room); for each, advances the watermark to the room's latest message (set_last_read) and clears its mentions (mark_mentions_read_for_room) - mirroring open-one exactly so the two paths converge. Best-effort per room; idempotent (upsert_dm_read keeps the max).
  • Response: the acting tab gets the re-rendered sidebar via sidebar_categories::render_sidebar_fragment (now pub(crate), enclave-aware through HX-Current-URL). Other tabs refresh via a new ChatEvent::ReadAllChanged (broadcast_to_user) whose WS arm re-renders the sidebar; render_event returns None for it.
  • DB: db::chat::latest_message_id(room_id).
  • Sidebar (partials/sidebar.html): a "Mark all as read" form in the footer link group.

Scope

Global mark-everything-read button for v1; a per-room mark-read affordance is a follow-up. Mentions are cleared alongside unread (matches open-one) and remain in /inbox + /activity.

Tests

routes_read_all.rs: seeds a room + DM with unread plus a mention row, POSTs /read-all, asserts unread counts and mentions are zero and the response is the sidebar; plus an idempotency check. The live cross-tab refresh is WS-driven (manual QA in LC-251). just test and just test-saas green; clippy + fmt clean.

No operator-visible surface, so no [operator-action] marker.

🤖 Generated with Claude Code

## Summary Implements LC-250 (subtask LC-251). A "Mark all as read" control in the sidebar footer clears the viewer's unread message badges (rooms + DMs) and the paired mention chips in one action, refreshing the sidebar - the acting tab via the swap response, other tabs live. ## Implementation - **Route** `POST /read-all` (`routes::read_all`): collects the distinct room ids the viewer has unread in (union of `list_room_unread_counts` admin-aware, `list_dm_unread_counts`, `count_unread_mentions_per_room`); for each, advances the watermark to the room's latest message (`set_last_read`) and clears its mentions (`mark_mentions_read_for_room`) - mirroring open-one exactly so the two paths converge. Best-effort per room; idempotent (`upsert_dm_read` keeps the max). - **Response**: the acting tab gets the re-rendered sidebar via `sidebar_categories::render_sidebar_fragment` (now `pub(crate)`, enclave-aware through `HX-Current-URL`). Other tabs refresh via a new `ChatEvent::ReadAllChanged` (`broadcast_to_user`) whose WS arm re-renders the sidebar; `render_event` returns None for it. - **DB**: `db::chat::latest_message_id(room_id)`. - **Sidebar** (`partials/sidebar.html`): a "Mark all as read" form in the footer link group. ## Scope Global mark-everything-read button for v1; a per-room mark-read affordance is a follow-up. Mentions are cleared alongside unread (matches open-one) and remain in `/inbox` + `/activity`. ## Tests `routes_read_all.rs`: seeds a room + DM with unread plus a mention row, POSTs `/read-all`, asserts unread counts and mentions are zero and the response is the sidebar; plus an idempotency check. The live cross-tab refresh is WS-driven (manual QA in LC-251). `just test` and `just test-saas` green; clippy + fmt clean. No operator-visible surface, so no `[operator-action]` marker. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(ui): mark all as read - clear unread across every conversation (LC-250)
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
check-secrets / Nosey parker (push) Successful in 3s
check-secrets / Kingfisher (push) Successful in 4s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 6s
check-secrets / Nosey parker (pull_request) Successful in 8s
check-secrets / Kingfisher (pull_request) Successful in 9s
Check / clippy + fmt + tests (pull_request) Successful in 4m3s
8c452f231d
Add a "Mark all as read" control to the sidebar footer that clears the viewer's unread message badges (rooms + DMs) and the paired mention chips in one action, then refreshes the sidebar - the acting tab via the swap response, other tabs live.

Route POST /read-all (routes::read_all): collects the distinct room ids the viewer has unread in - the union of list_room_unread_counts (admin-aware), list_dm_unread_counts, and count_unread_mentions_per_room - and for each advances the read watermark to the room's latest message (db::chat::set_last_read) and clears its unread mentions (db::mentions::mark_mentions_read_for_room). This mirrors exactly what opening one room does, so the bulk path and the open-one path converge. Best-effort per room so one failure does not abort the batch. Idempotent: upsert_dm_read keeps the max watermark.

The acting tab gets the re-rendered sidebar (reusing sidebar_categories::render_sidebar_fragment, now pub(crate), which respects the current enclave via HX-Current-URL); other tabs catch up via a new ChatEvent::ReadAllChanged broadcast_to_user, whose WS arm re-renders the sidebar (render_event returns None for it). New db helper latest_message_id(room_id).

Scope: the global mark-everything-read button for v1; a per-room mark-read affordance is a follow-up. Mentions are cleared alongside unread (matches open-one) and remain available in /inbox and /activity.

Tests: routes_read_all.rs seeds room + DM unread plus a mention row, POSTs /read-all, and asserts unread counts and mentions are zero and the response is the sidebar; plus an idempotency check. just test and just test-saas green; clippy + fmt clean.

#LC-250
#LC-251

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/lc-250-mark-all-read 2026-06-09 05:55:55 +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!302
No description provided.