diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts
index a56a00fc355..a3d40d2d714 100644
--- a/packages/types/src/global-settings.ts
+++ b/packages/types/src/global-settings.ts
@@ -148,6 +148,7 @@ export const globalSettingsSchema = z.object({
includeTaskHistoryInEnhance: z.boolean().optional(),
historyPreviewCollapsed: z.boolean().optional(),
reasoningBlockCollapsed: z.boolean().optional(),
+ useJumpingRooAnimation: z.boolean().optional(),
profileThresholds: z.record(z.string(), z.number()).optional(),
hasOpenedModeSelector: z.boolean().optional(),
lastModeExportPath: z.string().optional(),
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 91b86879668..cd815ce526c 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -1793,6 +1793,7 @@ export class ClineProvider
terminalCompressProgressBar,
historyPreviewCollapsed,
reasoningBlockCollapsed,
+ useJumpingRooAnimation,
cloudUserInfo,
cloudIsAuthenticated,
sharingEnabled,
@@ -1927,6 +1928,7 @@ export class ClineProvider
hasSystemPromptOverride,
historyPreviewCollapsed: historyPreviewCollapsed ?? false,
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
+ useJumpingRooAnimation: useJumpingRooAnimation ?? false,
cloudUserInfo,
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
cloudOrganizations,
@@ -2142,6 +2144,7 @@ export class ClineProvider
maxConcurrentFileReads: stateValues.maxConcurrentFileReads ?? 5,
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true,
+ useJumpingRooAnimation: stateValues.useJumpingRooAnimation ?? false,
cloudUserInfo,
cloudIsAuthenticated,
sharingEnabled,
diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts
index af5f9925c35..84c1e21da65 100644
--- a/src/core/webview/webviewMessageHandler.ts
+++ b/src/core/webview/webviewMessageHandler.ts
@@ -1621,6 +1621,10 @@ export const webviewMessageHandler = async (
await updateGlobalState("reasoningBlockCollapsed", message.bool ?? true)
// No need to call postStateToWebview here as the UI already updated optimistically
break
+ case "setUseJumpingRooAnimation":
+ await updateGlobalState("useJumpingRooAnimation", message.bool ?? false)
+ // No need to call postStateToWebview here as the UI already updated optimistically
+ break
case "toggleApiConfigPin":
if (message.text) {
const currentPinned = getGlobalState("pinnedApiConfigs") ?? {}
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index 66f389f81c1..380fea8bf6d 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -288,6 +288,7 @@ export type ExtensionState = Pick<
| "openRouterImageGenerationSelectedModel"
| "includeTaskHistoryInEnhance"
| "reasoningBlockCollapsed"
+ | "useJumpingRooAnimation"
> & {
version: string
clineMessages: ClineMessage[]
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index d43a2fce043..c11e1d40c9c 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -195,6 +195,7 @@ export interface WebviewMessage {
| "profileThresholds"
| "setHistoryPreviewCollapsed"
| "setReasoningBlockCollapsed"
+ | "setUseJumpingRooAnimation"
| "openExternal"
| "filterMarketplaceItems"
| "marketplaceButtonClicked"
diff --git a/webview-ui/src/components/chat/JumpingRoo.tsx b/webview-ui/src/components/chat/JumpingRoo.tsx
new file mode 100644
index 00000000000..1de20542b88
--- /dev/null
+++ b/webview-ui/src/components/chat/JumpingRoo.tsx
@@ -0,0 +1,101 @@
+import React from "react"
+
+export const JumpingRoo = () => (
+
+
+
+
+)
diff --git a/webview-ui/src/components/chat/ProgressIndicator.tsx b/webview-ui/src/components/chat/ProgressIndicator.tsx
index da35c196e1b..330fb86cb72 100644
--- a/webview-ui/src/components/chat/ProgressIndicator.tsx
+++ b/webview-ui/src/components/chat/ProgressIndicator.tsx
@@ -1,16 +1,26 @@
import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
+import { useExtensionState } from "@/context/ExtensionStateContext"
+import { JumpingRoo } from "./JumpingRoo"
-export const ProgressIndicator = () => (
-
-
-
+export const ProgressIndicator = () => {
+ const { useJumpingRooAnimation } = useExtensionState()
+
+ if (useJumpingRooAnimation) {
+ return
+ }
+
+ return (
+
-
-)
+ )
+}
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index 93b1b39e506..c3352186abc 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -782,6 +782,7 @@ const SettingsView = forwardRef
(({ onDone, t
{activeTab === "ui" && (
)}
diff --git a/webview-ui/src/components/settings/UISettings.tsx b/webview-ui/src/components/settings/UISettings.tsx
index 2de16e68822..e2c5d15d008 100644
--- a/webview-ui/src/components/settings/UISettings.tsx
+++ b/webview-ui/src/components/settings/UISettings.tsx
@@ -11,10 +11,16 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext"
interface UISettingsProps extends HTMLAttributes {
reasoningBlockCollapsed: boolean
+ useJumpingRooAnimation?: boolean
setCachedStateField: SetCachedStateField
}
-export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...props }: UISettingsProps) => {
+export const UISettings = ({
+ reasoningBlockCollapsed,
+ useJumpingRooAnimation,
+ setCachedStateField,
+ ...props
+}: UISettingsProps) => {
const { t } = useAppTranslation()
const handleReasoningBlockCollapsedChange = (value: boolean) => {
@@ -26,6 +32,15 @@ export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...pr
})
}
+ const handleJumpingRooAnimationChange = (value: boolean) => {
+ setCachedStateField("useJumpingRooAnimation", value)
+
+ // Track telemetry event
+ telemetryClient.capture("ui_settings_jumping_roo_changed", {
+ enabled: value,
+ })
+ }
+
return (
@@ -49,6 +64,19 @@ export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...pr
{t("settings:ui.collapseThinking.description")}
+
+ {/* Jumping Roo Animation Setting */}
+
+
handleJumpingRooAnimationChange(e.target.checked)}
+ data-testid="jumping-roo-animation-checkbox">
+ {t("settings:ui.jumpingRooAnimation.label")}
+
+
+ {t("settings:ui.jumpingRooAnimation.description")}
+
+
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index 542b2385c02..ec51dcaef7e 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -145,6 +145,8 @@ export interface ExtensionStateContextType extends ExtensionState {
setTerminalCompressProgressBar: (value: boolean) => void
setHistoryPreviewCollapsed: (value: boolean) => void
setReasoningBlockCollapsed: (value: boolean) => void
+ useJumpingRooAnimation?: boolean
+ setUseJumpingRooAnimation: (value: boolean) => void
autoCondenseContext: boolean
setAutoCondenseContext: (value: boolean) => void
autoCondenseContextPercent: number
@@ -285,6 +287,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
global: {},
})
const [includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance] = useState(true)
+ const [useJumpingRooAnimation, setUseJumpingRooAnimation] = useState(false)
const setListApiConfigMeta = useCallback(
(value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
@@ -322,6 +325,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
if ((newState as any).includeTaskHistoryInEnhance !== undefined) {
setIncludeTaskHistoryInEnhance((newState as any).includeTaskHistoryInEnhance)
}
+ // Update useJumpingRooAnimation if present in state message
+ if ((newState as any).useJumpingRooAnimation !== undefined) {
+ setUseJumpingRooAnimation((newState as any).useJumpingRooAnimation)
+ }
// Handle marketplace data if present in state message
if (newState.marketplaceItems !== undefined) {
setMarketplaceItems(newState.marketplaceItems)
@@ -537,6 +544,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })),
setReasoningBlockCollapsed: (value) =>
setState((prevState) => ({ ...prevState, reasoningBlockCollapsed: value })),
+ useJumpingRooAnimation,
+ setUseJumpingRooAnimation,
setHasOpenedModeSelector: (value) => setState((prevState) => ({ ...prevState, hasOpenedModeSelector: value })),
setAutoCondenseContext: (value) => setState((prevState) => ({ ...prevState, autoCondenseContext: value })),
setAutoCondenseContextPercent: (value) =>
diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json
index dfccc49cc4c..00af5b5406b 100644
--- a/webview-ui/src/i18n/locales/en/settings.json
+++ b/webview-ui/src/i18n/locales/en/settings.json
@@ -42,6 +42,10 @@
"collapseThinking": {
"label": "Collapse Thinking messages by default",
"description": "When enabled, thinking blocks will be collapsed by default until you interact with them"
+ },
+ "jumpingRooAnimation": {
+ "label": "Use Jumping Roo Animation",
+ "description": "Replace the standard spinner with a jumping kangaroo animation for progress indicators"
}
},
"prompts": {