From 0ae310dc24aeeeb365c9cfc929423527ac976be4 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Wed, 18 Feb 2026 15:21:49 -0500 Subject: [PATCH 1/6] feat: add React Error Boundary to prevent white-screen crashes Wraps AppRoutes in ErrorBoundary so any unhandled component error shows a recovery UI instead of a blank white screen. Displays the error message, a 'Try again' button (resets error state) and a 'Go home' button (navigates to /). Class component because React doesn't support error boundaries with hooks yet. Closes OPE-15 --- frontend/src/App.tsx | 9 ++-- frontend/src/components/ErrorBoundary.tsx | 57 +++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/ErrorBoundary.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2996b73..d9a161a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,6 +18,7 @@ import { ArchitecturePage } from './pages/ArchitecturePage'; import { ContributingPage } from './pages/ContributingPage'; import { GitHubCallbackPage } from './pages/GitHubCallbackPage'; import { ScrollToTop } from './components/ScrollToTop'; +import { ErrorBoundary } from './components/ErrorBoundary'; function ProtectedRoute({ children }: { children: React.ReactNode }) { const { user, loading } = useAuth(); @@ -126,9 +127,11 @@ export function App() { - - - + + + + + diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..67e5d99 --- /dev/null +++ b/frontend/src/components/ErrorBoundary.tsx @@ -0,0 +1,57 @@ +import { Component, type ReactNode } from 'react' +import { Button } from './ui/button' + +interface Props { + children: ReactNode +} + +interface State { + hasError: boolean + error: Error | null +} + +// class component required -- React doesn't support error boundaries with hooks yet +export class ErrorBoundary extends Component { + state: State = { hasError: false, error: null } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error } + } + + componentDidCatch(error: Error, info: React.ErrorInfo) { + console.error('ErrorBoundary caught:', error, info.componentStack) + } + + render() { + if (!this.state.hasError) return this.props.children + + return ( +
+
+

+ Something went wrong +

+

+ An unexpected error occurred. This has been logged. +

+ {this.state.error && ( +
+              {this.state.error.message}
+            
+ )} +
+ + +
+
+
+ ) + } +} From 4462c6faa1dd081d4b5aaf2e9d49af4fc481e251 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Wed, 18 Feb 2026 15:25:05 -0500 Subject: [PATCH 2/6] fix: NavLink remount bug, standardize Docker env vars NavLink (OPE-28): - Moved NavLink from inside Sidebar body to module scope - Was creating a new component type every render, destroying state - Now receives active/showLabels/onClick as props instead of closures Docker env vars (OPE-12): - docker-compose.yml now uses SUPABASE_ANON_KEY (not SUPABASE_KEY) - Added SUPABASE_JWT_SECRET to backend container env - auth.py reads SUPABASE_ANON_KEY which was never passed in Docker - Frontend build args already used SUPABASE_ANON_KEY via different name Root .env.example (OPE-21): - Added SUPABASE_SERVICE_ROLE_KEY - Added VOYAGE_API_KEY (was in backend .env.example but not root) --- .env.example | 7 +- docker-compose.yml | 7 +- frontend/src/components/dashboard/Sidebar.tsx | 129 ++++++++++-------- 3 files changed, 81 insertions(+), 62 deletions(-) diff --git a/.env.example b/.env.example index 4300079..0692f6c 100644 --- a/.env.example +++ b/.env.example @@ -19,7 +19,8 @@ PINECONE_INDEX_NAME=codeintel # Get from: https://app.supabase.com/project/_/settings/api SUPABASE_URL=https://your-project.supabase.co SUPABASE_ANON_KEY=eyJ... -SUPABASE_JWT_SECRET=your-jwt-secret # From Project Settings → API → JWT Secret +SUPABASE_JWT_SECRET=your-jwt-secret # From Project Settings -> API -> JWT Secret +SUPABASE_SERVICE_ROLE_KEY=eyJ... # From Project Settings -> API -> service_role key # Backend API API_KEY=change-this-secret-key-for-production @@ -46,3 +47,7 @@ ENVIRONMENT=development # development, staging, production # Free tier: 10K requests/month COHERE_API_KEY= SEARCH_V2_ENABLED=true + +# Voyage AI - code-specific embeddings (Optional - improves code search quality) +# Get from: https://dash.voyageai.com/ +VOYAGE_API_KEY= diff --git a/docker-compose.yml b/docker-compose.yml index c7b4fe2..2cd7abb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,8 @@ services: - PINECONE_API_KEY=${PINECONE_API_KEY} - PINECONE_INDEX_NAME=${PINECONE_INDEX_NAME} - SUPABASE_URL=${SUPABASE_URL} - - SUPABASE_KEY=${SUPABASE_KEY} + - SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY} + - SUPABASE_JWT_SECRET=${SUPABASE_JWT_SECRET} - SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY} - API_KEY=${API_KEY} - BACKEND_API_URL=http://backend:8000 @@ -62,14 +63,14 @@ services: args: - VITE_API_URL=http://localhost:8000 - VITE_SUPABASE_URL=${SUPABASE_URL} - - VITE_SUPABASE_ANON_KEY=${SUPABASE_KEY} + - VITE_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY} container_name: codeintel-frontend ports: - "3000:80" environment: - VITE_API_URL=http://localhost:8000 - VITE_SUPABASE_URL=${SUPABASE_URL} - - VITE_SUPABASE_ANON_KEY=${SUPABASE_KEY} + - VITE_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY} depends_on: - backend healthcheck: diff --git a/frontend/src/components/dashboard/Sidebar.tsx b/frontend/src/components/dashboard/Sidebar.tsx index 48c557b..0753e33 100644 --- a/frontend/src/components/dashboard/Sidebar.tsx +++ b/frontend/src/components/dashboard/Sidebar.tsx @@ -30,72 +30,74 @@ const bottomNavItems: NavItem[] = [ { name: 'Documentation', href: '/docs', icon: , external: true }, ] -export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: SidebarProps) { - const location = useLocation() - - const isActive = (href: string) => { - if (href === '/dashboard') { - return location.pathname === '/dashboard' || location.pathname.startsWith('/dashboard/repo/') - } - return location.pathname === href - } - - const handleNavClick = () => { - // Close mobile menu when navigating - onMobileClose?.() - } - - const NavLink = ({ item }: { item: NavItem }) => { - const active = isActive(item.href) - - const baseClasses = ` - flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all group - ${active - ? 'bg-primary/10 text-primary' - : 'text-muted-foreground hover:text-foreground hover:bg-muted' - } - ` - - // On mobile, always show full labels (not collapsed) - const showLabels = !collapsed || mobileOpen - - if (item.external) { - return ( - - - {item.icon} - - {showLabels && ( - <> - {item.name} - - - )} - - ) +// defined at module scope so React can reconcile it across renders +function NavLink({ + item, + active, + showLabels, + onClick, +}: { + item: NavItem + active: boolean + showLabels: boolean + onClick?: () => void +}) { + const baseClasses = ` + flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all group + ${active + ? 'bg-primary/10 text-primary' + : 'text-muted-foreground hover:text-foreground hover:bg-muted' } + ` + if (item.external) { return ( - + {item.icon} {showLabels && ( - {item.name} + <> + {item.name} + + )} - + ) } - // Determine sidebar width class + return ( + + + {item.icon} + + {showLabels && ( + {item.name} + )} + + ) +} + +export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: SidebarProps) { + const location = useLocation() + + const isActive = (href: string) => { + if (href === '/dashboard') { + return location.pathname === '/dashboard' || location.pathname.startsWith('/dashboard/repo/') + } + return location.pathname === href + } + + const showLabels = !collapsed || !!mobileOpen + const getWidthClass = () => { - if (mobileOpen) return 'w-[var(--sidebar-width)]' // Always full width on mobile when open + if (mobileOpen) return 'w-[var(--sidebar-width)]' if (collapsed) return 'w-[var(--sidebar-width-collapsed)]' return 'w-[var(--sidebar-width)]' } @@ -111,7 +113,7 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side lg:translate-x-0 `} > - {/* Mobile close button */} + {/* mobile close */}
Menu