summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-01-20 10:31:13 -0600
committerAdam <[email protected]>2026-01-20 10:31:57 -0600
commit7f862533d8e54369ffab402de9724c50fd37b93e (patch)
tree1d040baa3f285e4f555ec9b63d99aed975ecb4f1
parent8f62d4a5e3f18879ec1116336c3094de4a624b36 (diff)
downloadopencode-7f862533d8e54369ffab402de9724c50fd37b93e.tar.gz
opencode-7f862533d8e54369ffab402de9724c50fd37b93e.zip
fix(app): better pending states for workspace operations
-rw-r--r--packages/app/src/pages/layout.tsx45
1 files changed, 32 insertions, 13 deletions
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 64fc52739..cc18111ba 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -122,6 +122,17 @@ export default function Layout(props: ParentProps) {
active: "" as string,
value: "",
})
+ const [busyWorkspaces, setBusyWorkspaces] = createSignal<Set<string>>(new Set())
+ const setBusy = (directory: string, value: boolean) => {
+ const key = workspaceKey(directory)
+ setBusyWorkspaces((prev) => {
+ const next = new Set(prev)
+ if (value) next.add(key)
+ else next.delete(key)
+ return next
+ })
+ }
+ const isBusy = (directory: string) => busyWorkspaces().has(workspaceKey(directory))
const editorRef = { current: undefined as HTMLInputElement | undefined }
const autoselecting = createMemo(() => {
@@ -995,6 +1006,8 @@ export default function Layout(props: ParentProps) {
if (!current) return
if (directory === current.worktree) return
+ setBusy(directory, true)
+
const result = await globalSDK.client.worktree
.remove({ directory: current.worktree, worktreeRemoveInput: { directory } })
.then((x) => x.data)
@@ -1006,6 +1019,8 @@ export default function Layout(props: ParentProps) {
return false
})
+ setBusy(directory, false)
+
if (!result) return
layout.projects.close(directory)
@@ -1021,12 +1036,7 @@ export default function Layout(props: ParentProps) {
if (!current) return
if (directory === current.worktree) return
- const progress = showToast({
- persistent: true,
- title: "Resetting workspace",
- description: "This may take a minute.",
- })
- const dismiss = () => toaster.dismiss(progress)
+ setBusy(directory, true)
const sessions = await globalSDK.client.session
.list({ directory })
@@ -1037,7 +1047,6 @@ export default function Layout(props: ParentProps) {
.reset({ directory: current.worktree, worktreeResetInput: { directory } })
.then((x) => x.data)
.catch((err) => {
- dismiss()
showToast({
title: "Failed to reset workspace",
description: errorMessage(err),
@@ -1046,7 +1055,7 @@ export default function Layout(props: ParentProps) {
})
if (!result) {
- dismiss()
+ setBusy(directory, false)
return
}
@@ -1066,7 +1075,8 @@ export default function Layout(props: ParentProps) {
)
await globalSDK.client.instance.dispose({ directory }).catch(() => undefined)
- dismiss()
+
+ setBusy(directory, false)
const href = `/${base64Encode(directory)}/session`
navigate(href)
@@ -1590,6 +1600,7 @@ export default function Layout(props: ParentProps) {
const boot = createMemo(() => open() || active())
const loading = createMemo(() => open() && workspaceStore.status !== "complete" && sessions().length === 0)
const hasMore = createMemo(() => local() && workspaceStore.sessionTotal > workspaceStore.session.length)
+ const busy = createMemo(() => isBusy(props.directory))
const loadMore = async () => {
if (!local()) return
setWorkspaceStore("limit", (limit) => limit + 5)
@@ -1612,7 +1623,9 @@ export default function Layout(props: ParentProps) {
const header = () => (
<div class="flex items-center gap-1 min-w-0 flex-1">
<div class="flex items-center justify-center shrink-0 size-6">
- <Icon name="branch" size="small" />
+ <Show when={busy()} fallback={<Icon name="branch" size="small" />}>
+ <Spinner class="size-[15px]" />
+ </Show>
</div>
<span class="text-14-medium text-text-base shrink-0">{local() ? "local" : "sandbox"} :</span>
<Show
@@ -1649,7 +1662,13 @@ export default function Layout(props: ParentProps) {
return (
// @ts-ignore
- <div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
+ <div
+ use:sortable
+ classList={{
+ "opacity-30": sortable.isActiveDraggable,
+ "opacity-50 pointer-events-none": busy(),
+ }}
+ >
<Collapsible variant="ghost" open={open()} class="shrink-0" onOpenChange={openWrapper}>
<div class="px-2 py-1">
<div class="group/workspace relative">
@@ -1699,13 +1718,13 @@ export default function Layout(props: ParentProps) {
<DropdownMenu.ItemLabel>Rename</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Item
- disabled={local()}
+ disabled={local() || busy()}
onSelect={() => dialog.show(() => <DialogResetWorkspace directory={props.directory} />)}
>
<DropdownMenu.ItemLabel>Reset</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Item
- disabled={local()}
+ disabled={local() || busy()}
onSelect={() => dialog.show(() => <DialogDeleteWorkspace directory={props.directory} />)}
>
<DropdownMenu.ItemLabel>Delete</DropdownMenu.ItemLabel>