From fc6e7934bd365ad1665dea68556dbfc80ac3b611 Mon Sep 17 00:00:00 2001 From: Filip <34747899+neriousy@users.noreply.github.com> Date: Wed, 25 Feb 2026 07:39:58 +0100 Subject: feat(desktop): enhance Windows app resolution and UI loading states (#13320) Co-authored-by: Brendan Allan Co-authored-by: Brendan Allan --- .../app/src/components/session/session-header.tsx | 127 ++++++++++++++++----- 1 file changed, 98 insertions(+), 29 deletions(-) (limited to 'packages/app/src/components/session') diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 825d1dab6..d531fa50a 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -1,28 +1,28 @@ +import { AppIcon } from "@opencode-ai/ui/app-icon" +import { Button } from "@opencode-ai/ui/button" +import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" +import { Icon } from "@opencode-ai/ui/icon" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { Keybind } from "@opencode-ai/ui/keybind" +import { Popover } from "@opencode-ai/ui/popover" +import { Spinner } from "@opencode-ai/ui/spinner" +import { TextField } from "@opencode-ai/ui/text-field" +import { showToast } from "@opencode-ai/ui/toast" +import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" +import { getFilename } from "@opencode-ai/util/path" +import { useParams } from "@solidjs/router" import { createEffect, createMemo, For, onCleanup, Show } from "solid-js" import { createStore } from "solid-js/store" import { Portal } from "solid-js/web" -import { useParams } from "@solidjs/router" -import { useLayout } from "@/context/layout" import { useCommand } from "@/context/command" +import { useGlobalSDK } from "@/context/global-sdk" import { useLanguage } from "@/context/language" +import { useLayout } from "@/context/layout" import { usePlatform } from "@/context/platform" import { useServer } from "@/context/server" import { useSync } from "@/context/sync" -import { useGlobalSDK } from "@/context/global-sdk" -import { getFilename } from "@opencode-ai/util/path" import { decode64 } from "@/utils/base64" import { Persist, persisted } from "@/utils/persist" - -import { Icon } from "@opencode-ai/ui/icon" -import { IconButton } from "@opencode-ai/ui/icon-button" -import { Button } from "@opencode-ai/ui/button" -import { AppIcon } from "@opencode-ai/ui/app-icon" -import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" -import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" -import { Popover } from "@opencode-ai/ui/popover" -import { TextField } from "@opencode-ai/ui/text-field" -import { Keybind } from "@opencode-ai/ui/keybind" -import { showToast } from "@opencode-ai/ui/toast" import { StatusPopover } from "../status-popover" const OPEN_APPS = [ @@ -45,32 +45,67 @@ type OpenApp = (typeof OPEN_APPS)[number] type OS = "macos" | "windows" | "linux" | "unknown" const MAC_APPS = [ - { id: "vscode", label: "VS Code", icon: "vscode", openWith: "Visual Studio Code" }, + { + id: "vscode", + label: "VS Code", + icon: "vscode", + openWith: "Visual Studio Code", + }, { id: "cursor", label: "Cursor", icon: "cursor", openWith: "Cursor" }, { id: "zed", label: "Zed", icon: "zed", openWith: "Zed" }, { id: "textmate", label: "TextMate", icon: "textmate", openWith: "TextMate" }, - { id: "antigravity", label: "Antigravity", icon: "antigravity", openWith: "Antigravity" }, + { + id: "antigravity", + label: "Antigravity", + icon: "antigravity", + openWith: "Antigravity", + }, { id: "terminal", label: "Terminal", icon: "terminal", openWith: "Terminal" }, { id: "iterm2", label: "iTerm2", icon: "iterm2", openWith: "iTerm" }, { id: "ghostty", label: "Ghostty", icon: "ghostty", openWith: "Ghostty" }, { id: "xcode", label: "Xcode", icon: "xcode", openWith: "Xcode" }, - { id: "android-studio", label: "Android Studio", icon: "android-studio", openWith: "Android Studio" }, - { id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" }, + { + id: "android-studio", + label: "Android Studio", + icon: "android-studio", + openWith: "Android Studio", + }, + { + id: "sublime-text", + label: "Sublime Text", + icon: "sublime-text", + openWith: "Sublime Text", + }, ] as const const WINDOWS_APPS = [ { id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" }, { id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" }, { id: "zed", label: "Zed", icon: "zed", openWith: "zed" }, - { id: "powershell", label: "PowerShell", icon: "powershell", openWith: "powershell" }, - { id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" }, + { + id: "powershell", + label: "PowerShell", + icon: "powershell", + openWith: "powershell", + }, + { + id: "sublime-text", + label: "Sublime Text", + icon: "sublime-text", + openWith: "Sublime Text", + }, ] as const const LINUX_APPS = [ { id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" }, { id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" }, { id: "zed", label: "Zed", icon: "zed", openWith: "zed" }, - { id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" }, + { + id: "sublime-text", + label: "Sublime Text", + icon: "sublime-text", + openWith: "Sublime Text", + }, ] as const type OpenOption = (typeof MAC_APPS)[number] | (typeof WINDOWS_APPS)[number] | (typeof LINUX_APPS)[number] @@ -213,7 +248,9 @@ export function SessionHeader() { const view = createMemo(() => layout.view(sessionKey)) const os = createMemo(() => detectOS(platform)) - const [exists, setExists] = createStore>>({ finder: true }) + const [exists, setExists] = createStore>>({ + finder: true, + }) const apps = createMemo(() => { if (os() === "macos") return MAC_APPS @@ -259,18 +296,34 @@ export function SessionHeader() { const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp })) const [menu, setMenu] = createStore({ open: false }) + const [openRequest, setOpenRequest] = createStore({ + app: undefined as OpenApp | undefined, + }) const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal()) const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0]) + const opening = createMemo(() => openRequest.app !== undefined) + + createEffect(() => { + const value = prefs.app + if (options().some((o) => o.id === value)) return + setPrefs("app", options()[0]?.id ?? "finder") + }) const openDir = (app: OpenApp) => { + if (opening() || !canOpen() || !platform.openPath) return const directory = projectDirectory() if (!directory) return - if (!canOpen()) return const item = options().find((o) => o.id === app) const openWith = item && "openWith" in item ? item.openWith : undefined - Promise.resolve(platform.openPath?.(directory, openWith)).catch((err: unknown) => showRequestError(language, err)) + setOpenRequest("app", app) + platform + .openPath(directory, openWith) + .catch((err: unknown) => showRequestError(language, err)) + .finally(() => { + setOpenRequest("app", undefined) + }) } const copyPath = () => { @@ -315,7 +368,9 @@ export function SessionHeader() {
- {language.t("session.header.search.placeholder", { project: name() })} + {language.t("session.header.search.placeholder", { + project: name(), + })}
@@ -357,12 +412,21 @@ export function SessionHeader() {
@@ -377,7 +441,11 @@ export function SessionHeader() { as={IconButton} icon="chevron-down" variant="ghost" - class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-hover" + disabled={opening()} + class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active disabled:!cursor-default" + classList={{ + "bg-surface-raised-base-active": opening(), + }} aria-label={language.t("session.header.open.menu")} /> @@ -395,6 +463,7 @@ export function SessionHeader() { {(o) => ( { setMenu("open", false) openDir(o.id) -- cgit v1.2.3