(
)}
{!isEditing && (
-
+
+
+
{canShowEditAction && (
@@ -704,7 +707,7 @@ export const UserMessageItem = React.memo(
className="user-message-item__rollback-btn"
onClick={handleRollback}
disabled={!canRollback}
- title={rollbackTooltip}
+ aria-label={rollbackTooltip}
>
{sessionMutation?.kind === 'rollback' && sessionMutation.targetTurnId === turnId ? (
diff --git a/src/web-ui/src/shared/ui/OverflowText.test.tsx b/src/web-ui/src/shared/ui/OverflowText.test.tsx
index 7843300bb6..5acdecfdba 100644
--- a/src/web-ui/src/shared/ui/OverflowText.test.tsx
+++ b/src/web-ui/src/shared/ui/OverflowText.test.tsx
@@ -4,6 +4,7 @@ import React, { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ListboxOption, OverflowText, Select, Tooltip } from '@openbitfun/ui';
+import { CommandToolCard } from '@openbitfun/ui/flow-chat';
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
@@ -74,6 +75,41 @@ describe('overflow text full-content access', () => {
expect(button.getAttribute('aria-describedby')).toBe('help');
});
+ it('keeps the command tooltip open when portal enter precedes native trigger leave', () => {
+ render();
+ expect(host.querySelector('[title]')).toBeNull();
+ const label = host.querySelector('[data-openbitfun-part="command"] [data-overflow]')!;
+ const trigger = label.closest('[data-overflow-trigger]') ?? label;
+ hover(trigger);
+ reveal();
+ const popup = tooltip()!;
+ expect(popup.textContent).toBe(longLabel);
+ act(() => {
+ popup.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, relatedTarget: trigger }));
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { relatedTarget: popup }));
+ });
+ act(() => vi.advanceTimersByTime(1000));
+ expect(tooltip()).toBe(popup);
+ act(() => popup.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, relatedTarget: document.body })));
+ act(() => vi.advanceTimersByTime(500));
+ expect(tooltip()).toBeNull();
+ });
+
+ it('does not toggle the command card when clicking inside its tooltip', () => {
+ const onToggle = vi.fn();
+ render();
+ const label = host.querySelector('[data-openbitfun-part="command"] [data-overflow]')!;
+ hover(label.closest('[data-overflow-trigger]') ?? label);
+ reveal();
+ const popup = tooltip()!;
+ act(() => popup.querySelector('[data-openbitfun-part="body"]')!.click());
+ expect(onToggle).not.toHaveBeenCalled();
+ expect(tooltip()).toBe(popup);
+ act(() => label.click());
+ expect(onToggle).toHaveBeenCalledOnce();
+ });
+
it('reveals rich labels and plain-text arrays without waiting for a marquee', () => {
render(