feat(sidebar): jump-to-unread + /inbox (LC-81) [stacked] #140

Merged
nrupard merged 1 commit from feat/lc-81-jump-and-inbox into main 2026-05-18 19:44:09 +02:00
Owner

Summary

LC-81: two related affordances.

  1. Jump-to-unread keybind: Alt+Shift+ArrowDown / ArrowUp navigates the sidebar's room and DM list, jumping to the next anchor whose unread badge is non-empty. Wraps at both ends. Pure client-side (sidebar_jump.js). The Inbox header button uses the same handler via [data-lc-jump-unread].

  2. GET /inbox view: flattens every unread message the viewer can see (per-room and DMs) into a single newest-first timeline. Uses the existing dm_read_state watermark (room-keyed, covers both kinds). 30-item pages cursored by ?before={message_id}; empty state renders an explicit "All caught up". An HTMX revealed sentinel at the bottom auto-loads the next page; the handler returns the items fragment when ?fragment=1 is set.

Per-message rows deep-link to /{room|dm}/{id}#msg-{message_id}. Mark-read happens implicitly when the linked room opens (existing behaviour); per-row mark-read is out of scope.

Sidebar nav gains an "Inbox" link beside "Saved".

Acceptance criteria

  • Keybind navigates to next sidebar room with unreads, wrapping.
  • /inbox lists unread messages across all rooms in time order.
  • Clicking deep-links to the message (mark-read happens on room load via existing path).
  • DMs and channels appear in the same view (DM peer id resolved via db::chat::get_dm_peer).
  • Empty state renders "All caught up".
  • Pagination via cursor + HTMX revealed sentinel; no large lists materialized client-side.

Out of scope (open questions)

  • Inbox filters (mentions-only / DMs-only / per-room).
  • Mark-all-as-read button (global or per-room).

Stack

This branch is on top of LC-79 redesign + LC-79 follow-up + LC-80 stars (PRs #137, #138, #139). Merge this branch supersedes all four.

Test plan

  • just check (fmt + clippy across standalone + saas).
  • ./dev/cargo test -p lets-chat-server --test routes_inbox - 2/2 pass.
  • Manual: visit /inbox with no unreads - "All caught up". Have peer post in #general - reload - peer's message appears. Click - lands in room scrolled to that message. Press Alt+Shift+ArrowDown repeatedly - cycles through unread sidebar rooms.
## Summary LC-81: two related affordances. 1. **Jump-to-unread keybind**: `Alt+Shift+ArrowDown` / `ArrowUp` navigates the sidebar's room and DM list, jumping to the next anchor whose unread badge is non-empty. Wraps at both ends. Pure client-side (`sidebar_jump.js`). The Inbox header button uses the same handler via `[data-lc-jump-unread]`. 2. **`GET /inbox` view**: flattens every unread message the viewer can see (per-room and DMs) into a single newest-first timeline. Uses the existing `dm_read_state` watermark (room-keyed, covers both kinds). 30-item pages cursored by `?before={message_id}`; empty state renders an explicit "All caught up". An HTMX `revealed` sentinel at the bottom auto-loads the next page; the handler returns the items fragment when `?fragment=1` is set. Per-message rows deep-link to `/{room|dm}/{id}#msg-{message_id}`. Mark-read happens implicitly when the linked room opens (existing behaviour); per-row mark-read is out of scope. Sidebar nav gains an "Inbox" link beside "Saved". ## Acceptance criteria - [x] Keybind navigates to next sidebar room with unreads, wrapping. - [x] `/inbox` lists unread messages across all rooms in time order. - [x] Clicking deep-links to the message (mark-read happens on room load via existing path). - [x] DMs and channels appear in the same view (DM peer id resolved via `db::chat::get_dm_peer`). - [x] Empty state renders "All caught up". - [x] Pagination via cursor + HTMX `revealed` sentinel; no large lists materialized client-side. ## Out of scope (open questions) - Inbox filters (mentions-only / DMs-only / per-room). - Mark-all-as-read button (global or per-room). ## Stack This branch is on top of LC-79 redesign + LC-79 follow-up + LC-80 stars (PRs #137, #138, #139). Merge this branch supersedes all four. ## Test plan - [x] `just check` (fmt + clippy across standalone + saas). - [x] `./dev/cargo test -p lets-chat-server --test routes_inbox` - 2/2 pass. - [ ] Manual: visit `/inbox` with no unreads - "All caught up". Have peer post in #general - reload - peer's message appears. Click - lands in room scrolled to that message. Press `Alt+Shift+ArrowDown` repeatedly - cycles through unread sidebar rooms.
feat(sidebar): jump-to-unread keybind + /inbox view (LC-81)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 53s
250eb24089
Two related affordances per LC-81:

1. Jump-to-unread (`Alt+Shift+ArrowDown`/`Up` + `[data-lc-jump-unread]` button). Pure client-side: `sidebar_jump.js` walks `#sidebar a[href^="/room/"]`/`[href^="/dm/"]`, finds the next anchor whose `unread-{kind}-{id}` badge has non-empty text, and clicks it. Wraps at both ends. The Inbox header button uses the same handler.

2. `GET /inbox` flattens every unread message the viewer can see (per-room + DMs) into a single newest-first timeline. Uses the existing `dm_read_state` watermark (room-keyed, works for both kinds). 30-item pages with a cursor (`?before={message_id}`); the empty state renders an explicit "All caught up". HTMX `revealed` sentinel at the bottom loads the next page; the handler returns the items fragment instead of the full page when `?fragment=1`.

Per-message rows deep-link to `/{room|dm}/{id}#msg-{message_id}`. Mark-read happens implicitly when the user opens the linked room (existing behaviour); a separate per-row mark-read is out of scope for this PR.

Sidebar nav gains an "Inbox" link next to "Saved".

Skipped (open questions from the issue):
- Inbox filters (mentions-only / DMs-only / per-room).
- Mark-all-as-read (global or per-room).

Both can land in follow-ups; minimal viable inbox first.

Tests: empty-state renders "All caught up"; peer-authored message in General appears in the viewer's inbox with the expected deep-link.

Stacks on the LC-79 redesign + LC-79 follow-up + LC-80 stars.
nrupard deleted branch feat/lc-81-jump-and-inbox 2026-05-18 19:44:09 +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!140
No description provided.