From 032258b260ac88ee26057e595f454bda4119db79 Mon Sep 17 00:00:00 2001 From: Haileyesus <118998054+blackmammoth@users.noreply.github.com> Date: Mon, 29 Jun 2026 23:45:00 +0300 Subject: [PATCH] style(ui): rework light/dark theme to make it visually consistent Rework the color system around warm neutrals and route hardcoded surfaces through theme tokens for consistency. - Theme tokens (index.css, ThemeContext): warm cream light mode and neutral charcoal dark mode, replacing the pure-white/blue-tinted palette; update PWA theme-color meta - Code blocks: soft grey background in light mode via oneLight/oneDark, and drop the Tailwind Typography
shell that
framed the highlighter in a dark box
- Dropdowns/panels: convert CommandMenu, Quick Settings, and the JSON
response block from hardcoded gray/slate to popover/muted/border
tokens
- Git panel: Publish button purple -> primary blue
- Composer: drop top padding so the input sits flush with the thread
---
.../chat/view/subcomponents/ChatComposer.tsx | 2 +-
.../chat/view/subcomponents/CommandMenu.tsx | 22 ++---
.../chat/view/subcomponents/Markdown.tsx | 17 +++-
.../view/subcomponents/MessageComponent.tsx | 6 +-
.../markdown/MarkdownCodeBlock.tsx | 12 ++-
.../markdown/MarkdownPreview.tsx | 3 +
.../git-panel/view/GitPanelHeader.tsx | 2 +-
.../quick-settings-panel/constants.ts | 2 +-
.../view/QuickSettingsContent.tsx | 8 +-
.../view/QuickSettingsPanelHeader.tsx | 6 +-
.../view/QuickSettingsSection.tsx | 2 +-
.../view/QuickSettingsToggleRow.tsx | 4 +-
src/contexts/ThemeContext.jsx | 4 +-
src/index.css | 90 +++++++++----------
14 files changed, 98 insertions(+), 82 deletions(-)
diff --git a/src/components/chat/view/subcomponents/ChatComposer.tsx b/src/components/chat/view/subcomponents/ChatComposer.tsx
index bb1c4a45..c021d919 100644
--- a/src/components/chat/view/subcomponents/ChatComposer.tsx
+++ b/src/components/chat/view/subcomponents/ChatComposer.tsx
@@ -197,7 +197,7 @@ export default function ChatComposer({
const hasPendingPermissions = pendingPermissionRequests.length > 0;
return (
-
+
{!hasPendingPermissions && (
)}
diff --git a/src/components/chat/view/subcomponents/CommandMenu.tsx b/src/components/chat/view/subcomponents/CommandMenu.tsx
index 580ec92c..51b90e09 100644
--- a/src/components/chat/view/subcomponents/CommandMenu.tsx
+++ b/src/components/chat/view/subcomponents/CommandMenu.tsx
@@ -226,7 +226,7 @@ export default function CommandMenu({
return renderInPortal(
{orderedNamespaces.map((namespace) => (
{orderedNamespaces.length > 1 && (
-
+
{namespaceLabels[namespace] || namespace}
-
+
{(groupedCommands[namespace] || []).length}
@@ -273,15 +273,15 @@ export default function CommandMenu({
aria-selected={isSelected}
className={`command-item group relative mb-1 flex cursor-pointer items-start gap-2 rounded-md border px-2.5 py-2 transition-all ${
isSelected
- ? 'border-sky-200 bg-sky-50 shadow-sm dark:border-cyan-400/30 dark:bg-cyan-400/10'
- : 'border-transparent bg-transparent hover:border-gray-200 hover:bg-gray-50/90 dark:hover:border-slate-700 dark:hover:bg-slate-900/80'
+ ? 'border-primary/30 bg-primary/10 shadow-sm'
+ : 'border-transparent bg-transparent hover:border-border hover:bg-accent'
}`}
onMouseEnter={() => onSelect && commandIndex >= 0 && onSelect(command, commandIndex, true)}
onClick={() => onSelect && commandIndex >= 0 && onSelect(command, commandIndex, false)}
onMouseDown={(event) => event.preventDefault()}
>
{isSelected && (
-
+
)}
@@ -289,20 +289,20 @@ export default function CommandMenu({
{command.name}
{command.metadata?.type && (
-
+
{command.metadata.type}
)}
{command.description && (
{command.description}
@@ -310,7 +310,7 @@ export default function CommandMenu({
)}
{isSelected && (
-
+
)}
diff --git a/src/components/chat/view/subcomponents/Markdown.tsx b/src/components/chat/view/subcomponents/Markdown.tsx
index fc1b9f19..4bacad2c 100644
--- a/src/components/chat/view/subcomponents/Markdown.tsx
+++ b/src/components/chat/view/subcomponents/Markdown.tsx
@@ -4,11 +4,12 @@ import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
-import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
+import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { useTranslation } from 'react-i18next';
import { normalizeInlineCodeFences } from '../../utils/chatFormatting';
import { copyTextToClipboard } from '../../../../utils/clipboard';
import { usePaletteOps } from '../../../../contexts/PaletteOpsContext';
+import { useTheme } from '../../../../contexts/ThemeContext';
type MarkdownProps = {
children: React.ReactNode;
@@ -59,6 +60,7 @@ type CodeBlockProps = {
const CodeBlock = ({ node, inline, className, children, ...props }: CodeBlockProps) => {
const { t } = useTranslation('chat');
+ const { isDarkMode } = useTheme();
const [copied, setCopied] = useState(false);
const raw = Array.isArray(children) ? children.join('') : String(children ?? '');
const looksMultiline = /[\r\n]/.test(raw);
@@ -96,7 +98,7 @@ const CodeBlock = ({ node, inline, className, children, ...props }: CodeBlockPro
}
})
}
- className="absolute right-2 top-2 z-10 rounded-md border border-gray-600 bg-gray-700/80 px-2 py-1 text-xs text-white opacity-0 transition-opacity hover:bg-gray-700 focus:opacity-100 active:opacity-100 group-hover:opacity-100"
+ className="absolute right-2 top-2 z-10 rounded-md border border-border bg-card/90 px-2 py-1 text-xs text-foreground/80 opacity-0 transition-opacity hover:bg-muted focus:opacity-100 active:opacity-100 group-hover:opacity-100"
title={copied ? t('codeBlock.copied') : t('codeBlock.copyCode')}
aria-label={copied ? t('codeBlock.copied') : t('codeBlock.copyCode')}
>
@@ -132,17 +134,20 @@ const CodeBlock = ({ node, inline, className, children, ...props }: CodeBlockPro
@@ -154,6 +159,10 @@ const CodeBlock = ({ node, inline, className, children, ...props }: CodeBlockPro
const markdownComponents = {
code: CodeBlock,
+ // CodeBlock renders its own syntax-highlighted ; this passthrough stops
+ // react-markdown (and Tailwind Typography) from wrapping it in a second,
+ // dark-themed shell that would frame the block.
+ pre: ({ children }: { children?: React.ReactNode }) => <>{children}>,
blockquote: ({ children }: { children?: React.ReactNode }) => (
{children}
diff --git a/src/components/chat/view/subcomponents/MessageComponent.tsx b/src/components/chat/view/subcomponents/MessageComponent.tsx
index e9615a85..b326a876 100644
--- a/src/components/chat/view/subcomponents/MessageComponent.tsx
+++ b/src/components/chat/view/subcomponents/MessageComponent.tsx
@@ -377,15 +377,15 @@ const MessageComponent = memo(({ message, prevMessage, createDiff, onFileOpen, a
return (
-
+
{t('json.response')}
-
+
-
+
{formatted}
diff --git a/src/components/code-editor/view/subcomponents/markdown/MarkdownCodeBlock.tsx b/src/components/code-editor/view/subcomponents/markdown/MarkdownCodeBlock.tsx
index 21101ae5..9f7d611e 100644
--- a/src/components/code-editor/view/subcomponents/markdown/MarkdownCodeBlock.tsx
+++ b/src/components/code-editor/view/subcomponents/markdown/MarkdownCodeBlock.tsx
@@ -1,8 +1,9 @@
import { useState } from 'react';
import type { ComponentProps } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
-import { oneDark as prismOneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
+import { oneDark as prismOneDark, oneLight as prismOneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { copyTextToClipboard } from '../../../../../utils/clipboard';
+import { useTheme } from '../../../../../contexts/ThemeContext';
type MarkdownCodeBlockProps = {
inline?: boolean;
@@ -16,6 +17,7 @@ export default function MarkdownCodeBlock({
node: _node,
...props
}: MarkdownCodeBlockProps) {
+ const { isDarkMode } = useTheme();
const [copied, setCopied] = useState(false);
const rawContent = Array.isArray(children) ? children.join('') : String(children ?? '');
const looksMultiline = /[\r\n]/.test(rawContent);
@@ -50,20 +52,22 @@ export default function MarkdownCodeBlock({
setTimeout(() => setCopied(false), 2000);
}
})}
- className="absolute right-2 top-2 z-10 rounded-md border border-gray-600 bg-gray-700/80 px-2 py-1 text-xs text-white opacity-0 transition-opacity hover:bg-gray-700 group-hover:opacity-100"
+ className="absolute right-2 top-2 z-10 rounded-md border border-border bg-card/90 px-2 py-1 text-xs text-foreground/80 opacity-0 transition-opacity hover:bg-muted group-hover:opacity-100"
>
{copied ? 'Copied!' : 'Copy'}
{rawContent}
diff --git a/src/components/code-editor/view/subcomponents/markdown/MarkdownPreview.tsx b/src/components/code-editor/view/subcomponents/markdown/MarkdownPreview.tsx
index 976ebd3f..99e0c1ee 100644
--- a/src/components/code-editor/view/subcomponents/markdown/MarkdownPreview.tsx
+++ b/src/components/code-editor/view/subcomponents/markdown/MarkdownPreview.tsx
@@ -12,6 +12,9 @@ type MarkdownPreviewProps = {
const markdownPreviewComponents: Components = {
code: MarkdownCodeBlock,
+ // MarkdownCodeBlock renders its own highlighted ; passthrough prevents a
+ // second Typography-styled shell from framing it.
+ pre: ({ children }) => <>{children}>,
blockquote: ({ children }) => (
{children}
diff --git a/src/components/git-panel/view/GitPanelHeader.tsx b/src/components/git-panel/view/GitPanelHeader.tsx
index 9913cefb..31e64ba6 100644
--- a/src/components/git-panel/view/GitPanelHeader.tsx
+++ b/src/components/git-panel/view/GitPanelHeader.tsx
@@ -189,7 +189,7 @@ export default function GitPanelHeader({