From 29fdb7d628acdc2ccd008285723e538717f2ab9e Mon Sep 17 00:00:00 2001 From: chengyongru Date: Tue, 4 Aug 2026 18:17:52 +0800 Subject: [PATCH] fix(webui): align timestamp tooltip styles --- webui/src/components/MessageBubble.tsx | 55 ++++++++++++++++++++----- webui/src/tests/message-bubble.test.tsx | 19 ++++++--- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/webui/src/components/MessageBubble.tsx b/webui/src/components/MessageBubble.tsx index ce372f652..373a088cf 100644 --- a/webui/src/components/MessageBubble.tsx +++ b/webui/src/components/MessageBubble.tsx @@ -4,6 +4,7 @@ import { useMemo, useRef, useState, + type ComponentPropsWithoutRef, type ReactNode, } from "react"; import { @@ -80,6 +81,42 @@ function ForkArrowIcon({ className }: { className?: string }) { ); } +type MessageTimestampProps = Omit< + ComponentPropsWithoutRef<"time">, + "dateTime" | "title" +> & { + timestamp: number; + tooltipLabel: string; +}; + +function MessageTimestamp({ + timestamp, + tooltipLabel, + className, + children, + ...props +}: MessageTimestampProps) { + return ( + + + + + {tooltipLabel} + + ); +} + function MessageCopyButton({ content }: { content: string }) { const { t } = useTranslation(); const [copied, setCopied] = useState(false); @@ -307,14 +344,13 @@ export function MessageBubble({
{showCreatedAt ? ( - + ) : null} ) : null} {showAssistantTimestamp ? ( - + ) : null} {showAutomationTrigger ? ( { expect(onForkFromHere).toHaveBeenCalledTimes(1); }); - it("shows the assistant completion time in the former latency slot", () => { + it("shows the assistant completion time in the former latency slot", async () => { const completedAt = Date.UTC(2026, 6, 25, 12, 34, 56); const { container } = render( { const time = container.querySelector("[data-assistant-completed-at]"); expect(time).toHaveTextContent(formatMessageEndTime(completedAt)); expect(time).toHaveAttribute("dateTime", new Date(completedAt).toISOString()); - expect(time).toHaveAttribute("title", fmtDateTime(completedAt)); + expect(time).not.toHaveAttribute("title"); + expect(time).toHaveAttribute("tabIndex", "0"); expect(time).toHaveClass( + "cursor-help", "text-[11px]", "leading-none", "text-muted-foreground/70", "tabular-nums", ); + + fireEvent.pointerMove(time!); + expect(await screen.findByRole("tooltip")).toHaveTextContent(fmtDateTime(completedAt)); }); it("falls back to the assistant creation time when replay has no completion time", () => { @@ -391,11 +396,11 @@ describe("MessageBubble", () => { const time = container.querySelector("[data-message-timestamp]"); expect(time).toHaveTextContent(formatMessageEndTime(createdAt)); expect(time).toHaveAttribute("dateTime", new Date(createdAt).toISOString()); - expect(time).toHaveAttribute("title", fmtDateTime(createdAt)); + expect(time).not.toHaveAttribute("title"); expect(time).not.toHaveAttribute("data-assistant-completed-at"); }); - it("renders the creation time for user messages", () => { + it("renders the creation time for user messages", async () => { const createdAt = Date.UTC(2026, 6, 25, 12, 34, 56); const { container } = render( { const time = container.querySelector("[data-message-created-at]"); expect(time).toHaveTextContent(formatMessageEndTime(createdAt)); expect(time).toHaveAttribute("dateTime", new Date(createdAt).toISOString()); - expect(time).toHaveAttribute("title", fmtDateTime(createdAt)); + expect(time).not.toHaveAttribute("title"); + expect(time).toHaveAttribute("tabIndex", "0"); + + fireEvent.pointerMove(time!); + expect(await screen.findByRole("tooltip")).toHaveTextContent(fmtDateTime(createdAt)); }); it("does not infer completion time from the assistant creation timestamp", () => {