55import * as React from 'react'
66import { twMerge } from 'tailwind-merge'
77import { useNavigate , useSearch } from '@tanstack/react-router'
8+ import { useIsFetching } from '@tanstack/react-query'
89import { FileExplorer } from '~/components/FileExplorer'
910import { CodeViewer } from './CodeViewer'
1011import { LivePreview } from '../preview/LivePreview'
@@ -121,18 +122,34 @@ export function ExplorerPanel({
121122 }
122123
123124 const dryRun = useDryRun ( )
125+ const isFetchingDryRun = useIsFetching ( { queryKey : [ 'dry-run' ] } ) > 0
124126 const files = React . useMemo (
125127 ( ) => ( dryRun ?. files ?? { } ) as Record < string , string > ,
126128 [ dryRun ?. files ] ,
127129 )
130+ const hasFiles = Object . keys ( files ) . length > 0
131+
132+ // Keep track of previous files to avoid blank state during loading
133+ const previousFilesRef = React . useRef < Record < string , string > > ( files )
134+ React . useEffect ( ( ) => {
135+ if ( hasFiles ) {
136+ previousFilesRef . current = files
137+ }
138+ } , [ files , hasFiles ] )
139+
140+ // Use previous files while loading to avoid blank state
141+ const displayFiles = hasFiles ? files : previousFilesRef . current
128142
129143 // Convert flat files to tree structure for FileExplorer
130- const fileTree = React . useMemo ( ( ) => filesToGitHubTree ( files ) , [ files ] )
144+ const fileTree = React . useMemo (
145+ ( ) => filesToGitHubTree ( displayFiles ) ,
146+ [ displayFiles ] ,
147+ )
131148
132149 // Auto-select first file if none selected
133150 React . useEffect ( ( ) => {
134- if ( ! selectedFile && Object . keys ( files ) . length > 0 ) {
135- const fileKeys = Object . keys ( files )
151+ if ( ! selectedFile && Object . keys ( displayFiles ) . length > 0 ) {
152+ const fileKeys = Object . keys ( displayFiles )
136153 const preferredFiles = [
137154 'src/routes/index.tsx' ,
138155 'src/routes/__root.tsx' ,
@@ -157,17 +174,17 @@ export function ExplorerPanel({
157174 setSelectedFile ( fileKeys [ 0 ] . replace ( / ^ \. \/ / , '' ) )
158175 }
159176 // eslint-disable-next-line react-hooks/exhaustive-deps -- setSelectedFile is stable from useState
160- } , [ files , selectedFile ] )
177+ } , [ displayFiles , selectedFile ] )
161178
162179 // Try to find file content - files may have ./ prefix or not
163180 const selectedFileContent = selectedFile
164- ? ( files [ selectedFile ] ?? files [ `./${ selectedFile } ` ] ?? null )
181+ ? ( displayFiles [ selectedFile ] ?? displayFiles [ `./${ selectedFile } ` ] ?? null )
165182 : null
166183
167184 return (
168- < div className = "flex flex-col h-full" >
185+ < div className = "flex flex-col h-full min-h-0 overflow-hidden " >
169186 { /* Tab bar */ }
170- < div className = "flex items-center border-b border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900/50" >
187+ < div className = "flex items-center shrink-0 border-b border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900/50" >
171188 { /* Mobile config toggle */ }
172189 < button
173190 type = "button"
@@ -233,7 +250,7 @@ export function ExplorerPanel({
233250 </ div >
234251
235252 { /* Content area */ }
236- < div className = "flex-1 overflow-hidden flex flex-col" >
253+ < div className = "flex-1 overflow-hidden flex flex-col relative " >
237254 { activeTab === 'files' ? (
238255 < div className = "flex-1 flex min-h-0 overflow-hidden" >
239256 { /* File tree sidebar using existing FileExplorer */ }
@@ -264,6 +281,18 @@ export function ExplorerPanel({
264281 { canPreview && previewActivated && isTerminalOpen && (
265282 < Terminal onClose = { ( ) => setIsTerminalOpen ( false ) } />
266283 ) }
284+
285+ { /* Loading overlay */ }
286+ { isFetchingDryRun && (
287+ < div className = "absolute inset-0 bg-white/50 dark:bg-gray-900/50 backdrop-blur-[1px] flex items-center justify-center z-10" >
288+ < div className = "flex items-center gap-3 px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700" >
289+ < div className = "w-5 h-5 border-2 border-gray-300 dark:border-gray-600 border-t-blue-500 rounded-full animate-spin" />
290+ < span className = "text-sm text-gray-600 dark:text-gray-300" >
291+ Generating...
292+ </ span >
293+ </ div >
294+ </ div >
295+ ) }
267296 </ div >
268297 </ div >
269298 )
0 commit comments