web: reveal a reply icon beside the bubble on hover (desktop)
Desktop now shows a reply affordance on message hover (touch keeps double-tap). The button is only created when !isCoarse and only shown under @media (hover: hover); clicking it stages the reply. Reactions still use press-and-hold on desktop.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 files changed,
+37
−1
web/static/chat.js+20 −1web/static/style.css+17 −0
diff --git a/web/static/chat.js b/web/static/chat.js| index fd781fa..272dbd8 100644 |
| --- a/web/static/chat.js |
| +++ b/web/static/chat.js |
| @@ -447,6 +447,21 @@ async function start(session) { |
| return q; |
| } |
| + // Desktop-only: a reply icon that reveals beside the bubble on hover, so reply |
| + // is discoverable without the press-and-hold. (Touch uses double-tap instead.) |
| + function buildHoverReply(meta) { |
| + const b = document.createElement("button"); |
| + b.type = "button"; |
| + b.className = "reply-hover"; |
| + b.innerHTML = ICON_REPLY; |
| + b.setAttribute("aria-label", "Reply"); |
| + b.onclick = (e) => { |
| + e.stopPropagation(); |
| + startReply(meta); |
| + }; |
| + return b; |
| + } |
| + |
| async function render(msg) { |
| if (seen.has(msg.id)) return; |
| seen.add(msg.id); |
| @@ -497,10 +512,14 @@ async function start(session) { |
| } |
| if (payload.text) addLinkPreview(content, payload.text); |
| bubbleRow.appendChild(content); |
| + |
| + const replyMeta = { id: msg.id, name: payload.name, snippet: replySnippet(payload) }; |
| + // Desktop: a hover-revealed reply icon beside the bubble (touch uses double-tap). |
| + if (!isCoarse) bubbleRow.appendChild(buildHoverReply(replyMeta)); |
| el.appendChild(bubbleRow); |
| // React or reply: double-tap on touch, press-and-hold on desktop. |
| - attachReact(el, msg.id, { id: msg.id, name: payload.name, snippet: replySnippet(payload) }); |
| + attachReact(el, msg.id, replyMeta); |
| const reactions = document.createElement("div"); |
| reactions.className = "reactions"; |
diff --git a/web/static/style.css b/web/static/style.css| index b25da3c..0473d5c 100644 |
| --- a/web/static/style.css |
| +++ b/web/static/style.css |
| @@ -181,6 +181,23 @@ input:focus, textarea:focus { outline: 2px solid var(--accent); border-color: tr |
| .msg .bubble a { color: inherit; text-decoration: underline; text-underline-offset: 2px; } |
| .msg .time { font-size: 11px; color: var(--muted); margin: 3px 6px 0; } |
| +/* desktop hover reply icon, revealed beside the bubble on hover */ |
| +.reply-hover { |
| + flex: none; |
| + background: transparent; |
| + color: var(--muted); |
| + padding: 4px 6px; |
| + border-radius: 50%; |
| + display: none; |
| + align-items: center; |
| + line-height: 1; |
| +} |
| +.reply-hover svg { width: 18px; height: 18px; } |
| +.reply-hover:hover { background: var(--bubble-them); color: var(--accent); } |
| +@media (hover: hover) { |
| + .msg:hover .reply-hover { display: inline-flex; } |
| +} |
| + |
| /* message content (text bubble + any attachments) */ |
| .content { |
| display: flex; |