Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/web/src/components/LegacySidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
prStatusIndicator,
PrStatusTooltipContent,
terminalStatusFromRunningIds,
synchronizeTerminalPulse,
ThreadStatusLabel,
ThreadWorktreeIndicator,
useLinkedThreadPullRequest,
Expand Down Expand Up @@ -832,7 +833,8 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
}
>
<TerminalIcon
className={`size-3 ${terminalStatus.pulse ? "animate-status-pulse" : ""}`}
className={`size-3 ${terminalStatus.pulse ? "motion-safe:animate-status-pulse" : ""}`}
onAnimationStart={synchronizeTerminalPulse}
/>
</TooltipTrigger>
<TooltipPopup side="top">{terminalStatus.label}</TooltipPopup>
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ import {
prStatusIndicator,
resolveThreadPullRequestBadge,
terminalStatusFromRunningIds,
synchronizeTerminalPulse,
type TerminalStatusIndicator,
useLinkedThreadPullRequest,
} from "./ThreadStatusIndicators";
Expand Down Expand Up @@ -1518,7 +1519,10 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
data-testid={`sidebar-terminal-status-${thread.id}`}
className={cn("inline-flex shrink-0 items-center justify-center", terminalStatus.colorClass)}
>
<TerminalIcon className={cn("size-3.5", terminalStatus.pulse && "animate-status-pulse")} />
<TerminalIcon
className={cn("size-3.5", terminalStatus.pulse && "motion-safe:animate-status-pulse")}
onAnimationStart={synchronizeTerminalPulse}
/>
</span>
) : null;
// Same pen the new-thread draft rows lead with, so both kinds of unsent
Expand Down
17 changes: 17 additions & 0 deletions apps/web/src/components/ThreadStatusIndicators.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import { ProjectId, type PullRequestSummary, type VcsStatusResult } from "@t3tools/contracts";
import { describe, expect, it } from "@effect/vitest";
import type { AnimationEvent } from "react";

import {
ChangeRequestStatusIcon,
prStatusIndicator,
resolveThreadPullRequestBadgePresentation,
synchronizeTerminalPulse,
} from "./ThreadStatusIndicators";
import { newestPullRequestSummary } from "../state/pullRequests";
import { PullRequestGlyph } from "~/components/pullRequest/pullRequestIcons";

describe("synchronizeTerminalPulse", () => {
it("pins only the status pulse to the document clock", () => {
const pulse = { animationName: "status-pulse", startTime: 975 } as CSSAnimation;
const otherCss = { animationName: "other-animation", startTime: 125 } as CSSAnimation;
const otherAnimation = { startTime: 250 } as Animation;

synchronizeTerminalPulse({
animationName: "status-pulse",
currentTarget: { getAnimations: () => [pulse, otherCss, otherAnimation] },
} as AnimationEvent<SVGSVGElement>);

expect([pulse.startTime, otherCss.startTime, otherAnimation.startTime]).toEqual([0, 125, 250]);
});
});

describe("ChangeRequestStatusIcon", () => {
it.each([
["open", "open", false, PullRequestGlyph.pullRequest],
Expand Down
16 changes: 14 additions & 2 deletions apps/web/src/components/ThreadStatusIndicators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from "@t3tools/shared/threadPullRequests";
import { FolderGit2Icon, TerminalIcon } from "lucide-react";
import { useRender } from "@base-ui/react/use-render";
import { useMemo, type MouseEvent, type ReactElement } from "react";
import { useMemo, type AnimationEvent, type MouseEvent, type ReactElement } from "react";
import { cn } from "../lib/utils";
import { useEnvironment, usePrimaryEnvironmentId } from "../state/environments";
import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";
Expand Down Expand Up @@ -394,6 +394,17 @@ export function terminalStatusFromRunningIds(
};
}

/** Align newly started pulses with the document clock without a timer or frame loop. */
export function synchronizeTerminalPulse(event: AnimationEvent<SVGSVGElement>) {
if (event.animationName !== "status-pulse") return;

for (const animation of event.currentTarget.getAnimations()) {
if ("animationName" in animation && animation.animationName === "status-pulse") {
animation.startTime = 0;
}
}
}

export function ThreadWorktreeIndicator({
thread,
}: {
Expand Down Expand Up @@ -582,7 +593,8 @@ export function ThreadRowTrailingStatus({ thread }: { thread: SidebarThreadSumma
}
>
<TerminalIcon
className={`size-3 ${terminalStatus.pulse ? "animate-status-pulse" : ""}`}
className={`size-3 ${terminalStatus.pulse ? "motion-safe:animate-status-pulse" : ""}`}
onAnimationStart={synchronizeTerminalPulse}
/>
</TooltipTrigger>
<TooltipPopup side="top">{terminalStatus.label}</TooltipPopup>
Expand Down
Loading