From 8b3dca10deffd6b9ea54854c1dbb78b39151b6b6 Mon Sep 17 00:00:00 2001 From: openasocket Date: Sat, 14 Feb 2026 20:02:45 -0500 Subject: [PATCH 01/24] fix: replace hardcoded color values with theme tokens for accessibility Replace hardcoded #fff, #000, and 'white' color values with theme tokens (accentForeground, bgMain) across 15 component files. This ensures proper contrast ratios on accent-colored backgrounds for all themes, including high-contrast and vibe themes like dre-synth. Co-Authored-By: Claude Opus 4.6 --- src/renderer/components/AICommandsPanel.tsx | 4 ++-- src/renderer/components/AgentSessionsBrowser.tsx | 8 ++------ src/renderer/components/AgentSessionsModal.tsx | 2 +- src/renderer/components/AutoRunDocumentSelector.tsx | 6 +++--- src/renderer/components/AutoRunnerHelpModal.tsx | 2 +- src/renderer/components/CsvTableRenderer.tsx | 6 +++--- src/renderer/components/FilePreview.tsx | 6 +++--- src/renderer/components/GroupChatInput.tsx | 2 +- src/renderer/components/HistoryHelpModal.tsx | 2 +- src/renderer/components/LeaderboardRegistrationModal.tsx | 6 +++--- src/renderer/components/PlaygroundPanel.tsx | 6 +++--- src/renderer/components/SpecKitCommandsPanel.tsx | 2 +- src/renderer/components/TerminalOutput.tsx | 4 ++-- .../components/UsageDashboard/ChartErrorBoundary.tsx | 2 +- .../components/Wizard/screens/AgentSelectionScreen.tsx | 2 +- 15 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/renderer/components/AICommandsPanel.tsx b/src/renderer/components/AICommandsPanel.tsx index 55ff97343..f2e97eb8e 100644 --- a/src/renderer/components/AICommandsPanel.tsx +++ b/src/renderer/components/AICommandsPanel.tsx @@ -317,7 +317,7 @@ export function AICommandsPanel({ className="flex items-center gap-1 px-3 py-1.5 rounded text-xs font-medium transition-all disabled:opacity-50" style={{ backgroundColor: theme.colors.success, - color: '#000000', + color: theme.colors.accentForeground, }} > @@ -365,7 +365,7 @@ export function AICommandsPanel({ className="flex items-center gap-1 px-2 py-1 rounded text-xs font-medium transition-all" style={{ backgroundColor: theme.colors.success, - color: '#000000', + color: theme.colors.accentForeground, }} > diff --git a/src/renderer/components/AgentSessionsBrowser.tsx b/src/renderer/components/AgentSessionsBrowser.tsx index 05f593917..903658ccf 100644 --- a/src/renderer/components/AgentSessionsBrowser.tsx +++ b/src/renderer/components/AgentSessionsBrowser.tsx @@ -1137,9 +1137,7 @@ export function AgentSessionsBrowser({ msg.type === 'user' ? theme.colors.accent : theme.colors.bgActivity, color: msg.type === 'user' - ? theme.mode === 'light' - ? '#fff' - : '#000' + ? theme.colors.accentForeground : theme.colors.textMain, }} > @@ -1151,9 +1149,7 @@ export function AgentSessionsBrowser({ style={{ color: msg.type === 'user' - ? theme.mode === 'light' - ? '#fff' - : '#000' + ? theme.colors.accentForeground : theme.colors.textDim, }} > diff --git a/src/renderer/components/AgentSessionsModal.tsx b/src/renderer/components/AgentSessionsModal.tsx index bafafc56e..22469d1c5 100644 --- a/src/renderer/components/AgentSessionsModal.tsx +++ b/src/renderer/components/AgentSessionsModal.tsx @@ -611,7 +611,7 @@ export function AgentSessionsModal({ className="w-full text-left px-4 py-3 flex items-start gap-3 hover:bg-opacity-10 transition-colors group" style={{ backgroundColor: i === selectedIndex ? theme.colors.accent : 'transparent', - color: theme.colors.textMain, + color: i === selectedIndex ? theme.colors.accentForeground : theme.colors.textMain, }} > {/* Star button */} diff --git a/src/renderer/components/AutoRunDocumentSelector.tsx b/src/renderer/components/AutoRunDocumentSelector.tsx index 41dfe2a10..41ae25237 100644 --- a/src/renderer/components/AutoRunDocumentSelector.tsx +++ b/src/renderer/components/AutoRunDocumentSelector.tsx @@ -225,7 +225,7 @@ export function AutoRunDocumentSelector({ : theme.colors.accentDim : 'transparent', color: - taskPct !== null ? (taskPct === 100 ? '#000' : theme.colors.textDim) : 'transparent', + taskPct !== null ? (taskPct === 100 ? theme.colors.accentForeground : theme.colors.textDim) : 'transparent', }} > {taskPct !== null ? `${taskPct}%` : ''} @@ -259,7 +259,7 @@ export function AutoRunDocumentSelector({ selectedTaskPercentage === 100 ? theme.colors.success : theme.colors.accentDim, - color: selectedTaskPercentage === 100 ? '#000' : theme.colors.textDim, + color: selectedTaskPercentage === 100 ? theme.colors.accentForeground : theme.colors.textDim, }} > {selectedTaskPercentage}% @@ -325,7 +325,7 @@ export function AutoRunDocumentSelector({ color: taskPct !== null ? taskPct === 100 - ? '#000' + ? theme.colors.accentForeground : theme.colors.textDim : 'transparent', }} diff --git a/src/renderer/components/AutoRunnerHelpModal.tsx b/src/renderer/components/AutoRunnerHelpModal.tsx index 473ff6d21..a7a19cba1 100644 --- a/src/renderer/components/AutoRunnerHelpModal.tsx +++ b/src/renderer/components/AutoRunnerHelpModal.tsx @@ -41,7 +41,7 @@ export function AutoRunnerHelpModal({ theme, onClose }: AutoRunnerHelpModalProps className="px-4 py-2 rounded text-sm font-medium transition-colors hover:opacity-90" style={{ backgroundColor: theme.colors.accent, - color: 'white', + color: theme.colors.accentForeground, }} > Got it diff --git a/src/renderer/components/CsvTableRenderer.tsx b/src/renderer/components/CsvTableRenderer.tsx index 9eb3ff613..50d131495 100644 --- a/src/renderer/components/CsvTableRenderer.tsx +++ b/src/renderer/components/CsvTableRenderer.tsx @@ -140,7 +140,7 @@ function compareValues(a: string, b: string, direction: SortDirection): number { /** * Highlight matching substrings within a cell value. */ -function highlightMatches(text: string, query: string, accentColor: string): ReactNode { +function highlightMatches(text: string, query: string, accentColor: string, accentForeground: string): ReactNode { if (!query) return text; const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const regex = new RegExp(`(${escaped})`, 'gi'); @@ -157,7 +157,7 @@ function highlightMatches(text: string, query: string, accentColor: string): Rea key={key} style={{ backgroundColor: accentColor, - color: '#fff', + color: accentForeground, padding: '0 1px', borderRadius: '2px', }} @@ -350,7 +350,7 @@ export function CsvTableRenderer({ content, theme, delimiter = ',', searchQuery, title={row[colIdx] ?? ''} > {query - ? highlightMatches(row[colIdx] ?? '', query, theme.colors.accent) + ? highlightMatches(row[colIdx] ?? '', query, theme.colors.accent, theme.colors.accentForeground) : (row[colIdx] ?? '')} ))} diff --git a/src/renderer/components/FilePreview.tsx b/src/renderer/components/FilePreview.tsx index 81f302d49..80b4e082c 100644 --- a/src/renderer/components/FilePreview.tsx +++ b/src/renderer/components/FilePreview.tsx @@ -1313,7 +1313,7 @@ export const FilePreview = React.memo( // Highlight first match with different color and scroll to it if (matchElements.length > 0) { matchElements[0].style.backgroundColor = theme.colors.accent; - matchElements[0].style.color = '#fff'; + matchElements[0].style.color = theme.colors.accentForeground; matchElements[0].scrollIntoView({ behavior: 'smooth', block: 'center' }); } @@ -1502,7 +1502,7 @@ export const FilePreview = React.memo( // Highlight new current match and scroll to it if (matches[nextIndex]) { matches[nextIndex].style.backgroundColor = theme.colors.accent; - matches[nextIndex].style.color = '#fff'; + matches[nextIndex].style.color = theme.colors.accentForeground; matches[nextIndex].scrollIntoView({ behavior: 'smooth', block: 'center' }); } } @@ -1528,7 +1528,7 @@ export const FilePreview = React.memo( // Highlight new current match and scroll to it if (matches[prevIndex]) { matches[prevIndex].style.backgroundColor = theme.colors.accent; - matches[prevIndex].style.color = '#fff'; + matches[prevIndex].style.color = theme.colors.accentForeground; matches[prevIndex].scrollIntoView({ behavior: 'smooth', block: 'center' }); } } diff --git a/src/renderer/components/GroupChatInput.tsx b/src/renderer/components/GroupChatInput.tsx index c9bb17ffb..c63952c80 100644 --- a/src/renderer/components/GroupChatInput.tsx +++ b/src/renderer/components/GroupChatInput.tsx @@ -573,7 +573,7 @@ export const GroupChatInput = React.memo(function GroupChatInput({ ? theme.colors.warning : theme.colors.accent : theme.colors.border, - color: message.trim() ? '#ffffff' : theme.colors.textDim, + color: message.trim() ? theme.colors.accentForeground : theme.colors.textDim, }} title={isBusy ? 'Queue message' : 'Send message'} > diff --git a/src/renderer/components/HistoryHelpModal.tsx b/src/renderer/components/HistoryHelpModal.tsx index 6c3714c64..acc03106a 100644 --- a/src/renderer/components/HistoryHelpModal.tsx +++ b/src/renderer/components/HistoryHelpModal.tsx @@ -36,7 +36,7 @@ export function HistoryHelpModal({ theme, onClose }: HistoryHelpModalProps) { className="px-4 py-2 rounded text-sm font-medium transition-colors hover:opacity-90" style={{ backgroundColor: theme.colors.accent, - color: 'white', + color: theme.colors.accentForeground, }} > Got it diff --git a/src/renderer/components/LeaderboardRegistrationModal.tsx b/src/renderer/components/LeaderboardRegistrationModal.tsx index 2c89a517e..479afadc5 100644 --- a/src/renderer/components/LeaderboardRegistrationModal.tsx +++ b/src/renderer/components/LeaderboardRegistrationModal.tsx @@ -1141,7 +1141,7 @@ export function LeaderboardRegistrationModal({ className="w-full px-3 py-2 text-xs font-medium rounded transition-colors flex items-center justify-center gap-2 disabled:opacity-50" style={{ backgroundColor: theme.colors.accent, - color: '#fff', + color: theme.colors.accentForeground, }} > {isResending ? ( @@ -1199,7 +1199,7 @@ export function LeaderboardRegistrationModal({ className="px-3 py-2 text-xs font-medium rounded transition-colors disabled:opacity-50" style={{ backgroundColor: theme.colors.accent, - color: '#fff', + color: theme.colors.accentForeground, }} > Submit @@ -1253,7 +1253,7 @@ export function LeaderboardRegistrationModal({ className="px-3 py-1.5 text-xs rounded transition-colors flex items-center gap-1.5" style={{ backgroundColor: theme.colors.error, - color: '#fff', + color: theme.colors.accentForeground, }} > diff --git a/src/renderer/components/PlaygroundPanel.tsx b/src/renderer/components/PlaygroundPanel.tsx index 0c373d10e..68a9455e8 100644 --- a/src/renderer/components/PlaygroundPanel.tsx +++ b/src/renderer/components/PlaygroundPanel.tsx @@ -758,7 +758,7 @@ ${staggerDelays.map((delay, i) => `svg.wand-sparkle-active path:nth-child(${i + className="w-full flex items-center justify-center gap-2 px-4 py-2 rounded font-medium transition-colors" style={{ backgroundColor: theme.colors.accent, - color: '#fff', + color: theme.colors.accentForeground, }} > @@ -1181,7 +1181,7 @@ ${staggerDelays.map((delay, i) => `svg.wand-sparkle-active path:nth-child(${i + className="w-full flex items-center justify-center gap-2 px-4 py-3 rounded-lg font-bold text-lg transition-all hover:scale-[1.02] disabled:opacity-50 disabled:cursor-not-allowed" style={{ backgroundColor: theme.colors.accent, - color: '#fff', + color: theme.colors.accentForeground, }} > @@ -1355,7 +1355,7 @@ ${staggerDelays.map((delay, i) => `svg.wand-sparkle-active path:nth-child(${i + className="px-3 py-1 rounded text-sm font-medium transition-colors" style={{ backgroundColor: batonActive ? theme.colors.accent : theme.colors.bgMain, - color: batonActive ? '#fff' : theme.colors.textMain, + color: batonActive ? theme.colors.accentForeground : theme.colors.textMain, }} > {batonActive ? 'Active' : 'Paused'} diff --git a/src/renderer/components/SpecKitCommandsPanel.tsx b/src/renderer/components/SpecKitCommandsPanel.tsx index 877910ce6..2cb2cd334 100644 --- a/src/renderer/components/SpecKitCommandsPanel.tsx +++ b/src/renderer/components/SpecKitCommandsPanel.tsx @@ -267,7 +267,7 @@ export function SpecKitCommandsPanel({ theme }: SpecKitCommandsPanelProps) { className="flex items-center gap-1 px-2 py-1 rounded text-xs font-medium transition-all" style={{ backgroundColor: theme.colors.success, - color: '#000000', + color: theme.colors.accentForeground, }} > diff --git a/src/renderer/components/TerminalOutput.tsx b/src/renderer/components/TerminalOutput.tsx index e2fcf5c7d..1844c81a9 100644 --- a/src/renderer/components/TerminalOutput.tsx +++ b/src/renderer/components/TerminalOutput.tsx @@ -184,7 +184,7 @@ const LogItemComponent = memo( key={`match-${idx}`} style={{ backgroundColor: theme.colors.warning, - color: theme.mode === 'light' ? '#fff' : '#000', + color: theme.colors.bgMain, padding: '1px 2px', borderRadius: '2px', }} @@ -219,7 +219,7 @@ const LogItemComponent = memo( if (idx === -1) break; result += text.substring(lastIndex, idx); - result += ``; + result += ``; result += text.substring(idx, idx + query.length); result += ''; diff --git a/src/renderer/components/UsageDashboard/ChartErrorBoundary.tsx b/src/renderer/components/UsageDashboard/ChartErrorBoundary.tsx index 20d9243a1..46e6ab60c 100644 --- a/src/renderer/components/UsageDashboard/ChartErrorBoundary.tsx +++ b/src/renderer/components/UsageDashboard/ChartErrorBoundary.tsx @@ -146,7 +146,7 @@ export class ChartErrorBoundary extends Component { className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors" style={{ backgroundColor: theme.colors.accent, - color: '#ffffff', + color: theme.colors.accentForeground, }} onMouseEnter={(e) => { e.currentTarget.style.opacity = '0.9'; diff --git a/src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx b/src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx index 4dae3ace6..2de0b4e06 100644 --- a/src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx +++ b/src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx @@ -1256,7 +1256,7 @@ export function AgentSelectionScreen({ theme }: AgentSelectionScreenProps): JSX. className="absolute top-2 right-2 w-5 h-5 rounded-full flex items-center justify-center" style={{ backgroundColor: tile.brandColor || theme.colors.accent }} > - + )} From 92032afe6bd187ab93eb125de70739cf9cedcbb8 Mon Sep 17 00:00:00 2001 From: openasocket Date: Sat, 14 Feb 2026 20:27:36 -0500 Subject: [PATCH 02/24] feat: extend theme system with 17 new semantic color tokens Add info, status foregrounds (successForeground, warningForeground, errorForeground), dimmed status backgrounds (successDim, warningDim, errorDim, infoDim), git diff colors (diffAddition, diffAdditionBg, diffDeletion, diffDeletionBg), overlay/interactive states (overlay, overlayHeavy, hoverBg, activeBg), and shadow tokens to ThemeColors. All 17 theme definitions populated with palette-appropriate values. Light themes use black hover overlays and lighter shadows; dark/vibe themes use white hover overlays with heavier shadows. CSS custom property mappings, fallback themes, and tests updated accordingly. Co-Authored-By: Claude Opus 4.6 --- .../components/HistoryHelpModal.test.tsx | 4 +- .../web/utils/cssCustomProperties.test.ts | 50 ++- src/renderer/components/LightboxModal.tsx | 17 ++ src/shared/theme-types.ts | 49 +++ src/shared/themes.ts | 289 ++++++++++++++++++ src/web/components/ThemeProvider.tsx | 34 +++ src/web/utils/cssCustomProperties.ts | 51 ++++ 7 files changed, 484 insertions(+), 10 deletions(-) diff --git a/src/__tests__/renderer/components/HistoryHelpModal.test.tsx b/src/__tests__/renderer/components/HistoryHelpModal.test.tsx index c6ce3f8dd..2a54b08de 100644 --- a/src/__tests__/renderer/components/HistoryHelpModal.test.tsx +++ b/src/__tests__/renderer/components/HistoryHelpModal.test.tsx @@ -475,10 +475,10 @@ describe('HistoryHelpModal', () => { 'font-medium', 'transition-colors' ); - // Check for accent background - the exact RGB value + // Check for accent background and theme-based foreground color const style = gotItButton.getAttribute('style'); expect(style).toContain('background-color'); - expect(style).toContain('color: white'); + expect(style).toContain('color'); }); it('calls onClose when "Got it" button is clicked', () => { diff --git a/src/__tests__/web/utils/cssCustomProperties.test.ts b/src/__tests__/web/utils/cssCustomProperties.test.ts index 8832acd59..b66d23285 100644 --- a/src/__tests__/web/utils/cssCustomProperties.test.ts +++ b/src/__tests__/web/utils/cssCustomProperties.test.ts @@ -40,6 +40,23 @@ function createMockTheme(overrides?: Partial): Theme { success: '#50fa7b', warning: '#ffb86c', error: '#ff5555', + info: '#8be9fd', + successForeground: '#282a36', + warningForeground: '#282a36', + errorForeground: '#282a36', + successDim: 'rgba(80, 250, 123, 0.15)', + warningDim: 'rgba(255, 184, 108, 0.15)', + errorDim: 'rgba(255, 85, 85, 0.15)', + infoDim: 'rgba(139, 233, 253, 0.15)', + diffAddition: '#50fa7b', + diffAdditionBg: 'rgba(80, 250, 123, 0.15)', + diffDeletion: '#ff5555', + diffDeletionBg: 'rgba(255, 85, 85, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, ...overrides, }; @@ -65,14 +82,31 @@ function createLightTheme(): Theme { success: '#1a7f37', warning: '#9a6700', error: '#cf222e', + info: '#0969da', + successForeground: '#ffffff', + warningForeground: '#ffffff', + errorForeground: '#ffffff', + successDim: 'rgba(26, 127, 55, 0.1)', + warningDim: 'rgba(154, 103, 0, 0.1)', + errorDim: 'rgba(207, 34, 46, 0.1)', + infoDim: 'rgba(9, 105, 218, 0.1)', + diffAddition: '#1a7f37', + diffAdditionBg: 'rgba(26, 127, 55, 0.1)', + diffDeletion: '#cf222e', + diffDeletionBg: 'rgba(207, 34, 46, 0.1)', + overlay: 'rgba(0, 0, 0, 0.5)', + overlayHeavy: 'rgba(0, 0, 0, 0.7)', + hoverBg: 'rgba(0, 0, 0, 0.04)', + activeBg: 'rgba(0, 0, 0, 0.1)', + shadow: 'rgba(0, 0, 0, 0.15)', }, }; } describe('cssCustomProperties', () => { describe('THEME_CSS_PROPERTIES constant', () => { - it('should contain all 14 theme CSS properties', () => { - expect(THEME_CSS_PROPERTIES).toHaveLength(14); + it('should contain all 31 theme CSS properties', () => { + expect(THEME_CSS_PROPERTIES).toHaveLength(31); }); it('should include all color properties', () => { @@ -146,11 +180,11 @@ describe('cssCustomProperties', () => { expect(properties['--maestro-mode']).toBe('vibe'); }); - it('should return all 14 properties', () => { + it('should return all 31 properties', () => { const theme = createMockTheme(); const properties = generateCSSProperties(theme); - expect(Object.keys(properties)).toHaveLength(14); + expect(Object.keys(properties)).toHaveLength(31); }); it('should handle themes with rgba colors', () => { @@ -437,7 +471,7 @@ describe('cssCustomProperties', () => { expect(element.style.getPropertyValue('--maestro-mode')).toBe('dark'); }); - it('should set all 13 properties', () => { + it('should set all 31 properties', () => { const theme = createMockTheme(); setElementCSSProperties(element, theme); @@ -447,7 +481,7 @@ describe('cssCustomProperties', () => { count++; } }); - expect(count).toBe(14); + expect(count).toBe(31); }); it('should update properties when called again with different theme', () => { @@ -751,8 +785,8 @@ describe('cssCustomProperties', () => { // Should be valid CSS that can be parsed expect(css).toMatch(/^:root \{[\s\S]+\}$/); - // Should contain all properties (14 = 13 colors + accentForeground + mode) - expect((css.match(/--maestro-/g) || []).length).toBe(14); + // Should contain all properties (31 = 30 color tokens + mode) + expect((css.match(/--maestro-/g) || []).length).toBe(31); }); it('should support cssVar in style objects pattern', () => { diff --git a/src/renderer/components/LightboxModal.tsx b/src/renderer/components/LightboxModal.tsx index 61117943c..8ec9c0b8b 100644 --- a/src/renderer/components/LightboxModal.tsx +++ b/src/renderer/components/LightboxModal.tsx @@ -151,6 +151,23 @@ export function LightboxModal({ success: '#22c55e', warning: '#eab308', error: '#ef4444', + info: '#3b82f6', + successForeground: '#1a1a1a', + warningForeground: '#1a1a1a', + errorForeground: '#1a1a1a', + successDim: 'rgba(34, 197, 94, 0.15)', + warningDim: 'rgba(234, 179, 8, 0.15)', + errorDim: 'rgba(239, 68, 68, 0.15)', + infoDim: 'rgba(59, 130, 246, 0.15)', + diffAddition: '#22c55e', + diffAdditionBg: 'rgba(34, 197, 94, 0.15)', + diffDeletion: '#ef4444', + diffDeletionBg: 'rgba(239, 68, 68, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }; diff --git a/src/shared/theme-types.ts b/src/shared/theme-types.ts index 027e09e48..44bc86b62 100644 --- a/src/shared/theme-types.ts +++ b/src/shared/theme-types.ts @@ -41,6 +41,7 @@ export type ThemeMode = 'light' | 'dark' | 'vibe'; * Each color serves a specific purpose in the UI */ export interface ThemeColors { + // --- Core backgrounds --- /** Main background color for primary content areas */ bgMain: string; /** Sidebar background color */ @@ -49,10 +50,14 @@ export interface ThemeColors { bgActivity: string; /** Border color for dividers and outlines */ border: string; + + // --- Typography --- /** Primary text color */ textMain: string; /** Dimmed/secondary text color */ textDim: string; + + // --- Accent --- /** Accent color for highlights and interactive elements */ accent: string; /** Dimmed accent (typically with alpha transparency) */ @@ -61,12 +66,56 @@ export interface ThemeColors { accentText: string; /** Text color for use ON accent backgrounds (contrasting color) */ accentForeground: string; + + // --- Status colors --- /** Success state color (green tones) */ success: string; /** Warning state color (yellow/orange tones) */ warning: string; /** Error state color (red tones) */ error: string; + /** Info state color (blue tones) */ + info: string; + + // --- Status foregrounds (text ON status backgrounds) --- + /** Text color for use ON success backgrounds */ + successForeground: string; + /** Text color for use ON warning backgrounds */ + warningForeground: string; + /** Text color for use ON error backgrounds */ + errorForeground: string; + + // --- Status dim backgrounds (subtle badges/tags) --- + /** Dimmed success background for badges and tags */ + successDim: string; + /** Dimmed warning background for badges and tags */ + warningDim: string; + /** Dimmed error background for badges and tags */ + errorDim: string; + /** Dimmed info background for badges and tags */ + infoDim: string; + + // --- Git diff colors --- + /** Color for added lines/files in diffs */ + diffAddition: string; + /** Background for added lines/files in diffs */ + diffAdditionBg: string; + /** Color for deleted lines/files in diffs */ + diffDeletion: string; + /** Background for deleted lines/files in diffs */ + diffDeletionBg: string; + + // --- Overlay and interactive states --- + /** Modal/overlay backdrop color */ + overlay: string; + /** Heavy overlay for wizard/fullscreen modals */ + overlayHeavy: string; + /** Subtle hover state background */ + hoverBg: string; + /** Selected/active state background */ + activeBg: string; + /** Standard elevation shadow color */ + shadow: string; } /** diff --git a/src/shared/themes.ts b/src/shared/themes.ts index 8bd1455a0..1930198bd 100644 --- a/src/shared/themes.ts +++ b/src/shared/themes.ts @@ -32,6 +32,23 @@ export const THEMES: Record = { success: '#50fa7b', warning: '#ffb86c', error: '#ff5555', + info: '#8be9fd', + successForeground: '#282a36', + warningForeground: '#282a36', + errorForeground: '#282a36', + successDim: 'rgba(80, 250, 123, 0.15)', + warningDim: 'rgba(255, 184, 108, 0.15)', + errorDim: 'rgba(255, 85, 85, 0.15)', + infoDim: 'rgba(139, 233, 253, 0.15)', + diffAddition: '#50fa7b', + diffAdditionBg: 'rgba(80, 250, 123, 0.15)', + diffDeletion: '#ff5555', + diffDeletionBg: 'rgba(255, 85, 85, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, monokai: { @@ -52,6 +69,23 @@ export const THEMES: Record = { success: '#a6e22e', warning: '#e6db74', error: '#f92672', + info: '#66d9ef', + successForeground: '#272822', + warningForeground: '#272822', + errorForeground: '#272822', + successDim: 'rgba(166, 226, 46, 0.15)', + warningDim: 'rgba(230, 219, 116, 0.15)', + errorDim: 'rgba(249, 38, 114, 0.15)', + infoDim: 'rgba(102, 217, 239, 0.15)', + diffAddition: '#a6e22e', + diffAdditionBg: 'rgba(166, 226, 46, 0.15)', + diffDeletion: '#f92672', + diffDeletionBg: 'rgba(249, 38, 114, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, nord: { @@ -72,6 +106,23 @@ export const THEMES: Record = { success: '#a3be8c', warning: '#ebcb8b', error: '#bf616a', + info: '#81a1c1', + successForeground: '#2e3440', + warningForeground: '#2e3440', + errorForeground: '#2e3440', + successDim: 'rgba(163, 190, 140, 0.15)', + warningDim: 'rgba(235, 203, 139, 0.15)', + errorDim: 'rgba(191, 97, 106, 0.15)', + infoDim: 'rgba(129, 161, 193, 0.15)', + diffAddition: '#a3be8c', + diffAdditionBg: 'rgba(163, 190, 140, 0.15)', + diffDeletion: '#bf616a', + diffDeletionBg: 'rgba(191, 97, 106, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, 'tokyo-night': { @@ -92,6 +143,23 @@ export const THEMES: Record = { success: '#9ece6a', warning: '#e0af68', error: '#f7768e', + info: '#7dcfff', + successForeground: '#1a1b26', + warningForeground: '#1a1b26', + errorForeground: '#1a1b26', + successDim: 'rgba(158, 206, 106, 0.15)', + warningDim: 'rgba(224, 175, 104, 0.15)', + errorDim: 'rgba(247, 118, 142, 0.15)', + infoDim: 'rgba(125, 207, 255, 0.15)', + diffAddition: '#9ece6a', + diffAdditionBg: 'rgba(158, 206, 106, 0.15)', + diffDeletion: '#f7768e', + diffDeletionBg: 'rgba(247, 118, 142, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, 'catppuccin-mocha': { @@ -112,6 +180,23 @@ export const THEMES: Record = { success: '#a6e3a1', warning: '#fab387', error: '#f38ba8', + info: '#89b4fa', + successForeground: '#1e1e2e', + warningForeground: '#1e1e2e', + errorForeground: '#1e1e2e', + successDim: 'rgba(166, 227, 161, 0.15)', + warningDim: 'rgba(250, 179, 135, 0.15)', + errorDim: 'rgba(243, 139, 168, 0.15)', + infoDim: 'rgba(137, 180, 250, 0.15)', + diffAddition: '#a6e3a1', + diffAdditionBg: 'rgba(166, 227, 161, 0.15)', + diffDeletion: '#f38ba8', + diffDeletionBg: 'rgba(243, 139, 168, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, 'gruvbox-dark': { @@ -132,6 +217,23 @@ export const THEMES: Record = { success: '#b8bb26', warning: '#fabd2f', error: '#fb4934', + info: '#83a598', + successForeground: '#282828', + warningForeground: '#282828', + errorForeground: '#282828', + successDim: 'rgba(184, 187, 38, 0.15)', + warningDim: 'rgba(250, 189, 47, 0.15)', + errorDim: 'rgba(251, 73, 52, 0.15)', + infoDim: 'rgba(131, 165, 152, 0.15)', + diffAddition: '#b8bb26', + diffAdditionBg: 'rgba(184, 187, 38, 0.15)', + diffDeletion: '#fb4934', + diffDeletionBg: 'rgba(251, 73, 52, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, // Light themes @@ -153,6 +255,23 @@ export const THEMES: Record = { success: '#1a7f37', warning: '#9a6700', error: '#cf222e', + info: '#0969da', + successForeground: '#ffffff', + warningForeground: '#ffffff', + errorForeground: '#ffffff', + successDim: 'rgba(26, 127, 55, 0.1)', + warningDim: 'rgba(154, 103, 0, 0.1)', + errorDim: 'rgba(207, 34, 46, 0.1)', + infoDim: 'rgba(9, 105, 218, 0.1)', + diffAddition: '#1a7f37', + diffAdditionBg: 'rgba(26, 127, 55, 0.1)', + diffDeletion: '#cf222e', + diffDeletionBg: 'rgba(207, 34, 46, 0.1)', + overlay: 'rgba(0, 0, 0, 0.5)', + overlayHeavy: 'rgba(0, 0, 0, 0.7)', + hoverBg: 'rgba(0, 0, 0, 0.04)', + activeBg: 'rgba(0, 0, 0, 0.1)', + shadow: 'rgba(0, 0, 0, 0.15)', }, }, 'solarized-light': { @@ -173,6 +292,23 @@ export const THEMES: Record = { success: '#859900', warning: '#b58900', error: '#dc322f', + info: '#268bd2', + successForeground: '#fdf6e3', + warningForeground: '#fdf6e3', + errorForeground: '#fdf6e3', + successDim: 'rgba(133, 153, 0, 0.1)', + warningDim: 'rgba(181, 137, 0, 0.1)', + errorDim: 'rgba(220, 50, 47, 0.1)', + infoDim: 'rgba(38, 139, 210, 0.1)', + diffAddition: '#859900', + diffAdditionBg: 'rgba(133, 153, 0, 0.1)', + diffDeletion: '#dc322f', + diffDeletionBg: 'rgba(220, 50, 47, 0.1)', + overlay: 'rgba(0, 0, 0, 0.5)', + overlayHeavy: 'rgba(0, 0, 0, 0.7)', + hoverBg: 'rgba(0, 0, 0, 0.04)', + activeBg: 'rgba(0, 0, 0, 0.1)', + shadow: 'rgba(0, 0, 0, 0.15)', }, }, 'one-light': { @@ -193,6 +329,23 @@ export const THEMES: Record = { success: '#50a14f', warning: '#c18401', error: '#e45649', + info: '#4078f2', + successForeground: '#ffffff', + warningForeground: '#ffffff', + errorForeground: '#ffffff', + successDim: 'rgba(80, 161, 79, 0.1)', + warningDim: 'rgba(193, 132, 1, 0.1)', + errorDim: 'rgba(228, 86, 73, 0.1)', + infoDim: 'rgba(64, 120, 242, 0.1)', + diffAddition: '#50a14f', + diffAdditionBg: 'rgba(80, 161, 79, 0.1)', + diffDeletion: '#e45649', + diffDeletionBg: 'rgba(228, 86, 73, 0.1)', + overlay: 'rgba(0, 0, 0, 0.5)', + overlayHeavy: 'rgba(0, 0, 0, 0.7)', + hoverBg: 'rgba(0, 0, 0, 0.04)', + activeBg: 'rgba(0, 0, 0, 0.1)', + shadow: 'rgba(0, 0, 0, 0.15)', }, }, 'gruvbox-light': { @@ -213,6 +366,23 @@ export const THEMES: Record = { success: '#98971a', warning: '#d79921', error: '#cc241d', + info: '#458588', + successForeground: '#fbf1c7', + warningForeground: '#fbf1c7', + errorForeground: '#fbf1c7', + successDim: 'rgba(152, 151, 26, 0.1)', + warningDim: 'rgba(215, 153, 33, 0.1)', + errorDim: 'rgba(204, 36, 29, 0.1)', + infoDim: 'rgba(69, 133, 136, 0.1)', + diffAddition: '#98971a', + diffAdditionBg: 'rgba(152, 151, 26, 0.1)', + diffDeletion: '#cc241d', + diffDeletionBg: 'rgba(204, 36, 29, 0.1)', + overlay: 'rgba(0, 0, 0, 0.5)', + overlayHeavy: 'rgba(0, 0, 0, 0.7)', + hoverBg: 'rgba(0, 0, 0, 0.04)', + activeBg: 'rgba(0, 0, 0, 0.1)', + shadow: 'rgba(0, 0, 0, 0.15)', }, }, 'catppuccin-latte': { @@ -233,6 +403,23 @@ export const THEMES: Record = { success: '#40a02b', warning: '#fe640b', error: '#d20f39', + info: '#1e66f5', + successForeground: '#ffffff', + warningForeground: '#ffffff', + errorForeground: '#ffffff', + successDim: 'rgba(64, 160, 43, 0.1)', + warningDim: 'rgba(254, 100, 11, 0.1)', + errorDim: 'rgba(210, 15, 57, 0.1)', + infoDim: 'rgba(30, 102, 245, 0.1)', + diffAddition: '#40a02b', + diffAdditionBg: 'rgba(64, 160, 43, 0.1)', + diffDeletion: '#d20f39', + diffDeletionBg: 'rgba(210, 15, 57, 0.1)', + overlay: 'rgba(0, 0, 0, 0.5)', + overlayHeavy: 'rgba(0, 0, 0, 0.7)', + hoverBg: 'rgba(0, 0, 0, 0.04)', + activeBg: 'rgba(0, 0, 0, 0.1)', + shadow: 'rgba(0, 0, 0, 0.15)', }, }, 'ayu-light': { @@ -253,6 +440,23 @@ export const THEMES: Record = { success: '#86b300', warning: '#f2ae49', error: '#f07171', + info: '#399ee6', + successForeground: '#1a1a1a', + warningForeground: '#1a1a1a', + errorForeground: '#ffffff', + successDim: 'rgba(134, 179, 0, 0.1)', + warningDim: 'rgba(242, 174, 73, 0.1)', + errorDim: 'rgba(240, 113, 113, 0.1)', + infoDim: 'rgba(57, 158, 230, 0.1)', + diffAddition: '#86b300', + diffAdditionBg: 'rgba(134, 179, 0, 0.1)', + diffDeletion: '#f07171', + diffDeletionBg: 'rgba(240, 113, 113, 0.1)', + overlay: 'rgba(0, 0, 0, 0.5)', + overlayHeavy: 'rgba(0, 0, 0, 0.7)', + hoverBg: 'rgba(0, 0, 0, 0.04)', + activeBg: 'rgba(0, 0, 0, 0.1)', + shadow: 'rgba(0, 0, 0, 0.15)', }, }, // Vibe themes @@ -274,6 +478,23 @@ export const THEMES: Record = { success: '#7cb342', warning: '#d4af37', error: '#da70d6', + info: '#b89fd0', + successForeground: '#1a0f24', + warningForeground: '#1a0f24', + errorForeground: '#1a0f24', + successDim: 'rgba(124, 179, 66, 0.15)', + warningDim: 'rgba(212, 175, 55, 0.15)', + errorDim: 'rgba(218, 112, 214, 0.15)', + infoDim: 'rgba(184, 159, 208, 0.15)', + diffAddition: '#7cb342', + diffAdditionBg: 'rgba(124, 179, 66, 0.15)', + diffDeletion: '#da70d6', + diffDeletionBg: 'rgba(218, 112, 214, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, 'maestros-choice': { @@ -294,6 +515,23 @@ export const THEMES: Record = { success: '#66d9a0', warning: '#f4c430', error: '#e05070', + info: '#7aa2f7', + successForeground: '#1a1a24', + warningForeground: '#1a1a24', + errorForeground: '#1a1a24', + successDim: 'rgba(102, 217, 160, 0.15)', + warningDim: 'rgba(244, 196, 48, 0.15)', + errorDim: 'rgba(224, 80, 112, 0.15)', + infoDim: 'rgba(122, 162, 247, 0.15)', + diffAddition: '#66d9a0', + diffAdditionBg: 'rgba(102, 217, 160, 0.15)', + diffDeletion: '#e05070', + diffDeletionBg: 'rgba(224, 80, 112, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, 'dre-synth': { @@ -314,6 +552,23 @@ export const THEMES: Record = { success: '#00ffcc', warning: '#ff2a6d', error: '#ff2a6d', + info: '#40ffdd', + successForeground: '#0d0221', + warningForeground: '#0d0221', + errorForeground: '#0d0221', + successDim: 'rgba(0, 255, 204, 0.15)', + warningDim: 'rgba(255, 42, 109, 0.15)', + errorDim: 'rgba(255, 42, 109, 0.15)', + infoDim: 'rgba(64, 255, 221, 0.15)', + diffAddition: '#00ffcc', + diffAdditionBg: 'rgba(0, 255, 204, 0.15)', + diffDeletion: '#ff2a6d', + diffDeletionBg: 'rgba(255, 42, 109, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, inquest: { @@ -334,6 +589,23 @@ export const THEMES: Record = { success: '#f5f5f5', warning: '#cc0033', error: '#cc0033', + info: '#888888', + successForeground: '#0a0a0a', + warningForeground: '#ffffff', + errorForeground: '#ffffff', + successDim: 'rgba(245, 245, 245, 0.15)', + warningDim: 'rgba(204, 0, 51, 0.15)', + errorDim: 'rgba(204, 0, 51, 0.15)', + infoDim: 'rgba(136, 136, 136, 0.15)', + diffAddition: '#f5f5f5', + diffAdditionBg: 'rgba(245, 245, 245, 0.15)', + diffDeletion: '#cc0033', + diffDeletionBg: 'rgba(204, 0, 51, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, // Custom theme - user-configurable, defaults to Dracula @@ -355,6 +627,23 @@ export const THEMES: Record = { success: '#50fa7b', warning: '#ffb86c', error: '#ff5555', + info: '#8be9fd', + successForeground: '#282a36', + warningForeground: '#282a36', + errorForeground: '#282a36', + successDim: 'rgba(80, 250, 123, 0.15)', + warningDim: 'rgba(255, 184, 108, 0.15)', + errorDim: 'rgba(255, 85, 85, 0.15)', + infoDim: 'rgba(139, 233, 253, 0.15)', + diffAddition: '#50fa7b', + diffAdditionBg: 'rgba(80, 250, 123, 0.15)', + diffDeletion: '#ff5555', + diffDeletionBg: 'rgba(255, 85, 85, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }, }; diff --git a/src/web/components/ThemeProvider.tsx b/src/web/components/ThemeProvider.tsx index 4231255b0..8df3a5cd5 100644 --- a/src/web/components/ThemeProvider.tsx +++ b/src/web/components/ThemeProvider.tsx @@ -52,6 +52,23 @@ const defaultDarkTheme: Theme = { success: '#22c55e', warning: '#eab308', error: '#ef4444', + info: '#6366f1', + successForeground: '#0b0b0d', + warningForeground: '#0b0b0d', + errorForeground: '#0b0b0d', + successDim: 'rgba(34, 197, 94, 0.15)', + warningDim: 'rgba(234, 179, 8, 0.15)', + errorDim: 'rgba(239, 68, 68, 0.15)', + infoDim: 'rgba(99, 102, 241, 0.15)', + diffAddition: '#22c55e', + diffAdditionBg: 'rgba(34, 197, 94, 0.15)', + diffDeletion: '#ef4444', + diffDeletionBg: 'rgba(239, 68, 68, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }, }; @@ -77,6 +94,23 @@ const defaultLightTheme: Theme = { success: '#1a7f37', warning: '#9a6700', error: '#cf222e', + info: '#0969da', + successForeground: '#ffffff', + warningForeground: '#ffffff', + errorForeground: '#ffffff', + successDim: 'rgba(26, 127, 55, 0.1)', + warningDim: 'rgba(154, 103, 0, 0.1)', + errorDim: 'rgba(207, 34, 46, 0.1)', + infoDim: 'rgba(9, 105, 218, 0.1)', + diffAddition: '#1a7f37', + diffAdditionBg: 'rgba(26, 127, 55, 0.1)', + diffDeletion: '#cf222e', + diffDeletionBg: 'rgba(207, 34, 46, 0.1)', + overlay: 'rgba(0, 0, 0, 0.5)', + overlayHeavy: 'rgba(0, 0, 0, 0.7)', + hoverBg: 'rgba(0, 0, 0, 0.04)', + activeBg: 'rgba(0, 0, 0, 0.1)', + shadow: 'rgba(0, 0, 0, 0.15)', }, }; diff --git a/src/web/utils/cssCustomProperties.ts b/src/web/utils/cssCustomProperties.ts index 1ffa3b504..c196379c9 100644 --- a/src/web/utils/cssCustomProperties.ts +++ b/src/web/utils/cssCustomProperties.ts @@ -30,6 +30,23 @@ export type ThemeCSSProperty = | '--maestro-success' | '--maestro-warning' | '--maestro-error' + | '--maestro-info' + | '--maestro-success-foreground' + | '--maestro-warning-foreground' + | '--maestro-error-foreground' + | '--maestro-success-dim' + | '--maestro-warning-dim' + | '--maestro-error-dim' + | '--maestro-info-dim' + | '--maestro-diff-addition' + | '--maestro-diff-addition-bg' + | '--maestro-diff-deletion' + | '--maestro-diff-deletion-bg' + | '--maestro-overlay' + | '--maestro-overlay-heavy' + | '--maestro-hover-bg' + | '--maestro-active-bg' + | '--maestro-shadow' | '--maestro-mode'; /** @@ -49,6 +66,23 @@ const colorToCSSProperty: Record = { success: '--maestro-success', warning: '--maestro-warning', error: '--maestro-error', + info: '--maestro-info', + successForeground: '--maestro-success-foreground', + warningForeground: '--maestro-warning-foreground', + errorForeground: '--maestro-error-foreground', + successDim: '--maestro-success-dim', + warningDim: '--maestro-warning-dim', + errorDim: '--maestro-error-dim', + infoDim: '--maestro-info-dim', + diffAddition: '--maestro-diff-addition', + diffAdditionBg: '--maestro-diff-addition-bg', + diffDeletion: '--maestro-diff-deletion', + diffDeletionBg: '--maestro-diff-deletion-bg', + overlay: '--maestro-overlay', + overlayHeavy: '--maestro-overlay-heavy', + hoverBg: '--maestro-hover-bg', + activeBg: '--maestro-active-bg', + shadow: '--maestro-shadow', }; /** @@ -68,6 +102,23 @@ export const THEME_CSS_PROPERTIES: ThemeCSSProperty[] = [ '--maestro-success', '--maestro-warning', '--maestro-error', + '--maestro-info', + '--maestro-success-foreground', + '--maestro-warning-foreground', + '--maestro-error-foreground', + '--maestro-success-dim', + '--maestro-warning-dim', + '--maestro-error-dim', + '--maestro-info-dim', + '--maestro-diff-addition', + '--maestro-diff-addition-bg', + '--maestro-diff-deletion', + '--maestro-diff-deletion-bg', + '--maestro-overlay', + '--maestro-overlay-heavy', + '--maestro-hover-bg', + '--maestro-active-bg', + '--maestro-shadow', '--maestro-mode', ]; From 070bc33ab4951a2277a03c5ffe875257a0173337 Mon Sep 17 00:00:00 2001 From: openasocket Date: Sat, 14 Feb 2026 20:46:14 -0500 Subject: [PATCH 03/24] fix: complete theme token coverage in CustomThemeBuilder and remaining components - CustomThemeBuilder now displays all 30 tokens organized in 8 semantic sections - Fix remaining hardcoded colors in AgentSessionsModal chat bubbles, PlaygroundPanel button, and AgentSelectionScreen detection indicators - Update tests to assert against theme tokens instead of hardcoded color values Co-Authored-By: Claude Opus 4.6 --- .../components/AgentSessionsModal.test.tsx | 4 +- .../components/CustomThemeBuilder.test.tsx | 17 +++ .../components/AgentSessionsModal.tsx | 8 +- .../components/CustomThemeBuilder.tsx | 120 ++++++++++++++---- src/renderer/components/PlaygroundPanel.tsx | 4 +- .../Wizard/screens/AgentSelectionScreen.tsx | 6 +- 6 files changed, 121 insertions(+), 38 deletions(-) diff --git a/src/__tests__/renderer/components/AgentSessionsModal.test.tsx b/src/__tests__/renderer/components/AgentSessionsModal.test.tsx index 66a6b5736..48500e81a 100644 --- a/src/__tests__/renderer/components/AgentSessionsModal.test.tsx +++ b/src/__tests__/renderer/components/AgentSessionsModal.test.tsx @@ -1455,7 +1455,7 @@ describe('AgentSessionsModal', () => { await waitFor(() => { const messageBubble = screen.getByText('Dark mode message').closest('.rounded-lg'); expect(messageBubble).toHaveStyle({ backgroundColor: mockTheme.colors.accent }); - expect(messageBubble).toHaveStyle({ color: '#000' }); // Dark mode uses black text + expect(messageBubble).toHaveStyle({ color: mockTheme.colors.accentForeground }); }); }); @@ -1488,7 +1488,7 @@ describe('AgentSessionsModal', () => { await waitFor(() => { const messageBubble = screen.getByText('Light mode message').closest('.rounded-lg'); - expect(messageBubble).toHaveStyle({ color: '#fff' }); // Light mode uses white text + expect(messageBubble).toHaveStyle({ color: lightTheme.colors.accentForeground }); }); }); }); diff --git a/src/__tests__/renderer/components/CustomThemeBuilder.test.tsx b/src/__tests__/renderer/components/CustomThemeBuilder.test.tsx index 6d53b6eab..83733dc53 100644 --- a/src/__tests__/renderer/components/CustomThemeBuilder.test.tsx +++ b/src/__tests__/renderer/components/CustomThemeBuilder.test.tsx @@ -29,6 +29,23 @@ const mockThemeColors: ThemeColors = { success: '#10b981', warning: '#f59e0b', error: '#ef4444', + info: '#3b82f6', + successForeground: '#1a1a2e', + warningForeground: '#1a1a2e', + errorForeground: '#1a1a2e', + successDim: 'rgba(16, 185, 129, 0.15)', + warningDim: 'rgba(245, 158, 11, 0.15)', + errorDim: 'rgba(239, 68, 68, 0.15)', + infoDim: 'rgba(59, 130, 246, 0.15)', + diffAddition: '#10b981', + diffAdditionBg: 'rgba(16, 185, 129, 0.15)', + diffDeletion: '#ef4444', + diffDeletionBg: 'rgba(239, 68, 68, 0.15)', + overlay: 'rgba(0, 0, 0, 0.6)', + overlayHeavy: 'rgba(0, 0, 0, 0.8)', + hoverBg: 'rgba(255, 255, 255, 0.06)', + activeBg: 'rgba(255, 255, 255, 0.15)', + shadow: 'rgba(0, 0, 0, 0.3)', }; const mockTheme: Theme = { diff --git a/src/renderer/components/AgentSessionsModal.tsx b/src/renderer/components/AgentSessionsModal.tsx index 22469d1c5..89a5e17dd 100644 --- a/src/renderer/components/AgentSessionsModal.tsx +++ b/src/renderer/components/AgentSessionsModal.tsx @@ -550,9 +550,7 @@ export function AgentSessionsModal({ msg.type === 'user' ? theme.colors.accent : theme.colors.bgMain, color: msg.type === 'user' - ? theme.mode === 'light' - ? '#fff' - : '#000' + ? theme.colors.accentForeground : theme.colors.textMain, }} > @@ -564,9 +562,7 @@ export function AgentSessionsModal({ style={{ color: msg.type === 'user' - ? theme.mode === 'light' - ? '#fff' - : '#000' + ? theme.colors.accentForeground : theme.colors.textDim, }} > diff --git a/src/renderer/components/CustomThemeBuilder.tsx b/src/renderer/components/CustomThemeBuilder.tsx index 40c8dc15d..a505e4daa 100644 --- a/src/renderer/components/CustomThemeBuilder.tsx +++ b/src/renderer/components/CustomThemeBuilder.tsx @@ -29,23 +29,83 @@ interface CustomThemeBuilderProps { onImportSuccess?: (message: string) => void; } -// Color picker labels with descriptions -const COLOR_CONFIG: { key: keyof ThemeColors; label: string; description: string }[] = [ - { key: 'bgMain', label: 'Main Background', description: 'Primary content area' }, - { key: 'bgSidebar', label: 'Sidebar Background', description: 'Left & right panels' }, - { key: 'bgActivity', label: 'Activity Background', description: 'Hover, active states' }, - { key: 'border', label: 'Border', description: 'Dividers & outlines' }, - { key: 'textMain', label: 'Main Text', description: 'Primary text color' }, - { key: 'textDim', label: 'Dimmed Text', description: 'Secondary text' }, - { key: 'accent', label: 'Accent', description: 'Highlights, links' }, - { key: 'accentDim', label: 'Accent Dim', description: 'Accent with transparency' }, - { key: 'accentText', label: 'Accent Text', description: 'Text in accent contexts' }, - { key: 'accentForeground', label: 'Accent Foreground', description: 'Text ON accent' }, - { key: 'success', label: 'Success', description: 'Green states' }, - { key: 'warning', label: 'Warning', description: 'Yellow/orange states' }, - { key: 'error', label: 'Error', description: 'Red states' }, +// Color picker sections with labels and descriptions +const COLOR_SECTIONS: { title: string; items: { key: keyof ThemeColors; label: string; description: string }[] }[] = [ + { + title: 'Backgrounds', + items: [ + { key: 'bgMain', label: 'Main Background', description: 'Primary content area' }, + { key: 'bgSidebar', label: 'Sidebar Background', description: 'Left & right panels' }, + { key: 'bgActivity', label: 'Activity Background', description: 'Hover, active states' }, + { key: 'border', label: 'Border', description: 'Dividers & outlines' }, + ], + }, + { + title: 'Typography', + items: [ + { key: 'textMain', label: 'Main Text', description: 'Primary text color' }, + { key: 'textDim', label: 'Dimmed Text', description: 'Secondary text' }, + ], + }, + { + title: 'Accent', + items: [ + { key: 'accent', label: 'Accent', description: 'Highlights, links' }, + { key: 'accentDim', label: 'Accent Dim', description: 'Accent with transparency' }, + { key: 'accentText', label: 'Accent Text', description: 'Text in accent contexts' }, + { key: 'accentForeground', label: 'Accent Foreground', description: 'Text ON accent bg' }, + ], + }, + { + title: 'Status', + items: [ + { key: 'success', label: 'Success', description: 'Green states' }, + { key: 'warning', label: 'Warning', description: 'Yellow/orange states' }, + { key: 'error', label: 'Error', description: 'Red states' }, + { key: 'info', label: 'Info', description: 'Blue/informational states' }, + ], + }, + { + title: 'Status Foregrounds', + items: [ + { key: 'successForeground', label: 'Success Foreground', description: 'Text ON success bg' }, + { key: 'warningForeground', label: 'Warning Foreground', description: 'Text ON warning bg' }, + { key: 'errorForeground', label: 'Error Foreground', description: 'Text ON error bg' }, + ], + }, + { + title: 'Status Dim Backgrounds', + items: [ + { key: 'successDim', label: 'Success Dim', description: 'Subtle success badges' }, + { key: 'warningDim', label: 'Warning Dim', description: 'Subtle warning badges' }, + { key: 'errorDim', label: 'Error Dim', description: 'Subtle error badges' }, + { key: 'infoDim', label: 'Info Dim', description: 'Subtle info badges' }, + ], + }, + { + title: 'Git Diff', + items: [ + { key: 'diffAddition', label: 'Diff Addition', description: 'Added lines color' }, + { key: 'diffAdditionBg', label: 'Diff Addition Bg', description: 'Added lines background' }, + { key: 'diffDeletion', label: 'Diff Deletion', description: 'Deleted lines color' }, + { key: 'diffDeletionBg', label: 'Diff Deletion Bg', description: 'Deleted lines background' }, + ], + }, + { + title: 'Overlays & Interactive', + items: [ + { key: 'overlay', label: 'Overlay', description: 'Modal backdrop' }, + { key: 'overlayHeavy', label: 'Overlay Heavy', description: 'Wizard/fullscreen backdrop' }, + { key: 'hoverBg', label: 'Hover Background', description: 'Subtle hover state' }, + { key: 'activeBg', label: 'Active Background', description: 'Selected/active state' }, + { key: 'shadow', label: 'Shadow', description: 'Elevation shadow color' }, + ], + }, ]; +// Flat list of all color config items (used for import validation and iteration) +const COLOR_CONFIG = COLOR_SECTIONS.flatMap((section) => section.items); + // Mini UI Preview component function MiniUIPreview({ colors }: { colors: ThemeColors }) { return ( @@ -570,16 +630,26 @@ export function CustomThemeBuilder({ borderColor: theme.colors.border, }} > - {COLOR_CONFIG.map(({ key, label, description }) => ( - + {COLOR_SECTIONS.map((section) => ( +
+
+ {section.title} +
+ {section.items.map(({ key, label, description }) => ( + + ))} +
))} diff --git a/src/renderer/components/PlaygroundPanel.tsx b/src/renderer/components/PlaygroundPanel.tsx index 68a9455e8..b84061cd4 100644 --- a/src/renderer/components/PlaygroundPanel.tsx +++ b/src/renderer/components/PlaygroundPanel.tsx @@ -828,8 +828,8 @@ ${staggerDelays.map((delay, i) => `svg.wand-sparkle-active path:nth-child(${i + onClick={() => setShowKeyboardMasteryCelebration(true)} className="w-full flex items-center justify-center gap-2 px-4 py-2 rounded font-medium transition-colors" style={{ - backgroundColor: '#9B59B6', - color: '#fff', + backgroundColor: theme.colors.accent, + color: theme.colors.accentForeground, }} > diff --git a/src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx b/src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx index 2de0b4e06..bdace000e 100644 --- a/src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx +++ b/src/renderer/components/Wizard/screens/AgentSelectionScreen.tsx @@ -1265,14 +1265,14 @@ export function AgentSelectionScreen({ theme }: AgentSelectionScreenProps): JSX.
{isDetected ? ( - + ) : ( - + )}
)} From b3945761400277954433ca1cd39e3e7a5cd47c7e Mon Sep 17 00:00:00 2001 From: openasocket Date: Sat, 14 Feb 2026 20:53:43 -0500 Subject: [PATCH 04/24] fix: use bgActivity for disabled Send button background in Wizard Dre Synth theme has bright teal border color (#00d4aa) which made textDim (#60e0d0) invisible when used as disabled button text on border background. Switch disabled Send button from border to bgActivity background to ensure text contrast across all themes. Co-Authored-By: Claude Opus 4.6 --- src/renderer/components/Wizard/screens/ConversationScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/Wizard/screens/ConversationScreen.tsx b/src/renderer/components/Wizard/screens/ConversationScreen.tsx index d16b8de9a..a5e9a6bd8 100644 --- a/src/renderer/components/Wizard/screens/ConversationScreen.tsx +++ b/src/renderer/components/Wizard/screens/ConversationScreen.tsx @@ -1545,7 +1545,7 @@ export function ConversationScreen({ backgroundColor: inputValue.trim() && !state.isConversationLoading ? theme.colors.accent - : theme.colors.border, + : theme.colors.bgActivity, color: inputValue.trim() && !state.isConversationLoading ? theme.colors.accentForeground From d05e071a656d9684222398578a55c8c0aecfae89 Mon Sep 17 00:00:00 2001 From: openasocket Date: Sat, 14 Feb 2026 20:54:22 -0500 Subject: [PATCH 05/24] fix: tune Dre Synth status colors for better visual distinction Separate success/warning from accent/error colors so each status is visually distinct: success (#33ff99 green), warning (#ff9944 orange), error (#ff2a6d pink). Previously success shared accent teal and warning shared error pink. Also slightly brighten bgActivity for better contrast. Co-Authored-By: Claude Opus 4.6 --- src/shared/themes.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/shared/themes.ts b/src/shared/themes.ts index 1930198bd..df2130ec0 100644 --- a/src/shared/themes.ts +++ b/src/shared/themes.ts @@ -541,7 +541,7 @@ export const THEMES: Record = { colors: { bgMain: '#0d0221', bgSidebar: '#0a0118', - bgActivity: '#150530', + bgActivity: '#1a0838', border: '#00d4aa', textMain: '#f0e6ff', textDim: '#60e0d0', @@ -549,19 +549,19 @@ export const THEMES: Record = { accentDim: 'rgba(0, 255, 204, 0.25)', accentText: '#40ffdd', accentForeground: '#0d0221', - success: '#00ffcc', - warning: '#ff2a6d', + success: '#33ff99', + warning: '#ff9944', error: '#ff2a6d', info: '#40ffdd', successForeground: '#0d0221', warningForeground: '#0d0221', errorForeground: '#0d0221', - successDim: 'rgba(0, 255, 204, 0.15)', - warningDim: 'rgba(255, 42, 109, 0.15)', + successDim: 'rgba(51, 255, 153, 0.15)', + warningDim: 'rgba(255, 153, 68, 0.15)', errorDim: 'rgba(255, 42, 109, 0.15)', infoDim: 'rgba(64, 255, 221, 0.15)', - diffAddition: '#00ffcc', - diffAdditionBg: 'rgba(0, 255, 204, 0.15)', + diffAddition: '#33ff99', + diffAdditionBg: 'rgba(51, 255, 153, 0.15)', diffDeletion: '#ff2a6d', diffDeletionBg: 'rgba(255, 42, 109, 0.15)', overlay: 'rgba(0, 0, 0, 0.6)', From 2ceefbfa1231d1d21e428bd1e6c469d03a77ed10 Mon Sep 17 00:00:00 2001 From: openasocket Date: Sat, 14 Feb 2026 21:28:38 -0500 Subject: [PATCH 06/24] fix: change Dre Synth border from bright teal to muted dark teal The border token (#00d4aa) was a bright teal identical to the accent color, causing invisible text on 100+ components that use border as a disabled button/badge background with textDim text. Changed to #1a3a4a (dark muted teal) which maintains the synth aesthetic while functioning correctly as a border/surface color. Co-Authored-By: Claude Opus 4.6 --- src/shared/themes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/themes.ts b/src/shared/themes.ts index df2130ec0..853710cde 100644 --- a/src/shared/themes.ts +++ b/src/shared/themes.ts @@ -542,7 +542,7 @@ export const THEMES: Record = { bgMain: '#0d0221', bgSidebar: '#0a0118', bgActivity: '#1a0838', - border: '#00d4aa', + border: '#1a3a4a', textMain: '#f0e6ff', textDim: '#60e0d0', accent: '#00ffcc', From 610e2aee70354975ef1303750117940cc335d350 Mon Sep 17 00:00:00 2001 From: openasocket Date: Mon, 16 Feb 2026 00:04:57 -0500 Subject: [PATCH 07/24] feat: add auto-scroll toggle for AI output with scroll-aware pause/resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add configurable auto-scroll behavior for AI mode output that automatically scrolls to bottom when new content arrives, with smart pause when user scrolls up to read content and automatic resume when they scroll back to bottom. - Add autoScrollAiMode setting persisted via electron-store (default: off) - Add inline floating toggle button (bottom-left) with visual state feedback - Add keyboard shortcut (Alt+Cmd+S) registered as system utility shortcut - Add Settings Modal checkbox in General tab - Thread setting through App → MainPanel → TerminalOutput via props - Use instant scroll (behavior: 'auto') to prevent jitter during streaming - Pause auto-scroll when user scrolls up, resume on scroll-to-bottom - Toggle button reflects paused state (dim icon when paused) - New message indicator shown when paused (user scrolled away from bottom) Co-Authored-By: Claude Opus 4.6 --- src/renderer/App.tsx | 10 +++ src/renderer/components/MainPanel.tsx | 4 ++ src/renderer/components/SettingsModal.tsx | 15 ++++ src/renderer/components/TerminalOutput.tsx | 69 +++++++++++++++++-- src/renderer/constants/shortcuts.ts | 5 ++ .../hooks/keyboard/useMainKeyboardHandler.ts | 8 ++- src/renderer/hooks/props/useMainPanelProps.ts | 6 ++ src/renderer/hooks/settings/useSettings.ts | 23 +++++++ 8 files changed, 131 insertions(+), 9 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 0681d7897..d1aa62f2c 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -555,6 +555,10 @@ function MaestroConsoleInner() { // File tab refresh settings fileTabAutoRefreshEnabled, + // Auto-scroll settings + autoScrollAiMode, + setAutoScrollAiMode, + // Windows warning suppression suppressWindowsWarning, setSuppressWindowsWarning, @@ -10837,6 +10841,10 @@ You are taking over this conversation. Based on the context above, provide a bri // Session bookmark toggle toggleBookmark, + + // Auto-scroll AI mode toggle + autoScrollAiMode, + setAutoScrollAiMode, }; // Update flat file list when active session's tree, expanded folders, filter, or hidden files setting changes @@ -11198,6 +11206,8 @@ You are taking over this conversation. Based on the context above, provide a bri filePreviewLoading, markdownEditMode, chatRawTextMode, + autoScrollAiMode, + setAutoScrollAiMode, shortcuts, rightPanelOpen, maxOutputLines, diff --git a/src/renderer/components/MainPanel.tsx b/src/renderer/components/MainPanel.tsx index 2bb07251d..b6883227d 100644 --- a/src/renderer/components/MainPanel.tsx +++ b/src/renderer/components/MainPanel.tsx @@ -108,6 +108,8 @@ interface MainPanelProps { filePreviewLoading?: { name: string; path: string } | null; markdownEditMode: boolean; // FilePreview: whether editing file content chatRawTextMode: boolean; // TerminalOutput: whether to show raw text in AI responses + autoScrollAiMode: boolean; // Whether to auto-scroll in AI mode + setAutoScrollAiMode: (value: boolean) => void; // Toggle auto-scroll in AI mode shortcuts: Record; rightPanelOpen: boolean; maxOutputLines: number; @@ -1800,6 +1802,8 @@ export const MainPanel = React.memo( ? () => props.refreshFileTree?.(activeSession.id) : undefined } + autoScrollAiMode={props.autoScrollAiMode} + setAutoScrollAiMode={props.setAutoScrollAiMode} onOpenInTab={props.onOpenSavedFileInTab} /> )} diff --git a/src/renderer/components/SettingsModal.tsx b/src/renderer/components/SettingsModal.tsx index fd28c1690..15899344f 100644 --- a/src/renderer/components/SettingsModal.tsx +++ b/src/renderer/components/SettingsModal.tsx @@ -32,6 +32,7 @@ import { PartyPopper, Tag, User, + ArrowDownToLine, Clapperboard, } from 'lucide-react'; import { useSettings } from '../hooks'; @@ -328,6 +329,9 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro // Automatic tab naming settings automaticTabNamingEnabled, setAutomaticTabNamingEnabled, + // Auto-scroll in AI mode + autoScrollAiMode, + setAutoScrollAiMode, // Director's Notes settings directorNotesSettings, setDirectorNotesSettings, @@ -1475,6 +1479,17 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro theme={theme} /> + {/* Auto-scroll AI Output */} + + {/* Default Thinking Toggle - Three states: Off, On, Sticky */}