diff --git a/frontend-web/src/position-roundtable/components/PositionNodeChat.tsx b/frontend-web/src/position-roundtable/components/PositionNodeChat.tsx index 600d4c2b..f566fbb1 100644 --- a/frontend-web/src/position-roundtable/components/PositionNodeChat.tsx +++ b/frontend-web/src/position-roundtable/components/PositionNodeChat.tsx @@ -757,20 +757,28 @@ export function PositionNodeChat({ } // One write_file call should open at most one preview. The parent further // guards manual close state, because it owns the page-wide singleton sandbox. - // A report is often rewritten to the same output path. The tool-call key - // (not just the path) makes that a new previewable delivery. + // A report is often rewritten to the same output path — identity must be the + // tool-call key, not the path, otherwise a second write keeps the old panel + // and the sandbox never clears / remounts for the rewrite. + const toolCallKey = newStreamingArtifact.key; + const toolCallId = toolCallKey.includes(":") + ? toolCallKey.slice(toolCallKey.lastIndexOf(":") + 1) + : undefined; const startedPreview = activeWriteFilePreviewRef.current; - const previewKey = - startedPreview?.path === newStreamingArtifact.sourcePath - ? startedPreview.previewKey - : newStreamingArtifact.key; - if (!startedPreview || startedPreview.path !== newStreamingArtifact.sourcePath) { - activeWriteFilePreviewRef.current = { - path: newStreamingArtifact.sourcePath, - previewKey, - toolCallId: startedPreview?.toolCallId, - }; - } + const isSameToolCall = + startedPreview != null && + (startedPreview.previewKey === toolCallKey || + (toolCallId != null && + (startedPreview.toolCallId === toolCallId || + startedPreview.previewKey.endsWith(`:${toolCallId}`)))); + // Same tool call may upgrade a provisional filename → real path; keep the + // open panel. A different tool call is a new delivery even on the same path. + const previewKey = isSameToolCall ? startedPreview.previewKey : toolCallKey; + activeWriteFilePreviewRef.current = { + path: newStreamingArtifact.sourcePath, + previewKey, + toolCallId: toolCallId ?? startedPreview?.toolCallId, + }; // The path takes part in the identity so that a preview opened under a // provisional name is re-published once the real path streams in. The // preview key stays the same, so the parent treats it as an update of the