feat(ui): copy message link permalink that scrolls to + highlights the message (LC-246) #300

Merged
longjacksonle merged 1 commit from feat/lc-246-message-permalink into main 2026-06-09 04:58:58 +02:00

Summary

Implements LC-246 (subtask LC-247). Adds a "Copy link" item to the message hover menu and a GET /m/{message_id} permalink route. Clicking Copy link copies a short link to that message; opening it lands the viewer on the room/DM, scrolls the message into view, and briefly highlights it.

Implementation

  • Route (routes/room.rs::get_message_permalink, registered as /m/{message_id}): loads the message, access-checks the viewer against its room, and 302-redirects to the canonical page with a #msg-{id} fragment - /dm/{peer}#msg-{id} for a DM (peer resolved per viewer via get_dm_peer), /room/{room_id}#msg-{id} otherwise. A missing / deleted / quarantined / inaccessible message returns 404, not 403, so the endpoint never confirms a message exists in a room the viewer cannot see. The web room render already loads the full top-level history into #messages, so the target row is always in the DOM - no "load around a cursor" needed.
  • Copy action (room/message.html): the hover-menu button copies location.origin + "/m/" + id via navigator.clipboard.writeText with an execCommand fallback (defined once in auto_scroll.html for non-secure contexts) and flips its label to a localized "Copied" for ~1.5s.
  • Scroll + highlight (partials/auto_scroll.html): on load, when location.hash is #msg-<id> and the row exists, scrolls it to center and adds a 2s lc-msg-flash highlight (theme-aware via --accent-surface, disabled under prefers-reduced-motion) instead of the usual bottom/unread anchor.

Decisions (per the ticket)

  • URL is the short, room/DM-agnostic /m/{id} (resolves room-vs-DM server-side) rather than the illustrative /room/7/m/482 from the mock - the copier needs no room id and DMs work uniformly. Flag if you want the verbose form.
  • Thread replies live in the thread panel, not the main list, so a permalink to a reply redirects to the room and the highlight no-ops; deep-linking into an open thread is out of v1.

Tests

  • routes_permalink.rs: room and DM redirect targets, 404 for missing and for inaccessible (asserting not-403), and that the hover menu renders the copy-link control.
  • Scroll/highlight + clipboard are JS-only (LC-247 QA pass).
  • LC-77 render fixtures regenerated for the new button (diff verified to be only that button).
  • 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-246 (subtask LC-247). Adds a "Copy link" item to the message hover menu and a `GET /m/{message_id}` permalink route. Clicking Copy link copies a short link to that message; opening it lands the viewer on the room/DM, scrolls the message into view, and briefly highlights it. ## Implementation - **Route** (`routes/room.rs::get_message_permalink`, registered as `/m/{message_id}`): loads the message, access-checks the viewer against its room, and 302-redirects to the canonical page with a `#msg-{id}` fragment - `/dm/{peer}#msg-{id}` for a DM (peer resolved per viewer via `get_dm_peer`), `/room/{room_id}#msg-{id}` otherwise. A missing / deleted / quarantined / inaccessible message returns **404, not 403**, so the endpoint never confirms a message exists in a room the viewer cannot see. The web room render already loads the full top-level history into `#messages`, so the target row is always in the DOM - no "load around a cursor" needed. - **Copy action** (`room/message.html`): the hover-menu button copies `location.origin + "/m/" + id` via `navigator.clipboard.writeText` with an `execCommand` fallback (defined once in `auto_scroll.html` for non-secure contexts) and flips its label to a localized "Copied" for ~1.5s. - **Scroll + highlight** (`partials/auto_scroll.html`): on load, when `location.hash` is `#msg-<id>` and the row exists, scrolls it to center and adds a 2s `lc-msg-flash` highlight (theme-aware via `--accent-surface`, disabled under `prefers-reduced-motion`) instead of the usual bottom/unread anchor. ## Decisions (per the ticket) - URL is the short, room/DM-agnostic `/m/{id}` (resolves room-vs-DM server-side) rather than the illustrative `/room/7/m/482` from the mock - the copier needs no room id and DMs work uniformly. Flag if you want the verbose form. - Thread replies live in the thread panel, not the main list, so a permalink to a reply redirects to the room and the highlight no-ops; deep-linking into an open thread is out of v1. ## Tests - `routes_permalink.rs`: room and DM redirect targets, 404 for missing and for inaccessible (asserting not-403), and that the hover menu renders the copy-link control. - Scroll/highlight + clipboard are JS-only (LC-247 QA pass). - LC-77 render fixtures regenerated for the new button (diff verified to be only that button). - `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): copy message link permalink that scrolls to + highlights the message (LC-246)
All checks were successful
check-secrets / Nosey parker (push) Successful in 5s
check-secrets / TruffleHog (push) Successful in 8s
check-secrets / Kingfisher (push) Successful in 9s
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / Nosey parker (pull_request) Successful in 5s
check-secrets / Kingfisher (pull_request) Successful in 7s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 2m10s
6b7ea81ad8
Add a "Copy link" item to the message hover menu and a GET /m/{message_id} permalink route. Clicking Copy link copies a short link to that message; opening the link lands the viewer on the room/DM, scrolls the message into view, and briefly highlights it.

Route: /m/{id} loads the message, access-checks the viewer against its room, and 302-redirects to the canonical page with a #msg-{id} fragment - /dm/{peer}#msg-{id} for a DM (peer resolved per viewer), /room/{room_id}#msg-{id} otherwise. A missing, deleted, quarantined, or inaccessible message returns 404 (not 403) so the endpoint never confirms a message exists in a room the viewer cannot see. The web room render already loads the full top-level history into #messages, so the target row is always in the DOM (no load-around needed).

Client: the hover-menu button copies location.origin + "/m/" + id via navigator.clipboard with an execCommand fallback (defined once in auto_scroll.html for non-secure contexts) and flips its label to a localized "Copied" for ~1.5s. partials/auto_scroll.html, on load, when location.hash is #msg-<id> and the row exists, scrolls it to center and adds a 2s lc-msg-flash highlight (theme-aware via --accent-surface, disabled under prefers-reduced-motion) instead of the usual bottom/unread anchor.

Scope: rooms and DMs. Thread replies live in the thread panel, not the main list, so a permalink to a reply redirects to the room and the highlight no-ops; deep-linking into an open thread is out of v1.

Tests: new routes_permalink.rs pins the room/DM redirect targets, 404 for missing and for inaccessible (not 403), and that the hover menu renders the copy-link control. LC-77 render fixtures regenerated to capture the new button (diff verified to be only that button). just test and just test-saas green; clippy + fmt clean.

#LC-246
#LC-247

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/lc-246-message-permalink 2026-06-09 04:58:58 +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!300
No description provided.