diff --git a/packages/studio/src/components/editor/propertyPanelColorScopes.tsx b/packages/studio/src/components/editor/propertyPanelColorScopes.tsx
index a1ef9df45..7d2298a57 100644
--- a/packages/studio/src/components/editor/propertyPanelColorScopes.tsx
+++ b/packages/studio/src/components/editor/propertyPanelColorScopes.tsx
@@ -189,7 +189,14 @@ export function PropertyPanelColorScopes({
});
useEffect(() => {
- if (analysis && canvasRef.current) drawScope(canvasRef.current, mode, analysis);
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ if (analysis) {
+ drawScope(canvas, mode, analysis);
+ return;
+ }
+ const context = canvas.getContext("2d");
+ if (context) drawGrid(context, canvas.width, canvas.height);
}, [analysis, mode]);
return (
diff --git a/packages/studio/src/components/editor/useColorGradingScopes.test.tsx b/packages/studio/src/components/editor/useColorGradingScopes.test.tsx
index e0cce935a..61395ad22 100644
--- a/packages/studio/src/components/editor/useColorGradingScopes.test.tsx
+++ b/packages/studio/src/components/editor/useColorGradingScopes.test.tsx
@@ -27,6 +27,7 @@ function Harness({
return (
<>
{scopes.status}
+ {scopes.analysis ? "ready" : "empty"}
@@ -104,4 +105,39 @@ describe("useColorGradingScopes", () => {
expect(host.querySelector("[data-status]")?.textContent).toBe("unavailable");
act(() => root.unmount());
});
+
+ it("clears stale analysis while a refresh is pending and when it is unavailable", async () => {
+ vi.useFakeTimers();
+ installPixelDecode();
+ let resolveRefresh: (frame: ColorGradingCapturedFrame | null) => void = () => {};
+ const pendingRefresh = new Promise((resolve) => {
+ resolveRefresh = resolve;
+ });
+ const captureFrame = vi
+ .fn()
+ .mockResolvedValueOnce({
+ dataUrl: "data:image/png;base64,scope",
+ width: 1,
+ height: 1,
+ })
+ .mockReturnValueOnce(pendingRefresh);
+ const host = document.body.appendChild(document.createElement("div"));
+ const root = createRoot(host);
+ act(() => root.render());
+
+ await act(async () => vi.advanceTimersByTimeAsync(180));
+ expect(host.querySelector("[data-analysis]")?.textContent).toBe("ready");
+
+ act(() => {
+ host.querySelector("button")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ });
+ act(() => vi.advanceTimersByTime(180));
+ expect(host.querySelector("[data-status]")?.textContent).toBe("loading");
+ expect(host.querySelector("[data-analysis]")?.textContent).toBe("empty");
+
+ await act(async () => resolveRefresh(null));
+ expect(host.querySelector("[data-status]")?.textContent).toBe("unavailable");
+ expect(host.querySelector("[data-analysis]")?.textContent).toBe("empty");
+ act(() => root.unmount());
+ });
});
diff --git a/packages/studio/src/components/editor/useColorGradingScopes.ts b/packages/studio/src/components/editor/useColorGradingScopes.ts
index adf912908..dad6cd154 100644
--- a/packages/studio/src/components/editor/useColorGradingScopes.ts
+++ b/packages/studio/src/components/editor/useColorGradingScopes.ts
@@ -26,11 +26,13 @@ export function useColorGradingScopes({
useEffect(() => {
if (!open) {
+ setAnalysis(null);
setStatus("idle");
return;
}
let cancelled = false;
const timer = window.setTimeout(async () => {
+ setAnalysis(null);
setStatus("loading");
try {
const frame = await captureRef.current();