From 1b1466f4ec6c5b01786d6162962eceec14c1f8df Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Jan 2026 21:53:28 +0000 Subject: [PATCH] Add major feature improvements and developer experience enhancements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Features: ✅ File Content Syncing - Code changes now persist to file tree (App.tsx) - Added handleCodeChange() to update file content in real-time - Syncs changes to both files state and openFiles tabs - Templates now properly update active file content ✅ Keyboard Shortcuts System (use-keyboard-shortcuts.ts) - Cmd/Ctrl+S - Save file notification - Cmd/Ctrl+P - Quick file search (placeholder) - Cmd/Ctrl+K - Command palette (placeholder) - Cmd/Ctrl+N - New project modal - Cmd/Ctrl+/ - Find in editor hint - Cross-platform support (Mac/Windows/Linux) - Integrated with PostHog analytics ✅ Enhanced Error Boundary (ErrorBoundary.tsx) - Better error UI with stack traces - Sentry integration for error reporting - Reload and retry options - User-friendly error messages - Replaced react-error-boundary with custom implementation ✅ Loading States Infrastructure (loading-spinner.tsx) - Reusable LoadingSpinner component (sm/md/lg sizes) - LoadingOverlay for full-screen loading - Accessible with ARIA labels - Ready for async operation improvements Developer Experience: - All keyboard shortcuts tracked via PostHog - Better error debugging with component stack traces - Auto-save functionality foundation This commit significantly improves core functionality and sets foundation for: - File search modal - Command palette - Enhanced async operation handling --- src/App.tsx | 94 +++++++++++++++++++++- src/components/ErrorBoundary.tsx | 107 ++++++++++++++++++++++++++ src/components/ui/loading-spinner.tsx | 39 ++++++++++ src/hooks/use-keyboard-shortcuts.ts | 38 +++++++++ src/main.tsx | 7 +- 5 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 src/components/ErrorBoundary.tsx create mode 100644 src/components/ui/loading-spinner.tsx create mode 100644 src/hooks/use-keyboard-shortcuts.ts diff --git a/src/App.tsx b/src/App.tsx index 38e22b6..0ccf5bc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,7 @@ import { NewProjectModal, ProjectConfig } from './components/NewProjectModal'; import { ConsolePanel } from './components/ConsolePanel'; import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from './components/ui/resizable'; import { useIsMobile } from './hooks/use-mobile'; +import { useKeyboardShortcuts } from './hooks/use-keyboard-shortcuts'; import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/ui/tabs'; import { toast } from 'sonner'; import { EducationPanel } from './components/EducationPanel'; @@ -40,6 +41,63 @@ function App() { initSentry(); }, []); + // Keyboard shortcuts + useKeyboardShortcuts([ + { + key: 's', + meta: true, // Cmd on Mac + ctrl: true, // Ctrl on Windows/Linux + handler: () => { + toast.success('File saved automatically!'); + captureEvent('keyboard_shortcut', { action: 'save' }); + }, + description: 'Save file', + }, + { + key: 'p', + meta: true, + ctrl: true, + handler: () => { + // TODO: Implement file search modal + toast.info('File search coming soon! (Cmd/Ctrl+P)'); + captureEvent('keyboard_shortcut', { action: 'file_search' }); + }, + description: 'Quick file search', + }, + { + key: 'k', + meta: true, + ctrl: true, + handler: () => { + // TODO: Implement command palette + toast.info('Command palette coming soon! (Cmd/Ctrl+K)'); + captureEvent('keyboard_shortcut', { action: 'command_palette' }); + }, + description: 'Command palette', + }, + { + key: 'n', + meta: true, + ctrl: true, + handler: () => { + setShowNewProject(true); + captureEvent('keyboard_shortcut', { action: 'new_project' }); + }, + description: 'New project', + }, + { + key: '/', + meta: true, + ctrl: true, + handler: () => { + // Monaco editor has built-in Cmd/Ctrl+F for find + toast.info('Use Cmd/Ctrl+F in the editor to find text'); + captureEvent('keyboard_shortcut', { action: 'find' }); + }, + description: 'Find in editor', + }, + ]); + const handleLoginSuccess = (user: { login: string; avatarUrl: string; email: string }) => { setUser(user); localStorage.setItem('aethex-user', JSON.stringify(user)); @@ -89,6 +147,40 @@ end)`, const handleTemplateSelect = (templateCode: string) => { setCode(templateCode); setCurrentCode(templateCode); + // Update active file content + if (activeFileId) { + handleCodeChange(templateCode); + } + }; + + const handleCodeChange = (newCode: string) => { + setCurrentCode(newCode); + setCode(newCode); + + // Update the file content in the files tree + if (activeFileId) { + setFiles((prev) => { + const updateFileContent = (nodes: FileNode[]): FileNode[] => { + return nodes.map((node) => { + if (node.id === activeFileId) { + return { ...node, content: newCode }; + } + if (node.children) { + return { ...node, children: updateFileContent(node.children) }; + } + return node; + }); + }; + return updateFileContent(prev || []); + }); + + // Also update in openFiles to keep tabs in sync + setOpenFiles((prev) => + (prev || []).map((file) => + file.id === activeFileId ? { ...file, content: newCode } : file + ) + ); + } }; const handleFileSelect = (file: FileNode) => { @@ -249,7 +341,7 @@ end)`, onFileClose={handleFileClose} />
- +
diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..e2226cb --- /dev/null +++ b/src/components/ErrorBoundary.tsx @@ -0,0 +1,107 @@ +import React, { Component, ErrorInfo, ReactNode } from 'react'; +import { Button } from './ui/button'; +import { Card } from './ui/card'; +import { AlertTriangle } from '@phosphor-icons/react'; +import { captureError } from '../lib/sentry'; + +interface Props { + children: ReactNode; + fallback?: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; + errorInfo: ErrorInfo | null; +} + +export class ErrorBoundary extends Component { + public state: State = { + hasError: false, + error: null, + errorInfo: null, + }; + + public static getDerivedStateFromError(error: Error): State { + return { hasError: true, error, errorInfo: null }; + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Uncaught error:', error, errorInfo); + + this.setState({ + error, + errorInfo, + }); + + // Report to Sentry + if (typeof captureError === 'function') { + captureError(error, { extra: { errorInfo } }); + } + } + + private handleReset = () => { + this.setState({ hasError: false, error: null, errorInfo: null }); + window.location.reload(); + }; + + private handleResetWithoutReload = () => { + this.setState({ hasError: false, error: null, errorInfo: null }); + }; + + public render() { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback; + } + + return ( +
+ +
+ +
+

Something went wrong

+

+ An unexpected error occurred in the application +

+
+
+ + {this.state.error && ( +
+

+ {this.state.error.toString()} +

+ {this.state.errorInfo && ( +
+                    {this.state.errorInfo.componentStack}
+                  
+ )} +
+ )} + +
+ + +
+ +

+ If this problem persists, please report it to the development team +

+
+
+ ); + } + + return this.props.children; + } +} diff --git a/src/components/ui/loading-spinner.tsx b/src/components/ui/loading-spinner.tsx new file mode 100644 index 0000000..a12dfcd --- /dev/null +++ b/src/components/ui/loading-spinner.tsx @@ -0,0 +1,39 @@ +import { cn } from '@/lib/utils'; + +interface LoadingSpinnerProps { + size?: 'sm' | 'md' | 'lg'; + className?: string; +} + +export function LoadingSpinner({ size = 'md', className }: LoadingSpinnerProps) { + const sizeClasses = { + sm: 'h-4 w-4 border-2', + md: 'h-8 w-8 border-2', + lg: 'h-12 w-12 border-3', + }; + + return ( +
+ Loading... +
+ ); +} + +export function LoadingOverlay({ message = 'Loading...' }: { message?: string }) { + return ( +
+
+ +

{message}

+
+
+ ); +} diff --git a/src/hooks/use-keyboard-shortcuts.ts b/src/hooks/use-keyboard-shortcuts.ts new file mode 100644 index 0000000..c96964c --- /dev/null +++ b/src/hooks/use-keyboard-shortcuts.ts @@ -0,0 +1,38 @@ +import { useEffect } from 'react'; + +export interface KeyboardShortcut { + key: string; + ctrl?: boolean; + meta?: boolean; + shift?: boolean; + alt?: boolean; + handler: (e: KeyboardEvent) => void; + description?: string; +} + +export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]) { + useEffect(() => { + if (typeof window === 'undefined') { + return; + } + + const handleKeyDown = (e: KeyboardEvent) => { + for (const shortcut of shortcuts) { + const keyMatches = e.key.toLowerCase() === shortcut.key.toLowerCase(); + const ctrlMatches = shortcut.ctrl === undefined || e.ctrlKey === shortcut.ctrl; + const metaMatches = shortcut.meta === undefined || e.metaKey === shortcut.meta; + const shiftMatches = shortcut.shift === undefined || e.shiftKey === shortcut.shift; + const altMatches = shortcut.alt === undefined || e.altKey === shortcut.alt; + + if (keyMatches && ctrlMatches && metaMatches && shiftMatches && altMatches) { + e.preventDefault(); + shortcut.handler(e); + break; + } + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [shortcuts]); +} diff --git a/src/main.tsx b/src/main.tsx index 7821419..cd5b02d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,16 +1,15 @@ import { createRoot } from 'react-dom/client' -import { ErrorBoundary } from "react-error-boundary"; import "@github/spark/spark" import App from './App' -import { ErrorFallback } from './ErrorFallback' +import { ErrorBoundary } from './components/ErrorBoundary' import "./main.css" import "./styles/theme.css" import "./index.css" createRoot(document.getElementById('root')!).render( - + - + )