From 287fd88fe4dd4f7b3c516ad3ca3146c51e23f222 Mon Sep 17 00:00:00 2001
From: chengyongru <61816729+chengyongru@users.noreply.github.com>
Date: Tue, 4 Aug 2026 16:40:41 +0800
Subject: [PATCH] fix(webui): refine inline token highlights (#5241)
---
webui/src/components/CliAppMentionText.tsx | 1 +
webui/src/components/InlineTokenHighlight.tsx | 19 ++----------
webui/src/components/MarkdownTextRenderer.tsx | 1 +
webui/src/components/SlashCommandText.tsx | 1 -
webui/src/components/UserMessageText.tsx | 3 +-
webui/src/globals.css | 4 +--
.../src/tests/markdown-text-renderer.test.tsx | 3 ++
webui/src/tests/message-bubble.test.tsx | 29 +++++++++++--------
webui/src/tests/thread-composer.test.tsx | 4 +--
webui/src/tests/thread-shell.test.tsx | 2 +-
10 files changed, 31 insertions(+), 36 deletions(-)
diff --git a/webui/src/components/CliAppMentionText.tsx b/webui/src/components/CliAppMentionText.tsx
index 14a463d83..2d9697893 100644
--- a/webui/src/components/CliAppMentionText.tsx
+++ b/webui/src/components/CliAppMentionText.tsx
@@ -191,6 +191,7 @@ export function SessionMentionToken({
{token}
diff --git a/webui/src/components/InlineTokenHighlight.tsx b/webui/src/components/InlineTokenHighlight.tsx
index 1985b0705..818cabbd0 100644
--- a/webui/src/components/InlineTokenHighlight.tsx
+++ b/webui/src/components/InlineTokenHighlight.tsx
@@ -2,7 +2,7 @@ import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
-export const INLINE_TOKEN_HIGHLIGHT_COLOR = "hsl(var(--inline-token-highlight))";
+export const INLINE_TOKEN_HIGHLIGHT_COLOR = "var(--inline-token-highlight)";
export function InlineTokenHighlight({
children,
@@ -22,25 +22,12 @@ export function InlineTokenHighlight({
data-testid={testId}
title={title}
className={cn(
- "relative inline transition-[color,text-shadow] duration-150",
+ "relative inline font-[550] transition-colors duration-150",
className,
)}
- style={{
- color,
- textShadow: `0 0 10px ${alphaColor(color, 24)}`,
- }}
+ style={{ color }}
>
{children}
);
}
-
-function alphaColor(color: string, percent: number): string {
- if (/^#[0-9a-f]{6}$/i.test(color)) {
- const alpha = Math.round((percent / 100) * 255)
- .toString(16)
- .padStart(2, "0");
- return `${color}${alpha}`;
- }
- return `color-mix(in srgb, ${color} ${percent}%, transparent)`;
-}
diff --git a/webui/src/components/MarkdownTextRenderer.tsx b/webui/src/components/MarkdownTextRenderer.tsx
index 5e36d64ee..970f07140 100644
--- a/webui/src/components/MarkdownTextRenderer.tsx
+++ b/webui/src/components/MarkdownTextRenderer.tsx
@@ -618,6 +618,7 @@ export default function MarkdownTextRenderer({
{markdownChildren}
diff --git a/webui/src/components/SlashCommandText.tsx b/webui/src/components/SlashCommandText.tsx
index c6c3fdf2c..622c5e5be 100644
--- a/webui/src/components/SlashCommandText.tsx
+++ b/webui/src/components/SlashCommandText.tsx
@@ -14,7 +14,6 @@ export function SlashCommandText({
{command}
diff --git a/webui/src/components/UserMessageText.tsx b/webui/src/components/UserMessageText.tsx
index bc1735460..0129988c0 100644
--- a/webui/src/components/UserMessageText.tsx
+++ b/webui/src/components/UserMessageText.tsx
@@ -91,9 +91,8 @@ export function UserMessageText({
testId={`message-skill-reference-${segment.name.toLowerCase()}`}
title={t("message.skill", { name: segment.name })}
color={INLINE_TOKEN_HIGHLIGHT_COLOR}
- className="font-medium"
>
- {segment.text}
+ {segment.name}
);
return (
diff --git a/webui/src/globals.css b/webui/src/globals.css
index c5b2c13db..441088f1f 100644
--- a/webui/src/globals.css
+++ b/webui/src/globals.css
@@ -32,7 +32,7 @@
--border: 40 8% 90.5%;
--input: 40 8% 90.5%;
--ring: 0 0% 3.9%;
- --inline-token-highlight: 221 70% 50%;
+ --inline-token-highlight: #ef8e30;
--radius: 0.4375rem;
--sidebar: 40 8% 96.8%;
--sidebar-foreground: 0 0% 3.9%;
@@ -66,7 +66,7 @@
--border: 0 0% 28%;
--input: var(--border);
--ring: 0 0% 83.1%;
- --inline-token-highlight: 217 92% 72%;
+ --inline-token-highlight: #ef8e30;
--sidebar: var(--card);
--sidebar-foreground: 0 0% 98%;
--sidebar-accent: var(--background);
diff --git a/webui/src/tests/markdown-text-renderer.test.tsx b/webui/src/tests/markdown-text-renderer.test.tsx
index c5670f768..6252025a8 100644
--- a/webui/src/tests/markdown-text-renderer.test.tsx
+++ b/webui/src/tests/markdown-text-renderer.test.tsx
@@ -23,6 +23,9 @@ describe("MarkdownTextRenderer", () => {
const link = screen.getByRole("link", { name: "收费设计" });
expect(link).toHaveAttribute("href", "#/chat/websocket%3Apricing");
expect(link).not.toHaveAttribute("target");
+ expect(link.getAttribute("style")).toContain(
+ "text-decoration-color: var(--inline-token-highlight)",
+ );
});
it("does not link non-WebUI session references", () => {
diff --git a/webui/src/tests/message-bubble.test.tsx b/webui/src/tests/message-bubble.test.tsx
index 6f2621a94..55ae0a0b9 100644
--- a/webui/src/tests/message-bubble.test.tsx
+++ b/webui/src/tests/message-bubble.test.tsx
@@ -226,12 +226,12 @@ describe("MessageBubble", () => {
const command = screen.getByTestId("message-slash-command");
expect(command).toHaveTextContent("/model");
expect(command).toHaveClass(
- "font-medium",
- "transition-[color,text-shadow]",
+ "font-[550]",
+ "transition-colors",
"duration-150",
);
- expect(command).not.toHaveClass("font-mono", "font-semibold");
- expect(command.getAttribute("style")).toContain("text-shadow");
+ expect(command).not.toHaveClass("font-mono");
+ expect(command.getAttribute("style")).not.toContain("text-shadow");
expect(command.getAttribute("style")).toContain("var(--inline-token-highlight)");
expect(command.className).not.toMatch(/(?:^|\s)(?:bg-|border|ring|rounded)/);
expect(command.parentElement).toHaveTextContent("/model gpt-5");
@@ -299,16 +299,17 @@ describe("MessageBubble", () => {
);
const skill = screen.getByTestId("message-skill-reference-github");
- expect(skill).toHaveTextContent("$github");
+ expect(skill).toHaveTextContent(/^github$/);
expect(skill).toHaveClass(
- "font-medium",
- "transition-[color,text-shadow]",
+ "font-[550]",
+ "transition-colors",
"duration-150",
);
+ expect(skill.getAttribute("style")).not.toContain("text-shadow");
expect(skill.getAttribute("style")).toContain("var(--inline-token-highlight)");
expect(skill.className).not.toMatch(/(?:^|\s)(?:bg-|border|ring|rounded)/);
expect(screen.getByTestId("message-cli-mention-zoom")).toHaveTextContent("@zoom");
- expect(skill.parentElement).toHaveTextContent("Ask $github to review this with @zoom");
+ expect(skill.parentElement).toHaveTextContent("Ask github to review this with @zoom");
});
it("highlights well-formed skill references and leaves a bare marker plain", () => {
@@ -321,13 +322,13 @@ describe("MessageBubble", () => {
render();
- expect(screen.getByTestId("message-skill-reference-unknown")).toHaveTextContent("$unknown");
+ expect(screen.getByTestId("message-skill-reference-unknown")).toHaveTextContent(/^unknown$/);
expect(screen.getByTestId("message-skill-reference-blocked-skill"))
- .toHaveTextContent("$blocked-skill");
+ .toHaveTextContent(/^blocked-skill$/);
const references = screen.getAllByTestId(/^message-skill-reference-/);
expect(references).toHaveLength(2);
expect(references[0].parentElement)
- .toHaveTextContent("Try $unknown or $blocked-skill and $");
+ .toHaveTextContent("Try unknown or blocked-skill and $");
});
it("renders fork control in completed assistant action rows", () => {
@@ -444,10 +445,11 @@ describe("MessageBubble", () => {
const token = screen.getByTestId("message-cli-mention-zoom");
expect(token).toHaveTextContent("@zoom");
expect(token).toHaveAttribute("title", "CLI app: Zoom");
+ expect(token).toHaveClass("font-[550]");
expect(token.className).not.toContain("rounded");
expect(token.className).not.toContain("px-");
expect(token.getAttribute("style")).toContain("color: #0B5CFF");
- expect(token.getAttribute("style")).toContain("text-shadow");
+ expect(token.getAttribute("style")).not.toContain("text-shadow");
expect(screen.getByTestId("message-cli-mention-logo-zoom")).toBeInTheDocument();
expect(screen.queryByTestId("message-cli-mention-krita")).not.toBeInTheDocument();
expect(screen.getByText(/not @krita/)).toBeInTheDocument();
@@ -531,6 +533,9 @@ describe("MessageBubble", () => {
expect(token).toHaveTextContent("@收费设计");
expect(token).toHaveAttribute("title", "Session: 收费设计");
expect(token.closest("a")).toHaveAttribute("href", "#/chat/websocket%3Apricing");
+ expect(token.closest("a")?.getAttribute("style")).toContain(
+ "text-decoration-color: var(--inline-token-highlight)",
+ );
});
it("copies completed assistant replies from the action row", async () => {
diff --git a/webui/src/tests/thread-composer.test.tsx b/webui/src/tests/thread-composer.test.tsx
index af99b1a20..ba384a3b7 100644
--- a/webui/src/tests/thread-composer.test.tsx
+++ b/webui/src/tests/thread-composer.test.tsx
@@ -1887,12 +1887,12 @@ describe("ThreadComposer", () => {
expect(input).toHaveValue("meeting in @gimp");
const token = screen.getByTestId("composer-cli-mention-gimp");
expect(token).toHaveTextContent("@gimp");
- expect(token.className).not.toContain("font-semibold");
+ expect(token).toHaveClass("font-[550]");
expect(token.className).not.toContain("zoom-in");
expect(token.className).not.toContain("px-");
expect(token.className).not.toContain("mx-");
expect(token.getAttribute("style")).toContain("color: #5C5543");
- expect(token.getAttribute("style")).toContain("text-shadow");
+ expect(token.getAttribute("style")).not.toContain("text-shadow");
expect(screen.queryByTestId("composer-cli-app-tray")).not.toBeInTheDocument();
const logo = screen.getByTestId("composer-cli-mention-logo-gimp");
expect(logo.className).toContain("top-1/2");
diff --git a/webui/src/tests/thread-shell.test.tsx b/webui/src/tests/thread-shell.test.tsx
index 50e290960..4405b8523 100644
--- a/webui/src/tests/thread-shell.test.tsx
+++ b/webui/src/tests/thread-shell.test.tsx
@@ -868,7 +868,7 @@ describe("ThreadShell", () => {
expectSendMessageWithTurn(client, "skill-reference", "Use $github for this"),
);
expect(screen.getByTestId("message-skill-reference-github"))
- .toHaveTextContent("$github");
+ .toHaveTextContent(/^github$/);
});
it("clears the old thread when the active session is removed", async () => {