Merge pull request #962 from pikasTech/fix/issue955-stable-replay-timer

fix: keep compact trace replay timers stable
This commit is contained in:
Lyon
2026-06-06 01:05:45 +08:00
committed by GitHub
2 changed files with 23 additions and 5 deletions
@@ -52,10 +52,23 @@ test("conversation panel does not consume replay opportunity before timer fires"
const effectStart = source.indexOf("const replayRequestedTraceRef = useRef<string | null>(null);");
const timerStart = source.indexOf("const timer = window.setTimeout", effectStart);
const markRequested = source.indexOf("replayRequestedTraceRef.current = traceId", effectStart);
const replayCall = source.indexOf("onReplayTrace();", effectStart);
const replayCall = source.indexOf("replayHandlerRef.current();", effectStart);
assert.ok(effectStart > 0, "replay guard ref exists");
assert.ok(timerStart > effectStart, "auto replay uses delayed timer");
assert.ok(markRequested > timerStart, "route changes that cancel the timer must not mark replay as already requested");
assert.ok(replayCall > markRequested, "replay is marked requested only immediately before dispatching replay");
});
test("conversation panel keeps replay timer stable across parent rerenders", async () => {
const source = await Bun.file(new URL("./ConversationPanel.tsx", import.meta.url)).text();
const handlerRef = source.indexOf("const replayHandlerRef = useRef(onReplayTrace);");
const effectStart = source.indexOf("const timer = window.setTimeout", handlerRef);
const effectEnd = source.indexOf("if (message.role === \"agent\")", effectStart);
const effectSource = source.slice(effectStart, effectEnd);
assert.ok(handlerRef > 0, "onReplayTrace is kept behind a stable ref");
assert.match(effectSource, /replayHandlerRef\.current\(\);/u);
assert.match(effectSource, /\}, \[shouldReplayTrace, traceId\]\);/u);
assert.doesNotMatch(effectSource, /\bonReplayTrace\b/u);
});
@@ -131,16 +131,21 @@ const HISTORICAL_TRACE_REPLAY_DELAY_MS = 4500;
function MessageCard({ message, firstScreenHistory, codeAgentTimeoutMs, onCancel, onRetry, onReplayTrace }: { message: ChatMessage; firstScreenHistory: boolean; codeAgentTimeoutMs: number; onCancel(): void; onRetry(): void; onReplayTrace(): void }): ReactElement {
const traceKey = `hwlab.workbench.trace-open.${message.traceId ?? message.id}`;
const replayRequestedTraceRef = useRef<string | null>(null);
const replayHandlerRef = useRef(onReplayTrace);
useEffect(() => {
replayHandlerRef.current = onReplayTrace;
}, [onReplayTrace]);
const traceId = message.traceId ?? message.runnerTrace?.traceId ?? null;
const shouldReplayTrace = shouldAutoReplayTrace(message);
useEffect(() => {
const traceId = message.traceId ?? message.runnerTrace?.traceId ?? null;
if (traceId && replayRequestedTraceRef.current === traceId) return;
if (!shouldAutoReplayTrace(message)) return;
if (!shouldReplayTrace) return;
const timer = window.setTimeout(() => {
replayRequestedTraceRef.current = traceId;
onReplayTrace();
replayHandlerRef.current();
}, HISTORICAL_TRACE_REPLAY_DELAY_MS);
return () => window.clearTimeout(timer);
}, [message, onReplayTrace]);
}, [shouldReplayTrace, traceId]);
if (message.role === "agent") {
const hasFinalResponse = message.status !== "running" && message.text.trim().length > 0;
return (