diff --git a/desktop/playwright.config.ts b/desktop/playwright.config.ts
index 7d06c4da91..fb2d2b85a0 100644
--- a/desktop/playwright.config.ts
+++ b/desktop/playwright.config.ts
@@ -21,6 +21,7 @@ export default defineConfig({
testMatch: [
"**/smoke.spec.ts",
"**/sidebar-offcanvas-rail.spec.ts",
+ "**/tooltip-semantics.spec.ts",
"**/search-scope-screenshots.spec.ts",
"**/onboarding-docked-cta-screenshots.spec.ts",
"**/identity-key-help.spec.ts",
diff --git a/desktop/src/features/agents/ui/RestartDiffBadge.tsx b/desktop/src/features/agents/ui/RestartDiffBadge.tsx
index 1bdb781226..e15a57fde4 100644
--- a/desktop/src/features/agents/ui/RestartDiffBadge.tsx
+++ b/desktop/src/features/agents/ui/RestartDiffBadge.tsx
@@ -88,8 +88,8 @@ function ChangeDescription({ change }: { change: RestartChange }) {
const TOOLTIP_CAP = 6;
/**
- * `tooltip` — renders inside the dark `bg-primary` tooltip; uses
- * `text-primary-foreground` variants for contrast there.
+ * `tooltip` — renders inside the semantic secondary tooltip surface; uses
+ * `text-secondary-foreground` variants for contrast there.
* `inline` — renders inside the amber Runtime banner or other light
* surfaces; inherits foreground from the container instead.
*/
@@ -107,10 +107,10 @@ function DiffList({
cap !== undefined && entries.length > cap ? entries.length - cap : 0;
const valueClass =
- variant === "tooltip" ? "text-primary-foreground/80" : "text-foreground";
+ variant === "tooltip" ? "text-secondary-foreground/80" : "text-foreground";
const overflowClass =
variant === "tooltip"
- ? "text-primary-foreground/60"
+ ? "text-secondary-foreground/60"
: "text-muted-foreground";
return (
@@ -180,7 +180,7 @@ export function RestartDiffBadge({
Config changed since last start:
-
+
{autoRestartEnabled ? AUTO_RESTART_ON_BLURB : AUTO_RESTART_OFF_BLURB}
diff --git a/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx b/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx
index 512ee6899f..a3bad85629 100644
--- a/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx
+++ b/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx
@@ -143,7 +143,7 @@ export function AddChannelBotTeamsSection({
{team.name}
{team.description ? (
-
+
{team.description}
) : null}
@@ -153,15 +153,15 @@ export function AddChannelBotTeamsSection({
inChannelPersonaIds?.has(persona.id) ?? false;
return (
-
+
{persona.displayName}
{personaInChannel ? (
diff --git a/desktop/src/features/projects/ui/ProjectAuthorIdentity.tsx b/desktop/src/features/projects/ui/ProjectAuthorIdentity.tsx
index 8126292894..21a70d6dcb 100644
--- a/desktop/src/features/projects/ui/ProjectAuthorIdentity.tsx
+++ b/desktop/src/features/projects/ui/ProjectAuthorIdentity.tsx
@@ -60,7 +60,7 @@ export function ProjectAuthorIdentity({
/>
{label}
-
+
{roleLabel}
diff --git a/desktop/src/shared/ui/tooltip.tsx b/desktop/src/shared/ui/tooltip.tsx
index 6af9e48188..6fa5891973 100644
--- a/desktop/src/shared/ui/tooltip.tsx
+++ b/desktop/src/shared/ui/tooltip.tsx
@@ -34,7 +34,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
- "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-tooltip-content-transform-origin)",
+ "z-50 overflow-hidden rounded-md bg-secondary px-3 py-1.5 text-xs text-secondary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-tooltip-content-transform-origin)",
className,
)}
{...props}
diff --git a/desktop/tests/e2e/tooltip-semantics.spec.ts b/desktop/tests/e2e/tooltip-semantics.spec.ts
new file mode 100644
index 0000000000..2b6f68348e
--- /dev/null
+++ b/desktop/tests/e2e/tooltip-semantics.spec.ts
@@ -0,0 +1,88 @@
+import { expect, test, type Locator, type Page } from "@playwright/test";
+
+import { installMockBridge } from "../helpers/bridge";
+import { waitForAnimations } from "../helpers/animations";
+
+const THEMES = ["buzz", "catppuccin-mocha"] as const;
+
+async function seedTheme(page: Page, theme: (typeof THEMES)[number]) {
+ await page.addInitScript((value) => {
+ window.localStorage.setItem("buzz-theme", value);
+ }, theme);
+}
+
+async function expectSecondarySurface(tooltip: Locator) {
+ const colors = await tooltip.evaluate((element) => {
+ const sample = document.createElement("div");
+ sample.style.cssText =
+ "position:fixed;visibility:hidden;background:hsl(var(--secondary));color:hsl(var(--secondary-foreground))";
+ document.body.append(sample);
+ const tooltipStyle = getComputedStyle(element);
+ const sampleStyle = getComputedStyle(sample);
+ const result = {
+ background: tooltipStyle.backgroundColor,
+ color: tooltipStyle.color,
+ expectedBackground: sampleStyle.backgroundColor,
+ expectedColor: sampleStyle.color,
+ };
+ sample.remove();
+ return result;
+ });
+
+ expect(colors.background).toBe(colors.expectedBackground);
+ expect(colors.color).toBe(colors.expectedColor);
+}
+
+for (const theme of THEMES) {
+ test(`simple and rich tooltips use the secondary surface — ${theme}`, async ({
+ page,
+ }) => {
+ await seedTheme(page, theme);
+ await installMockBridge(page, {
+ personas: [
+ {
+ id: "tooltip-reviewer",
+ displayName: "Tooltip Reviewer",
+ systemPrompt: "Review tooltip contrast.",
+ },
+ ],
+ teams: [
+ {
+ id: "tooltip-team",
+ name: "Contrast Crew",
+ description: "Checks semantic surface contrast",
+ personaIds: ["tooltip-reviewer"],
+ },
+ ],
+ });
+ await page.goto("/");
+
+ await page.getByTestId("channel-random").click();
+ await page.getByTestId("channel-members-trigger").hover();
+ const simpleTooltip = page.getByRole("tooltip");
+ await expect(simpleTooltip).toHaveText("Channel members");
+ await expectSecondarySurface(simpleTooltip);
+
+ await page.mouse.move(0, 0);
+ await page.getByTestId("channel-intro-action-create-agent").click();
+ const dialog = page.getByTestId("add-channel-bot-dialog");
+ await expect(dialog).toBeVisible();
+ await dialog.getByRole("button", { name: "Contrast Crew" }).hover();
+
+ const richTooltip = page.getByRole("tooltip");
+ await expect(richTooltip).toContainText("Checks semantic surface contrast");
+ await expect(richTooltip).toContainText("Tooltip Reviewer");
+ await expectSecondarySurface(richTooltip);
+ await expect(
+ richTooltip.getByText("Checks semantic surface contrast"),
+ ).toHaveClass(/text-secondary-foreground\/80/);
+ await expect(richTooltip.getByText("Tooltip Reviewer")).toHaveClass(
+ /text-secondary-foreground/,
+ );
+
+ await waitForAnimations(page);
+ await dialog.screenshot({
+ path: `test-results/tooltip-semantics/${theme}-team-tooltip.png`,
+ });
+ });
+}