diff --git a/apps/web/src/components/ChatMarkdown.tsx b/apps/web/src/components/ChatMarkdown.tsx index 4a1f616aed3b..7e4390b9ae7c 100644 --- a/apps/web/src/components/ChatMarkdown.tsx +++ b/apps/web/src/components/ChatMarkdown.tsx @@ -2304,12 +2304,23 @@ function useChatMarkdownState({ githubMedia = false, }: ChatMarkdownProps) { const { resolvedTheme } = useTheme(); - const [localMediaPreview, setLocalMediaPreview] = useState(null); + const [localMediaPreviewDialog, setLocalMediaPreviewDialog] = useState<{ + open: boolean; + preview: ExpandedImagePreview; + generation: number; + } | null>(null); const markdownRef = useRef(null); - const expandMedia = onImageExpand ?? setLocalMediaPreview; + const openLocalMediaPreview = useCallback((preview: ExpandedImagePreview) => { + setLocalMediaPreviewDialog((current) => ({ + open: true, + preview, + generation: (current?.generation ?? 0) + 1, + })); + }, []); + const expandMedia = onImageExpand ?? openLocalMediaPreview; const mediaRequestId = useRef(0); useEffect(() => { - setLocalMediaPreview(null); + setLocalMediaPreviewDialog(null); return () => { mediaRequestId.current += 1; }; @@ -2755,8 +2766,8 @@ function useChatMarkdownState({ handleCopy, markdownRef, markdownUrlTransform, - localMediaPreview, - setLocalMediaPreview, + localMediaPreviewDialog, + setLocalMediaPreviewDialog, }; } @@ -3328,8 +3339,8 @@ function ChatMarkdown({ handleCopy, markdownRef, markdownUrlTransform, - localMediaPreview, - setLocalMediaPreview, + localMediaPreviewDialog, + setLocalMediaPreviewDialog, } = useChatMarkdownState({ text, ...props }); const incrementalParsing = props.isStreaming === true && @@ -3369,10 +3380,21 @@ function ChatMarkdown({ {text} - {localMediaPreview ? ( + {localMediaPreviewDialog ? ( setLocalMediaPreview(null)} + key={localMediaPreviewDialog.generation} + preview={localMediaPreviewDialog.preview} + open={localMediaPreviewDialog.open} + onOpenChange={(open) => { + if (!open) { + setLocalMediaPreviewDialog((current) => + current ? { ...current, open: false } : current, + ); + } + }} + onOpenChangeComplete={(open) => { + if (!open) setLocalMediaPreviewDialog(null); + }} /> ) : null} diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts index 528024de4393..fa54f0122d8e 100644 --- a/apps/web/src/components/ChatView.logic.ts +++ b/apps/web/src/components/ChatView.logic.ts @@ -791,6 +791,7 @@ export function collectUserMessageBlobPreviewUrls(message: ChatMessage): string[ } export interface PullRequestDialogState { + open: boolean; initialReference: string | null; key: number; } diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index db13c8f21c80..017c06e50357 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -377,7 +377,7 @@ import { resolveTimelineIsAtEnd, worktreeSetupAgentStarted } from "./chat/Messag import { resolveComposerTimelineInset, resolveScrollToEndClearance } from "./composerFooterLayout"; import { ChatHeader } from "./chat/ChatHeader"; import { PanelLayoutControls, RightPanelMaximizeControl } from "./chat/PanelLayoutControls"; -import { expandedImageKey, type ExpandedImagePreview } from "./chat/ExpandedImagePreview"; +import type { ExpandedImagePreview } from "./chat/ExpandedImagePreview"; import { NoActiveThreadState } from "./NoActiveThreadState"; import { WorkspacePageHeader } from "./WorkspacePageHeader"; import { @@ -1688,13 +1688,17 @@ export default function ChatView(props: ChatViewProps) { ); const [isWorkspaceFileDragActive, setIsWorkspaceFileDragActive] = useState(false); const [showScrollToBottom, setShowScrollToBottom] = useState(false); - const [expandedImage, setExpandedImage] = useState(null); + const [expandedImageDialog, setExpandedImageDialog] = useState<{ + readonly open: boolean; + readonly preview: ExpandedImagePreview; + readonly generation: number; + } | null>(null); useEffect(() => { - const item = expandedImage?.images[expandedImage.index]; + const item = expandedImageDialog?.preview.images[expandedImageDialog.preview.index]; if (item?.type !== "video" || item.src === null || !item.src.startsWith("blob:")) return; const src = item.src; return () => revokeBlobPreviewUrl(src); - }, [expandedImage]); + }, [expandedImageDialog?.preview]); const [optimisticUserMessages, setOptimisticUserMessages] = useState([]); // Last live snapshot from the setup stream. The server drops a finished // snapshot after a grace period and emits null; holding it here bridges the @@ -2072,7 +2076,7 @@ export default function ChatView(props: ChatViewProps) { [activeRightPanelSurface, rightPanelState.surfaces], ); const rightPanelPresence = usePanelPresence( - rightPanelOpen && activeThreadRef !== null, + (rightPanelOpen || shouldUseRightPanelSheet) && activeThreadRef !== null, rightPanelPresenceValue, panelAnimationsActive, activeThreadKey, @@ -2492,6 +2496,7 @@ export default function ChatView(props: ChatViewProps) { return; } setPullRequestDialogState({ + open: true, initialReference: reference ?? null, key: Date.now(), }); @@ -2500,7 +2505,7 @@ export default function ChatView(props: ChatViewProps) { ); const closePullRequestDialog = useCallback(() => { - setPullRequestDialogState(null); + setPullRequestDialogState((current) => (current ? { ...current, open: false } : current)); }, []); const openOrReuseProjectDraftThread = useCallback( @@ -5814,11 +5819,11 @@ export default function ChatView(props: ChatViewProps) { return []; }); resetLocalDispatch(); - setExpandedImage(null); + setExpandedImageDialog(null); }, [draftId, resetLocalDispatch, threadId]); const closeExpandedImage = useCallback(() => { - setExpandedImage(null); + setExpandedImageDialog((current) => (current ? { ...current, open: false } : current)); }, []); const activeWorktreePath = activeThread?.worktreePath ?? null; @@ -9411,7 +9416,11 @@ export default function ChatView(props: ChatViewProps) { }; const onExpandTimelineImage = useCallback((preview: ExpandedImagePreview) => { - setExpandedImage(preview); + setExpandedImageDialog((current) => ({ + open: true, + preview, + generation: (current?.generation ?? 0) + 1, + })); }, []); const onOpenTurnDiff = useCallback( (turnId: TurnId, filePath?: string) => { @@ -10217,7 +10226,7 @@ export default function ChatView(props: ChatViewProps) { {pullRequestDialogState ? ( { + if (!open) setPullRequestDialogState(null); + }} onPrepared={handlePreparedPullRequestThread} /> ) : null} @@ -10309,6 +10321,7 @@ export default function ChatView(props: ChatViewProps) { > - {expandedImage && ( + {expandedImageDialog ? ( { + if (!open) closeExpandedImage(); + }} + onOpenChangeComplete={(open) => { + if (!open) setExpandedImageDialog(null); + }} /> - )} + ) : null} ); } diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index 95f2df5928ae..090a8719d742 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -489,13 +489,23 @@ export function CommandPalette({ children }: { children: ReactNode }) { mode: "command", openIntent: null, }); - const setOpen = useCallback((open: boolean) => dispatch({ _tag: "SetOpen", open }), []); - const toggleMode = useCallback( - (mode: SearchOverlayMode) => dispatch({ _tag: "ToggleMode", mode }), - [], - ); - const openAddProject = useCallback(() => dispatch({ _tag: "OpenAddProject" }), []); - const openNewThreadIn = useCallback(() => dispatch({ _tag: "OpenNewThreadIn" }), []); + const [dialogContentMounted, setDialogContentMounted] = useState(false); + const setOpen = useCallback((open: boolean) => { + if (open) setDialogContentMounted(true); + dispatch({ _tag: "SetOpen", open }); + }, []); + const toggleMode = useCallback((mode: SearchOverlayMode) => { + setDialogContentMounted(true); + dispatch({ _tag: "ToggleMode", mode }); + }, []); + const openAddProject = useCallback(() => { + setDialogContentMounted(true); + dispatch({ _tag: "OpenAddProject" }); + }, []); + const openNewThreadIn = useCallback(() => { + setDialogContentMounted(true); + dispatch({ _tag: "OpenNewThreadIn" }); + }, []); const clearOpenIntent = useCallback(() => dispatch({ _tag: "ClearOpenIntent" }), []); const keybindings = useAtomValue(primaryServerKeybindingsAtom); const { theme, themeHalves, resolvedTheme, appearanceMode, setAppearanceMode } = useTheme(); @@ -563,6 +573,7 @@ export function CommandPalette({ children }: { children: ReactNode }) { event.preventDefault(); event.stopPropagation(); if (event.repeat) return; + setDialogContentMounted(true); dispatch({ _tag: "OpenChangeTheme" }); return; } @@ -615,6 +626,7 @@ export function CommandPalette({ children }: { children: ReactNode }) { } else if (detail.open === "add-project") { openAddProject(); } else if (detail.query !== undefined) { + setDialogContentMounted(true); dispatch({ _tag: "OpenSearch", query: detail.query, @@ -639,18 +651,23 @@ export function CommandPalette({ children }: { children: ReactNode }) { } setOpen(open); }} + onOpenChangeComplete={(open) => { + if (!open) setDialogContentMounted(false); + }} > {/* Block background focus calls for the entire time the palette is open. */}
{children}
- + {dialogContentMounted ? ( + + ) : null} ); diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index 2c26f1e69b1d..f5e965ab515f 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -582,11 +582,8 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { const handleOpenChange = useCallback( (open: boolean) => { props.onOpenChange(open); - if (!open) { - resetState(); - } }, - [props, resetState], + [props], ); const openSourceControlSettings = useCallback(() => { @@ -595,7 +592,13 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { }, [handleOpenChange, navigate]); return ( - + { + if (!open) resetState(); + }} + > void; + onOpenChangeComplete: (open: boolean) => void; onPrepared: (input: { branch: string; worktreePath: string | null }) => Promise | void; } @@ -43,6 +44,7 @@ export function PullRequestThreadDialog({ cwd, initialReference, onOpenChange, + onOpenChangeComplete, onPrepared, }: PullRequestThreadDialogProps) { const referenceInputRef = useRef(null); @@ -193,6 +195,7 @@ export function PullRequestThreadDialog({ onOpenChange(nextOpen); } }} + onOpenChangeComplete={onOpenChangeComplete} > diff --git a/apps/web/src/components/RightPanelTabs.tsx b/apps/web/src/components/RightPanelTabs.tsx index 001342e48262..4a12d8140b4b 100644 --- a/apps/web/src/components/RightPanelTabs.tsx +++ b/apps/web/src/components/RightPanelTabs.tsx @@ -83,6 +83,8 @@ interface RightPanelTabsProps { mode: PreviewPanelMode; maximized?: boolean; open?: boolean; + /** False while a kept-mounted panel is closed, so its launcher letters stay inert. */ + launcherShortcutsEnabled?: boolean; /** Forwarded to PreviewPanelShell so this surface persists its own width. */ widthStorageKey?: string; /** Forwarded to PreviewPanelShell as the initial width before a user resize. */ @@ -335,6 +337,7 @@ function RightPanelEmptyState(props: { agentsAvailable: boolean; deviceAvailable: boolean; liveAgentCount: number; + shortcutsEnabled: boolean; }) { // -1 means no highlight: it only appears on hover or arrow use. const [highlight, setHighlight] = useState(-1); @@ -426,11 +429,14 @@ function RightPanelEmptyState(props: { // Capture phase so app-level key handlers cannot swallow the event first; // typing contexts and already-handled events are left alone. const shortcutActionsRef = useRef(availableActions); + const shortcutsEnabledRef = useRef(props.shortcutsEnabled); useEffect(() => { shortcutActionsRef.current = availableActions; + shortcutsEnabledRef.current = props.shortcutsEnabled; }); useEffect(() => { const handler = (event: KeyboardEvent) => { + if (!shortcutsEnabledRef.current) return; const action = surfaceShortcutActionForKey(shortcutActionsRef.current, event); if (!action) return; if (document.querySelector(LAUNCHER_SHORTCUT_BLOCKING_LAYERS)) return; @@ -1425,6 +1431,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { agentsAvailable={props.agentsAvailable} deviceAvailable={props.deviceAvailable} liveAgentCount={props.liveAgentCount} + shortcutsEnabled={props.launcherShortcutsEnabled ?? true} /> ) : ( props.children diff --git a/apps/web/src/components/chat/ExpandedImageDialog.tsx b/apps/web/src/components/chat/ExpandedImageDialog.tsx index f750693b6494..e8e65b6afbd7 100644 --- a/apps/web/src/components/chat/ExpandedImageDialog.tsx +++ b/apps/web/src/components/chat/ExpandedImageDialog.tsx @@ -28,7 +28,9 @@ import { composerFloatingLayerProps } from "./composerEventScope"; interface ExpandedImageDialogProps { preview: ExpandedImagePreview; - onClose: () => void; + open: boolean; + onOpenChange: (open: boolean) => void; + onOpenChangeComplete: (open: boolean) => void; } const EXPANDED_MEDIA_STATE_CLASS_NAME = @@ -69,7 +71,9 @@ function ExpandedVideo({ item }: { readonly item: ExpandedImageItem }) { export const ExpandedImageDialog = memo(function ExpandedImageDialog({ preview, - onClose, + open, + onOpenChange, + onOpenChangeComplete, }: ExpandedImageDialogProps) { const [imageOffset, setImageOffset] = useState(0); const [failedImageSrc, setFailedImageSrc] = useState(null); @@ -96,7 +100,7 @@ export const ExpandedImageDialog = memo(function ExpandedImageDialog({ ...source, onOpenFile: () => { openFile(); - onClose(); + onOpenChange(false); }, } : source; @@ -142,14 +146,14 @@ export const ExpandedImageDialog = memo(function ExpandedImageDialog({ useEffect(() => { const onEscape = (event: globalThis.KeyboardEvent) => { - if (event.key !== "Escape" || isContextMenuOpen()) return; + if (!open || event.key !== "Escape" || isContextMenuOpen()) return; event.preventDefault(); event.stopPropagation(); - onClose(); + onOpenChange(false); }; window.addEventListener("keydown", onEscape, { capture: true }); return () => window.removeEventListener("keydown", onEscape, { capture: true }); - }, [onClose]); + }, [onOpenChange, open]); if (!item) return null; const mediaLabel = item.type === "video" ? "video" : "image"; @@ -169,9 +173,11 @@ export const ExpandedImageDialog = memo(function ExpandedImageDialog({ return ( { - if (!open) onClose(); + open={open} + onOpenChange={onOpenChange} + onOpenChangeComplete={(nextOpen) => { + if (!nextOpen) setImageOffset(0); + onOpenChangeComplete(nextOpen); }} > returnFocusTarget} onClick={(event) => { - if (event.target === event.currentTarget) onClose(); + if (event.target === event.currentTarget) onOpenChange(false); }} > Expanded {mediaLabel} preview @@ -208,7 +214,7 @@ export const ExpandedImageDialog = memo(function ExpandedImageDialog({ size="icon-xs" variant="media-close" className="absolute right-0 -top-10 z-20" - onClick={onClose} + onClick={() => onOpenChange(false)} aria-label={`Close ${mediaLabel} preview`} > diff --git a/apps/web/src/components/chat/ExpandedImagePreview.tsx b/apps/web/src/components/chat/ExpandedImagePreview.tsx index 7e6e3892d06b..1addba5e633f 100644 --- a/apps/web/src/components/chat/ExpandedImagePreview.tsx +++ b/apps/web/src/components/chat/ExpandedImagePreview.tsx @@ -153,12 +153,6 @@ export function buildAttachmentVideoPreview( }; } -export function expandedImageKey(preview: ExpandedImagePreview): string { - const item = preview.images[preview.index]; - const asset = item?.actionsSource?.asset; - return `${item?.src ?? (asset ? JSON.stringify([asset.environmentId, asset.resource]) : "image")}:${preview.index}`; -} - export function attachVideoThumbnail(video: HTMLVideoElement, file: File): () => void { const url = URL.createObjectURL(file); video.src = url; diff --git a/apps/web/src/components/desktop/SshPasswordPromptDialog.tsx b/apps/web/src/components/desktop/SshPasswordPromptDialog.tsx index d3727ae210bd..de38740b7b5e 100644 --- a/apps/web/src/components/desktop/SshPasswordPromptDialog.tsx +++ b/apps/web/src/components/desktop/SshPasswordPromptDialog.tsx @@ -68,6 +68,7 @@ function ActiveSshPasswordPrompt({ readonly onRemove: (requestId: string) => void; }) { const [password, setPassword] = useState(""); + const [open, setOpen] = useState(false); const [isResponding, setIsResponding] = useState(false); const [now, setNow] = useState(() => Date.now()); const [responseError, setResponseError] = useState(null); @@ -77,8 +78,7 @@ function ActiveSshPasswordPrompt({ useEffect(() => { const frame = window.requestAnimationFrame(() => { - inputRef.current?.focus(); - inputRef.current?.select(); + setOpen(true); }); return () => { window.cancelAnimationFrame(frame); @@ -120,10 +120,10 @@ function ActiveSshPasswordPrompt({ setResponseError(null); try { await window.desktopBridge?.resolveSshPasswordPrompt(requestId, nextPassword); - onRemove(requestId); + setOpen(false); } catch (error) { if (nextPassword === null) { - onRemove(requestId); + setOpen(false); } else { setResponseError(getPromptErrorMessage(error)); } @@ -134,14 +134,18 @@ function ActiveSshPasswordPrompt({ }; const dismissExpiredPrompt = () => { - onRemove(request.requestId); + setOpen(false); }; const cancelPrompt = () => { + if (isRespondingRef.current) { + return; + } if (isExpired) { dismissExpiredPrompt(); return; } + setOpen(false); void respond(null); }; @@ -149,14 +153,17 @@ function ActiveSshPasswordPrompt({ return ( { if (!open) { cancelPrompt(); } }} + onOpenChangeComplete={(open) => { + if (!open) onRemove(request.requestId); + }} > - + SSH Password Required diff --git a/apps/web/src/components/settings/AddProviderInstanceDialog.tsx b/apps/web/src/components/settings/AddProviderInstanceDialog.tsx index b1cb8bf3555f..2361cf0c3d79 100644 --- a/apps/web/src/components/settings/AddProviderInstanceDialog.tsx +++ b/apps/web/src/components/settings/AddProviderInstanceDialog.tsx @@ -228,17 +228,37 @@ export function AddProviderInstanceDialog({ } }; + const resetState = () => { + setAddingChatGptAccount(false); + setWizardStep(0); + setDriver(DEFAULT_DRIVER_KIND); + setLabel(""); + setAccentColor(""); + setInstanceIdOverride(null); + setConfigByDriver({}); + setHasAttemptedSubmit(false); + }; + if (addingChatGptAccount) { return ( onOpenChange(false)} + onClose={() => { + resetState(); + onOpenChange(false); + }} /> ); } return ( - + { + if (!nextOpen) resetState(); + }} + > { - setDialogOpen(open); - if (!open) { - setPairingLabel(""); - setPairingScopes([...AuthStandardClientScopes]); - } + onOpenChange={setDialogOpen} + onOpenChangeComplete={(open) => { + if (open) return; + setPairingLabel(""); + setPairingScopes([...AuthStandardClientScopes]); }} > (null); - const isWslConfirmDialogOpen = pendingWslChange !== null; - const [pendingTailscaleServeEndpoint, setPendingTailscaleServeEndpoint] = - useState(null); + const isWslConfirmDialogOpen = pendingWslChange?.open ?? false; + const [pendingTailscaleServeSetup, setPendingTailscaleServeSetup] = useState<{ + readonly open: boolean; + readonly endpoint: AdvertisedEndpoint; + } | null>(null); + const pendingTailscaleServeEndpoint = pendingTailscaleServeSetup?.endpoint ?? null; const [disableTailscaleServeDialogOpen, setDisableTailscaleServeDialogOpen] = useState(false); const [tailscaleServePortInput, setTailscaleServePortInput] = useState( String(DEFAULT_TAILSCALE_SERVE_PORT), @@ -2170,7 +2171,7 @@ export function ConnectionsSettings() { port: parsedTailscaleServePort, }); refreshDesktopNetworkAccessState(); - setPendingTailscaleServeEndpoint(null); + setPendingTailscaleServeSetup((current) => (current ? { ...current, open: false } : current)); } catch (error) { const message = error instanceof Error ? error.message : "Failed to configure Tailscale HTTPS."; @@ -2192,7 +2193,7 @@ export function ConnectionsSettings() { setTailscaleServePortInput( String(desktopServerExposureState?.tailscaleServePort ?? DEFAULT_TAILSCALE_SERVE_PORT), ); - setPendingTailscaleServeEndpoint(endpoint); + setPendingTailscaleServeSetup({ open: true, endpoint }); }, [desktopServerExposureState?.tailscaleServePort], ); @@ -2928,7 +2929,7 @@ export function ConnectionsSettings() { // on (turning the only running backend off needs to switch // back to Windows and restart — always consequential). if (hasWslRegistrationToLose || wasWslOnly) { - setPendingWslChange({ kind: "disable", wasWslOnly }); + setPendingWslChange({ open: true, kind: "disable", wasWslOnly }); return; } void applyWslSettingChange(() => desktopBridge.setWslBackendEnabled(false)); @@ -2941,7 +2942,7 @@ export function ConnectionsSettings() { // backends or only WSL. We always ask here so the user picks // the mode upfront instead of having to discover the wsl-only // switch afterwards. - setPendingWslChange({ kind: "enable", nextDistro }); + setPendingWslChange({ open: true, kind: "enable", nextDistro }); return; } // Already enabled — treat as a distro switch. Skip the change if @@ -2953,7 +2954,7 @@ export function ConnectionsSettings() { // the app (the IPC handler does this) rather than swapping a secondary, // and the user should see that coming. if (hasWslRegistrationToLose || desktopWslState.wslOnly) { - setPendingWslChange({ kind: "distro", nextDistro }); + setPendingWslChange({ open: true, kind: "distro", nextDistro }); return; } void applyWslSettingChange(() => desktopBridge.setWslDistro(nextDistro)); @@ -2966,7 +2967,7 @@ export function ConnectionsSettings() { (mode: "both" | "wsl-only") => { if (!desktopBridge || !pendingWslChange || pendingWslChange.kind !== "enable") return; const nextDistro = pendingWslChange.nextDistro; - setPendingWslChange(null); + setPendingWslChange({ ...pendingWslChange, open: false }); const persistedDistro = desktopWslState?.distro ?? null; void applyWslSettingChange(() => applyWslEnableSelection({ @@ -2989,7 +2990,7 @@ export function ConnectionsSettings() { // anything itself; the renderer warns the user to expect a // restart and (in a follow-up) can trigger it automatically. // Always prompt — even enabling is consequential here. - setPendingWslChange({ kind: "wsl-only", nextValue: enabled }); + setPendingWslChange({ open: true, kind: "wsl-only", nextValue: enabled }); }, [desktopBridge, desktopWslState], ); @@ -3000,7 +3001,7 @@ export function ConnectionsSettings() { // The enable kind resolves through handleConfirmEnableWsl, not // this single Confirm path. if (change.kind === "enable") return; - setPendingWslChange(null); + setPendingWslChange({ ...pendingWslChange, open: false }); if (change.kind === "disable") { void applyWslSettingChange(async () => { const next = await desktopBridge.setWslBackendEnabled(false); @@ -3465,6 +3466,11 @@ export function ConnectionsSettings() { open={isWslConfirmDialogOpen} onOpenChange={(open) => { if (isUpdatingWslBackend) return; + if (!open) { + setPendingWslChange((current) => (current ? { ...current, open: false } : current)); + } + }} + onOpenChangeComplete={(open) => { if (!open) setPendingWslChange(null); }} > @@ -3608,10 +3614,17 @@ export function ConnectionsSettings() { { if (isUpdatingTailscaleServe) return; - if (!open) setPendingTailscaleServeEndpoint(null); + if (!open) { + setPendingTailscaleServeSetup((current) => + current ? { ...current, open: false } : current, + ); + } + }} + onOpenChangeComplete={(open) => { + if (!open) setPendingTailscaleServeSetup(null); }} > diff --git a/apps/web/src/components/settings/ProviderSettingsPanel.tsx b/apps/web/src/components/settings/ProviderSettingsPanel.tsx index aa947d5d1de7..df3cf77a17d7 100644 --- a/apps/web/src/components/settings/ProviderSettingsPanel.tsx +++ b/apps/web/src/components/settings/ProviderSettingsPanel.tsx @@ -1243,14 +1243,12 @@ export function EnvironmentProviderSettings({ /> - {isAddInstanceDialogOpen ? ( - - ) : null} + ); }