From fd925f91072260105719f6d4b3e16ee4629a22d8 Mon Sep 17 00:00:00 2001 From: Mickael Farina Date: Tue, 9 Jun 2026 22:45:48 +0200 Subject: [PATCH] =?UTF-8?q?fix(chat):=20message=20buttons=20=E2=86=92=20on?= =?UTF-8?q?e=20inline=20action=20row=20(copy=20/=20edit=20/=20regenerate?= =?UTF-8?q?=20/=20speak)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-message buttons were absolute-positioned at ±32px outside the bubble corners, hover-only — they clipped off-screen, overlapped, and the Speak button had no CSS at all (rendered as a raw native button). Replaced with a single .msg-meta row under each bubble (timestamp + ghost icon buttons), Claude-style: full hit areas, role-gated (regen/speak on assistant, edit on user), theme-aware, larger touch targets on mobile. History reloads and streamed messages render the same row (all paths go through addMessage). Co-Authored-By: Claude Fable 5 --- codec_chat.html | 65 ++++++++++++++++++++----------------------------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/codec_chat.html b/codec_chat.html index dfb3fc1..abef1cd 100644 --- a/codec_chat.html +++ b/codec_chat.html @@ -109,37 +109,24 @@ .msg-time{font-family:var(--mono);font-size:11px;color:var(--text-dim);margin-top:3px;padding:0 4px} .msg.user .msg-time{text-align:right} -/* ── Copy button on messages ── */ -.msg-copy{ - position:absolute;top:4px;right:-32px; - background:var(--surface);border:1px solid var(--border);color:var(--text-dim); - width:24px;height:24px;border-radius:5px;cursor:pointer; - display:flex;align-items:center;justify-content:center; - opacity:0;transition:all .15s; +/* ── Message action row: timestamp + regenerate / edit / copy / speak ── + One inline row beneath each bubble (replaces the old corner-floating + absolute buttons that clipped off-screen and left .msg-speak unstyled). */ +.msg-meta{display:flex;align-items:center;gap:1px;margin-top:4px; + opacity:.5;transition:opacity .15s} +.msg:hover .msg-meta{opacity:1} +.msg.user .msg-meta{justify-content:flex-end} +.msg-meta .msg-time{margin-top:0} +.msg-act{ + background:none;border:none;color:var(--text-dim);cursor:pointer; + width:28px;height:28px;border-radius:6px;padding:0; + display:inline-flex;align-items:center;justify-content:center; + transition:color .12s,background .12s; } -.msg.assistant:hover .msg-copy{opacity:1} -.msg.user .msg-copy{right:auto;left:-32px} -.msg.user:hover .msg-copy{opacity:1} -.msg-copy:hover{color:var(--accent);border-color:var(--accent)} -.msg-copy.copied{color:var(--success);border-color:var(--success)} -.msg-regen{ - position:absolute;bottom:4px;right:-32px; - background:var(--surface);border:1px solid var(--border);color:var(--text-dim); - width:24px;height:24px;border-radius:5px;cursor:pointer; - display:flex;align-items:center;justify-content:center; - opacity:0;transition:all .15s; -} -.msg.assistant:hover .msg-regen{opacity:1} -.msg-regen:hover{color:var(--accent);border-color:var(--accent)} -.msg-edit{ - position:absolute;bottom:4px;left:-32px; - background:var(--surface);border:1px solid var(--border);color:var(--text-dim); - width:24px;height:24px;border-radius:5px;cursor:pointer; - display:flex;align-items:center;justify-content:center; - opacity:0;transition:all .15s; -} -.msg.user:hover .msg-edit{opacity:1} -.msg-edit:hover{color:var(--accent);border-color:var(--accent)} +.msg-act:hover{color:var(--text);background:var(--surface-2)} +.msg-act:active{transform:scale(.9)} +.msg-act.copied{color:var(--success)} +.msg-act.speaking{color:var(--accent)} .typing{color:var(--accent);font-size:13px;padding:8px 16px} .typing span{animation:blink 1.4s infinite} @@ -248,10 +235,8 @@ #crewSelect{width:100%} #agentBuilder .ab-row{flex-direction:column} #agentBuilder .ab-field{min-width:100%;max-width:100%!important} - .msg-copy{right:-4px;top:-4px;opacity:0.7} - .msg.user .msg-copy{left:-4px;right:auto} - .msg-regen{right:-4px;bottom:-4px;opacity:0.7} - .msg-edit{left:-4px;bottom:-4px;opacity:0.7} + .msg-meta{opacity:.7} + .msg-act{width:32px;height:32px} .ibtn{width:36px;height:36px} } @media (max-width:480px){ @@ -763,12 +748,14 @@

CODEC

if(animate===false)div.style.animation='none'; var time=new Date().toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}); var enc=encodeURIComponent(content); - var copyBtn=''; - var editBtn=role==='user'?'':''; - var regenBtn=role==='assistant'?'':''; + var copyBtn=''; + var editBtn=role==='user'?'':''; + var regenBtn=role==='assistant'?'':''; // ── Speak button per assistant message — manual TTS playback regardless of global toggle - var speakBtn=role==='assistant'?'':''; - div.innerHTML=copyBtn+editBtn+regenBtn+speakBtn+'
'+formatMsg(content)+'
'+time+'
'; + var speakBtn=role==='assistant'?'':''; + // One inline action row beneath the bubble: timestamp + regenerate / edit / copy / speak (role-gated) + var actions=regenBtn+editBtn+copyBtn+speakBtn; + div.innerHTML='
'+formatMsg(content)+'
'+time+''+actions+'
'; document.getElementById('messages').appendChild(div); scrollBottom(); // ── Auto-speak via Kokoro when global "Voice Replies" toggle is ON