fix: model replay review cancellation

This commit is contained in:
lda
2026-07-09 15:34:43 +07:00 Verified
parent bc3d912537
commit 2de037c9fe
4 changed files with 41 additions and 2 deletions
@@ -109,4 +109,20 @@ describe("demoTimelineReducer", () => {
expect(restarted.mode).toBe("replay");
expect(restarted.appliedCount).toBe(0);
});
it("cancels review as a terminal non-autoplay phase", () => {
const reviewing = {
...initialDemoTimelineState,
mode: "replay" as const,
phase: "review" as const,
events: [event(0, "interrupt")],
appliedCount: 1,
autoplay: false,
};
const cancelled = demoTimelineReducer(reviewing, { type: "cancel_review" });
expect(cancelled.phase).toBe("cancelled");
expect(cancelled.autoplay).toBe(false);
expect(cancelled.appliedCount).toBe(1);
});
});
@@ -6,6 +6,7 @@ export type DemoTimelinePhase =
| "running"
| "paused"
| "review"
| "cancelled"
| "completed"
| "failed";
@@ -35,6 +36,7 @@ export type DemoTimelineAction =
| { readonly type: "pause" }
| { readonly type: "play" }
| { readonly type: "continue_review" }
| { readonly type: "cancel_review" }
| { readonly type: "fail"; readonly message: string; readonly event?: DemoEvent }
| { readonly type: "restart" }
| {
@@ -102,6 +104,10 @@ export const demoTimelineReducer = (
return state.phase === "review"
? { ...state, phase: "running", autoplay: true }
: state;
case "cancel_review":
return state.phase === "review"
? { ...state, phase: "cancelled", autoplay: false }
: state;
case "fail":
return {
...state,
@@ -395,6 +395,23 @@ describe("useDemoTimeline", () => {
expect(result.current.trace?.frames.length).toBeGreaterThan(0);
});
it("replay cancellation stops without consuming submitted branch", async () => {
vi.useFakeTimers();
const { result } = renderHook(() => useDemoTimeline(null, vi.fn()));
act(() => result.current.setMode("replay"));
act(() => result.current.start());
for (let i = 0; i < 3; i++) {
await act(async () => vi.advanceTimersByTimeAsync(900));
}
await act(async () => result.current.cancelReview("Cancelled."));
await act(async () => vi.advanceTimersByTimeAsync(1800));
expect(result.current.state.phase).toBe("cancelled");
expect(result.current.state.events[result.current.state.appliedCount - 1]?.stage).toBe("interrupt");
expect(result.current.output).toBeNull();
});
it("missingDeploymentMessage shows when live mode with null target", () => {
const { result } = renderHook(() => useDemoTimeline(null, vi.fn()));
expect(result.current.missingDeploymentMessage).toContain("Not connected");
+2 -2
View File
@@ -292,8 +292,8 @@ export const useDemoTimeline = (
comment,
outcome: "cancelled",
};
dispatch({ type: "continue_review" });
}, []);
dispatch({ type: state.mode === "live" ? "continue_review" : "cancel_review" });
}, [state.mode]);
const restart = useCallback(() => {
resetRuntime();