diff --git a/apps/desktop/src/components/editor/AiAssistant.vue b/apps/desktop/src/components/editor/AiAssistant.vue
index 225983f304..9b651a508d 100644
--- a/apps/desktop/src/components/editor/AiAssistant.vue
+++ b/apps/desktop/src/components/editor/AiAssistant.vue
@@ -5192,13 +5192,26 @@ async function openExternalUrl(url: string) {
+
diff --git a/apps/desktop/src/components/editor/__tests__/AiAssistant.scrollRing.spec.ts b/apps/desktop/src/components/editor/__tests__/AiAssistant.scrollRing.spec.ts
new file mode 100644
index 0000000000..485a4ded05
--- /dev/null
+++ b/apps/desktop/src/components/editor/__tests__/AiAssistant.scrollRing.spec.ts
@@ -0,0 +1,70 @@
+import { readFileSync } from "node:fs";
+import { describe, expect, it } from "vitest";
+
+// Regression coverage for the AI status ring on the scroll-to-bottom button:
+//
+// While a run is active for the visible conversation (`isGenerating`), the
+// scroll-to-bottom button must stay visible even when the user is already at
+// the bottom, and must render a decorative rotating arc ring (an "assistant is
+// working" marker, Si-Octo-style). The ring is aria-hidden decoration driven
+// purely by `isGenerating` — the scroll-driven hysteresis (`showScrollToBottom`
+// + the MESSAGE_SCROLL_BUTTON_* thresholds) must stay the single source of the
+// scroll path, and clicking must still force-scroll to the bottom.
+//
+// AiAssistant.vue is a ~6000-line SFC the suite never mounts (see
+// .trellis/spec/app-tests/frontend/component-guidelines.md): these assertions
+// pin the template wiring as text, matching the house source-assertion style.
+const source = readFileSync(new URL("../AiAssistant.vue", import.meta.url), "utf8");
+
+const buttonMatch = source.match(/