Skip to content

Commit 3c9c997

Browse files
juliusmarmingecodex
andcommitted
Revert "Fix stale send spinner after completed turns (#1700)"
This reverts commit 48481aa. Co-authored-by: codex <codex@users.noreply.github.com>
1 parent 752f96e commit 3c9c997

3 files changed

Lines changed: 16 additions & 118 deletions

File tree

apps/web/src/components/ChatView.tsx

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ import {
5252
deriveWorkLogEntries,
5353
hasActionableProposedPlan,
5454
hasToolActivityForTurn,
55-
isSessionActivelyRunningTurn,
5655
isLatestTurnSettled,
5756
formatElapsed,
5857
} from "../session-logic";
@@ -1121,11 +1120,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
11211120
activePendingUserInput: activePendingUserInput?.requestId ?? null,
11221121
threadError: activeThread?.error,
11231122
});
1124-
const isTurnRunning = isSessionActivelyRunningTurn(
1125-
activeLatestTurn,
1126-
activeThread?.session ?? null,
1127-
);
1128-
const isWorking = isTurnRunning || isSendBusy || isConnecting || isRevertingCheckpoint;
1123+
const isWorking = phase === "running" || isSendBusy || isConnecting || isRevertingCheckpoint;
11291124
const nowIso = new Date(nowTick).toISOString();
11301125
const activeWorkStartedAt = deriveActiveWorkStartedAt(
11311126
activeLatestTurn,
@@ -1142,7 +1137,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
11421137
if (activePendingProgress) {
11431138
return `pending:${activePendingProgress.questionIndex}:${activePendingProgress.isLastQuestion}:${activePendingIsResponding}`;
11441139
}
1145-
if (isTurnRunning) {
1140+
if (phase === "running") {
11461141
return "running";
11471142
}
11481143
if (showPlanFollowUpPrompt) {
@@ -1156,7 +1151,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
11561151
isConnecting,
11571152
isPreparingWorktree,
11581153
isSendBusy,
1159-
isTurnRunning,
1154+
phase,
11601155
prompt,
11611156
showPlanFollowUpPrompt,
11621157
]);
@@ -2316,10 +2311,10 @@ export default function ChatView({ threadId }: ChatViewProps) {
23162311
scheduleStickToBottom();
23172312
}, [messageCount, scheduleStickToBottom]);
23182313
useEffect(() => {
2319-
if (!isTurnRunning) return;
2314+
if (phase !== "running") return;
23202315
if (!shouldAutoScrollRef.current) return;
23212316
scheduleStickToBottom();
2322-
}, [isTurnRunning, scheduleStickToBottom, timelineEntries]);
2317+
}, [phase, scheduleStickToBottom, timelineEntries]);
23232318

23242319
useEffect(() => {
23252320
setExpandedWorkGroups({});
@@ -2538,14 +2533,14 @@ export default function ChatView({ threadId }: ChatViewProps) {
25382533
: "local";
25392534

25402535
useEffect(() => {
2541-
if (!isTurnRunning) return;
2536+
if (phase !== "running") return;
25422537
const timer = window.setInterval(() => {
25432538
setNowTick(Date.now());
25442539
}, 1000);
25452540
return () => {
25462541
window.clearInterval(timer);
25472542
};
2548-
}, [isTurnRunning]);
2543+
}, [phase]);
25492544

25502545
useEffect(() => {
25512546
if (!activeThreadId) return;
@@ -2765,7 +2760,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
27652760
const api = readNativeApi();
27662761
if (!api || !activeThread || isRevertingCheckpoint) return;
27672762

2768-
if (isTurnRunning || isSendBusy || isConnecting) {
2763+
if (phase === "running" || isSendBusy || isConnecting) {
27692764
setThreadError(activeThread.id, "Interrupt the current turn before reverting checkpoints.");
27702765
return;
27712766
}
@@ -2798,7 +2793,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
27982793
}
27992794
setIsRevertingCheckpoint(false);
28002795
},
2801-
[activeThread, isConnecting, isRevertingCheckpoint, isSendBusy, isTurnRunning, setThreadError],
2796+
[activeThread, isConnecting, isRevertingCheckpoint, isSendBusy, phase, setThreadError],
28022797
);
28032798

28042799
const onSend = async (e?: { preventDefault: () => void }) => {
@@ -4387,7 +4382,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
43874382
}
43884383
: null
43894384
}
4390-
isRunning={isTurnRunning}
4385+
isRunning={phase === "running"}
43914386
showPlanFollowUpPrompt={
43924387
pendingUserInputs.length === 0 && showPlanFollowUpPrompt
43934388
}

apps/web/src/session-logic.test.ts

Lines changed: 3 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
findSidebarProposedPlan,
2121
hasActionableProposedPlan,
2222
hasToolActivityForTurn,
23-
isSessionActivelyRunningTurn,
2423
isLatestTurnSettled,
2524
} from "./session-logic";
2625

@@ -1073,30 +1072,15 @@ describe("isLatestTurnSettled", () => {
10731072
} as const;
10741073

10751074
it("returns false while the same turn is still active in a running session", () => {
1076-
expect(
1077-
isLatestTurnSettled(
1078-
{
1079-
...latestTurn,
1080-
completedAt: null,
1081-
},
1082-
{
1083-
orchestrationStatus: "running",
1084-
activeTurnId: TurnId.makeUnsafe("turn-1"),
1085-
},
1086-
),
1087-
).toBe(false);
1088-
});
1089-
1090-
it("returns true when the turn completed but the session status stayed stale-running", () => {
10911075
expect(
10921076
isLatestTurnSettled(latestTurn, {
10931077
orchestrationStatus: "running",
10941078
activeTurnId: TurnId.makeUnsafe("turn-1"),
10951079
}),
1096-
).toBe(true);
1080+
).toBe(false);
10971081
});
10981082

1099-
it("returns false while a different turn is still running", () => {
1083+
it("returns false while any turn is running to avoid stale latest-turn banners", () => {
11001084
expect(
11011085
isLatestTurnSettled(latestTurn, {
11021086
orchestrationStatus: "running",
@@ -1128,56 +1112,6 @@ describe("isLatestTurnSettled", () => {
11281112
});
11291113
});
11301114

1131-
describe("isSessionActivelyRunningTurn", () => {
1132-
const completedTurn = {
1133-
turnId: TurnId.makeUnsafe("turn-1"),
1134-
startedAt: "2026-02-27T21:10:00.000Z",
1135-
completedAt: "2026-02-27T21:10:06.000Z",
1136-
} as const;
1137-
1138-
it("returns true when the current turn has not completed yet", () => {
1139-
expect(
1140-
isSessionActivelyRunningTurn(
1141-
{
1142-
...completedTurn,
1143-
completedAt: null,
1144-
},
1145-
{
1146-
orchestrationStatus: "running",
1147-
activeTurnId: TurnId.makeUnsafe("turn-1"),
1148-
},
1149-
),
1150-
).toBe(true);
1151-
});
1152-
1153-
it("returns false when the same turn already completed and only the session is stale", () => {
1154-
expect(
1155-
isSessionActivelyRunningTurn(completedTurn, {
1156-
orchestrationStatus: "running",
1157-
activeTurnId: TurnId.makeUnsafe("turn-1"),
1158-
}),
1159-
).toBe(false);
1160-
});
1161-
1162-
it("returns true when a different turn is still active", () => {
1163-
expect(
1164-
isSessionActivelyRunningTurn(completedTurn, {
1165-
orchestrationStatus: "running",
1166-
activeTurnId: TurnId.makeUnsafe("turn-2"),
1167-
}),
1168-
).toBe(true);
1169-
});
1170-
1171-
it("returns false when the session is not running", () => {
1172-
expect(
1173-
isSessionActivelyRunningTurn(completedTurn, {
1174-
orchestrationStatus: "ready",
1175-
activeTurnId: undefined,
1176-
}),
1177-
).toBe(false);
1178-
});
1179-
});
1180-
11811115
describe("deriveActiveWorkStartedAt", () => {
11821116
const latestTurn = {
11831117
turnId: TurnId.makeUnsafe("turn-1"),
@@ -1186,22 +1120,6 @@ describe("deriveActiveWorkStartedAt", () => {
11861120
} as const;
11871121

11881122
it("prefers the in-flight turn start when the latest turn is not settled", () => {
1189-
expect(
1190-
deriveActiveWorkStartedAt(
1191-
{
1192-
...latestTurn,
1193-
completedAt: null,
1194-
},
1195-
{
1196-
orchestrationStatus: "running",
1197-
activeTurnId: TurnId.makeUnsafe("turn-1"),
1198-
},
1199-
"2026-02-27T21:11:00.000Z",
1200-
),
1201-
).toBe("2026-02-27T21:10:00.000Z");
1202-
});
1203-
1204-
it("falls back to sendStartedAt when only the session status is stale-running", () => {
12051123
expect(
12061124
deriveActiveWorkStartedAt(
12071125
latestTurn,
@@ -1211,7 +1129,7 @@ describe("deriveActiveWorkStartedAt", () => {
12111129
},
12121130
"2026-02-27T21:11:00.000Z",
12131131
),
1214-
).toBe("2026-02-27T21:11:00.000Z");
1132+
).toBe("2026-02-27T21:10:00.000Z");
12151133
});
12161134

12171135
it("falls back to sendStartedAt once the latest turn is settled", () => {

apps/web/src/session-logic.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -128,30 +128,15 @@ export function formatElapsed(startIso: string, endIso: string | undefined): str
128128
type LatestTurnTiming = Pick<OrchestrationLatestTurn, "turnId" | "startedAt" | "completedAt">;
129129
type SessionActivityState = Pick<ThreadSession, "orchestrationStatus" | "activeTurnId">;
130130

131-
export function isSessionActivelyRunningTurn(
132-
latestTurn: LatestTurnTiming | null,
133-
session: SessionActivityState | null,
134-
): boolean {
135-
if (!session || session.orchestrationStatus !== "running") return false;
136-
if (!latestTurn) return true;
137-
138-
const activeTurnId = session.activeTurnId;
139-
if (activeTurnId === undefined) {
140-
return latestTurn.completedAt === null;
141-
}
142-
if (latestTurn.turnId !== activeTurnId) {
143-
return true;
144-
}
145-
return latestTurn.completedAt === null;
146-
}
147-
148131
export function isLatestTurnSettled(
149132
latestTurn: LatestTurnTiming | null,
150133
session: SessionActivityState | null,
151134
): boolean {
152135
if (!latestTurn?.startedAt) return false;
153136
if (!latestTurn.completedAt) return false;
154-
return !isSessionActivelyRunningTurn(latestTurn, session);
137+
if (!session) return true;
138+
if (session.orchestrationStatus === "running") return false;
139+
return true;
155140
}
156141

157142
export function deriveActiveWorkStartedAt(

0 commit comments

Comments
 (0)