@@ -211,21 +211,13 @@ export function GraphTab({ project }: GraphTabProps) {
211211 ) ;
212212 }
213213
214- if ( ! data || ! filteredData || filteredData . nodes . length === 0 ) {
214+ /* No data, or the project genuinely has no nodes — there are no filters to
215+ interact with, so show a plain full-screen message. The "all filtered out"
216+ case is handled inside the layout below so the filter sidebar stays put. */
217+ if ( ! data || ! filteredData || data . nodes . length === 0 ) {
215218 return (
216219 < div className = "flex items-center justify-center h-full" >
217- < div className = "text-center" >
218- < p className = "text-white/30 text-sm mb-3" >
219- { data && filteredData ?. nodes . length === 0
220- ? "All nodes filtered out"
221- : "No nodes in this project" }
222- </ p >
223- { data && filteredData ?. nodes . length === 0 && (
224- < Button size = "sm" onClick = { enableAll } >
225- Reset Filters
226- </ Button >
227- ) }
228- </ div >
220+ < p className = "text-white/30 text-sm" > No nodes in this project</ p >
229221 </ div >
230222 ) ;
231223 }
@@ -267,65 +259,78 @@ export function GraphTab({ project }: GraphTabProps) {
267259
268260 { /* Graph area */ }
269261 < div className = "flex-1 relative overflow-hidden" >
270- < ErrorBoundary >
271- < GraphScene
272- data = { filteredData }
273- highlightedIds = { highlightedIds }
274- cameraTarget = { cameraTarget }
275- showLabels = { showLabels }
276- onNodeClick = { handleNodeClick }
277- />
278- </ ErrorBoundary >
262+ { filteredData . nodes . length === 0 ? (
263+ < div className = "flex items-center justify-center h-full" >
264+ < div className = "text-center" >
265+ < p className = "text-white/30 text-sm mb-3" > All nodes filtered out</ p >
266+ < Button size = "sm" onClick = { enableAll } >
267+ Reset Filters
268+ </ Button >
269+ </ div >
270+ </ div >
271+ ) : (
272+ < >
273+ < ErrorBoundary >
274+ < GraphScene
275+ data = { filteredData }
276+ highlightedIds = { highlightedIds }
277+ cameraTarget = { cameraTarget }
278+ showLabels = { showLabels }
279+ onNodeClick = { handleNodeClick }
280+ />
281+ </ ErrorBoundary >
279282
280- { /* HUD */ }
281- < div className = "absolute top-4 left-4 text-[11px] text-white/30 pointer-events-none font-mono" >
282- < p >
283- { filteredData . nodes . length . toLocaleString ( ) } nodes /{ " " }
284- { filteredData . edges . length . toLocaleString ( ) } edges
285- </ p >
286- { data . nodes . length > filteredData . nodes . length && (
287- < p className = "text-white/25 mt-0.5" >
288- filtered from { data . nodes . length . toLocaleString ( ) }
289- </ p >
290- ) }
291- { limitNotice && (
292- < p className = "text-amber-300/80 mt-0.5" > { limitNotice } </ p >
293- ) }
294- { highlightedIds && highlightedIds . size > 0 && (
295- < p className = "text-cyan-400/50 mt-0.5" >
296- { highlightedIds . size } selected
297- </ p >
298- ) }
299- </ div >
283+ { /* HUD */ }
284+ < div className = "absolute top-4 left-4 text-[11px] text-white/30 pointer-events-none font-mono" >
285+ < p >
286+ { filteredData . nodes . length . toLocaleString ( ) } nodes /{ " " }
287+ { filteredData . edges . length . toLocaleString ( ) } edges
288+ </ p >
289+ { data . nodes . length > filteredData . nodes . length && (
290+ < p className = "text-white/25 mt-0.5" >
291+ filtered from { data . nodes . length . toLocaleString ( ) }
292+ </ p >
293+ ) }
294+ { limitNotice && (
295+ < p className = "text-amber-300/80 mt-0.5" > { limitNotice } </ p >
296+ ) }
297+ { highlightedIds && highlightedIds . size > 0 && (
298+ < p className = "text-cyan-400/50 mt-0.5" >
299+ { highlightedIds . size } selected
300+ </ p >
301+ ) }
302+ </ div >
300303
301- < div className = "absolute top-4 right-4 flex gap-2" >
302- { highlightedIds && (
303- < Button
304- size = "sm"
305- onClick = { ( ) => {
306- setHighlightedIds ( null ) ;
307- setSelectedPath ( null ) ;
308- setSelectedNode ( null ) ;
309- setCameraTarget ( null ) ;
310- } }
311- >
312- Clear
313- </ Button >
314- ) }
315- < Button
316- variant = "outline"
317- size = "sm"
318- onClick = { ( ) => {
319- setHighlightedIds ( null ) ;
320- setSelectedPath ( null ) ;
321- setSelectedNode ( null ) ;
322- setCameraTarget ( null ) ;
323- fetchOverview ( project ) ;
324- } }
325- >
326- Refresh
327- </ Button >
328- </ div >
304+ < div className = "absolute top-4 right-4 flex gap-2" >
305+ { highlightedIds && (
306+ < Button
307+ size = "sm"
308+ onClick = { ( ) => {
309+ setHighlightedIds ( null ) ;
310+ setSelectedPath ( null ) ;
311+ setSelectedNode ( null ) ;
312+ setCameraTarget ( null ) ;
313+ } }
314+ >
315+ Clear
316+ </ Button >
317+ ) }
318+ < Button
319+ variant = "outline"
320+ size = "sm"
321+ onClick = { ( ) => {
322+ setHighlightedIds ( null ) ;
323+ setSelectedPath ( null ) ;
324+ setSelectedNode ( null ) ;
325+ setCameraTarget ( null ) ;
326+ fetchOverview ( project ) ;
327+ } }
328+ >
329+ Refresh
330+ </ Button >
331+ </ div >
332+ </ >
333+ ) }
329334 </ div >
330335
331336 { /* Right detail panel — resizable */ }
0 commit comments