Skip to content

Commit 4462c6f

Browse files
committed
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)
1 parent 0ae310d commit 4462c6f

3 files changed

Lines changed: 81 additions & 62 deletions

File tree

.env.example

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ PINECONE_INDEX_NAME=codeintel
1919
# Get from: https://app.supabase.com/project/_/settings/api
2020
SUPABASE_URL=https://your-project.supabase.co
2121
SUPABASE_ANON_KEY=eyJ...
22-
SUPABASE_JWT_SECRET=your-jwt-secret # From Project Settings → API → JWT Secret
22+
SUPABASE_JWT_SECRET=your-jwt-secret # From Project Settings -> API -> JWT Secret
23+
SUPABASE_SERVICE_ROLE_KEY=eyJ... # From Project Settings -> API -> service_role key
2324

2425
# Backend API
2526
API_KEY=change-this-secret-key-for-production
@@ -46,3 +47,7 @@ ENVIRONMENT=development # development, staging, production
4647
# Free tier: 10K requests/month
4748
COHERE_API_KEY=
4849
SEARCH_V2_ENABLED=true
50+
51+
# Voyage AI - code-specific embeddings (Optional - improves code search quality)
52+
# Get from: https://dash.voyageai.com/
53+
VOYAGE_API_KEY=

docker-compose.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ services:
3232
- PINECONE_API_KEY=${PINECONE_API_KEY}
3333
- PINECONE_INDEX_NAME=${PINECONE_INDEX_NAME}
3434
- SUPABASE_URL=${SUPABASE_URL}
35-
- SUPABASE_KEY=${SUPABASE_KEY}
35+
- SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
36+
- SUPABASE_JWT_SECRET=${SUPABASE_JWT_SECRET}
3637
- SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
3738
- API_KEY=${API_KEY}
3839
- BACKEND_API_URL=http://backend:8000
@@ -62,14 +63,14 @@ services:
6263
args:
6364
- VITE_API_URL=http://localhost:8000
6465
- VITE_SUPABASE_URL=${SUPABASE_URL}
65-
- VITE_SUPABASE_ANON_KEY=${SUPABASE_KEY}
66+
- VITE_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
6667
container_name: codeintel-frontend
6768
ports:
6869
- "3000:80"
6970
environment:
7071
- VITE_API_URL=http://localhost:8000
7172
- VITE_SUPABASE_URL=${SUPABASE_URL}
72-
- VITE_SUPABASE_ANON_KEY=${SUPABASE_KEY}
73+
- VITE_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
7374
depends_on:
7475
- backend
7576
healthcheck:

frontend/src/components/dashboard/Sidebar.tsx

Lines changed: 71 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -30,72 +30,74 @@ const bottomNavItems: NavItem[] = [
3030
{ name: 'Documentation', href: '/docs', icon: <BookOpen className="w-5 h-5" />, external: true },
3131
]
3232

33-
export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: SidebarProps) {
34-
const location = useLocation()
35-
36-
const isActive = (href: string) => {
37-
if (href === '/dashboard') {
38-
return location.pathname === '/dashboard' || location.pathname.startsWith('/dashboard/repo/')
39-
}
40-
return location.pathname === href
41-
}
42-
43-
const handleNavClick = () => {
44-
// Close mobile menu when navigating
45-
onMobileClose?.()
46-
}
47-
48-
const NavLink = ({ item }: { item: NavItem }) => {
49-
const active = isActive(item.href)
50-
51-
const baseClasses = `
52-
flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all group
53-
${active
54-
? 'bg-primary/10 text-primary'
55-
: 'text-muted-foreground hover:text-foreground hover:bg-muted'
56-
}
57-
`
58-
59-
// On mobile, always show full labels (not collapsed)
60-
const showLabels = !collapsed || mobileOpen
61-
62-
if (item.external) {
63-
return (
64-
<a
65-
href={item.href}
66-
target="_blank"
67-
rel="noopener noreferrer"
68-
className={baseClasses}
69-
onClick={handleNavClick}
70-
>
71-
<span className={active ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground'}>
72-
{item.icon}
73-
</span>
74-
{showLabels && (
75-
<>
76-
<span className="text-sm font-medium truncate">{item.name}</span>
77-
<ExternalLink className="w-3 h-3 ml-auto opacity-50" />
78-
</>
79-
)}
80-
</a>
81-
)
33+
// defined at module scope so React can reconcile it across renders
34+
function NavLink({
35+
item,
36+
active,
37+
showLabels,
38+
onClick,
39+
}: {
40+
item: NavItem
41+
active: boolean
42+
showLabels: boolean
43+
onClick?: () => void
44+
}) {
45+
const baseClasses = `
46+
flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all group
47+
${active
48+
? 'bg-primary/10 text-primary'
49+
: 'text-muted-foreground hover:text-foreground hover:bg-muted'
8250
}
51+
`
8352

53+
if (item.external) {
8454
return (
85-
<Link to={item.href} className={baseClasses} onClick={handleNavClick}>
55+
<a
56+
href={item.href}
57+
target="_blank"
58+
rel="noopener noreferrer"
59+
className={baseClasses}
60+
onClick={onClick}
61+
>
8662
<span className={active ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground'}>
8763
{item.icon}
8864
</span>
8965
{showLabels && (
90-
<span className="text-sm font-medium truncate">{item.name}</span>
66+
<>
67+
<span className="text-sm font-medium truncate">{item.name}</span>
68+
<ExternalLink className="w-3 h-3 ml-auto opacity-50" />
69+
</>
9170
)}
92-
</Link>
71+
</a>
9372
)
9473
}
9574

96-
// Determine sidebar width class
75+
return (
76+
<Link to={item.href} className={baseClasses} onClick={onClick}>
77+
<span className={active ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground'}>
78+
{item.icon}
79+
</span>
80+
{showLabels && (
81+
<span className="text-sm font-medium truncate">{item.name}</span>
82+
)}
83+
</Link>
84+
)
85+
}
86+
87+
export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: SidebarProps) {
88+
const location = useLocation()
89+
90+
const isActive = (href: string) => {
91+
if (href === '/dashboard') {
92+
return location.pathname === '/dashboard' || location.pathname.startsWith('/dashboard/repo/')
93+
}
94+
return location.pathname === href
95+
}
96+
97+
const showLabels = !collapsed || !!mobileOpen
98+
9799
const getWidthClass = () => {
98-
if (mobileOpen) return 'w-[var(--sidebar-width)]' // Always full width on mobile when open
100+
if (mobileOpen) return 'w-[var(--sidebar-width)]'
99101
if (collapsed) return 'w-[var(--sidebar-width-collapsed)]'
100102
return 'w-[var(--sidebar-width)]'
101103
}
@@ -111,7 +113,7 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
111113
lg:translate-x-0
112114
`}
113115
>
114-
{/* Mobile close button */}
116+
{/* mobile close */}
115117
<div className="lg:hidden flex items-center justify-between p-3 border-b border-border">
116118
<span className="font-semibold text-foreground">Menu</span>
117119
<button
@@ -125,16 +127,27 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
125127

126128
<nav className="flex-1 p-3 space-y-1">
127129
{mainNavItems.map((item) => (
128-
<NavLink key={item.href} item={item} />
130+
<NavLink
131+
key={item.href}
132+
item={item}
133+
active={isActive(item.href)}
134+
showLabels={showLabels}
135+
onClick={onMobileClose}
136+
/>
129137
))}
130138
</nav>
131139

132140
<div className="p-3 border-t border-border space-y-1">
133141
{bottomNavItems.map((item) => (
134-
<NavLink key={item.href} item={item} />
142+
<NavLink
143+
key={item.href}
144+
item={item}
145+
active={isActive(item.href)}
146+
showLabels={showLabels}
147+
onClick={onMobileClose}
148+
/>
135149
))}
136150

137-
{/* Collapse toggle - desktop only */}
138151
<button
139152
onClick={onToggle}
140153
className="hidden lg:flex items-center gap-3 px-3 py-2.5 w-full rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-all"

0 commit comments

Comments
 (0)