3 Commits

Author SHA1 Message Date
simos
72e97c4fbc Release 1.10.5 2025-11-01 11:05:16 +01:00
viper151
b5d1fed354 feat(chat): add CLAUDE.md support and fix scroll behavior (#222) 2025-11-01 07:01:25 +01:00
simos
fefcc0f338 feat(editor): Move code editor preferences to settings and add option to expand editor
Add global settings integration and persistent user preferences for
the code editor. Settings are now stored in localStorage and persist
across sessions.

Changes:
- Add theme, word wrap, minimap, line numbers, and font size settings
- Load editor preferences from localStorage on initialization
- Expose global openSettings function for cross-component access
- Add settingsInitialTab state to control which settings tab opens
- Pass initialTab prop to Settings component for navigation

This improves UX by remembering user preferences and allows other
components to open settings to specific tabs programmatically.
2025-10-31 12:11:47 +00:00
10 changed files with 468 additions and 126 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@siteboon/claude-code-ui", "name": "@siteboon/claude-code-ui",
"version": "1.10.4", "version": "1.10.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@siteboon/claude-code-ui", "name": "@siteboon/claude-code-ui",
"version": "1.10.4", "version": "1.10.5",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.29", "@anthropic-ai/claude-agent-sdk": "^0.1.29",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@siteboon/claude-code-ui", "name": "@siteboon/claude-code-ui",
"version": "1.10.4", "version": "1.10.5",
"description": "A web-based UI for Claude Code CLI", "description": "A web-based UI for Claude Code CLI",
"type": "module", "type": "module",
"main": "server/index.js", "main": "server/index.js",

View File

@@ -79,6 +79,16 @@ function mapCliOptionsToSDK(options = {}) {
// Map model (default to sonnet) // Map model (default to sonnet)
sdkOptions.model = options.model || 'sonnet'; sdkOptions.model = options.model || 'sonnet';
// Map system prompt configuration
sdkOptions.systemPrompt = {
type: 'preset',
preset: 'claude_code' // Required to use CLAUDE.md
};
// Map setting sources for CLAUDE.md loading
// This loads CLAUDE.md from project, user (~/.config/claude/CLAUDE.md), and local directories
sdkOptions.settingSources = ['project', 'user', 'local'];
// Map resume session // Map resume session
if (sessionId) { if (sessionId) {
sdkOptions.resume = sessionId; sdkOptions.resume = sessionId;
@@ -374,7 +384,7 @@ async function queryClaudeSDK(command, options = {}, ws) {
for await (const message of queryInstance) { for await (const message of queryInstance) {
// Capture session ID from first message // Capture session ID from first message
if (message.session_id && !capturedSessionId) { if (message.session_id && !capturedSessionId) {
console.log('📝 Captured session ID:', message.session_id);
capturedSessionId = message.session_id; capturedSessionId = message.session_id;
addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir); addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir);

View File

@@ -502,16 +502,16 @@ router.post('/generate-commit-message', async (req, res) => {
*/ */
async function generateCommitMessageWithAI(files, diffContext, provider, projectPath) { async function generateCommitMessageWithAI(files, diffContext, provider, projectPath) {
// Create the prompt // Create the prompt
const prompt = `You are a git commit message generator. Based on the following file changes and diffs, generate a commit message in conventional commit format. const prompt = `Generate a conventional commit message for these changes.
REQUIREMENTS: REQUIREMENTS:
- Use conventional commit format: type(scope): subject - Format: type(scope): subject
- Include a body that explains what changed and why - Include body explaining what changed and why
- Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore - Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
- Keep subject line under 50 characters - Subject under 50 chars, body wrapped at 72 chars
- Wrap body at 72 characters - Focus on user-facing changes, not implementation details
- Be specific and descriptive - Consider what's being added AND removed
- Return ONLY the commit message, nothing else - no markdown, no explanations, no code blocks - Return ONLY the commit message (no markdown, explanations, or code blocks)
FILES CHANGED: FILES CHANGED:
${files.map(f => `- ${f}`).join('\n')} ${files.map(f => `- ${f}`).join('\n')}
@@ -519,7 +519,7 @@ ${files.map(f => `- ${f}`).join('\n')}
DIFFS: DIFFS:
${diffContext.substring(0, 4000)} ${diffContext.substring(0, 4000)}
Generate the commit message now:`; Generate the commit message:`;
try { try {
// Create a simple writer that collects the response // Create a simple writer that collects the response

View File

@@ -55,6 +55,7 @@ function AppContent() {
const [isLoadingProjects, setIsLoadingProjects] = useState(true); const [isLoadingProjects, setIsLoadingProjects] = useState(true);
const [isInputFocused, setIsInputFocused] = useState(false); const [isInputFocused, setIsInputFocused] = useState(false);
const [showSettings, setShowSettings] = useState(false); const [showSettings, setShowSettings] = useState(false);
const [settingsInitialTab, setSettingsInitialTab] = useState('tools');
const [showQuickSettings, setShowQuickSettings] = useState(false); const [showQuickSettings, setShowQuickSettings] = useState(false);
const [autoExpandTools, setAutoExpandTools] = useLocalStorage('autoExpandTools', false); const [autoExpandTools, setAutoExpandTools] = useLocalStorage('autoExpandTools', false);
const [showRawParameters, setShowRawParameters] = useLocalStorage('showRawParameters', false); const [showRawParameters, setShowRawParameters] = useLocalStorage('showRawParameters', false);
@@ -308,6 +309,12 @@ function AppContent() {
// Expose fetchProjects globally for component access // Expose fetchProjects globally for component access
window.refreshProjects = fetchProjects; window.refreshProjects = fetchProjects;
// Expose openSettings function globally for component access
window.openSettings = useCallback((tab = 'tools') => {
setSettingsInitialTab(tab);
setShowSettings(true);
}, []);
// Handle URL-based session loading // Handle URL-based session loading
useEffect(() => { useEffect(() => {
if (sessionId && projects.length > 0) { if (sessionId && projects.length > 0) {
@@ -927,6 +934,7 @@ function AppContent() {
isOpen={showSettings} isOpen={showSettings}
onClose={() => setShowSettings(false)} onClose={() => setShowSettings(false)}
projects={projects} projects={projects}
initialTab={settingsInitialTab}
/> />
{/* Version Upgrade Modal */} {/* Version Upgrade Modal */}

View File

@@ -2603,7 +2603,8 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
const scrollToBottom = useCallback(() => { const scrollToBottom = useCallback(() => {
if (scrollContainerRef.current) { if (scrollContainerRef.current) {
scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight; scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight;
setIsUserScrolledUp(false); // Don't reset isUserScrolledUp here - let the scroll handler manage it
// This prevents fighting with user's scroll position during streaming
} }
}, []); }, []);
@@ -3522,7 +3523,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
const prevTop = scrollPositionRef.current.top; const prevTop = scrollPositionRef.current.top;
const newHeight = container.scrollHeight; const newHeight = container.scrollHeight;
const heightDiff = newHeight - prevHeight; const heightDiff = newHeight - prevHeight;
// If content was added above the current view, adjust scroll position // If content was added above the current view, adjust scroll position
if (heightDiff > 0 && prevTop > 0) { if (heightDiff > 0 && prevTop > 0) {
container.scrollTop = prevTop + heightDiff; container.scrollTop = prevTop + heightDiff;
@@ -3536,9 +3537,12 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
if (scrollContainerRef.current && chatMessages.length > 0 && !isLoadingSessionRef.current) { if (scrollContainerRef.current && chatMessages.length > 0 && !isLoadingSessionRef.current) {
// Only scroll if we're not in the middle of loading a session // Only scroll if we're not in the middle of loading a session
// This prevents the "double scroll" effect during session switching // This prevents the "double scroll" effect during session switching
// Also reset scroll state // Reset scroll state when switching sessions
setIsUserScrolledUp(false); setIsUserScrolledUp(false);
setTimeout(() => scrollToBottom(), 200); // Delay to ensure full rendering setTimeout(() => {
scrollToBottom();
// After scrolling, the scroll event handler will naturally set isUserScrolledUp based on position
}, 200); // Delay to ensure full rendering
} }
}, [selectedSession?.id, selectedProject?.name]); // Only trigger when session/project changes }, [selectedSession?.id, selectedProject?.name]); // Only trigger when session/project changes

View File

@@ -10,23 +10,37 @@ import { oneDark } from '@codemirror/theme-one-dark';
import { EditorView, showPanel, ViewPlugin } from '@codemirror/view'; import { EditorView, showPanel, ViewPlugin } from '@codemirror/view';
import { unifiedMergeView, getChunks } from '@codemirror/merge'; import { unifiedMergeView, getChunks } from '@codemirror/merge';
import { showMinimap } from '@replit/codemirror-minimap'; import { showMinimap } from '@replit/codemirror-minimap';
import { X, Save, Download, Maximize2, Minimize2, Eye, EyeOff } from 'lucide-react'; import { X, Save, Download, Maximize2, Minimize2 } from 'lucide-react';
import { api } from '../utils/api'; import { api } from '../utils/api';
function CodeEditor({ file, onClose, projectPath, isSidebar = false }) { function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded = false, onToggleExpand = null }) {
const [content, setContent] = useState(''); const [content, setContent] = useState('');
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false);
const [isDarkMode, setIsDarkMode] = useState(true); const [isDarkMode, setIsDarkMode] = useState(() => {
const savedTheme = localStorage.getItem('codeEditorTheme');
return savedTheme ? savedTheme === 'dark' : true;
});
const [saveSuccess, setSaveSuccess] = useState(false); const [saveSuccess, setSaveSuccess] = useState(false);
const [showDiff, setShowDiff] = useState(!!file.diffInfo); const [showDiff, setShowDiff] = useState(!!file.diffInfo);
const [wordWrap, setWordWrap] = useState(false); const [wordWrap, setWordWrap] = useState(() => {
return localStorage.getItem('codeEditorWordWrap') === 'true';
});
const [minimapEnabled, setMinimapEnabled] = useState(() => {
return localStorage.getItem('codeEditorShowMinimap') !== 'false';
});
const [showLineNumbers, setShowLineNumbers] = useState(() => {
return localStorage.getItem('codeEditorLineNumbers') !== 'false';
});
const [fontSize, setFontSize] = useState(() => {
return localStorage.getItem('codeEditorFontSize') || '14';
});
const editorRef = useRef(null); const editorRef = useRef(null);
// Create minimap extension with chunk-based gutters // Create minimap extension with chunk-based gutters
const minimapExtension = useMemo(() => { const minimapExtension = useMemo(() => {
if (!file.diffInfo || !showDiff) return []; if (!file.diffInfo || !showDiff || !minimapEnabled) return [];
const gutters = {}; const gutters = {};
@@ -58,7 +72,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
}; };
}) })
]; ];
}, [file.diffInfo, showDiff, isDarkMode]); }, [file.diffInfo, showDiff, minimapEnabled, isDarkMode]);
// Create extension to scroll to first chunk on mount // Create extension to scroll to first chunk on mount
const scrollToFirstChunkExtension = useMemo(() => { const scrollToFirstChunkExtension = useMemo(() => {
@@ -89,24 +103,28 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
]; ];
}, [file.diffInfo, showDiff]); }, [file.diffInfo, showDiff]);
// Create diff navigation panel extension // Create editor toolbar panel - always visible
const diffNavigationPanel = useMemo(() => { const editorToolbarPanel = useMemo(() => {
if (!file.diffInfo || !showDiff) return [];
const createPanel = (view) => { const createPanel = (view) => {
const dom = document.createElement('div'); const dom = document.createElement('div');
dom.className = 'cm-diff-navigation-panel'; dom.className = 'cm-editor-toolbar-panel';
let currentIndex = 0; let currentIndex = 0;
const updatePanel = () => { const updatePanel = () => {
// Use getChunks API to get ALL chunks regardless of viewport // Check if we have diff info and it's enabled
const chunksData = getChunks(view.state); const hasDiff = file.diffInfo && showDiff;
const chunksData = hasDiff ? getChunks(view.state) : null;
const chunks = chunksData?.chunks || []; const chunks = chunksData?.chunks || [];
const chunkCount = chunks.length; const chunkCount = chunks.length;
dom.innerHTML = ` // Build the toolbar HTML
<div style="display: flex; align-items: center; gap: 8px;"> let toolbarHTML = '<div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">';
// Left side - diff navigation (if applicable)
toolbarHTML += '<div style="display: flex; align-items: center; gap: 8px;">';
if (hasDiff) {
toolbarHTML += `
<span style="font-weight: 500;">${chunkCount > 0 ? `${currentIndex + 1}/${chunkCount}` : '0'} changes</span> <span style="font-weight: 500;">${chunkCount > 0 ? `${currentIndex + 1}/${chunkCount}` : '0'} changes</span>
<button class="cm-diff-nav-btn cm-diff-nav-prev" title="Previous change" ${chunkCount === 0 ? 'disabled' : ''}> <button class="cm-diff-nav-btn cm-diff-nav-prev" title="Previous change" ${chunkCount === 0 ? 'disabled' : ''}>
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -118,41 +136,110 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg> </svg>
</button> </button>
</div> `;
}
toolbarHTML += '</div>';
// Right side - action buttons
toolbarHTML += '<div style="display: flex; align-items: center; gap: 4px;">';
// Show/hide diff button (only if there's diff info)
if (file.diffInfo) {
toolbarHTML += `
<button class="cm-toolbar-btn cm-toggle-diff-btn" title="${showDiff ? 'Hide diff highlighting' : 'Show diff highlighting'}">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
${showDiff ?
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />' :
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />'
}
</svg>
</button>
`;
}
// Settings button
toolbarHTML += `
<button class="cm-toolbar-btn cm-settings-btn" title="Editor Settings">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</button>
`; `;
const prevBtn = dom.querySelector('.cm-diff-nav-prev'); // Expand button (only in sidebar mode)
const nextBtn = dom.querySelector('.cm-diff-nav-next'); if (isSidebar && onToggleExpand) {
toolbarHTML += `
<button class="cm-toolbar-btn cm-expand-btn" title="${isExpanded ? 'Collapse editor' : 'Expand editor to full width'}">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
${isExpanded ?
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25" />' :
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />'
}
</svg>
</button>
`;
}
prevBtn?.addEventListener('click', () => { toolbarHTML += '</div>';
if (chunks.length === 0) return; toolbarHTML += '</div>';
currentIndex = currentIndex > 0 ? currentIndex - 1 : chunks.length - 1;
// Navigate to the chunk - use fromB which is the position in the current document dom.innerHTML = toolbarHTML;
const chunk = chunks[currentIndex];
if (chunk) { // Attach event listeners for diff navigation
// Scroll to the start of the chunk in the B side (current document) if (hasDiff) {
view.dispatch({ const prevBtn = dom.querySelector('.cm-diff-nav-prev');
effects: EditorView.scrollIntoView(chunk.fromB, { y: 'center' }) const nextBtn = dom.querySelector('.cm-diff-nav-next');
});
prevBtn?.addEventListener('click', () => {
if (chunks.length === 0) return;
currentIndex = currentIndex > 0 ? currentIndex - 1 : chunks.length - 1;
const chunk = chunks[currentIndex];
if (chunk) {
view.dispatch({
effects: EditorView.scrollIntoView(chunk.fromB, { y: 'center' })
});
}
updatePanel();
});
nextBtn?.addEventListener('click', () => {
if (chunks.length === 0) return;
currentIndex = currentIndex < chunks.length - 1 ? currentIndex + 1 : 0;
const chunk = chunks[currentIndex];
if (chunk) {
view.dispatch({
effects: EditorView.scrollIntoView(chunk.fromB, { y: 'center' })
});
}
updatePanel();
});
}
// Attach event listener for toggle diff button
if (file.diffInfo) {
const toggleDiffBtn = dom.querySelector('.cm-toggle-diff-btn');
toggleDiffBtn?.addEventListener('click', () => {
setShowDiff(!showDiff);
});
}
// Attach event listener for settings button
const settingsBtn = dom.querySelector('.cm-settings-btn');
settingsBtn?.addEventListener('click', () => {
if (window.openSettings) {
window.openSettings('appearance');
} }
updatePanel();
}); });
nextBtn?.addEventListener('click', () => { // Attach event listener for expand button
if (chunks.length === 0) return; if (isSidebar && onToggleExpand) {
currentIndex = currentIndex < chunks.length - 1 ? currentIndex + 1 : 0; const expandBtn = dom.querySelector('.cm-expand-btn');
expandBtn?.addEventListener('click', () => {
// Navigate to the chunk - use fromB which is the position in the current document onToggleExpand();
const chunk = chunks[currentIndex]; });
if (chunk) { }
// Scroll to the start of the chunk in the B side (current document)
view.dispatch({
effects: EditorView.scrollIntoView(chunk.fromB, { y: 'center' })
});
}
updatePanel();
});
}; };
updatePanel(); updatePanel();
@@ -165,7 +252,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
}; };
return [showPanel.of(createPanel)]; return [showPanel.of(createPanel)];
}, [file.diffInfo, showDiff]); }, [file.diffInfo, showDiff, isSidebar, isExpanded, onToggleExpand]);
// Get language extension based on file extension // Get language extension based on file extension
const getLanguageExtension = (filename) => { const getLanguageExtension = (filename) => {
@@ -290,6 +377,57 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
setIsFullscreen(!isFullscreen); setIsFullscreen(!isFullscreen);
}; };
// Save theme preference to localStorage
useEffect(() => {
localStorage.setItem('codeEditorTheme', isDarkMode ? 'dark' : 'light');
}, [isDarkMode]);
// Save word wrap preference to localStorage
useEffect(() => {
localStorage.setItem('codeEditorWordWrap', wordWrap.toString());
}, [wordWrap]);
// Listen for settings changes from the Settings modal
useEffect(() => {
const handleStorageChange = () => {
const newTheme = localStorage.getItem('codeEditorTheme');
if (newTheme) {
setIsDarkMode(newTheme === 'dark');
}
const newWordWrap = localStorage.getItem('codeEditorWordWrap');
if (newWordWrap !== null) {
setWordWrap(newWordWrap === 'true');
}
const newShowMinimap = localStorage.getItem('codeEditorShowMinimap');
if (newShowMinimap !== null) {
setMinimapEnabled(newShowMinimap !== 'false');
}
const newShowLineNumbers = localStorage.getItem('codeEditorLineNumbers');
if (newShowLineNumbers !== null) {
setShowLineNumbers(newShowLineNumbers !== 'false');
}
const newFontSize = localStorage.getItem('codeEditorFontSize');
if (newFontSize) {
setFontSize(newFontSize);
}
};
// Listen for storage events (changes from other tabs/windows)
window.addEventListener('storage', handleStorageChange);
// Custom event for same-window updates
window.addEventListener('codeEditorSettingsChanged', handleStorageChange);
return () => {
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener('codeEditorSettingsChanged', handleStorageChange);
};
}, []);
// Handle keyboard shortcuts // Handle keyboard shortcuts
useEffect(() => { useEffect(() => {
const handleKeyDown = (e) => { const handleKeyDown = (e) => {
@@ -329,7 +467,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
</div> </div>
</div> </div>
) : ( ) : (
<div className="fixed inset-0 z-50 md:bg-black/50 md:flex md:items-center md:justify-center"> <div className="fixed inset-0 z-40 md:bg-black/50 md:flex md:items-center md:justify-center">
<div className="code-editor-loading w-full h-full md:rounded-lg md:w-auto md:h-auto p-8 flex items-center justify-center"> <div className="code-editor-loading w-full h-full md:rounded-lg md:w-auto md:h-auto p-8 flex items-center justify-center">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div> <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
@@ -381,8 +519,8 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
background-color: ${isDarkMode ? '#1e1e1e' : '#f5f5f5'}; background-color: ${isDarkMode ? '#1e1e1e' : '#f5f5f5'};
} }
/* Diff navigation panel styling */ /* Editor toolbar panel styling */
.cm-diff-navigation-panel { .cm-editor-toolbar-panel {
padding: 8px 12px; padding: 8px 12px;
background-color: ${isDarkMode ? '#1f2937' : '#ffffff'}; background-color: ${isDarkMode ? '#1f2937' : '#ffffff'};
border-bottom: 1px solid ${isDarkMode ? '#374151' : '#e5e7eb'}; border-bottom: 1px solid ${isDarkMode ? '#374151' : '#e5e7eb'};
@@ -390,7 +528,8 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
font-size: 14px; font-size: 14px;
} }
.cm-diff-nav-btn { .cm-diff-nav-btn,
.cm-toolbar-btn {
padding: 4px; padding: 4px;
background: transparent; background: transparent;
border: none; border: none;
@@ -400,9 +539,11 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: inherit; color: inherit;
transition: background-color 0.2s;
} }
.cm-diff-nav-btn:hover { .cm-diff-nav-btn:hover,
.cm-toolbar-btn:hover {
background-color: ${isDarkMode ? '#374151' : '#f3f4f6'}; background-color: ${isDarkMode ? '#374151' : '#f3f4f6'};
} }
@@ -414,7 +555,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
</style> </style>
<div className={isSidebar ? <div className={isSidebar ?
'w-full h-full flex flex-col' : 'w-full h-full flex flex-col' :
`fixed inset-0 z-50 ${ `fixed inset-0 z-40 ${
// Mobile: native fullscreen, Desktop: modal with backdrop // Mobile: native fullscreen, Desktop: modal with backdrop
'md:bg-black/50 md:flex md:items-center md:justify-center md:p-4' 'md:bg-black/50 md:flex md:items-center md:justify-center md:p-4'
} ${isFullscreen ? 'md:p-0' : ''}`}> } ${isFullscreen ? 'md:p-0' : ''}`}>
@@ -433,7 +574,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
<h3 className="font-medium text-gray-900 dark:text-white truncate">{file.name}</h3> <h3 className="font-medium text-gray-900 dark:text-white truncate">{file.name}</h3>
{file.diffInfo && ( {file.diffInfo && (
<span className="text-xs bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 px-2 py-1 rounded whitespace-nowrap"> <span className="text-xs bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 px-2 py-1 rounded whitespace-nowrap">
📝 Has changes Showing changes
</span> </span>
)} )}
</div> </div>
@@ -442,36 +583,6 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
</div> </div>
<div className="flex items-center gap-1 md:gap-2 flex-shrink-0"> <div className="flex items-center gap-1 md:gap-2 flex-shrink-0">
{file.diffInfo && (
<button
onClick={() => setShowDiff(!showDiff)}
className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
title={showDiff ? "Hide diff highlighting" : "Show diff highlighting"}
>
{showDiff ? <EyeOff className="w-5 h-5 md:w-4 md:h-4" /> : <Eye className="w-5 h-5 md:w-4 md:h-4" />}
</button>
)}
<button
onClick={() => setWordWrap(!wordWrap)}
className={`p-2 md:p-2 rounded-md hover:bg-gray-100 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center ${
wordWrap
? 'text-blue-600 bg-blue-50'
: 'text-gray-600 hover:text-gray-900'
}`}
title={wordWrap ? 'Disable word wrap' : 'Enable word wrap'}
>
<span className="text-sm md:text-xs font-mono font-bold"></span>
</button>
<button
onClick={() => setIsDarkMode(!isDarkMode)}
className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
title="Toggle theme"
>
<span className="text-lg md:text-base">{isDarkMode ? '☀️' : '🌙'}</span>
</button>
<button <button
onClick={handleDownload} onClick={handleDownload}
className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center" className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
@@ -532,6 +643,9 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
onChange={setContent} onChange={setContent}
extensions={[ extensions={[
...getLanguageExtension(file.name), ...getLanguageExtension(file.name),
// Always show the toolbar
...editorToolbarPanel,
// Only show diff-related extensions when diff is enabled
...(file.diffInfo && showDiff && file.diffInfo.old_string !== undefined ...(file.diffInfo && showDiff && file.diffInfo.old_string !== undefined
? [ ? [
unifiedMergeView({ unifiedMergeView({
@@ -543,8 +657,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
// NOTE: NO collapseUnchanged - this shows the full file! // NOTE: NO collapseUnchanged - this shows the full file!
}), }),
...minimapExtension, ...minimapExtension,
...scrollToFirstChunkExtension, ...scrollToFirstChunkExtension
...diffNavigationPanel
] ]
: []), : []),
...(wordWrap ? [EditorView.lineWrapping] : []) ...(wordWrap ? [EditorView.lineWrapping] : [])
@@ -552,11 +665,11 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
theme={isDarkMode ? oneDark : undefined} theme={isDarkMode ? oneDark : undefined}
height="100%" height="100%"
style={{ style={{
fontSize: '14px', fontSize: `${fontSize}px`,
height: '100%', height: '100%',
}} }}
basicSetup={{ basicSetup={{
lineNumbers: true, lineNumbers: showLineNumbers,
foldGutter: true, foldGutter: true,
dropCursor: false, dropCursor: false,
allowMultipleSelections: false, allowMultipleSelections: false,

View File

@@ -63,6 +63,7 @@ function MainContent({
const [showTaskDetail, setShowTaskDetail] = useState(false); const [showTaskDetail, setShowTaskDetail] = useState(false);
const [editorWidth, setEditorWidth] = useState(600); const [editorWidth, setEditorWidth] = useState(600);
const [isResizing, setIsResizing] = useState(false); const [isResizing, setIsResizing] = useState(false);
const [editorExpanded, setEditorExpanded] = useState(false);
const resizeRef = useRef(null); const resizeRef = useRef(null);
// PRD Editor state // PRD Editor state
@@ -130,6 +131,11 @@ function MainContent({
const handleCloseEditor = () => { const handleCloseEditor = () => {
setEditingFile(null); setEditingFile(null);
setEditorExpanded(false);
};
const handleToggleEditorExpand = () => {
setEditorExpanded(!editorExpanded);
}; };
const handleTaskClick = (task) => { const handleTaskClick = (task) => {
@@ -461,7 +467,7 @@ function MainContent({
{/* Content Area with Right Sidebar */} {/* Content Area with Right Sidebar */}
<div className="flex-1 flex min-h-0 overflow-hidden"> <div className="flex-1 flex min-h-0 overflow-hidden">
{/* Main Content */} {/* Main Content */}
<div className={`flex-1 flex flex-col min-h-0 overflow-hidden ${editingFile ? 'mr-0' : ''}`}> <div className={`flex-1 flex flex-col min-h-0 overflow-hidden ${editingFile ? 'mr-0' : ''} ${editorExpanded ? 'hidden' : ''}`}>
<div className={`h-full ${activeTab === 'chat' ? 'block' : 'hidden'}`}> <div className={`h-full ${activeTab === 'chat' ? 'block' : 'hidden'}`}>
<ErrorBoundary showDetails={true}> <ErrorBoundary showDetails={true}>
<ChatInterface <ChatInterface
@@ -569,27 +575,31 @@ function MainContent({
{/* Code Editor Right Sidebar - Desktop only, Mobile uses modal */} {/* Code Editor Right Sidebar - Desktop only, Mobile uses modal */}
{editingFile && !isMobile && ( {editingFile && !isMobile && (
<> <>
{/* Resize Handle */} {/* Resize Handle - Hidden when expanded */}
<div {!editorExpanded && (
ref={resizeRef} <div
onMouseDown={handleMouseDown} ref={resizeRef}
className="flex-shrink-0 w-1 bg-gray-200 dark:bg-gray-700 hover:bg-blue-500 dark:hover:bg-blue-600 cursor-col-resize transition-colors relative group" onMouseDown={handleMouseDown}
title="Drag to resize" className="flex-shrink-0 w-1 bg-gray-200 dark:bg-gray-700 hover:bg-blue-500 dark:hover:bg-blue-600 cursor-col-resize transition-colors relative group"
> title="Drag to resize"
{/* Visual indicator on hover */} >
<div className="absolute inset-y-0 left-1/2 -translate-x-1/2 w-1 bg-blue-500 dark:bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity" /> {/* Visual indicator on hover */}
</div> <div className="absolute inset-y-0 left-1/2 -translate-x-1/2 w-1 bg-blue-500 dark:bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity" />
</div>
)}
{/* Editor Sidebar */} {/* Editor Sidebar */}
<div <div
className="flex-shrink-0 border-l border-gray-200 dark:border-gray-700 h-full overflow-hidden" className={`flex-shrink-0 border-l border-gray-200 dark:border-gray-700 h-full overflow-hidden ${editorExpanded ? 'flex-1' : ''}`}
style={{ width: `${editorWidth}px` }} style={editorExpanded ? {} : { width: `${editorWidth}px` }}
> >
<CodeEditor <CodeEditor
file={editingFile} file={editingFile}
onClose={handleCloseEditor} onClose={handleCloseEditor}
projectPath={selectedProject?.path} projectPath={selectedProject?.path}
isSidebar={true} isSidebar={true}
isExpanded={editorExpanded}
onToggleExpand={handleToggleEditorExpand}
/> />
</div> </div>
</> </>

View File

@@ -112,7 +112,7 @@ const QuickSettingsPanel = ({
type="checkbox" type="checkbox"
checked={autoExpandTools} checked={autoExpandTools}
onChange={(e) => onAutoExpandChange(e.target.checked)} onChange={(e) => onAutoExpandChange(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 dark:focus:ring-blue-400 dark:bg-gray-800 dark:checked:bg-blue-600" className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 focus:ring-2 dark:focus:ring-blue-400 bg-gray-100 dark:bg-gray-800 checked:bg-blue-600 dark:checked:bg-blue-600"
/> />
</label> </label>
@@ -125,7 +125,7 @@ const QuickSettingsPanel = ({
type="checkbox" type="checkbox"
checked={showRawParameters} checked={showRawParameters}
onChange={(e) => onShowRawParametersChange(e.target.checked)} onChange={(e) => onShowRawParametersChange(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 dark:focus:ring-blue-400 dark:bg-gray-800 dark:checked:bg-blue-600" className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 focus:ring-2 dark:focus:ring-blue-400 bg-gray-100 dark:bg-gray-800 checked:bg-blue-600 dark:checked:bg-blue-600"
/> />
</label> </label>
@@ -138,7 +138,7 @@ const QuickSettingsPanel = ({
type="checkbox" type="checkbox"
checked={showThinking} checked={showThinking}
onChange={(e) => onShowThinkingChange(e.target.checked)} onChange={(e) => onShowThinkingChange(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 dark:focus:ring-blue-400 dark:bg-gray-800 dark:checked:bg-blue-600" className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 focus:ring-2 dark:focus:ring-blue-400 bg-gray-100 dark:bg-gray-800 checked:bg-blue-600 dark:checked:bg-blue-600"
/> />
</label> </label>
</div> </div>
@@ -155,7 +155,7 @@ const QuickSettingsPanel = ({
type="checkbox" type="checkbox"
checked={autoScrollToBottom} checked={autoScrollToBottom}
onChange={(e) => onAutoScrollChange(e.target.checked)} onChange={(e) => onAutoScrollChange(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 dark:focus:ring-blue-400 dark:bg-gray-800 dark:checked:bg-blue-600" className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 focus:ring-2 dark:focus:ring-blue-400 bg-gray-100 dark:bg-gray-800 checked:bg-blue-600 dark:checked:bg-blue-600"
/> />
</label> </label>
</div> </div>
@@ -173,7 +173,7 @@ const QuickSettingsPanel = ({
type="checkbox" type="checkbox"
checked={sendByCtrlEnter} checked={sendByCtrlEnter}
onChange={(e) => onSendByCtrlEnterChange(e.target.checked)} onChange={(e) => onSendByCtrlEnterChange(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 dark:focus:ring-blue-400 dark:bg-gray-800 dark:checked:bg-blue-600" className="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-500 focus:ring-blue-500 focus:ring-2 dark:focus:ring-blue-400 bg-gray-100 dark:bg-gray-800 checked:bg-blue-600 dark:checked:bg-blue-600"
/> />
</label> </label>
<p className="text-xs text-gray-500 dark:text-gray-400 ml-3"> <p className="text-xs text-gray-500 dark:text-gray-400 ml-3">

View File

@@ -10,7 +10,7 @@ import ClaudeLogo from './ClaudeLogo';
import CursorLogo from './CursorLogo'; import CursorLogo from './CursorLogo';
import CredentialsSettings from './CredentialsSettings'; import CredentialsSettings from './CredentialsSettings';
function Settings({ isOpen, onClose, projects = [] }) { function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const { isDarkMode, toggleDarkMode } = useTheme(); const { isDarkMode, toggleDarkMode } = useTheme();
const { const {
tasksEnabled, tasksEnabled,
@@ -52,9 +52,26 @@ function Settings({ isOpen, onClose, projects = [] }) {
const [mcpTestResults, setMcpTestResults] = useState({}); const [mcpTestResults, setMcpTestResults] = useState({});
const [mcpServerTools, setMcpServerTools] = useState({}); const [mcpServerTools, setMcpServerTools] = useState({});
const [mcpToolsLoading, setMcpToolsLoading] = useState({}); const [mcpToolsLoading, setMcpToolsLoading] = useState({});
const [activeTab, setActiveTab] = useState('tools'); const [activeTab, setActiveTab] = useState(initialTab);
const [jsonValidationError, setJsonValidationError] = useState(''); const [jsonValidationError, setJsonValidationError] = useState('');
const [toolsProvider, setToolsProvider] = useState('claude'); // 'claude' or 'cursor' const [toolsProvider, setToolsProvider] = useState('claude'); // 'claude' or 'cursor'
// Code Editor settings
const [codeEditorTheme, setCodeEditorTheme] = useState(() =>
localStorage.getItem('codeEditorTheme') || 'dark'
);
const [codeEditorWordWrap, setCodeEditorWordWrap] = useState(() =>
localStorage.getItem('codeEditorWordWrap') === 'true'
);
const [codeEditorShowMinimap, setCodeEditorShowMinimap] = useState(() =>
localStorage.getItem('codeEditorShowMinimap') !== 'false' // Default true
);
const [codeEditorLineNumbers, setCodeEditorLineNumbers] = useState(() =>
localStorage.getItem('codeEditorLineNumbers') !== 'false' // Default true
);
const [codeEditorFontSize, setCodeEditorFontSize] = useState(() =>
localStorage.getItem('codeEditorFontSize') || '14'
);
// Cursor-specific states // Cursor-specific states
const [cursorAllowedCommands, setCursorAllowedCommands] = useState([]); const [cursorAllowedCommands, setCursorAllowedCommands] = useState([]);
@@ -327,8 +344,36 @@ function Settings({ isOpen, onClose, projects = [] }) {
useEffect(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
loadSettings(); loadSettings();
// Set the active tab when the modal opens
setActiveTab(initialTab);
} }
}, [isOpen]); }, [isOpen, initialTab]);
// Persist code editor settings to localStorage
useEffect(() => {
localStorage.setItem('codeEditorTheme', codeEditorTheme);
window.dispatchEvent(new Event('codeEditorSettingsChanged'));
}, [codeEditorTheme]);
useEffect(() => {
localStorage.setItem('codeEditorWordWrap', codeEditorWordWrap.toString());
window.dispatchEvent(new Event('codeEditorSettingsChanged'));
}, [codeEditorWordWrap]);
useEffect(() => {
localStorage.setItem('codeEditorShowMinimap', codeEditorShowMinimap.toString());
window.dispatchEvent(new Event('codeEditorSettingsChanged'));
}, [codeEditorShowMinimap]);
useEffect(() => {
localStorage.setItem('codeEditorLineNumbers', codeEditorLineNumbers.toString());
window.dispatchEvent(new Event('codeEditorSettingsChanged'));
}, [codeEditorLineNumbers]);
useEffect(() => {
localStorage.setItem('codeEditorFontSize', codeEditorFontSize);
window.dispatchEvent(new Event('codeEditorSettingsChanged'));
}, [codeEditorFontSize]);
const loadSettings = async () => { const loadSettings = async () => {
try { try {
@@ -625,7 +670,7 @@ function Settings({ isOpen, onClose, projects = [] }) {
if (!isOpen) return null; if (!isOpen) return null;
return ( return (
<div className="modal-backdrop fixed inset-0 flex items-center justify-center z-[100] md:p-4 bg-background/95"> <div className="modal-backdrop fixed inset-0 flex items-center justify-center z-[9999] md:p-4 bg-background/95">
<div className="bg-background border border-border md:rounded-lg shadow-xl w-full md:max-w-4xl h-full md:h-[90vh] flex flex-col"> <div className="bg-background border border-border md:rounded-lg shadow-xl w-full md:max-w-4xl h-full md:h-[90vh] flex flex-col">
<div className="flex items-center justify-between p-4 md:p-6 border-b border-border flex-shrink-0"> <div className="flex items-center justify-between p-4 md:p-6 border-b border-border flex-shrink-0">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -758,6 +803,158 @@ function Settings({ isOpen, onClose, projects = [] }) {
</div> </div>
</div> </div>
</div> </div>
{/* Code Editor Settings */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-foreground">Code Editor</h3>
{/* Editor Theme */}
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-foreground">
Editor Theme
</div>
<div className="text-sm text-muted-foreground">
Default theme for the code editor
</div>
</div>
<button
onClick={() => setCodeEditorTheme(codeEditorTheme === 'dark' ? 'light' : 'dark')}
className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
role="switch"
aria-checked={codeEditorTheme === 'dark'}
aria-label="Toggle editor theme"
>
<span className="sr-only">Toggle editor theme</span>
<span
className={`${
codeEditorTheme === 'dark' ? 'translate-x-7' : 'translate-x-1'
} inline-block h-6 w-6 transform rounded-full bg-white shadow-lg transition-transform duration-200 flex items-center justify-center`}
>
{codeEditorTheme === 'dark' ? (
<Moon className="w-3.5 h-3.5 text-gray-700" />
) : (
<Sun className="w-3.5 h-3.5 text-yellow-500" />
)}
</span>
</button>
</div>
</div>
{/* Word Wrap */}
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-foreground">
Word Wrap
</div>
<div className="text-sm text-muted-foreground">
Enable word wrapping by default in the editor
</div>
</div>
<button
onClick={() => setCodeEditorWordWrap(!codeEditorWordWrap)}
className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
role="switch"
aria-checked={codeEditorWordWrap}
aria-label="Toggle word wrap"
>
<span className="sr-only">Toggle word wrap</span>
<span
className={`${
codeEditorWordWrap ? 'translate-x-7' : 'translate-x-1'
} inline-block h-6 w-6 transform rounded-full bg-white shadow-lg transition-transform duration-200`}
/>
</button>
</div>
</div>
{/* Show Minimap */}
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-foreground">
Show Minimap
</div>
<div className="text-sm text-muted-foreground">
Display a minimap for easier navigation in diff view
</div>
</div>
<button
onClick={() => setCodeEditorShowMinimap(!codeEditorShowMinimap)}
className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
role="switch"
aria-checked={codeEditorShowMinimap}
aria-label="Toggle minimap"
>
<span className="sr-only">Toggle minimap</span>
<span
className={`${
codeEditorShowMinimap ? 'translate-x-7' : 'translate-x-1'
} inline-block h-6 w-6 transform rounded-full bg-white shadow-lg transition-transform duration-200`}
/>
</button>
</div>
</div>
{/* Show Line Numbers */}
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-foreground">
Show Line Numbers
</div>
<div className="text-sm text-muted-foreground">
Display line numbers in the editor
</div>
</div>
<button
onClick={() => setCodeEditorLineNumbers(!codeEditorLineNumbers)}
className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
role="switch"
aria-checked={codeEditorLineNumbers}
aria-label="Toggle line numbers"
>
<span className="sr-only">Toggle line numbers</span>
<span
className={`${
codeEditorLineNumbers ? 'translate-x-7' : 'translate-x-1'
} inline-block h-6 w-6 transform rounded-full bg-white shadow-lg transition-transform duration-200`}
/>
</button>
</div>
</div>
{/* Font Size */}
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-foreground">
Font Size
</div>
<div className="text-sm text-muted-foreground">
Editor font size in pixels
</div>
</div>
<select
value={codeEditorFontSize}
onChange={(e) => setCodeEditorFontSize(e.target.value)}
className="text-sm bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2 w-24"
>
<option value="10">10px</option>
<option value="11">11px</option>
<option value="12">12px</option>
<option value="13">13px</option>
<option value="14">14px</option>
<option value="15">15px</option>
<option value="16">16px</option>
<option value="18">18px</option>
<option value="20">20px</option>
</select>
</div>
</div>
</div>
</div> </div>
)} )}
@@ -818,7 +1015,7 @@ function Settings({ isOpen, onClose, projects = [] }) {
type="checkbox" type="checkbox"
checked={skipPermissions} checked={skipPermissions}
onChange={(e) => setSkipPermissions(e.target.checked)} onChange={(e) => setSkipPermissions(e.target.checked)}
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500" className="w-4 h-4 text-blue-600 bg-gray-100 dark:bg-gray-700 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500 focus:ring-2 checked:bg-blue-600 dark:checked:bg-blue-600"
/> />
<div> <div>
<div className="font-medium text-orange-900 dark:text-orange-100"> <div className="font-medium text-orange-900 dark:text-orange-100">
@@ -1578,7 +1775,7 @@ function Settings({ isOpen, onClose, projects = [] }) {
type="checkbox" type="checkbox"
checked={cursorSkipPermissions} checked={cursorSkipPermissions}
onChange={(e) => setCursorSkipPermissions(e.target.checked)} onChange={(e) => setCursorSkipPermissions(e.target.checked)}
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500" className="w-4 h-4 text-blue-600 bg-gray-100 dark:bg-gray-700 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500 focus:ring-2 checked:bg-blue-600 dark:checked:bg-blue-600"
/> />
<div> <div>
<div className="font-medium text-orange-900 dark:text-orange-100"> <div className="font-medium text-orange-900 dark:text-orange-100">