diff --git a/server/routes/git.js b/server/routes/git.js
index da917d0..0f4f10d 100755
--- a/server/routes/git.js
+++ b/server/routes/git.js
@@ -502,16 +502,16 @@ router.post('/generate-commit-message', async (req, res) => {
*/
async function generateCommitMessageWithAI(files, diffContext, provider, projectPath) {
// 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:
-- Use conventional commit format: type(scope): subject
-- Include a body that explains what changed and why
-- Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
-- Keep subject line under 50 characters
-- Wrap body at 72 characters
-- Be specific and descriptive
-- Return ONLY the commit message, nothing else - no markdown, no explanations, no code blocks
+- Format: type(scope): subject
+- Include body explaining what changed and why
+- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
+- Subject under 50 chars, body wrapped at 72 chars
+- Focus on user-facing changes, not implementation details
+- Consider what's being added AND removed
+- Return ONLY the commit message (no markdown, explanations, or code blocks)
FILES CHANGED:
${files.map(f => `- ${f}`).join('\n')}
@@ -519,7 +519,7 @@ ${files.map(f => `- ${f}`).join('\n')}
DIFFS:
${diffContext.substring(0, 4000)}
-Generate the commit message now:`;
+Generate the commit message:`;
try {
// Create a simple writer that collects the response
diff --git a/src/App.jsx b/src/App.jsx
index 3a84f0f..4d10c9c 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -55,6 +55,7 @@ function AppContent() {
const [isLoadingProjects, setIsLoadingProjects] = useState(true);
const [isInputFocused, setIsInputFocused] = useState(false);
const [showSettings, setShowSettings] = useState(false);
+ const [settingsInitialTab, setSettingsInitialTab] = useState('tools');
const [showQuickSettings, setShowQuickSettings] = useState(false);
const [autoExpandTools, setAutoExpandTools] = useLocalStorage('autoExpandTools', false);
const [showRawParameters, setShowRawParameters] = useLocalStorage('showRawParameters', false);
@@ -308,6 +309,12 @@ function AppContent() {
// Expose fetchProjects globally for component access
window.refreshProjects = fetchProjects;
+ // Expose openSettings function globally for component access
+ window.openSettings = useCallback((tab = 'tools') => {
+ setSettingsInitialTab(tab);
+ setShowSettings(true);
+ }, []);
+
// Handle URL-based session loading
useEffect(() => {
if (sessionId && projects.length > 0) {
@@ -927,6 +934,7 @@ function AppContent() {
isOpen={showSettings}
onClose={() => setShowSettings(false)}
projects={projects}
+ initialTab={settingsInitialTab}
/>
{/* Version Upgrade Modal */}
diff --git a/src/components/CodeEditor.jsx b/src/components/CodeEditor.jsx
index 770a1c8..172242d 100644
--- a/src/components/CodeEditor.jsx
+++ b/src/components/CodeEditor.jsx
@@ -10,23 +10,37 @@ import { oneDark } from '@codemirror/theme-one-dark';
import { EditorView, showPanel, ViewPlugin } from '@codemirror/view';
import { unifiedMergeView, getChunks } from '@codemirror/merge';
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';
-function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
+function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded = false, onToggleExpand = null }) {
const [content, setContent] = useState('');
const [loading, setLoading] = useState(true);
const [saving, setSaving] = 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 [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);
// Create minimap extension with chunk-based gutters
const minimapExtension = useMemo(() => {
- if (!file.diffInfo || !showDiff) return [];
+ if (!file.diffInfo || !showDiff || !minimapEnabled) return [];
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
const scrollToFirstChunkExtension = useMemo(() => {
@@ -89,24 +103,28 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
];
}, [file.diffInfo, showDiff]);
- // Create diff navigation panel extension
- const diffNavigationPanel = useMemo(() => {
- if (!file.diffInfo || !showDiff) return [];
-
+ // Create editor toolbar panel - always visible
+ const editorToolbarPanel = useMemo(() => {
const createPanel = (view) => {
const dom = document.createElement('div');
- dom.className = 'cm-diff-navigation-panel';
+ dom.className = 'cm-editor-toolbar-panel';
let currentIndex = 0;
const updatePanel = () => {
- // Use getChunks API to get ALL chunks regardless of viewport
- const chunksData = getChunks(view.state);
+ // Check if we have diff info and it's enabled
+ const hasDiff = file.diffInfo && showDiff;
+ const chunksData = hasDiff ? getChunks(view.state) : null;
const chunks = chunksData?.chunks || [];
const chunkCount = chunks.length;
- dom.innerHTML = `
-
+ // Build the toolbar HTML
+ let toolbarHTML = '
';
+
+ // Left side - diff navigation (if applicable)
+ toolbarHTML += '
';
+ if (hasDiff) {
+ toolbarHTML += `
${chunkCount > 0 ? `${currentIndex + 1}/${chunkCount}` : '0'} changes
-
+ `;
+ }
+ toolbarHTML += '
';
+
+ // Right side - action buttons
+ toolbarHTML += '
';
+
+ // Show/hide diff button (only if there's diff info)
+ if (file.diffInfo) {
+ toolbarHTML += `
+
+ `;
+ }
+
+ // Settings button
+ toolbarHTML += `
+
`;
- const prevBtn = dom.querySelector('.cm-diff-nav-prev');
- const nextBtn = dom.querySelector('.cm-diff-nav-next');
+ // Expand button (only in sidebar mode)
+ if (isSidebar && onToggleExpand) {
+ toolbarHTML += `
+
+ `;
+ }
- prevBtn?.addEventListener('click', () => {
- if (chunks.length === 0) return;
- currentIndex = currentIndex > 0 ? currentIndex - 1 : chunks.length - 1;
+ toolbarHTML += '
';
+ toolbarHTML += '
';
- // Navigate to the chunk - use fromB which is the position in the current document
- 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' })
- });
+ dom.innerHTML = toolbarHTML;
+
+ // Attach event listeners for diff navigation
+ if (hasDiff) {
+ const prevBtn = dom.querySelector('.cm-diff-nav-prev');
+ 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', () => {
- if (chunks.length === 0) return;
- currentIndex = currentIndex < chunks.length - 1 ? currentIndex + 1 : 0;
-
- // Navigate to the chunk - use fromB which is the position in the current document
- 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();
- });
+ // Attach event listener for expand button
+ if (isSidebar && onToggleExpand) {
+ const expandBtn = dom.querySelector('.cm-expand-btn');
+ expandBtn?.addEventListener('click', () => {
+ onToggleExpand();
+ });
+ }
};
updatePanel();
@@ -165,7 +252,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
};
return [showPanel.of(createPanel)];
- }, [file.diffInfo, showDiff]);
+ }, [file.diffInfo, showDiff, isSidebar, isExpanded, onToggleExpand]);
// Get language extension based on file extension
const getLanguageExtension = (filename) => {
@@ -290,6 +377,57 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
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
useEffect(() => {
const handleKeyDown = (e) => {
@@ -329,7 +467,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
) : (
-
+
@@ -381,8 +519,8 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
background-color: ${isDarkMode ? '#1e1e1e' : '#f5f5f5'};
}
- /* Diff navigation panel styling */
- .cm-diff-navigation-panel {
+ /* Editor toolbar panel styling */
+ .cm-editor-toolbar-panel {
padding: 8px 12px;
background-color: ${isDarkMode ? '#1f2937' : '#ffffff'};
border-bottom: 1px solid ${isDarkMode ? '#374151' : '#e5e7eb'};
@@ -390,7 +528,8 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
font-size: 14px;
}
- .cm-diff-nav-btn {
+ .cm-diff-nav-btn,
+ .cm-toolbar-btn {
padding: 4px;
background: transparent;
border: none;
@@ -400,9 +539,11 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
align-items: center;
justify-content: center;
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'};
}
@@ -414,7 +555,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
@@ -433,7 +574,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
{file.name}
{file.diffInfo && (
- 📝 Has changes
+ Showing changes
)}
@@ -442,36 +583,6 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false }) {
- {file.diffInfo && (
-
- )}
-
-
-
-
-