feat(ui): float the reaction emoji picker as an anchored popover (LC-384) #394

Merged
longjacksonle merged 1 commit from feat/lc-384-reaction-popover into main 2026-06-20 03:54:48 +02:00

Closes LC-384. The headline fix of the LC-383 room-polish-2 epic (from data/room1.png / data/room2.png).

Problem

The reaction picker swapped inline in place of the add-reaction button (hx-target="this" hx-swap="outerHTML"), so the search box + Recent row + grid rendered as a block in the message flow: it reflowed the conversation, pushed later messages down, overlapped the next message, and could be open on several messages at once. It was also unstyled.

Fix: float it into a fixed host

  • New singleton #lc-reaction-popover host in layout.html. The add-reaction button (data-lc-reaction-open) now loads the picker there (hx-target="#lc-reaction-popover", innerHTML) instead of swapping inline.
  • A new layout.html script positions the host next to the trigger. Fixed positioning escapes the #messages overflow clip, so the picker floats above the conversation and never reflows it. It flips above the trigger when there's no room below.
  • One host = inherently single-open. Dismisses on outside-click, Escape (focus returns to the trigger), a react, and a scroll of the message list (but not the picker's own grid scroll).

Redesigned card

A compact card: full-width search input + close x in a bordered header, a labelled Recent section, and a clean emoji grid of consistent .lc-emoji-cell squares with a sunken hover.

Preserved

The root keeps id="picker-{id}" (the LC-288 recent-row JS derives the message id from it; the LC-274 filter keys on [id^="picker-"]) and the data-lc-emoji-filter / -recent / -grid hooks - so filtering, the recent MRU, custom emojis, and the react/toggle wiring (#reactions-{id}) are unchanged. The old hx-get .../cancel inline restore is replaced by a client-side close (cancel_picker is now unused).

Tests

  • The four LC-77 golden fixtures regenerated for the add-button retarget; diff verified to contain only that.
  • ./dev/cargo check, just build-css, just test, just test-saas - all green.

🤖 Generated with Claude Code

Closes LC-384. The headline fix of the LC-383 room-polish-2 epic (from `data/room1.png` / `data/room2.png`). ## Problem The reaction picker swapped **inline** in place of the add-reaction button (`hx-target="this" hx-swap="outerHTML"`), so the search box + Recent row + grid rendered as a block in the message flow: it reflowed the conversation, pushed later messages down, overlapped the next message, and could be open on several messages at once. It was also unstyled. ## Fix: float it into a fixed host - New singleton `#lc-reaction-popover` host in `layout.html`. The add-reaction button (`data-lc-reaction-open`) now loads the picker there (`hx-target="#lc-reaction-popover"`, `innerHTML`) instead of swapping inline. - A new `layout.html` script positions the host next to the trigger. **Fixed positioning escapes the `#messages` overflow clip**, so the picker floats above the conversation and never reflows it. It flips above the trigger when there's no room below. - One host = **inherently single-open**. Dismisses on outside-click, **Escape** (focus returns to the trigger), a react, and a scroll of the message list (but not the picker's own grid scroll). ## Redesigned card A compact card: full-width search input + close `x` in a bordered header, a labelled Recent section, and a clean emoji grid of consistent `.lc-emoji-cell` squares with a sunken hover. ## Preserved The root keeps `id="picker-{id}"` (the LC-288 recent-row JS derives the message id from it; the LC-274 filter keys on `[id^="picker-"]`) and the `data-lc-emoji-filter` / `-recent` / `-grid` hooks - so filtering, the recent MRU, custom emojis, and the react/toggle wiring (`#reactions-{id}`) are unchanged. The old `hx-get .../cancel` inline restore is replaced by a client-side close (`cancel_picker` is now unused). ## Tests - The four LC-77 golden fixtures regenerated for the add-button retarget; diff verified to contain only that. - `./dev/cargo check`, `just build-css`, `just test`, `just test-saas` - all green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(ui): float the reaction emoji picker as an anchored popover (LC-384)
All checks were successful
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / Kingfisher (push) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 5s
check-secrets / Nosey parker (push) Successful in 6s
check-secrets / TruffleHog (push) Successful in 7s
check-secrets / TruffleHog (pull_request) Successful in 9s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 4m16s
981e646ab5
The reaction picker swapped inline in place of the add-reaction button (hx-target="this" hx-swap="outerHTML"), so the search box + Recent row + grid rendered as a block in the message flow: it reflowed the conversation, pushed later messages down, overlapped the next message, and could be open on several messages at once.

Render it into a shared, fixed-position host (#lc-reaction-popover, added once in layout.html) instead. The add-reaction button now loads the picker there (hx-target="#lc-reaction-popover", data-lc-reaction-open); a new layout.html script positions the host next to the trigger - fixed positioning escapes the #messages overflow clip, so the picker floats above the conversation and never reflows it. One host means it is inherently single-open; it dismisses on outside-click, Escape (focus returns to the trigger), a react, and a scroll of the message list (but not the picker's own grid scroll), and flips above the trigger when there is no room below.

The picker markup is redesigned as a compact card: a full-width search input + a close affordance in a bordered header, a labelled Recent section, and a clean emoji grid of consistent .lc-emoji-cell squares with a sunken hover. The root keeps id="picker-{id}" (the LC-288 recent-row JS derives the message id from it; the LC-274 filter keys on [id^="picker-"]) and the data-lc-emoji-filter / -recent / -grid hooks, so filtering, the recent MRU, custom emojis, and the react/toggle wiring (#reactions-{id}) are all unchanged. The old hx-get .../cancel inline restore is replaced by a client-side close; cancel_picker is now unused.

The four LC-77 golden fixtures are regenerated for the add-button retarget; diff verified to contain only that.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/lc-384-reaction-popover 2026-06-20 03:54:48 +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!394
No description provided.