fix: show spinner for running review sessions (#1363)

This commit is contained in:
Carlos Florêncio
2026-06-14 17:58:52 +01:00
committed by GitHub
parent 45dd77ae17
commit 1038d1d5ea
3 changed files with 27 additions and 12 deletions
+12 -2
View File
@@ -3,7 +3,13 @@
import { useState } from "react";
import { CSS, type Transform } from "@dnd-kit/utilities";
import type { DraggableAttributes, DraggableSyntheticListeners } from "@dnd-kit/core";
import { IconAlertCircle, IconArrowsMaximize, IconDots, IconSubtask } from "@tabler/icons-react";
import {
IconAlertCircle,
IconArrowsMaximize,
IconDots,
IconLoader2,
IconSubtask,
} from "@tabler/icons-react";
import { Badge } from "@kandev/ui/badge";
import { Card, CardContent } from "@kandev/ui/card";
import { Checkbox } from "@kandev/ui/checkbox";
@@ -193,7 +199,11 @@ function KanbanCardActions({
const hasPendingClarificationRequest = useTaskPendingClarification(task.primarySessionId);
const showQuestionIcon = shouldUseQuestionTaskIcon(task.state, hasPendingClarificationRequest);
const showRunningSpinner = shouldShowTaskRunningSpinner(task.state, task.primarySessionState);
const statusIcon = getTaskStateIcon(task.state, "h-4 w-4", hasPendingClarificationRequest);
const statusIcon = showRunningSpinner ? (
<IconLoader2 className="h-4 w-4 text-blue-500 animate-spin" />
) : (
getTaskStateIcon(task.state, "h-4 w-4", hasPendingClarificationRequest)
);
const hasKnownSession =
Boolean(task.primarySessionId) || Boolean(task.sessionCount && task.sessionCount > 0);
+8 -2
View File
@@ -23,13 +23,19 @@ describe("getTaskStateIcon", () => {
});
describe("shouldShowTaskRunningSpinner", () => {
it("returns false for non-loading task states regardless of session state", () => {
it("returns false for non-loading task states without an active session", () => {
expect(shouldShowTaskRunningSpinner("COMPLETED")).toBe(false);
expect(shouldShowTaskRunningSpinner("FAILED")).toBe(false);
expect(shouldShowTaskRunningSpinner("CANCELLED")).toBe(false);
expect(shouldShowTaskRunningSpinner("REVIEW")).toBe(false);
expect(shouldShowTaskRunningSpinner("TODO")).toBe(false);
expect(shouldShowTaskRunningSpinner("REVIEW", "RUNNING")).toBe(false);
});
it("returns true when any task state has an actively running primary session", () => {
expect(shouldShowTaskRunningSpinner("REVIEW", "RUNNING")).toBe(true);
expect(shouldShowTaskRunningSpinner("COMPLETED", "RUNNING")).toBe(true);
expect(shouldShowTaskRunningSpinner("FAILED", "RUNNING")).toBe(true);
expect(shouldShowTaskRunningSpinner("CANCELLED", "RUNNING")).toBe(true);
});
it("returns true for SCHEDULING with no primary session yet", () => {
+7 -8
View File
@@ -83,11 +83,11 @@ const ACTIVE_SESSION_STATES: ReadonlySet<string> = new Set<TaskSessionState>([
]);
/**
* Returns true when the kanban card should show the spinning loader for an
* in-progress task. The task workflow state and the primary session's runtime
* state are decoupled — the workflow can keep a task in `IN_PROGRESS` after
* the agent has finished, errored, or paused waiting for input — so we gate
* the spinner on the primary session being actively running.
* Returns true when the kanban card should show the spinning loader. The task
* workflow state and the primary session's runtime state are decoupled — the
* workflow can keep a task in `IN_PROGRESS` after the agent has finished, or
* move it to `REVIEW` while the current primary session is still running — so
* an explicit primary session state takes precedence.
*
* When no primary session is attached yet (task just created / scheduling),
* we still show the spinner so users see the imminent work; otherwise we
@@ -97,9 +97,8 @@ export function shouldShowTaskRunningSpinner(
taskState?: TaskState,
primarySessionState?: string | null,
): boolean {
if (taskState !== "IN_PROGRESS" && taskState !== "SCHEDULING") return false;
if (!primarySessionState) return true;
return ACTIVE_SESSION_STATES.has(primarySessionState);
if (primarySessionState) return ACTIVE_SESSION_STATES.has(primarySessionState);
return taskState === "IN_PROGRESS" || taskState === "SCHEDULING";
}
export function shouldUsePermissionTaskIcon(hasPendingPermission = false): boolean {