diff options
| author | Adam <[email protected]> | 2026-01-20 10:31:13 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-01-20 10:31:57 -0600 |
| commit | 7f862533d8e54369ffab402de9724c50fd37b93e (patch) | |
| tree | 1d040baa3f285e4f555ec9b63d99aed975ecb4f1 | |
| parent | 8f62d4a5e3f18879ec1116336c3094de4a624b36 (diff) | |
| download | opencode-7f862533d8e54369ffab402de9724c50fd37b93e.tar.gz opencode-7f862533d8e54369ffab402de9724c50fd37b93e.zip | |
fix(app): better pending states for workspace operations
| -rw-r--r-- | packages/app/src/pages/layout.tsx | 45 |
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> |
