@@ -203,11 +203,13 @@ const VsCodeAccessPanel = (
203203 cfState,
204204 info,
205205 onClose,
206+ onRefresh,
206207 onRetry
207208 } : {
208209 readonly cfState : CfTunnelState
209210 readonly info : VsCodeAccessInfo
210211 readonly onClose : ( ) => void
212+ readonly onRefresh : ( ) => void
211213 readonly onRetry : ( ) => void
212214 }
213215) : JSX . Element => {
@@ -230,13 +232,12 @@ const VsCodeAccessPanel = (
230232 } } >
231233 < div style = { { alignItems : "center" , display : "flex" , justifyContent : "space-between" , marginBottom : "10px" } } >
232234 < div style = { { color : "#8be9fd" , fontWeight : "bold" } } > VS Code / SSH access</ div >
233- < button
234- onClick = { onClose }
235- style = { { ...vsCodePanelCopyBtnStyle , color : "#f87171" } }
236- type = "button"
237- >
238- ✕ close
239- </ button >
235+ < div style = { { display : "flex" , gap : "4px" } } >
236+ { cfState . tag === "ready" && (
237+ < button onClick = { onRefresh } style = { { ...vsCodePanelCopyBtnStyle , color : "#7fdfff" } } type = "button" > ↻ refresh</ button >
238+ ) }
239+ < button onClick = { onClose } style = { { ...vsCodePanelCopyBtnStyle , color : "#f87171" } } type = "button" > ✕ close</ button >
240+ </ div >
240241 </ div >
241242
242243 { cfState . tag === "loading" && (
@@ -397,6 +398,28 @@ export const TerminalPane = (props: TerminalPaneProps): JSX.Element => {
397398 }
398399 } , [ vsCodePanelOpen , runtime . browserProjectKey , cfState . tag ] )
399400
401+ // Poll every 30s when panel is open: restart tunnel if process died
402+ useEffect ( ( ) => {
403+ if ( ! vsCodePanelOpen || cfState . tag !== "ready" || runtime . browserProjectKey === undefined ) return
404+ const projectKey = runtime . browserProjectKey
405+ const id = setInterval ( ( ) => {
406+ void Effect . runPromise (
407+ startProjectSshTunnel ( projectKey ) . pipe (
408+ Effect . match ( {
409+ onFailure : ( ) => { setCfState ( { tag : "failed" } ) } ,
410+ onSuccess : ( { hostname, sshPassword } ) => {
411+ if ( hostname === null ) { setCfState ( { tag : "failed" } ) ; return }
412+ setCfState ( ( prev ) =>
413+ prev . tag === "ready" && prev . hostname === hostname ? prev : { tag : "ready" , hostname, sshPassword }
414+ )
415+ }
416+ } )
417+ )
418+ )
419+ } , 30_000 )
420+ return ( ) => { clearInterval ( id ) }
421+ } , [ vsCodePanelOpen , cfState . tag , runtime . browserProjectKey ] )
422+
400423 const vsCodeInfo = buildVsCodeAccessInfo ( props . project )
401424 const onOpenVsCode = vsCodeInfo !== null ? ( ) => { setVsCodePanelOpen ( true ) } : undefined
402425 const vsCodeBodyContent = vsCodePanelOpen && vsCodeInfo !== null
@@ -405,6 +428,11 @@ export const TerminalPane = (props: TerminalPaneProps): JSX.Element => {
405428 cfState = { cfState }
406429 info = { vsCodeInfo }
407430 onClose = { ( ) => { setVsCodePanelOpen ( false ) } }
431+ onRefresh = { ( ) => {
432+ if ( runtime . browserProjectKey !== undefined ) {
433+ startTunnel ( runtime . browserProjectKey , setCfState )
434+ }
435+ } }
408436 onRetry = { ( ) => {
409437 if ( runtime . browserProjectKey !== undefined ) {
410438 startTunnel ( runtime . browserProjectKey , setCfState )
0 commit comments