diff --git a/frontend/src/features/agents/CustomAgentChatView.test.tsx b/frontend/src/features/agents/CustomAgentChatView.test.tsx index af38c524..de71147a 100644 --- a/frontend/src/features/agents/CustomAgentChatView.test.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.test.tsx @@ -12,7 +12,6 @@ import { hasProjectCliInteraction, } from "@/features/projects/projectCliInteractionActivity"; import { publishProjectIconChanged } from "@/features/projects/projectIcons"; -import { publishProjectThemeChanged } from "@/features/projects/projectTheme"; import { PluginCommandRegistry, PluginLayoutRegistry, @@ -217,16 +216,10 @@ describe("CustomAgentChatView", () => { 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)"); + expect(watermark.textContent).toBe("🐍"); + expect(watermark.parentElement?.className).toContain("project-cell-illustration-area"); 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.getPropertyValue("--project-illustration-tint")).toBe("#945DF0"); - }); + await waitFor(() => expect(watermark.textContent).toBe("⭐")); 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 e55f6f4d..89b0bbaa 100644 --- a/frontend/src/features/agents/CustomAgentChatView.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.tsx @@ -15,7 +15,6 @@ import { useRef, useState, type ClipboardEvent, - type CSSProperties, type KeyboardEvent, type MouseEvent as ReactMouseEvent, type RefObject, @@ -46,11 +45,6 @@ import { } from "@/features/projects/projectCliInteractionActivity"; import { Button, EntryTimestamp, Spinner, cn, normalizeTimestamp } from "@/shared"; import { onProjectIconChanged, ProjectIcon } from "@/features/projects/projectIcons"; -import { - accessibleProjectAccent, - onProjectThemeChanged, - validThemeColor, -} from "@/features/projects/projectTheme"; import type { ChatAttachmentInput, ChatDraftScope, @@ -1077,7 +1071,6 @@ export function CustomAgentChatView({ const [stopTerminalOverride, setStopTerminalOverride] = useState(false); const [error, setError] = useState(null); const [projectIcon, setProjectIcon] = useState(null); - const [projectThemeColor, setProjectThemeColor] = useState(null); const scrollRef = useRef(null); const composerRef = useRef(null); const chatRootRef = useRef(null); @@ -1135,21 +1128,14 @@ export function CustomAgentChatView({ void project?.readProjectSettings(projectId).then((settings) => { if (!cancelled) { setProjectIcon(settings.icon ?? null); - setProjectThemeColor(validThemeColor(settings.themeColor)); } }).catch(() => undefined); const unsubscribeIcon = onProjectIconChanged((change) => { if (change.projectId === projectId) setProjectIcon(change.icon); }); - const unsubscribeTheme = onProjectThemeChanged((change) => { - if (change.projectId === projectId) { - setProjectThemeColor(validThemeColor(change.themeColor)); - } - }); return () => { cancelled = true; unsubscribeIcon(); - unsubscribeTheme(); }; }, [project, projectId]); @@ -2789,28 +2775,6 @@ export function CustomAgentChatView({ data-testid="custom-agent-chat-view" className="custom-agent-chat-view relative z-0 flex h-full min-h-0 flex-col overflow-hidden bg-surface text-content" > -
)} -
-
- + - {opening && displayTurns.length === 0 ? ( -
- - - {externalSessionId ? t("custom.resume") : t("custom.opening")} - -
- ) : unavailableLinkedTranscript ? ( -
-

- {tr("La conversation liĂ©e n’a pas pu ĂȘtre restaurĂ©e.", "The linked conversation could not be restored.")} -

-

- {unavailableLinkedTranscript === "conversationNotFound" - ? tr("Cette conversation est introuvable dans le stockage disponible.", "This conversation cannot be found in available storage.") - : tr("Sa transcription n’est plus disponible aprùs la reprise.", "Its transcript is no longer available after restoration.")} -

-
- - -
-
- ) : displayTurns.length === 0 ? ( -

- {t("custom.empty")} -

- ) : ( - displayTurns.map((turn, index) => ( - - setExpandedProgressKeys((prev) => { - const next = new Set(prev); - if (next.has(key)) { - next.delete(key); - } else { - next.add(key); - } - return next; - }) - } +
+
+ - )) - )} +
+ {opening && displayTurns.length === 0 ? ( +
+ + + {externalSessionId ? t("custom.resume") : t("custom.opening")} + +
+ ) : unavailableLinkedTranscript ? ( +
+

+ {tr("La conversation liĂ©e n’a pas pu ĂȘtre restaurĂ©e.", "The linked conversation could not be restored.")} +

+

+ {unavailableLinkedTranscript === "conversationNotFound" + ? tr("Cette conversation est introuvable dans le stockage disponible.", "This conversation cannot be found in available storage.") + : tr("Sa transcription n’est plus disponible aprùs la reprise.", "Its transcript is no longer available after restoration.")} +

+
+ + +
+
+ ) : displayTurns.length === 0 ? ( +

+ {t("custom.empty")} +

+ ) : ( + displayTurns.map((turn, index) => ( + + setExpandedProgressKeys((prev) => { + const next = new Set(prev); + if (next.has(key)) { + next.delete(key); + } else { + next.add(key); + } + return next; + }) + } + /> + )) + )} +
{ expect(getByRole("img").dataset.projectIcon).toBe("none"); }); - it("requests a monochrome text presentation for watermark emoji only", () => { + it("keeps the full emoji artwork available for grayscale watermark rendering", () => { const { getByTestId } = render( <> @@ -57,8 +57,8 @@ describe("project emoji catalogue", () => { ); expect(getByTestId("product-icon").textContent).toBe("⭐"); - expect(getByTestId("watermark-icon").textContent).toBe("⭐\uFE0E"); - expect((getByTestId("watermark-icon") as HTMLElement).style.fontFamily).toContain("system-ui"); + expect(getByTestId("watermark-icon").textContent).toBe("⭐"); + expect((getByTestId("watermark-icon") as HTMLElement).style.fontFamily).toContain("Color Emoji"); }); }); diff --git a/frontend/src/features/projects/projectIcons.tsx b/frontend/src/features/projects/projectIcons.tsx index dda5ccad..5f065dae 100644 --- a/frontend/src/features/projects/projectIcons.tsx +++ b/frontend/src/features/projects/projectIcons.tsx @@ -210,9 +210,6 @@ export function ProjectIcon({ icon: ref, fallbackLabel, size = 32, className, st const label = entry?.label ?? fallbackInitial(ref, fallbackLabel); const numericSize = typeof size === "number" ? size : null; const glyph = entry?.glyph ?? fallbackInitial(ref, fallbackLabel); - const renderedGlyph = surface === "watermark" && entry - ? `${glyph.replace(/\uFE0F/g, "\uFE0E")}${glyph.includes("\uFE0F") ? "" : "\uFE0E"}` - : glyph; return ( - {renderedGlyph} + {glyph} ); } diff --git a/frontend/src/shared/styles/theme.css b/frontend/src/shared/styles/theme.css index 043d2dc1..446309ab 100644 --- a/frontend/src/shared/styles/theme.css +++ b/frontend/src/shared/styles/theme.css @@ -107,23 +107,21 @@ container-type: size; } + .project-cell-illustration-area { + container-type: size; + } + .project-cell-illustration { inset: 6px 4px; - color: transparent; - opacity: 0.045; + opacity: 0.085; } - .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. */ + /* Keep the operating system's full-colour emoji artwork as the source image: + grayscale preserves internal tones and details that a monochrome alpha + mask (for example color: transparent + text-shadow) would flatten. */ .project-cell-illustration__glyph { - color: transparent; - -webkit-text-fill-color: transparent; - text-shadow: 0 0 0 var(--project-illustration-tint, var(--color-muted)); + filter: grayscale(1) contrast(0.92); + font-size: min(calc(100cqw - 8px), calc(100cqh - 12px)) !important; } .project-cell-empty-label {