fix: consistent theming across tool and message components

- MessageContent: Make prose-invert conditional (dark:prose-invert)
  and use !important on inline code colors to override Typography
- ToolCallDisplay: Replace theme variables with explicit Tailwind
  dark/light classes for reliable styling
- ToolResultDisplay: Same treatment - explicit slate colors for
  both light and dark modes

All components now properly respect light/dark mode toggle.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-01 07:28:15 +01:00
parent 77bc72078a
commit 3faf1e9f34
3 changed files with 45 additions and 42 deletions

View File

@@ -391,7 +391,7 @@
{:else if part.type === 'tool-result'}
<ToolResultDisplay content={part.content} />
{:else}
<div class="prose prose-sm prose-invert max-w-none">
<div class="prose prose-sm max-w-none dark:prose-invert">
{@html renderMarkdown(part.content)}
</div>
{/if}
@@ -489,7 +489,9 @@
}
.message-content :global(.prose code:not(pre code)) {
@apply rounded bg-slate-200 px-1.5 py-0.5 text-sm text-emerald-700;
@apply rounded px-1.5 py-0.5 text-sm;
background-color: rgb(226 232 240) !important; /* slate-200 */
color: rgb(4 120 87) !important; /* emerald-700 */
}
.message-content :global(.prose a) {
@@ -544,7 +546,8 @@
}
:global(.dark) .message-content :global(.prose code:not(pre code)) {
@apply bg-slate-700 text-emerald-400;
background-color: rgb(51 65 85) !important; /* slate-700 */
color: rgb(52 211 153) !important; /* emerald-400 */
}
:global(.dark) .message-content :global(.prose a) {

View File

@@ -187,14 +187,14 @@
{@const isExpanded = expandedCalls.has(call.id)}
<div
class="overflow-hidden rounded-xl border border-theme/50 bg-gradient-to-r {meta.color} p-[1px] shadow-lg"
class="overflow-hidden rounded-xl border border-slate-300 bg-gradient-to-r dark:border-slate-700 {meta.color} p-[1px] shadow-lg"
>
<div class="rounded-xl bg-theme-primary/95 backdrop-blur">
<div class="rounded-xl bg-white/95 backdrop-blur dark:bg-slate-800/95">
<!-- Header -->
<button
type="button"
onclick={() => toggleExpand(call.id)}
class="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-theme-secondary/50"
class="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-slate-100/50 dark:hover:bg-slate-700/50"
>
<!-- Icon -->
<span class="text-xl" role="img" aria-label={meta.label}>{meta.icon}</span>
@@ -202,14 +202,14 @@
<!-- Tool name and summary -->
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2">
<span class="font-medium text-theme-primary">{meta.label}</span>
<span class="font-mono text-xs text-theme-muted">{call.name}</span>
<span class="font-medium text-slate-800 dark:text-slate-100">{meta.label}</span>
<span class="font-mono text-xs text-slate-500 dark:text-slate-400">{call.name}</span>
</div>
<!-- Quick preview of main argument -->
{#if argEntries.length > 0}
{@const [firstKey, firstValue] = argEntries[0]}
<p class="mt-0.5 truncate text-sm text-theme-muted">
<p class="mt-0.5 truncate text-sm text-slate-500 dark:text-slate-400">
{#if call.name === 'web_search' && typeof firstValue === 'string'}
Searching: "{firstValue}"
{:else if call.name === 'fetch_url' && typeof firstValue === 'string'}
@@ -227,7 +227,7 @@
<!-- Expand indicator -->
<svg
class="h-5 w-5 flex-shrink-0 text-theme-muted transition-transform duration-200"
class="h-5 w-5 flex-shrink-0 text-slate-400 transition-transform duration-200 dark:text-slate-500"
class:rotate-180={isExpanded}
fill="none"
viewBox="0 0 24 24"
@@ -240,14 +240,14 @@
<!-- Expanded arguments -->
{#if isExpanded && argEntries.length > 0}
<div class="border-t border-theme px-4 py-3">
<div class="border-t border-slate-200 px-4 py-3 dark:border-slate-700">
<div class="space-y-2">
{#each argEntries as [key, value]}
<div class="flex items-start gap-3 text-sm">
<span class="w-24 flex-shrink-0 font-medium text-theme-muted">
<span class="w-24 flex-shrink-0 font-medium text-slate-500 dark:text-slate-400">
{argLabel(key)}
</span>
<span class="break-all font-mono text-theme-secondary">
<span class="break-all font-mono text-slate-700 dark:text-slate-200">
{formatValue(value)}
</span>
</div>
@@ -262,25 +262,25 @@
{@const parsed = parseResult(call.result)}
{@const isResultExpanded = expandedResults.has(call.id)}
<div class="border-t border-theme">
<div class="border-t border-slate-200 dark:border-slate-700">
<button
type="button"
onclick={() => toggleResult(call.id)}
class="flex w-full items-center gap-2 px-4 py-2 text-left text-sm transition-colors hover:bg-theme-secondary/50"
class="flex w-full items-center gap-2 px-4 py-2 text-left text-sm transition-colors hover:bg-slate-100/50 dark:hover:bg-slate-700/50"
>
<!-- Status icon -->
{#if call.error}
<span class="text-red-400"></span>
<span class="flex-1 text-red-300">Error: {call.error}</span>
<span class="text-red-500 dark:text-red-400"></span>
<span class="flex-1 text-red-600 dark:text-red-300">Error: {call.error}</span>
{:else}
<span class="text-emerald-400"></span>
<span class="flex-1 text-theme-muted">{parsed.summary}</span>
<span class="text-emerald-500 dark:text-emerald-400"></span>
<span class="flex-1 text-slate-500 dark:text-slate-400">{parsed.summary}</span>
{/if}
<!-- Expand arrow -->
{#if hasResult && parsed.full}
<svg
class="h-4 w-4 flex-shrink-0 text-theme-muted transition-transform duration-200"
class="h-4 w-4 flex-shrink-0 text-slate-400 transition-transform duration-200 dark:text-slate-500"
class:rotate-180={isResultExpanded}
fill="none"
viewBox="0 0 24 24"
@@ -294,8 +294,8 @@
<!-- Expanded result content -->
{#if isResultExpanded && hasResult && parsed.full}
<div class="max-h-96 overflow-auto border-t border-theme/50 bg-theme-primary/50 px-4 py-3">
<pre class="whitespace-pre-wrap break-words text-xs text-theme-muted">{parsed.full.length > 10000 ? parsed.full.substring(0, 10000) + '\n\n... (truncated)' : parsed.full}</pre>
<div class="max-h-96 overflow-auto border-t border-slate-200/50 bg-slate-50/50 px-4 py-3 dark:border-slate-700/50 dark:bg-slate-900/50">
<pre class="whitespace-pre-wrap break-words text-xs text-slate-600 dark:text-slate-400">{parsed.full.length > 10000 ? parsed.full.substring(0, 10000) + '\n\n... (truncated)' : parsed.full}</pre>
</div>
{/if}
</div>

View File

@@ -106,7 +106,7 @@
<div class="flex items-center gap-3 px-4 py-3">
<span class="text-2xl">📍</span>
<div>
<p class="font-medium text-theme-primary">
<p class="font-medium text-slate-800 dark:text-slate-100">
{#if loc.location?.city}
{loc.location.city}{#if loc.location.country}, {loc.location.country}{/if}
{:else if loc.message}
@@ -116,9 +116,9 @@
{/if}
</p>
{#if loc.source === 'ip'}
<p class="text-xs text-theme-muted">Based on IP address (approximate)</p>
<p class="text-xs text-slate-500 dark:text-slate-400">Based on IP address (approximate)</p>
{:else if loc.source === 'gps'}
<p class="text-xs text-theme-muted">From device GPS</p>
<p class="text-xs text-slate-500 dark:text-slate-400">From device GPS</p>
{/if}
</div>
</div>
@@ -127,7 +127,7 @@
{:else if parsed.type === 'search'}
{@const search = parsed.data as SearchData}
<div class="my-3 space-y-2">
<div class="flex items-center gap-2 text-sm text-theme-muted">
<div class="flex items-center gap-2 text-sm text-slate-500 dark:text-slate-400">
<span>🔍</span>
<span>Found {search.resultCount || search.results?.length || 0} results for "{search.query}"</span>
</div>
@@ -139,15 +139,15 @@
href={result.url}
target="_blank"
rel="noopener noreferrer"
class="block rounded-lg border border-theme/50 bg-theme-secondary/50 p-3 transition-colors hover:border-blue-500/50 hover:bg-theme-secondary"
class="block rounded-lg border border-slate-200 bg-slate-100/50 p-3 transition-colors hover:border-blue-500/50 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-800/50 dark:hover:bg-slate-800"
>
<div class="flex items-start gap-2">
<span class="mt-0.5 text-blue-400">#{result.rank}</span>
<span class="mt-0.5 text-blue-500 dark:text-blue-400">#{result.rank}</span>
<div class="min-w-0 flex-1">
<p class="font-medium text-blue-400 hover:underline">{result.title}</p>
<p class="mt-0.5 truncate text-xs text-theme-muted">{result.url}</p>
<p class="font-medium text-blue-600 hover:underline dark:text-blue-400">{result.title}</p>
<p class="mt-0.5 truncate text-xs text-slate-500 dark:text-slate-400">{result.url}</p>
{#if result.snippet && result.snippet !== '(no snippet available)'}
<p class="mt-1 text-sm text-theme-muted">{result.snippet}</p>
<p class="mt-1 text-sm text-slate-600 dark:text-slate-400">{result.snippet}</p>
{/if}
</div>
</div>
@@ -158,10 +158,10 @@
</div>
{:else if parsed.type === 'error'}
<div class="my-3 rounded-xl border border-red-500/30 bg-red-500/10 px-4 py-3">
<div class="my-3 rounded-xl border border-red-300 bg-red-50 px-4 py-3 dark:border-red-500/30 dark:bg-red-500/10">
<div class="flex items-center gap-2">
<span class="text-red-400">⚠️</span>
<span class="text-sm text-red-300">{parsed.data}</span>
<span class="text-red-500 dark:text-red-400">⚠️</span>
<span class="text-sm text-red-700 dark:text-red-300">{parsed.data}</span>
</div>
</div>
@@ -172,32 +172,32 @@
<div class="flex items-center gap-2 text-sm">
<span>🌐</span>
{#if data.title}
<span class="font-medium text-theme-secondary">{data.title}</span>
<span class="font-medium text-slate-700 dark:text-slate-200">{data.title}</span>
{:else if data.url}
<a href={String(data.url)} target="_blank" rel="noopener noreferrer" class="text-violet-400 hover:underline">
<a href={String(data.url)} target="_blank" rel="noopener noreferrer" class="text-violet-600 hover:underline dark:text-violet-400">
{data.url}
</a>
{:else}
<span class="text-theme-muted">Fetched content</span>
<span class="text-slate-500 dark:text-slate-400">Fetched content</span>
{/if}
</div>
{#if data.text && typeof data.text === 'string'}
<p class="mt-2 line-clamp-4 text-sm text-theme-muted">{data.text.substring(0, 300)}{data.text.length > 300 ? '...' : ''}</p>
<p class="mt-2 line-clamp-4 text-sm text-slate-600 dark:text-slate-400">{data.text.substring(0, 300)}{data.text.length > 300 ? '...' : ''}</p>
{/if}
</div>
</div>
{:else if parsed.type === 'json'}
{@const data = parsed.data as Record<string, unknown>}
<div class="my-3 rounded-xl border border-theme/50 bg-theme-secondary/50 p-3">
<pre class="overflow-x-auto text-xs text-theme-muted">{JSON.stringify(data, null, 2)}</pre>
<div class="my-3 rounded-xl border border-slate-200 bg-slate-100/50 p-3 dark:border-slate-700 dark:bg-slate-800/50">
<pre class="overflow-x-auto text-xs text-slate-600 dark:text-slate-400">{JSON.stringify(data, null, 2)}</pre>
</div>
{:else}
<!-- Fallback: just show the text (if not empty/whitespace) -->
{#if typeof parsed.data === 'string' && parsed.data.trim().length > 0}
<div class="my-3 rounded-xl border border-theme/50 bg-theme-secondary/50 p-3">
<p class="text-sm text-theme-secondary">{parsed.data}</p>
<div class="my-3 rounded-xl border border-slate-200 bg-slate-100/50 p-3 dark:border-slate-700 dark:bg-slate-800/50">
<p class="text-sm text-slate-700 dark:text-slate-200">{parsed.data}</p>
</div>
{/if}
{/if}