fix: session tooltip click-through, copy buttons stopPropagation, rename spacebar (v1.64.2)

This commit is contained in:
Nicolas Varrot
2026-02-15 20:39:28 +00:00
parent 5ee2d119a5
commit 414e2ccae5
4 changed files with 28 additions and 16 deletions

View File

@@ -67,15 +67,17 @@ export function CodeBlock(props: HTMLAttributes<HTMLPreElement>) {
const hasEnoughLines = lines.length > LINE_THRESHOLD;
const isCollapsible = lines.length > COLLAPSE_THRESHOLD;
const handleCopy = useCallback(() => {
if (typeof code === 'string') {
copyToClipboard(code).then((ok) => {
if (ok) {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
});
}
const handleCopy = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
const text = typeof code === 'string' ? code : '';
if (!text) return;
copyToClipboard(text).then((ok) => {
if (ok) {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
});
}, [code]);
const toggleLineNumbers = useCallback(() => {

View File

@@ -1,4 +1,4 @@
import { useCallback, useState, useRef, useEffect } from 'react';
import { useCallback, useState, useRef, useEffect, forwardRef } from 'react';
import { Menu, Sparkles, LogOut, Cpu, Bot, Download, Minimize2, Info, Copy, Check, Settings } from 'lucide-react';
import type { ConnectionStatus, Session, ChatMessage } from '../types';
import { useT } from '../hooks/useLocale';
@@ -28,11 +28,17 @@ export function Header({ status, sessionKey, onToggleSidebar, activeSessionData,
const [settingsOpen, setSettingsOpen] = useState(false);
const sessionInfoRef = useRef<HTMLDivElement>(null);
// Close popover on outside click
const popoverRef = useRef<HTMLDivElement>(null);
// Close popover on outside click — check both the trigger area AND the popover itself
useEffect(() => {
if (!showSessionInfo) return;
const handler = (e: MouseEvent) => {
if (sessionInfoRef.current && !sessionInfoRef.current.contains(e.target as Node)) {
const target = e.target as Node;
if (
sessionInfoRef.current && !sessionInfoRef.current.contains(target) &&
popoverRef.current && !popoverRef.current.contains(target)
) {
setShowSessionInfo(false);
}
};
@@ -74,7 +80,7 @@ export function Header({ status, sessionKey, onToggleSidebar, activeSessionData,
</span>
</button>
{showSessionInfo && activeSessionData && (
<SessionInfoPopover session={activeSessionData} sessionKey={sessionKey} messageCount={messages?.length ?? 0} onClose={() => setShowSessionInfo(false)} />
<SessionInfoPopover ref={popoverRef} session={activeSessionData} sessionKey={sessionKey} messageCount={messages?.length ?? 0} onClose={() => setShowSessionInfo(false)} />
)}
</div>
<div className="flex items-center gap-2 text-sm">
@@ -169,7 +175,7 @@ function CopyField({ value }: { value: string }) {
);
}
function SessionInfoPopover({ session, sessionKey, messageCount, onClose }: { session: Session; sessionKey: string; messageCount: number; onClose: () => void }) {
const SessionInfoPopover = forwardRef<HTMLDivElement, { session: Session; sessionKey: string; messageCount: number; onClose: () => void }>(function SessionInfoPopover({ session, sessionKey, messageCount, onClose }, ref) {
const t = useT();
const rows: Array<{ label: string; value: string; copyable?: boolean }> = [
{ label: t('sessionInfo.sessionKey'), value: sessionKey, copyable: true },
@@ -191,6 +197,7 @@ function SessionInfoPopover({ session, sessionKey, messageCount, onClose }: { se
return (
<div
ref={ref}
className="absolute top-full left-0 mt-2 z-50 w-72 rounded-xl border border-pc-border bg-[var(--pc-bg-surface)] shadow-xl backdrop-blur-xl animate-in fade-in slide-in-from-top-2 duration-200"
role="dialog"
aria-label={t('header.sessionInfo')}
@@ -212,7 +219,7 @@ function SessionInfoPopover({ session, sessionKey, messageCount, onClose }: { se
</div>
</div>
);
}
});
function CompactButton({ sessionKey, onCompact }: { sessionKey: string; onCompact: (key: string) => Promise<boolean> }) {
const [compacting, setCompacting] = useState(false);

View File

@@ -543,6 +543,7 @@ export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit,
onChange={(e) => setRenameValue(e.target.value)}
onBlur={commitRename}
onKeyDown={(e) => {
e.stopPropagation();
if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
if (e.key === 'Escape') { e.preventDefault(); cancelRename(); }
}}

View File

@@ -153,7 +153,9 @@ function WrapToggle({ wrap, onToggle }: { wrap: boolean; onToggle: () => void })
/** Small copy-to-clipboard button for tool call content blocks. */
function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
const handleCopy = useCallback(() => {
const handleCopy = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
copyToClipboard(text).then((ok) => {
if (ok) {
setCopied(true);