@@ -22,14 +22,16 @@ import { useRoute } from "../context/route"
2222export type MoveSessionSelection = { type : "directory" ; directory : string ; subdirectory : boolean } | { type : "new" }
2323type ProjectDirectory = ProjectDirectories [ number ]
2424
25- export function DialogMoveSession ( props : {
25+ type DialogMoveSessionProps = {
2626 projectID : string
2727 current ?: MoveSessionSelection
2828 onSelect : ( selection : MoveSessionSelection ) => void
2929 onCurrentChange ?: ( selection : MoveSessionSelection ) => void
3030 initialDirectories ?: ProjectDirectory [ ]
3131 initialRemoving ?: string
32- } ) {
32+ }
33+
34+ export function DialogMoveSession ( props : DialogMoveSessionProps ) {
3335 const dialog = useDialog ( )
3436 const sdk = useSDK ( )
3537 const dimensions = useTerminalDimensions ( )
@@ -43,69 +45,75 @@ export function DialogMoveSession(props: {
4345 const [ toDelete , setToDelete ] = createSignal < string > ( )
4446 const [ removing , setRemoving ] = createSignal ( props . initialRemoving )
4547 const [ replacementCurrent , setReplacementCurrent ] = createSignal < string > ( )
48+ const [ loadError , setLoadError ] = createSignal < unknown > ( )
4649 const deleteHint = useCommandShortcut ( "dialog.move_session.delete" )
50+ onMount ( ( ) => dialog . setSize ( "xlarge" ) )
4751
4852 function reopen ( initialRemoving ?: string ) {
4953 dialog . replace ( ( ) => (
50- < DialogMoveSession { ...props } initialDirectories = { directories ( ) } initialRemoving = { initialRemoving } />
54+ < DialogMoveSession { ...props } initialDirectories = { directoryData ( ) } initialRemoving = { initialRemoving } />
5155 ) )
5256 }
5357
58+ // A failed current-checkout lookup only affects which row is highlighted, so
59+ // swallow it and let the directory list render without a current marker.
5460 const [ loadedProject ] = createResource (
5561 ( ) => ( projectContext . project ( ) === props . projectID ? undefined : props . projectID ) ,
56- async ( projectID ) => {
57- const result = await sdk . client . project . current ( { } , { throwOnError : true } )
58- return result . data ?. id === projectID ? result . data . worktree : undefined
59- } ,
60- )
61- const currentCheckout = createMemo ( ( ) =>
62- projectContext . project ( ) === props . projectID ? projectContext . instance . path ( ) . worktree : loadedProject ( ) ,
62+ ( projectID ) =>
63+ sdk . client . project
64+ . current ( { } , { throwOnError : true } )
65+ . then ( ( result ) => ( result . data ?. id === projectID ? result . data . worktree : undefined ) )
66+ . catch ( ( ) => undefined ) ,
6367 )
68+ const currentCheckout = createMemo ( ( ) => {
69+ if ( projectContext . project ( ) === props . projectID ) return projectContext . instance . path ( ) . worktree
70+ return loadedProject ( )
71+ } )
6472
6573 const [ directories , { refetch } ] = createResource (
6674 ( ) => ( props . initialRemoving ? undefined : props . projectID ) ,
67- async ( projectID ) => {
68- setWorking ( true )
75+ async ( projectID , info ) : Promise < ProjectDirectory [ ] | undefined > => {
6976 try {
7077 await sdk . client . v2 . projectCopy . refresh (
7178 { projectID, location : { directory : sdk . directory } } ,
7279 { throwOnError : true } ,
7380 )
7481 const directories = await sdk . client . project . directories ( { projectID } , { throwOnError : true } )
82+ setLoadError ( undefined )
7583 return directories . data ?? [ ]
7684 } catch ( error ) {
77- toast . show ( {
78- variant : "error" ,
79- title : "Failed to load project directories" ,
80- message : errorMessage ( error ) ,
81- } )
82- return [ ]
83- } finally {
84- setWorking ( false )
85+ setLoadError ( error )
86+ // An initial load with no data surfaces the inline error view below. A
87+ // failed refresh intentionally stays quiet and keeps the already-shown
88+ // list interactive; reopening the dialog retries the load.
89+ return info . value
8590 }
8691 } ,
87- { initialValue : props . initialDirectories } ,
8892 )
93+ const directoryData = createMemo ( ( ) => directories ( ) ?? props . initialDirectories )
94+ // Show the locked error view only when we have nothing to display. A refresh
95+ // that fails after the list rendered keeps the list and its actions.
96+ const showError = createMemo ( ( ) => Boolean ( loadError ( ) ) && ! directoryData ( ) )
8997
9098 const currentDirectory = createMemo (
9199 ( ) => replacementCurrent ( ) ?? ( props . current ?. type === "directory" ? props . current . directory : currentCheckout ( ) ) ,
92100 )
93101 const currentRoot = createMemo < ProjectDirectory | undefined > ( ( ) => {
102+ if ( showError ( ) ) return
94103 const directory = currentDirectory ( )
95104 if ( ! directory ) return
96105 return (
97- directories ( )
106+ directoryData ( )
98107 ?. filter ( ( root ) => contains ( root . directory , directory ) )
99108 . toSorted ( ( a , b ) => b . directory . length - a . directory . length ) [ 0 ] ?? { directory }
100109 )
101110 } )
102111
103112 const options = createMemo < DialogSelectOption < MoveSessionSelection | undefined > [ ] > ( ( ) => {
104- const data = directories ( )
113+ if ( showError ( ) ) return [ ]
114+ const data = directoryData ( )
105115 const current = currentRoot ( ) ?. directory
106116 if ( directories . loading && ! data && ! current ) return [ { title : "Loading project directories..." , value : undefined } ]
107- if ( directories . error && ! data && ! current )
108- return [ { title : "Failed to load project directories" , value : undefined } ]
109117 const roots = [ ...( data ?? [ ] ) ]
110118 if ( current && ! roots . some ( ( item ) => item . directory === current ) ) roots . unshift ( { directory : current } )
111119 roots . sort ( ( a , b ) => {
@@ -201,7 +209,7 @@ export function DialogMoveSession(props: {
201209
202210 async function remove ( option : DialogSelectOption < MoveSessionSelection | undefined > ) {
203211 if ( ! option . value || option . value . type !== "directory" || option . value . subdirectory || removing ( ) ) return
204- const data = directories ( )
212+ const data = directoryData ( )
205213 const selected = option . value
206214 const root = data ?. find ( ( item ) => item . directory === selected . directory )
207215 if ( ! root ?. strategy ) return
@@ -271,51 +279,68 @@ export function DialogMoveSession(props: {
271279 if ( await removedCurrent ( deletingCurrent ) ) return
272280 }
273281
274- onMount ( ( ) => dialog . setSize ( "xlarge" ) )
282+ const fullHeight = createMemo ( ( ) =>
283+ Math . max ( 8 , Math . min ( 16 , dimensions ( ) . height - Math . floor ( dimensions ( ) . height / 4 ) - 2 ) ) ,
284+ )
275285
276286 return (
277- < box minHeight = { Math . max ( 8 , Math . min ( 16 , dimensions ( ) . height - Math . floor ( dimensions ( ) . height / 4 ) - 2 ) ) } >
287+ < box minHeight = { showError ( ) ? 5 : fullHeight ( ) } >
278288 < DialogSelect
279289 title = "Move session"
280290 titleView = {
281291 < box flexDirection = "row" gap = { 1 } >
282292 < text fg = { theme . text } attributes = { TextAttributes . BOLD } >
283293 Move session
284294 </ text >
285- < Show when = { working ( ) } >
295+ < Show when = { working ( ) || directories . loading || loadedProject . loading } >
286296 < Spinner />
287297 </ Show >
288298 </ box >
289299 }
300+ renderFilter = { ! showError ( ) }
290301 options = { options ( ) }
291- locked = { directories . loading || loadedProject . loading || Boolean ( removing ( ) ) }
302+ emptyView = {
303+ showError ( ) ? (
304+ < box paddingLeft = { 4 } paddingRight = { 4 } >
305+ < text fg = { theme . error } attributes = { TextAttributes . BOLD } >
306+ Could not load project directories
307+ </ text >
308+ < text fg = { theme . textMuted } > { errorMessage ( loadError ( ) ) } </ text >
309+ </ box >
310+ ) : undefined
311+ }
312+ locked = { showError ( ) || directories . loading || loadedProject . loading || Boolean ( removing ( ) ) }
292313 current = { current ( ) }
293314 onSelect = { ( option ) => {
294315 if ( option . value ) props . onSelect ( option . value )
295316 } }
296317 onMove = { ( ) => setToDelete ( undefined ) }
297- actions = { [
298- {
299- command : "dialog.move_session.new" ,
300- title : "new" ,
301- onTrigger : ( ) => props . onSelect ( { type : "new" } ) ,
302- } ,
303- {
304- command : "dialog.move_session.delete" ,
305- title : "delete" ,
306- disabled : ( option ) => {
307- const value = option ?. value
308- if ( ! value || value . type !== "directory" || value . subdirectory ) return true
309- return ! directories ( ) ?. find ( ( item ) => item . directory === value . directory ) ?. strategy
310- } ,
311- onTrigger : remove ,
312- } ,
313- {
314- command : "dialog.move_session.refresh" ,
315- title : "refresh" ,
316- onTrigger : ( ) => void refetch ( ) ,
317- } ,
318- ] }
318+ actions = {
319+ showError ( )
320+ ? [ ]
321+ : [
322+ {
323+ command : "dialog.move_session.new" ,
324+ title : "new" ,
325+ onTrigger : ( ) => props . onSelect ( { type : "new" } ) ,
326+ } ,
327+ {
328+ command : "dialog.move_session.delete" ,
329+ title : "delete" ,
330+ disabled : ( option ) => {
331+ const value = option ?. value
332+ if ( ! value || value . type !== "directory" || value . subdirectory ) return true
333+ return ! directoryData ( ) ?. find ( ( item ) => item . directory === value . directory ) ?. strategy
334+ } ,
335+ onTrigger : remove ,
336+ } ,
337+ {
338+ command : "dialog.move_session.refresh" ,
339+ title : "refresh" ,
340+ onTrigger : ( ) => void refetch ( ) ,
341+ } ,
342+ ]
343+ }
319344 />
320345 </ box >
321346 )
0 commit comments