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 = ( ) => {
@@ -346,12 +360,18 @@ export function SessionHeader() {
346360 < div class = "flex h-[24px] box-border items-center rounded-md border border-border-base bg-surface-panel overflow-hidden" >
347361 < Button
348362 variant = "ghost"
349- class = "rounded-none h-full py-0 pr-3 pl-2 gap-1.5 border-none shadow-none"
363+ class = "rounded-none h-full py-0 pr-3 pl-2 gap-1.5 border-none shadow-none disabled:!cursor-default"
364+ classList = { {
365+ "bg-surface-raised-base-active" : openTask . loading ,
366+ } }
350367 onClick = { ( ) => openDir ( current ( ) . id ) }
368+ disabled = { openTask . loading }
351369 aria-label = { language . t ( "session.header.open.ariaLabel" , { app : current ( ) . label } ) }
352370 >
353371 < div class = "flex size-5 shrink-0 items-center justify-center" >
354- < AppIcon id = { current ( ) . icon } class = "size-4" />
372+ < Show when = { openTask . loading } fallback = { < AppIcon id = { current ( ) . icon } class = "size-4" /> } >
373+ < Spinner class = "size-3.5 text-icon-base" />
374+ </ Show >
355375 </ div >
356376 < span class = "text-12-regular text-text-strong" > Open</ span >
357377 </ Button >
@@ -366,7 +386,11 @@ export function SessionHeader() {
366386 as = { IconButton }
367387 icon = "chevron-down"
368388 variant = "ghost"
369- class = "rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active"
389+ disabled = { openTask . loading }
390+ class = "rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active disabled:!cursor-default"
391+ classList = { {
392+ "bg-surface-raised-base-active" : openTask . loading ,
393+ } }
370394 aria-label = { language . t ( "session.header.open.menu" ) }
371395 />
372396 < DropdownMenu . Portal >
@@ -383,6 +407,7 @@ export function SessionHeader() {
383407 { options ( ) . map ( ( o ) => (
384408 < DropdownMenu . RadioItem
385409 value = { o . id }
410+ disabled = { openTask . loading }
386411 onSelect = { ( ) => {
387412 setMenu ( "open" , false )
388413 openDir ( o . id )
0 commit comments