feat(ui): mark a conversation unread from a message (LC-286) #321

Merged
longjacksonle merged 1 commit from feat/lc-286-mark-unread into main 2026-06-14 20:07:08 +02:00

What

Adds a "Mark unread" hover action - the inverse of "mark all read" (LC-250) and per-room read (LC-258). It rewinds the room's read watermark to just before the chosen message, so that message and everything after it count as unread again and the sidebar badge reappears. Useful for triage ("I'll deal with this later").

How

  • The existing watermark setter only advances (upsert_dm_read keeps MAX(...)), so it cannot rewind. New db::chat::rewind_dm_read is an unconditional upsert that can move the watermark backward; set_last_read / the read paths stay advance-only (mark-read still can't be clobbered by a stale lower value).
  • POST /messages/{id}/unread (routes::read_all::post_message_unread): look up the message (404 if missing), is_room_accessible gate (403), set the watermark to max(0, id - 1) so get_unread_count's id > watermark re-raises this message and newer; broadcast ReadAllChanged (live sidebar refresh across tabs); return the re-rendered sidebar.
  • message.html: a "Mark unread" button in the hover menu (hx-post, targets #sidebar). Strings in en + es.

Re-flagging the room's mentions as unread is out of scope for v1 (mentions have their own read state); this rewinds the message watermark. The four LC-77 fixtures are regenerated for the new button (diff is exactly that button, purely additive). No new env. Not operator-visible.

Tests

routes_read_all: marking a read message unread raises get_unread_count; the access gate 403s a non-member on a private room; a missing message 404s. just test and just test-saas pass.

QA note

Worth a manual pass: in a room you've read, hover an older message and "Mark unread" - the sidebar badge for that room reappears and updates in another open tab; navigate away and back and the room re-clears on open (expected, Slack-style). Confirm it's gated on rooms you can't access.

## What Adds a "Mark unread" hover action - the inverse of "mark all read" (LC-250) and per-room read (LC-258). It rewinds the room's read watermark to just before the chosen message, so that message and everything after it count as unread again and the sidebar badge reappears. Useful for triage ("I'll deal with this later"). ## How - The existing watermark setter only advances (`upsert_dm_read` keeps `MAX(...)`), so it cannot rewind. New `db::chat::rewind_dm_read` is an unconditional upsert that can move the watermark backward; `set_last_read` / the read paths stay advance-only (mark-read still can't be clobbered by a stale lower value). - `POST /messages/{id}/unread` (`routes::read_all::post_message_unread`): look up the message (404 if missing), `is_room_accessible` gate (403), set the watermark to `max(0, id - 1)` so `get_unread_count`'s `id > watermark` re-raises this message and newer; broadcast `ReadAllChanged` (live sidebar refresh across tabs); return the re-rendered sidebar. - `message.html`: a "Mark unread" button in the hover menu (`hx-post`, targets `#sidebar`). Strings in en + es. Re-flagging the room's mentions as unread is out of scope for v1 (mentions have their own read state); this rewinds the message watermark. The four LC-77 fixtures are regenerated for the new button (diff is exactly that button, purely additive). No new env. Not operator-visible. ## Tests `routes_read_all`: marking a read message unread raises `get_unread_count`; the access gate 403s a non-member on a private room; a missing message 404s. `just test` and `just test-saas` pass. ## QA note Worth a manual pass: in a room you've read, hover an older message and "Mark unread" - the sidebar badge for that room reappears and updates in another open tab; navigate away and back and the room re-clears on open (expected, Slack-style). Confirm it's gated on rooms you can't access.
feat(ui): mark a conversation unread from a message (LC-286)
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 5s
check-secrets / TruffleHog (push) Successful in 6s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 7s
check-secrets / TruffleHog (pull_request) Successful in 7s
Check / clippy + fmt + tests (pull_request) Successful in 2m18s
7570383a7f
Adds a "Mark unread" hover action - the inverse of the LC-250/LC-258 read paths. It rewinds the room's read watermark to just before the chosen message, so that message and everything after it count as unread again and the sidebar badge reappears (for later triage).

The existing watermark setter only advances (upsert_dm_read keeps MAX), so this adds db::chat::rewind_dm_read, an unconditional upsert that can move the watermark backward; the read paths stay advance-only. POST /messages/{id}/unread (routes::read_all) looks up the message (404), gates on is_room_accessible (403), sets the watermark to max(0, id-1), broadcasts ReadAllChanged (live sidebar refresh across tabs), and returns the re-rendered sidebar. The hover button targets #sidebar.

Strings localized in en + es. The four LC-77 fixtures are regenerated for the new button (diff is exactly that button, purely additive). Tests cover the rewind (unread count rises), the access gate (403), and missing message (404).

#LC-286
#LC-287

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/lc-286-mark-unread 2026-06-14 20:07:08 +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!321
No description provided.