Skip to content

Commit d2bc79e

Browse files
authored
chore(code): sync spinner animations (#1343)
## Problem surely i have better things to do, but the spinners being out-of-sync was bothering me a bit 😛 <!-- Who is this for and what problem does it solve? --> <!-- Closes #ISSUE_ID --> ## Changes - [Screen Recording 2026-03-26 at 8.51.18 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/bf481fbe-f77f-40e8-9bae-fda979767391.mov" />](https://app.graphite.com/user-attachments/video/bf481fbe-f77f-40e8-9bae-fda979767391.mov) syncs the spinner animations - fixes cloud run loader (spinner was overlapping the cloud icon) <!-- What did you change and why? --> <!-- If there are frontend changes, include screenshots. --> ## How did you test this? manually <!-- Describe what you tested -- manual steps, automated tests, or both. --> <!-- If you're an agent, only list tests you actually ran. -->
1 parent 9adc809 commit d2bc79e

File tree

2 files changed

+34
-16
lines changed

2 files changed

+34
-16
lines changed

apps/code/src/renderer/components/DotsCircleSpinner.tsx

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,38 @@
1-
import { useEffect, useState } from "react";
1+
import { useSyncExternalStore } from "react";
22

33
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
44
const INTERVAL = 80;
55

6+
let globalFrameIndex = 0;
7+
let subscriberCount = 0;
8+
let globalTimer: ReturnType<typeof setInterval> | null = null;
9+
const listeners = new Set<() => void>();
10+
11+
function subscribe(callback: () => void) {
12+
listeners.add(callback);
13+
subscriberCount++;
14+
if (subscriberCount === 1) {
15+
globalTimer = setInterval(() => {
16+
globalFrameIndex = (globalFrameIndex + 1) % FRAMES.length;
17+
for (const listener of listeners) {
18+
listener();
19+
}
20+
}, INTERVAL);
21+
}
22+
return () => {
23+
listeners.delete(callback);
24+
subscriberCount--;
25+
if (subscriberCount === 0 && globalTimer) {
26+
clearInterval(globalTimer);
27+
globalTimer = null;
28+
}
29+
};
30+
}
31+
32+
function getSnapshot() {
33+
return globalFrameIndex;
34+
}
35+
636
interface DotsCircleSpinnerProps {
737
size?: number;
838
className?: string;
@@ -12,15 +42,7 @@ export function DotsCircleSpinner({
1242
size = 12,
1343
className,
1444
}: DotsCircleSpinnerProps) {
15-
const [frameIndex, setFrameIndex] = useState(0);
16-
17-
useEffect(() => {
18-
const timer = setInterval(() => {
19-
setFrameIndex((prev) => (prev + 1) % FRAMES.length);
20-
}, INTERVAL);
21-
22-
return () => clearInterval(timer);
23-
}, []);
45+
const frameIndex = useSyncExternalStore(subscribe, getSnapshot);
2446

2547
return (
2648
<span

apps/code/src/renderer/features/sidebar/components/items/TaskItem.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,8 @@ function CloudStatusIcon({
136136
if (taskRunStatus === "started" || taskRunStatus === "in_progress") {
137137
return (
138138
<Tooltip content="Cloud (running)" side="right">
139-
<span className="relative flex items-center justify-center">
140-
<CloudIcon size={ICON_SIZE} className="text-accent-11" />
141-
<DotsCircleSpinner
142-
size={8}
143-
className="-right-0.5 -bottom-0.5 absolute text-accent-11"
144-
/>
139+
<span className="flex items-center justify-center">
140+
<CloudIcon size={ICON_SIZE} className="ph-pulse" />
145141
</span>
146142
</Tooltip>
147143
);

0 commit comments

Comments
 (0)