Conversation
The default user avatar filled with a literal rgb(121, 137, 255) in Avatar, MessageIcon and the endpoint Icon, and agent and assistant avatars sat on surface-secondary in light and a dark: surface-tertiary override, so no theme could repaint either. avatar-fill and avatar-text paint the default avatar and its glyph, and avatar-placeholder paints the backdrop behind an agent or assistant image. Every bundled theme reproduces today's pixels. A theme that sets only its surfaces keeps the placeholder on the surface its mode used before the role existed, both when resolved and through the legacy applyTheme path. ClickHouse takes Click UI's avatar fill and text and its muted background.
The sidebar account avatar falls back to the default avatar when its image fails; with no theme it keeps rgb(121, 137, 255) in both modes and the placeholder keeps the surface its mode used, and under ClickHouse both take the Click UI values.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
The sidebar Avatar, the only default avatar the chat renders, still inked its glyph in text-primary, so a theme's avatar-text never reached it and ClickHouse drew a dark glyph where Click UI draws white. Avatar now reads avatar-text, which defaults to each bundled theme's primary text and follows rgb-text-primary in a theme that does not set it, through resolveTheme and the legacy applyTheme path, so every rendered default avatar keeps its pixels.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 640e3930e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| boxShadow: 'rgba(240, 246, 252, 0.1) 0px 0px 0px 1px', | ||
| }} | ||
| className={`relative flex items-center justify-center rounded-full p-1 text-text-primary ${className}`} | ||
| className={`bg-avatar-fill text-text-primary relative flex items-center justify-center rounded-full p-1 ${className}`} |
There was a problem hiding this comment.
Apply the avatar text role to the shared fallback
When the shared Avatar falls back to its default glyph, including the sidebar account avatar exercised by the new e2e scenario, it still uses text-text-primary, so rgb-avatar-text never controls that glyph. ClickHouse therefore renders its light/dark primary text (#161517/#ffffff) rather than the declared avatar text (#ffffff/#1f1f1c), while the new contrast test only checks the unused token pair. Use the dedicated role here, splitting the role or adjusting its defaults if preserving the stock component's existing pixels is required.
AGENTS.md reference: AGENTS.md:L92-L97
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 6c64ae0: Avatar now inks its glyph with text-avatar-text, and avatar-text defaults to each bundled theme's primary text (what the glyph painted before) and follows rgb-text-primary in a theme that does not set it. clickhouse-avatar-follows-click-ui asserts the sidebar glyph takes Click UI's avatar text in both modes; the default stays pixel-identical.
| const avatarPlaceholder = | ||
| mode === 'dark' ? colors['rgb-surface-tertiary'] : colors['rgb-surface-secondary']; |
There was a problem hiding this comment.
Infer dark mode for legacy applyTheme callers
For existing consumers of the publicly exported applyTheme(colors, root, base?) API, the new optional mode argument is necessarily omitted and mapColors defaults it to light. On a root already carrying .dark, a legacy theme that defines both surfaces now writes --avatar-placeholder from rgb-surface-secondary, whereas the pre-change dark:bg-surface-tertiary selected the tertiary value. The in-repo provider passes the new argument, but external callers silently regress; derive the default from the target root/base or otherwise preserve the old signature's dark behavior.
AGENTS.md reference: AGENTS.md:L42-L45
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b090466: applyTheme falls back to the root's dark class when no mode is passed; the new spec 'reads the mode off the root when a caller does not pass it' fails without it.
applyTheme is exported, and a caller that applies a legacy palette in dark mode without the new mode argument derived the avatar placeholder from the light surface. It now falls back to the root's dark class.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b09046610c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| mapColors(themeRGB, base).forEach(([property, value]) => { | ||
| const activeMode = mode ?? (root.classList.contains('dark') ? 'dark' : 'light'); |
There was a problem hiding this comment.
Infer legacy mode from ancestors or the supplied base
For a legacy three-argument caller that scopes the exported applyTheme(colors, root, darkTheme) to an element beneath <html class="dark">, this check sees no class on root and derives light mode. It consequently writes --avatar-placeholder from rgb-surface-secondary, whereas the former dark:bg-surface-tertiary matched the dark ancestor. The fresh evidence after the prior applyTheme review is that the implemented fallback checks only the target itself, not its ancestors or the already-supplied dark base; infer from those as well when mode is omitted.
AGENTS.md reference: AGENTS.md:L42-L45
Useful? React with 👍 / 👎.
| --avatar-text: 33 33 33; | ||
| --avatar-placeholder: 247 247 248; |
There was a problem hiding this comment.
Preserve CSS-variable fallback links for legacy themes
For consumers that theme through the documented CSS properties—such as overriding --text-primary, --surface-secondary, and --surface-tertiary after importing @librechat/client/theme.css—these hard-coded defaults leave the avatars on LibreChat's stock colors. Before this change the shared avatar read --text-primary and the image backdrops read the surface roles directly, while the new JavaScript fallbacks never run for CSS-only themes. Define these defaults in terms of the legacy source variables, using the tertiary surface in .dark, so existing CSS themes continue to repaint the same elements.
AGENTS.md reference: AGENTS.md:L42-L45
Useful? React with 👍 / 👎.
Summary
The default user avatar filled with a literal
rgb(121, 137, 255)inAvatar, the shareMessageIconand the endpointIcon, and agent and assistant avatars sat onsurface-secondarywith adark:surface-tertiaryoverride, so no theme could repaint either and the ClickHouse theme kept LibreChat's purple.This adds three color roles next to
brand-purple:avatar-fillandavatar-textfor the default avatar and its glyph, andavatar-placeholderfor the backdrop behind an agent or assistant image. Every bundled theme reproduces today's pixels. A theme that sets only its surfaces keeps the placeholder on the surface its mode used before, both throughresolveThemeand through the legacyapplyThemepath, which now receives the mode. ClickHouse takes Click UI'savatar.color.background.defaultandavatar.color.text.defaultand its muted background, and the Click UI parity spec cites those tokens.avatar-textdefaults to each bundled theme's primary text, which is what the sidebarAvatarinked before, and followsrgb-text-primaryin a theme that does not set it. The chat and share layouts do not render a user message icon, so the sidebar account avatar is the default avatar users see, and its pixels are unchanged in every bundled theme. The bundled dark default keeps its 2.6:1 glyph contrast; raising it is a design change tracked in berry-13#207.Closes berry-13#194
Type of change
Testing
Tested environments/configuration: mock e2e harness (Chromium), jest (jsdom), default and ClickHouse themes in light and dark.
Automated tests:
clickhouse-theme.spec.ts:default-avatar-keeps-its-fill(rgb(121, 137, 255), the old glyph ink and the old placeholder surface in both modes with no theme) andclickhouse-avatar-follows-click-ui; both pass.packages/clienttheme suites: 425 passed, including registry andapplyThemefallback specs and the glyph contrast checks.clientsuites: 6341 passed.tsc --noEmitforpackages/client,clientandpackages/data-provider; ESLint, Prettier and import order clean.Screenshots / recordings
Sidebar account avatar with its image failing, so the default avatar draws. The default theme is pixel-identical before and after; ClickHouse moves from LibreChat's purple to Click UI's avatar fill and glyph.