From 9f67c9e5dc4d2b9ec555f77c3c400baad5227e47 Mon Sep 17 00:00:00 2001 From: Nicolas Varrot Date: Thu, 12 Feb 2026 23:22:05 +0000 Subject: [PATCH] fix: metadata viewer popup clipped by overflow-hidden parent The MetadataViewer popup was rendered inside the message bubble which has overflow-hidden, causing the popup to be invisible. Fix by using createPortal to render the popup directly on document.body with fixed positioning. Also adds click-outside-to-close behavior. Closes feedback #46 --- FEEDBACK.md | 9 +++++++++ src/components/ChatMessage.tsx | 33 ++++++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/FEEDBACK.md b/FEEDBACK.md index 74bf2c5..d9f6024 100644 --- a/FEEDBACK.md +++ b/FEEDBACK.md @@ -491,3 +491,12 @@ - Check the gateway WebSocket session/handshake data for avatar info - Fallback to the current default icon if no avatar is configured - Should also appear in the header next to the agent name + +## Item #46 +- **Date:** 2026-02-12 +- **Priority:** high +- **Status:** in-progress +- **Description:** Bug: metadata viewer (ℹ️ button) doesn't work + - Clicking the info button on messages does nothing — no panel appears + - Introduced in v1.15.0 (commit `b4813f0`) + - Fix the click handler / panel display logic diff --git a/src/components/ChatMessage.tsx b/src/components/ChatMessage.tsx index 551c927..015dc33 100644 --- a/src/components/ChatMessage.tsx +++ b/src/components/ChatMessage.tsx @@ -1,5 +1,6 @@ -import { useState, useCallback } from 'react'; +import { useState, useCallback, useRef, useEffect } from 'react'; +import { createPortal } from 'react-dom'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import remarkBreaks from 'remark-breaks'; @@ -242,11 +243,32 @@ function CopyButton({ text }: { text: string }) { function MetadataViewer({ metadata }: { metadata?: Record }) { const [open, setOpen] = useState(false); + const btnRef = useRef(null); + const panelRef = useRef(null); + const [pos, setPos] = useState<{ top: number; left: number } | null>(null); + + useEffect(() => { + if (!open) return; + const btn = btnRef.current; + if (btn) { + const r = btn.getBoundingClientRect(); + setPos({ top: r.top - 4, left: r.left }); + } + const handleClick = (e: MouseEvent) => { + if (panelRef.current && !panelRef.current.contains(e.target as Node) && !btnRef.current?.contains(e.target as Node)) { + setOpen(false); + } + }; + document.addEventListener('mousedown', handleClick); + return () => document.removeEventListener('mousedown', handleClick); + }, [open]); + if (!metadata || Object.keys(metadata).length === 0) return null; return (
- {open && ( -
+ {open && pos && createPortal( +
{Object.entries(metadata).map(([k, v]) => (
{k}: {typeof v === 'object' ? JSON.stringify(v) : String(v)}
))} -
+
, + document.body )}
); @@ -337,7 +360,7 @@ export function ChatMessageComponent({ message, onRetry }: { message: ChatMessag {!isUser && !message.isStreaming && getPlainText(message).trim() && ( )} -
+
{/* Retry button (user messages only) */}