diff --git a/apps/web/components/panel-loading-state.tsx b/apps/web/components/panel-loading-state.tsx
new file mode 100644
index 0000000000..7c9cb32e07
--- /dev/null
+++ b/apps/web/components/panel-loading-state.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { GridSpinner } from "@/components/grid-spinner";
+import { cn } from "@/lib/utils";
+
+type PanelLoadingStateProps = {
+ label: string;
+ /** Override the outer container layout (e.g. absolute overlay for the terminal). */
+ className?: string;
+ testId?: string;
+};
+
+/**
+ * Shared panel loading placeholder: a grid spinner above a label, matching the
+ * terminal's "Connecting terminal..." look. Used by the file browser, terminal,
+ * and agent preview panels so loading states stay visually consistent.
+ */
+export function PanelLoadingState({ label, className, testId }: PanelLoadingStateProps) {
+ return (
+
+ );
+}
diff --git a/apps/web/components/task/file-browser-load-state.tsx b/apps/web/components/task/file-browser-load-state.tsx
index 7d64be0bf6..4bb63013fe 100644
--- a/apps/web/components/task/file-browser-load-state.tsx
+++ b/apps/web/components/task/file-browser-load-state.tsx
@@ -1,6 +1,6 @@
"use client";
-import { IconLoader2 } from "@tabler/icons-react";
+import { PanelLoadingState } from "@/components/panel-loading-state";
import type { FileTreeNode } from "@/lib/types/backend";
type RenderSessionOrLoadStateInput = {
@@ -31,18 +31,10 @@ export function renderSessionOrLoadState({
);
}
if ((loadState === "loading" || isLoadingTree) && !tree) {
- return Loading files...
;
+ return ;
}
if (loadState === "waiting") {
- return (
-
-
- Preparing workspace...
-
- );
+ return ;
}
if (loadState === "manual") {
return (
diff --git a/apps/web/components/task/passthrough-terminal.tsx b/apps/web/components/task/passthrough-terminal.tsx
index da50d2fd58..2ee57586f6 100644
--- a/apps/web/components/task/passthrough-terminal.tsx
+++ b/apps/web/components/task/passthrough-terminal.tsx
@@ -6,7 +6,7 @@ import { FitAddon } from "@xterm/addon-fit";
import { AttachAddon } from "@xterm/addon-attach";
import { WebglAddon } from "@xterm/addon-webgl";
import "@xterm/xterm/css/xterm.css";
-import { GridSpinner } from "@/components/grid-spinner";
+import { PanelLoadingState } from "@/components/panel-loading-state";
import { useAppStore } from "@/components/state-provider";
import { useSession } from "@/hooks/domains/session/use-session";
import { useSessionAgentctl } from "@/hooks/domains/session/use-session-agentctl";
@@ -277,17 +277,11 @@ export function PassthroughTerminal(props: PassthroughTerminalProps) {
{!isConnected && (
-
-
-
-
- {mode === "agent" ? "Preparing workspace..." : "Connecting terminal..."}
-
-
-
+
)}
);
diff --git a/apps/web/components/task/preview-session-tabs.tsx b/apps/web/components/task/preview-session-tabs.tsx
index 14a23be223..1ec72aee85 100644
--- a/apps/web/components/task/preview-session-tabs.tsx
+++ b/apps/web/components/task/preview-session-tabs.tsx
@@ -1,10 +1,10 @@
"use client";
import { useCallback, useMemo } from "react";
-import { IconLoader2 } from "@tabler/icons-react";
import { Button } from "@kandev/ui/button";
import { AgentLogo } from "@/components/agent-logo";
import { GridSpinner } from "@/components/grid-spinner";
+import { PanelLoadingState } from "@/components/panel-loading-state";
import { SessionTabs, type SessionTab } from "@/components/session-tabs";
import { useAppStore } from "@/components/state-provider";
import { useToast } from "@/components/toast-provider";
@@ -177,15 +177,7 @@ function RunningSpinner() {
}
function PreviewLoadingState({ label }: { label: string }) {
- return (
-
-
- {label}
-
- );
+ return ;
}
function PreviewEmptyState() {