feat(ui): jump-to-latest pill with new-message count in the message list (LC-242) #298

Merged
longjacksonle merged 1 commit from feat/lc-242-jump-to-latest-pill into main 2026-06-09 03:21:52 +02:00

Summary

Implements LC-242 (subtask LC-243). Adds a floating "jump to latest" pill over the room/DM message list: hidden while the viewer is pinned to the bottom, shown when they scroll up to read history. Messages arriving live while scrolled up increment a count on the pill; clicking it scrolls to the bottom and clears the count. Fills the gap where live WebSocket arrivals appended off-screen with no indicator and no quick way back.

Implementation

Front-end only. No schema, route, or server change.

  • partials/jump_to_latest.html: the pill button. Included by room/page.html and dm/page.html inside a new relative wrapper around #messages, so absolute anchors it to the bottom-right of the message viewport, above the composer.
  • All behaviour lives in the existing partials/auto_scroll.html IIFE, reusing its #messages ref, isAtBottom() / wasAtBottom scroll tracking (same 50px slack), and the evt.target === messages teardown:
    • Visibility toggles off wasAtBottom (hidden at bottom, shown when scrolled up).
    • Count increments on htmx:oobAfterSwap only when the swap targeted #messages (a new-message beforeend append, not an edit/reaction/poll which target id-scoped nodes) AND the viewer is scrolled up.
    • Own sends do not increment: the LC-230 optimistic echo jumps to the bottom first, so wasAtBottom is true when the canonical render lands.
    • Click scrolls to bottom (smooth; instant under prefers-reduced-motion), resets the count, hides.
    • Reaching the bottom by scrolling resets and hides.

i18n

The count is client-dynamic, so it cannot be server-rendered through |tn. Localized one/many forms ride as data attributes (the established data-lc-echo-* pattern); the %n% token is substituted with the live count in JS. en + es added. aria-live on the count, aria-label on the button.

Scope

Main #messages list in rooms and DMs. The thread panel is a separate, smaller scroller and is out of scope for v1 (noted on the ticket).

Tests

  • Pill renders hidden with resolved i18n on the room page (routes_drafts) and the DM page (routes_dm_mute).
  • Counter/scroll behaviour is JS-only; covered by the LC-243 QA pass.
  • just test and just test-saas green; clippy + fmt clean.

No operator-visible surface (no env var / config / API change), so no [operator-action] marker.

🤖 Generated with Claude Code

## Summary Implements LC-242 (subtask LC-243). Adds a floating "jump to latest" pill over the room/DM message list: hidden while the viewer is pinned to the bottom, shown when they scroll up to read history. Messages arriving live while scrolled up increment a count on the pill; clicking it scrolls to the bottom and clears the count. Fills the gap where live WebSocket arrivals appended off-screen with no indicator and no quick way back. ## Implementation Front-end only. No schema, route, or server change. - `partials/jump_to_latest.html`: the pill button. Included by `room/page.html` and `dm/page.html` inside a new `relative` wrapper around `#messages`, so `absolute` anchors it to the bottom-right of the message viewport, above the composer. - All behaviour lives in the existing `partials/auto_scroll.html` IIFE, reusing its `#messages` ref, `isAtBottom()` / `wasAtBottom` scroll tracking (same 50px slack), and the `evt.target === messages` teardown: - Visibility toggles off `wasAtBottom` (hidden at bottom, shown when scrolled up). - Count increments on `htmx:oobAfterSwap` only when the swap targeted `#messages` (a new-message `beforeend` append, not an edit/reaction/poll which target id-scoped nodes) AND the viewer is scrolled up. - Own sends do not increment: the LC-230 optimistic echo jumps to the bottom first, so `wasAtBottom` is true when the canonical render lands. - Click scrolls to bottom (smooth; instant under `prefers-reduced-motion`), resets the count, hides. - Reaching the bottom by scrolling resets and hides. ## i18n The count is client-dynamic, so it cannot be server-rendered through `|tn`. Localized one/many forms ride as data attributes (the established `data-lc-echo-*` pattern); the `%n%` token is substituted with the live count in JS. `en` + `es` added. `aria-live` on the count, `aria-label` on the button. ## Scope Main `#messages` list in rooms and DMs. The thread panel is a separate, smaller scroller and is out of scope for v1 (noted on the ticket). ## Tests - Pill renders hidden with resolved i18n on the room page (`routes_drafts`) and the DM page (`routes_dm_mute`). - Counter/scroll behaviour is JS-only; covered by the LC-243 QA pass. - `just test` and `just test-saas` green; clippy + fmt clean. No operator-visible surface (no env var / config / API change), so no `[operator-action]` marker. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(ui): jump-to-latest pill with new-message count in the message list (LC-242)
All checks were successful
check-secrets / Nosey parker (push) Successful in 5s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 4s
Check / clippy + fmt + tests (pull_request) Successful in 2m3s
check-secrets / TruffleHog (push) Successful in 8s
check-secrets / Kingfisher (push) Successful in 9s
check-secrets / Kingfisher (pull_request) Successful in 6s
Create release / Create release from merged PR (pull_request) Has been skipped
c4e87a1507
Add a floating "jump to latest" pill over the room/DM message list. It is hidden while the viewer is pinned to the bottom and appears when they scroll up to read history; messages arriving live while scrolled up increment a count on the pill ("3 new messages"), and clicking it scrolls to the bottom and clears the count. Fills the gap where live WS arrivals appended off-screen with no indicator and no quick way back.

Front-end only, no schema/route/server change. Markup is a shared partial (partials/jump_to_latest.html) included by room/page.html and dm/page.html inside a new relative wrapper around #messages so the pill anchors to the bottom-right of the message viewport above the composer. All behaviour lives in the existing auto_scroll.html IIFE, reusing its #messages reference, isAtBottom()/wasAtBottom scroll tracking, and the evt.target===messages teardown: visibility toggles off wasAtBottom; the count increments on htmx:oobAfterSwap only when the swap targeted #messages (a new-message beforeend append, not an edit/reaction/poll which target id-scoped nodes) and the viewer is scrolled up; own sends do not increment because the LC-230 optimistic echo jumps to bottom first; click scrolls (smooth, or instant under prefers-reduced-motion), resets, and hides.

i18n: the count is client-dynamic so it cannot be server-rendered via |tn; localized one/many forms ride as data attributes (the data-lc-echo-* pattern) and the %n% token is substituted in JS. en + es added; aria-live on the count, aria-label on the button. Scope is the main #messages list in rooms and DMs; the thread panel is out of scope for v1.

Tests: pill renders hidden with resolved i18n on the room page (routes_drafts) and the DM page (routes_dm_mute). just test and just test-saas green; clippy + fmt clean.

#LC-242
#LC-243

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/lc-242-jump-to-latest-pill 2026-06-09 03:21:52 +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!298
No description provided.