1- import { createEffect , createMemo , onCleanup , Show } from "solid-js"
1+ import { createEffect , createMemo , createResource , onCleanup , Show } from "solid-js"
22import { createStore } from "solid-js/store"
33import { Portal } from "solid-js/web"
44import { useParams } from "@solidjs/router"
@@ -18,6 +18,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
1818import { Button } from "@opencode-ai/ui/button"
1919import { AppIcon } from "@opencode-ai/ui/app-icon"
2020import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
21+ import { Spinner } from "@opencode-ai/ui/spinner"
2122import { Tooltip , TooltipKeybind } from "@opencode-ai/ui/tooltip"
2223import { Popover } from "@opencode-ai/ui/popover"
2324import { TextField } from "@opencode-ai/ui/text-field"
@@ -167,6 +168,7 @@ export function SessionHeader() {
167168
168169 const [ prefs , setPrefs ] = persisted ( Persist . global ( "open.app" ) , createStore ( { app : "finder" as OpenApp } ) )
169170 const [ menu , setMenu ] = createStore ( { open : false } )
171+ const [ openRequest , setOpenRequest ] = createStore ( { app : undefined as OpenApp | undefined , version : 0 } )
170172
171173 const canOpen = createMemo ( ( ) => platform . platform === "desktop" && ! ! platform . openPath && server . isLocal ( ) )
172174 const current = createMemo ( ( ) => options ( ) . find ( ( o ) => o . id === prefs . app ) ?? options ( ) [ 0 ] )
@@ -179,20 +181,32 @@ export function SessionHeader() {
179181 setPrefs ( "app" , options ( ) [ 0 ] ?. id ?? "finder" )
180182 } )
181183
182- const openDir = ( app : OpenApp ) => {
183- const directory = projectDirectory ( )
184- if ( ! directory ) return
185- if ( ! canOpen ( ) ) return
186-
187- const item = options ( ) . find ( ( o ) => o . id === app )
188- const openWith = item && "openWith" in item ? item . openWith : undefined
189- Promise . resolve ( platform . openPath ?.( directory , openWith ) ) . catch ( ( err : unknown ) => {
190- showToast ( {
191- variant : "error" ,
192- title : language . t ( "common.requestFailed" ) ,
193- description : err instanceof Error ? err . message : String ( err ) ,
194- } )
184+ const [ openTask ] = createResource (
185+ ( ) => openRequest . app && openRequest . version ,
186+ async ( ) => {
187+ const app = openRequest . app
188+ const directory = projectDirectory ( )
189+ if ( ! app || ! directory || ! canOpen ( ) ) return
190+
191+ const item = options ( ) . find ( ( o ) => o . id === app )
192+ const openWith = item && "openWith" in item ? item . openWith : undefined
193+ await platform . openPath ?.( directory , openWith )
194+ } ,
195+ )
196+
197+ createEffect ( ( ) => {
198+ const err = openTask . error
199+ if ( ! err ) return
200+ showToast ( {
201+ variant : "error" ,
202+ title : language . t ( "common.requestFailed" ) ,
203+ description : err instanceof Error ? err . message : String ( err ) ,
195204 } )
205+ } )
206+
207+ const openDir = ( app : OpenApp ) => {
208+ if ( openTask . loading ) return
209+ setOpenRequest ( { app, version : openRequest . version + 1 } )
196210 }
197211
198212 const copyPath = ( ) => {
@@ -329,29 +343,37 @@ export function SessionHeader() {
329343 < Show
330344 when = { canOpen ( ) }
331345 fallback = {
332- < Button
333- variant = "ghost"
334- class = "rounded-sm h-[24px] py-1.5 pr-3 pl-2 gap-2 border-none shadow-none"
335- onClick = { copyPath }
336- aria-label = { language . t ( "session.header.open.copyPath" ) }
337- >
338- < Icon name = "copy" size = "small" class = "text-icon-base" />
339- < span class = "text-12-regular text-text-strong" >
340- { language . t ( "session.header.open.copyPath" ) }
341- </ span >
342- </ Button >
346+ < div class = "flex h-[24px] box-border items-center rounded-md border border-border-base bg-surface-panel overflow-hidden" >
347+ < Button
348+ variant = "ghost"
349+ class = "rounded-none h-full py-0 pr-3 pl-2 gap-2 border-none shadow-none"
350+ onClick = { copyPath }
351+ aria-label = { language . t ( "session.header.open.copyPath" ) }
352+ >
353+ < Icon name = "copy" size = "small" class = "text-icon-base" />
354+ < span class = "text-12-regular text-text-strong" >
355+ { language . t ( "session.header.open.copyPath" ) }
356+ </ span >
357+ </ Button >
358+ </ div >
343359 }
344360 >
345361 < div class = "flex items-center" >
346362 < div class = "flex h-[24px] box-border items-center rounded-md border border-border-base bg-surface-panel overflow-hidden" >
347363 < Button
348364 variant = "ghost"
349- class = "rounded-none h-full py-0 pr-3 pl-2 gap-1.5 border-none shadow-none"
365+ class = "rounded-none h-full py-0 pr-3 pl-2 gap-1.5 border-none shadow-none disabled:!cursor-default"
366+ classList = { {
367+ "bg-surface-raised-base-active" : openTask . loading ,
368+ } }
350369 onClick = { ( ) => openDir ( current ( ) . id ) }
370+ disabled = { openTask . loading }
351371 aria-label = { language . t ( "session.header.open.ariaLabel" , { app : current ( ) . label } ) }
352372 >
353373 < div class = "flex size-5 shrink-0 items-center justify-center" >
354- < AppIcon id = { current ( ) . icon } class = "size-4" />
374+ < Show when = { openTask . loading } fallback = { < AppIcon id = { current ( ) . icon } class = "size-4" /> } >
375+ < Spinner class = "size-3.5 text-icon-base" />
376+ </ Show >
355377 </ div >
356378 < span class = "text-12-regular text-text-strong" > Open</ span >
357379 </ Button >
@@ -366,7 +388,11 @@ export function SessionHeader() {
366388 as = { IconButton }
367389 icon = "chevron-down"
368390 variant = "ghost"
369- class = "rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active"
391+ disabled = { openTask . loading }
392+ class = "rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active disabled:!cursor-default"
393+ classList = { {
394+ "bg-surface-raised-base-active" : openTask . loading ,
395+ } }
370396 aria-label = { language . t ( "session.header.open.menu" ) }
371397 />
372398 < DropdownMenu . Portal >
@@ -383,6 +409,7 @@ export function SessionHeader() {
383409 { options ( ) . map ( ( o ) => (
384410 < DropdownMenu . RadioItem
385411 value = { o . id }
412+ disabled = { openTask . loading }
386413 onSelect = { ( ) => {
387414 setMenu ( "open" , false )
388415 openDir ( o . id )
0 commit comments