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 () => {