diff --git a/studio/frontend/src/features/chat/artifacts/artifact-card.tsx b/studio/frontend/src/features/chat/artifacts/artifact-card.tsx index ee8c26abf1..0345dc6e2a 100644 --- a/studio/frontend/src/features/chat/artifacts/artifact-card.tsx +++ b/studio/frontend/src/features/chat/artifacts/artifact-card.tsx @@ -8,7 +8,7 @@ import { cn } from "@/lib/utils"; import { useAuiState } from "@assistant-ui/react"; import { LayoutTwoColumnIcon as Layout2ColumnIcon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; -import { useLayoutEffect, useMemo } from "react"; +import { useLayoutEffect, useMemo, useRef } from "react"; import { useChatRuntimeStore } from "../stores/chat-runtime-store"; import type { ArtifactViewMode } from "./html-frame"; import { @@ -83,15 +83,18 @@ export function ArtifactCard({ ], ); const surface = artifactThreadId ? "panel" : "overlay"; + // Once per mount, so a view-change cleanup can't re-trigger a stale open. + const autoOpenAttemptedRef = useRef(false); useLayoutEffect(() => { if (selectedArtifactId === artifact.id) { updateArtifact(artifact); } - if (!autoOpen) { + if (!autoOpen || autoOpenAttemptedRef.current) { return; } + autoOpenAttemptedRef.current = true; if (hasAutoOpenedArtifact(artifact.id)) { return; } diff --git a/studio/frontend/src/features/chat/chat-page.tsx b/studio/frontend/src/features/chat/chat-page.tsx index 380ce0e0ab..ec0ad977bf 100644 --- a/studio/frontend/src/features/chat/chat-page.tsx +++ b/studio/frontend/src/features/chat/chat-page.tsx @@ -247,13 +247,13 @@ const SingleContent = memo(function SingleContent({ useState(false); const [isArtifactSurfaceVisible, setIsArtifactSurfaceVisible] = useState(false); + // Without a URL threadId the artifact must belong to the active thread. const showArtifactPanel = Boolean( artifact && artifactSurface === "panel" && (threadId ? !artifact.threadId || artifact.threadId === threadId - : Boolean(newThreadNonce) || - Boolean(artifact.threadId && artifact.threadId === activeThreadId)), + : Boolean(artifact.threadId && artifact.threadId === activeThreadId)), ); const artifactLayoutActive = showArtifactPanel || isArtifactPanelLayoutActive; @@ -1771,10 +1771,8 @@ export function ChatPage({ useEffect(() => { if (view.mode !== "single") return; - if (view.threadId || view.newThreadNonce || !selectedArtifact) return; - // view excludes __LOCALID_ threads (they fall through to mode:"single" - // with no threadId/nonce). Don't close a canvas whose thread is the - // active local thread. + if (view.threadId || !selectedArtifact) return; + // Close any canvas that doesn't belong to the active thread. if ( selectedArtifact.threadId && selectedArtifact.threadId === activeThreadId