diff --git a/frontend/src/features/agents/CustomAgentChatView.test.tsx b/frontend/src/features/agents/CustomAgentChatView.test.tsx index 480ef894..0fef93de 100644 --- a/frontend/src/features/agents/CustomAgentChatView.test.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.test.tsx @@ -212,16 +212,19 @@ describe("CustomAgentChatView", () => { const watermark = await screen.findByTestId("project-icon-watermark"); expect(watermark.style.pointerEvents).toBe("none"); expect(watermark.getAttribute("aria-hidden")).toBe("true"); - expect((watermark.firstElementChild as HTMLElement | null)?.style.width).toBe("92%"); - expect((watermark.firstElementChild as HTMLElement | null)?.style.height).toBe("92%"); + expect(watermark.className).toContain("project-cell-illustration"); + expect((watermark.firstElementChild as HTMLElement | null)?.style.width).toBe("100%"); + expect((watermark.firstElementChild as HTMLElement | null)?.style.height).toBe("100%"); + expect((watermark.firstElementChild as HTMLElement | null)?.className).toContain("project-cell-illustration__glyph"); expect(watermark.textContent).toBe("🐍\uFE0E"); expect(watermark.dataset.colorMode).toBe("monochrome"); + expect(watermark.style.getPropertyValue("--project-illustration-tint")).toBe("var(--color-muted, #9aa7b4)"); publishProjectIconChanged({ projectId: "project-1", icon: { set: "emoji", name: "star" } }); await waitFor(() => expect(watermark.textContent).toBe("⭐\uFE0E")); publishProjectThemeChanged({ projectId: "project-1", themeColor: "#7c3aed" }); await waitFor(() => { expect(watermark.dataset.colorMode).toBe("project"); - expect(watermark.style.color).toBe("rgb(124, 58, 237)"); + expect(watermark.style.getPropertyValue("--project-illustration-tint")).toBe("#945DF0"); }); expect(screen.getByTestId("custom-agent-chat-scroll").className).toContain("z-10"); expect(screen.getByTestId("custom-agent-chat-composer").className).toContain("bg-raised/40"); diff --git a/frontend/src/features/agents/CustomAgentChatView.tsx b/frontend/src/features/agents/CustomAgentChatView.tsx index ceb45662..366a5004 100644 --- a/frontend/src/features/agents/CustomAgentChatView.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.tsx @@ -15,6 +15,7 @@ import { useRef, useState, type ClipboardEvent, + type CSSProperties, type KeyboardEvent, type MouseEvent as ReactMouseEvent, type RefObject, @@ -45,7 +46,11 @@ import { } from "@/features/projects/projectCliInteractionActivity"; import { Button, EntryTimestamp, Spinner, cn, normalizeTimestamp } from "@/shared"; import { onProjectIconChanged, ProjectIcon } from "@/features/projects/projectIcons"; -import { onProjectThemeChanged, validThemeColor } from "@/features/projects/projectTheme"; +import { + accessibleProjectAccent, + onProjectThemeChanged, + validThemeColor, +} from "@/features/projects/projectTheme"; import type { ChatAttachmentInput, ChatDraftScope, @@ -2504,26 +2509,28 @@ export function CustomAgentChatView({
) : displayTurns.length === 0 ? ( -

+

{t("custom.empty")}

) : ( diff --git a/frontend/src/shared/styles/theme.css b/frontend/src/shared/styles/theme.css index 11406731..76fa0f02 100644 --- a/frontend/src/shared/styles/theme.css +++ b/frontend/src/shared/styles/theme.css @@ -103,6 +103,51 @@ } @layer components { + .custom-agent-chat-view { + container-type: size; + } + + .project-cell-illustration { + inset: 6px 4px; + color: transparent; + opacity: 0.045; + } + + .project-cell-illustration[data-color-mode="project"] { + opacity: 0.065; + } + + /* Emoji fonts may ignore the text-variation selector and retain their own + palette. Painting a zero-offset text shadow through the glyph alpha mask + guarantees one semantic project tint without depending on the host font. */ + .project-cell-illustration__glyph { + color: transparent; + -webkit-text-fill-color: transparent; + text-shadow: 0 0 0 var(--project-illustration-tint, var(--color-muted)); + } + + .project-cell-empty-label { + position: relative; + z-index: 1; + max-width: min(34rem, 88%); + margin: auto auto 8px; + text-align: center; + } + + /* Preserve the illustration when a split cell becomes constrained; the + explanatory copy is the first element to yield. */ + @container (max-width: 280px) { + .project-cell-empty-label { + display: none; + } + } + + @container (max-height: 240px) { + .project-cell-empty-label { + display: none; + } + } + .stopped-agent-cell { position: absolute; inset: 0;