fix: improve light theme readability for tool badges and user bubbles

- Expose resolvedTheme in ThemeContext for theme-aware rendering
- Tool call badges: darker text colors and higher bg opacity in light theme
- User message bubbles: increased bg opacity and border strength in light theme
- Progress bars and send button already use accent CSS variables (no change needed)

Closes feedback #60
This commit is contained in:
Nicolas Varrot
2026-02-13 09:27:33 +00:00
parent 0a0acbe4e0
commit 5acc5a820d
5 changed files with 38 additions and 13 deletions

View File

@@ -7,6 +7,7 @@ import remarkBreaks from 'remark-breaks';
import rehypeHighlight from 'rehype-highlight';
import { rehypeHighlightOptions } from '../lib/highlight';
import type { ChatMessage as ChatMessageType, MessageBlock } from '../types';
import { useTheme } from '../hooks/useTheme';
import { ThinkingBlock } from './ThinkingBlock';
import { ThinkingIndicator } from './ThinkingIndicator';
import { CodeBlock } from './CodeBlock';
@@ -382,6 +383,8 @@ function SystemEventMessage({ message }: { message: ChatMessageType }) {
export function ChatMessageComponent({ message: rawMessage, onRetry, agentAvatarUrl }: { message: ChatMessageType; onRetry?: (text: string) => void; agentAvatarUrl?: string }) {
useLocale(); // re-render on locale change
const { resolvedTheme } = useTheme();
const isLight = resolvedTheme === 'light';
const [showRawJson, setShowRawJson] = useState(false);
// Strip webhook/hook scaffolding from user messages before rendering
@@ -446,7 +449,9 @@ export function ChatMessageComponent({ message: rawMessage, onRetry, agentAvatar
<div className={`min-w-0 max-w-[80%] ${isUser ? 'text-right' : ''}`}>
<div className={`group relative inline-block text-left rounded-3xl px-4 py-3 text-sm leading-relaxed max-w-full overflow-hidden ${
isUser
? 'bg-[rgba(var(--pc-accent-rgb),0.08)] text-pc-text border border-[rgba(var(--pc-accent-rgb),0.2)]'
? (isLight
? 'bg-[rgba(var(--pc-accent-rgb),0.12)] text-pc-text border border-[rgba(var(--pc-accent-rgb),0.3)]'
: 'bg-[rgba(var(--pc-accent-rgb),0.08)] text-pc-text border border-[rgba(var(--pc-accent-rgb),0.2)]')
: 'bg-pc-elevated/40 text-pc-text border border-pc-border shadow-[0_0_0_1px_rgba(255,255,255,0.03)]'
}`}>
{/* Action buttons */}

View File

@@ -2,6 +2,7 @@ import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
import { ChevronRight, ChevronDown, Check, Copy, WrapText, AlignLeft } from 'lucide-react';
import hljs from '../lib/highlight';
import { useT } from '../hooks/useLocale';
import { useTheme } from '../hooks/useTheme';
import { ImageBlock } from './ImageBlock';
import { useToolCollapse } from '../hooks/useToolCollapse';
@@ -31,13 +32,19 @@ const defaultRGB: ToolRGB = { r: 161, g: 161, b: 170 }; // zinc
function rgbStr(c: ToolRGB): string { return `${c.r},${c.g},${c.b}`; }
function getColorStyles(name: string): { badge: React.CSSProperties; text: React.CSSProperties; expand: React.CSSProperties; glow: string } {
function getColorStyles(name: string, isLight = false): { badge: React.CSSProperties; text: React.CSSProperties; expand: React.CSSProperties; glow: string } {
const c = toolRGBs[name] || defaultRGB;
const rgb = rgbStr(c);
// Use darker text and higher bg opacity in light theme for readability
const badgeBgAlpha = isLight ? 0.15 : 0.10;
const expandBgAlpha = isLight ? 0.08 : 0.05;
const textColor = isLight
? `rgb(${Math.round(c.r * 0.6)},${Math.round(c.g * 0.6)},${Math.round(c.b * 0.6)})`
: `rgb(${c.r},${c.g},${c.b})`;
return {
badge: { borderColor: `rgba(${rgb},0.3)`, backgroundColor: `rgba(${rgb},0.10)` },
text: { color: `rgb(${c.r},${c.g},${c.b})` },
expand: { borderColor: `rgba(${rgb},0.2)`, backgroundColor: `rgba(${rgb},0.05)` },
badge: { borderColor: `rgba(${rgb},0.3)`, backgroundColor: `rgba(${rgb},${badgeBgAlpha})` },
text: { color: textColor },
expand: { borderColor: `rgba(${rgb},0.2)`, backgroundColor: `rgba(${rgb},${expandBgAlpha})` },
glow: `shadow-[0_0_8px_rgba(${rgb},0.15)]`,
};
}
@@ -253,8 +260,9 @@ export function ToolCall({ name, input, result }: { name: string; input?: Record
const [open, setOpen] = useState(false);
const [wrap, setWrap] = useState(true);
const { globalState, version } = useToolCollapse();
const { resolvedTheme } = useTheme();
const lastVersion = useRef(version);
const cs = getColorStyles(name);
const cs = getColorStyles(name, resolvedTheme === 'light');
// Respond to global collapse/expand commands
useEffect(() => {

View File

@@ -180,8 +180,10 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
return () => mq.removeEventListener('change', handler);
}, [theme]);
const resolvedTheme = resolveTheme(theme);
return (
<ThemeContext.Provider value={{ theme, accent, setTheme, setAccent }}>
<ThemeContext.Provider value={{ theme, accent, resolvedTheme, setTheme, setAccent }}>
{children}
</ThemeContext.Provider>
);

View File

@@ -6,6 +6,8 @@ export type AccentColor = 'cyan' | 'violet' | 'emerald' | 'amber' | 'rose' | 'bl
export interface ThemeContextValue {
theme: ThemeName;
accent: AccentColor;
/** Resolved concrete theme (never 'system'). */
resolvedTheme: 'dark' | 'light' | 'oled';
setTheme: (t: ThemeName) => void;
setAccent: (a: AccentColor) => void;
}
@@ -13,6 +15,7 @@ export interface ThemeContextValue {
export const ThemeContext = createContext<ThemeContextValue>({
theme: 'dark',
accent: 'cyan',
resolvedTheme: 'dark',
setTheme: () => {},
setAccent: () => {},
});