fix(ui): publish long-thread metrics after StrictMode cleanup

Co-Authored-By: Paperclip <noreply@paperclip.ing>
(cherry picked from commit 3807ee526d)
This commit is contained in:
Dotta committed 2026-11-09 03:43:11 -06:00
1 parent 8ce4371cd2
commit 9375ac95cc
2 files changed
+77 -11

No files matched your search

@@ -0,0 +1,58 @@
// @vitest-environment jsdom
import { StrictMode } from "react";
import { flushSync } from "react-dom";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { IssueChatLongThreadPerf } from "./IssueChatLongThreadPerf";
vi.mock("../components/IssueChatThread", () => ({ IssueChatThread: () => null }));
const mounted: Array<{ root: Root; container: HTMLDivElement }> = [];
beforeEach(() => vi.useFakeTimers());
afterEach(() => {
for (const { root, container } of mounted.splice(0)) {
flushSync(() => root.unmount());
container.remove();
}
vi.useRealTimers();
});
function mount(strict: boolean) {
const container = document.createElement("div");
document.body.append(container);
const root = createRoot(container);
const view = { root, container };
mounted.push(view);
flushSync(() => root.render(strict ? <StrictMode><IssueChatLongThreadPerf /></StrictMode> : <IssueChatLongThreadPerf />));
return view;
}
function metric(container: HTMLElement, name: string) {
return container.querySelector(`[data-testid="perf-${name}"]`)!.textContent!;
}
it.each([false, true])("publishes finite actual fixture metrics with StrictMode=%s", strict => {
const { container } = mount(strict);
flushSync(() => vi.runOnlyPendingTimers());
for (const name of ["mount-duration", "latest-duration", "max-duration", "total-duration"])
expect(metric(container, name)).toMatch(/^\d+(?:\.\d+)? ms$/);
expect(Number(metric(container, "commit-count"))).toBeGreaterThanOrEqual(1);
expect(vi.getTimerCount()).toBe(0);
});
it("cancels unpublished metrics on unmount and allows a fresh StrictMode mount", () => {
const first = mount(true);
expect(vi.getTimerCount()).toBe(1);
flushSync(() => first.root.unmount());
first.container.remove();
mounted.splice(mounted.indexOf(first), 1);
expect(vi.getTimerCount()).toBe(0);
flushSync(() => vi.runOnlyPendingTimers());
expect(first.container.textContent).toBe("");
const second = mount(true);
flushSync(() => vi.runOnlyPendingTimers());
expect(metric(second.container, "mount-duration")).toMatch(/^\d+(?:\.\d+)? ms$/);
expect(vi.getTimerCount()).toBe(0);
});
+19 -11
View File
@@ -1,4 +1,4 @@
import { Profiler, useEffect, useLayoutEffect, useMemo, useRef, useState, type ProfilerOnRenderCallback } from "react";
import { Profiler, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type ProfilerOnRenderCallback } from "react";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { IssueChatThread } from "../components/IssueChatThread";
@@ -63,12 +63,25 @@ export function IssueChatLongThreadPerf() {
[],
);
useEffect(() => () => {
if (publishTimerRef.current !== null) {
window.clearTimeout(publishTimerRef.current);
}
const schedulePublication = useCallback(() => {
if (publishedRef.current || publishTimerRef.current !== null) return;
publishTimerRef.current = window.setTimeout(() => {
publishTimerRef.current = null;
publishedRef.current = true;
setMetrics(metricsRef.current);
}, 0);
}, []);
useEffect(() => {
if (metricsRef.current.commitCount > 0) schedulePublication();
return () => {
if (publishTimerRef.current !== null) {
window.clearTimeout(publishTimerRef.current);
publishTimerRef.current = null;
}
};
}, [schedulePublication]);
useLayoutEffect(() => {
if (publishedRef.current || metricsRef.current.commitCount > 0) return;
const mountDuration = performance.now() - renderStartedAtRef.current;
@@ -96,12 +109,7 @@ export function IssueChatLongThreadPerf() {
totalActualDuration: current.totalActualDuration + actualDuration,
};
if (publishedRef.current || publishTimerRef.current !== null) return;
publishTimerRef.current = window.setTimeout(() => {
publishTimerRef.current = null;
publishedRef.current = true;
setMetrics(metricsRef.current);
}, 0);
schedulePublication();
};
return (