From 5343bc92e6808a789b7738f7000a50d6cb201f08 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 7 Apr 2026 09:34:10 -0700 Subject: [PATCH 1/4] fix(knowledge): prevent navigation on context menu actions and widen tags modal --- .../document-tags-modal.tsx | 2 +- .../base-tags-modal/base-tags-modal.tsx | 2 +- .../components/base-card/base-card.tsx | 21 +++++++++++++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx index 0dd45e35f0c..4d7ecc781bf 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx @@ -383,7 +383,7 @@ export function DocumentTagsModal({ return ( - +
Document Tags diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/base-tags-modal/base-tags-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/base-tags-modal/base-tags-modal.tsx index e15dbf0a679..e762eb75765 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/base-tags-modal/base-tags-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/base-tags-modal/base-tags-modal.tsx @@ -261,7 +261,7 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM return ( <> - +
Tags diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx index 50933913e03..49aacd67fa9 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useState } from 'react' +import { useCallback, useRef, useState } from 'react' import { useParams, useRouter } from 'next/navigation' import { Badge, DocumentAttachment, Tooltip } from '@/components/emcn' import { formatAbsoluteDate, formatRelativeTime } from '@/lib/core/utils/formatting' @@ -100,6 +100,7 @@ export function BaseCard({ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) const [isTagsModalOpen, setIsTagsModalOpen] = useState(false) const [isDeleting, setIsDeleting] = useState(false) + const actionTakenRef = useRef(false) const searchParams = new URLSearchParams({ kbName: title, @@ -110,7 +111,7 @@ export function BaseCard({ const handleClick = useCallback( (e: React.MouseEvent) => { - if (isContextMenuOpen) { + if (isContextMenuOpen || actionTakenRef.current) { e.preventDefault() return } @@ -130,19 +131,35 @@ export function BaseCard({ ) const handleOpenInNewTab = useCallback(() => { + actionTakenRef.current = true window.open(href, '_blank') + requestAnimationFrame(() => { + actionTakenRef.current = false + }) }, [href]) const handleViewTags = useCallback(() => { + actionTakenRef.current = true setIsTagsModalOpen(true) + requestAnimationFrame(() => { + actionTakenRef.current = false + }) }, []) const handleEdit = useCallback(() => { + actionTakenRef.current = true setIsEditModalOpen(true) + requestAnimationFrame(() => { + actionTakenRef.current = false + }) }, []) const handleDelete = useCallback(() => { + actionTakenRef.current = true setIsDeleteModalOpen(true) + requestAnimationFrame(() => { + actionTakenRef.current = false + }) }, []) const handleConfirmDelete = useCallback(async () => { From dea71d147fbc13929907dd0a43f86df73de58d2f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 7 Apr 2026 10:30:42 -0700 Subject: [PATCH 2/4] fix(knowledge): guard onCopyId against navigation and use setTimeout for robustness --- .../components/base-card/base-card.tsx | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx index 49aacd67fa9..b475dc49167 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx @@ -133,35 +133,44 @@ export function BaseCard({ const handleOpenInNewTab = useCallback(() => { actionTakenRef.current = true window.open(href, '_blank') - requestAnimationFrame(() => { + setTimeout(() => { actionTakenRef.current = false - }) + }, 0) }, [href]) const handleViewTags = useCallback(() => { actionTakenRef.current = true setIsTagsModalOpen(true) - requestAnimationFrame(() => { + setTimeout(() => { actionTakenRef.current = false - }) + }, 0) }, []) const handleEdit = useCallback(() => { actionTakenRef.current = true setIsEditModalOpen(true) - requestAnimationFrame(() => { + setTimeout(() => { actionTakenRef.current = false - }) + }, 0) }, []) const handleDelete = useCallback(() => { actionTakenRef.current = true setIsDeleteModalOpen(true) - requestAnimationFrame(() => { + setTimeout(() => { actionTakenRef.current = false - }) + }, 0) }, []) + const handleCopyId = useCallback(() => { + if (!id) return + actionTakenRef.current = true + navigator.clipboard.writeText(id) + setTimeout(() => { + actionTakenRef.current = false + }, 0) + }, [id]) + const handleConfirmDelete = useCallback(async () => { if (!id || !onDelete) return setIsDeleting(true) @@ -257,7 +266,7 @@ export function BaseCard({ onClose={closeContextMenu} onOpenInNewTab={handleOpenInNewTab} onViewTags={handleViewTags} - onCopyId={id ? () => navigator.clipboard.writeText(id) : undefined} + onCopyId={id ? handleCopyId : undefined} onEdit={handleEdit} onDelete={handleDelete} showOpenInNewTab={true} From 7673726a868a358b308fad9ad60b2b3eebd25caa Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 7 Apr 2026 10:34:52 -0700 Subject: [PATCH 3/4] refactor(knowledge): extract withActionGuard helper to deduplicate context menu guard --- .../components/base-card/base-card.tsx | 53 ++++++++----------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx index b475dc49167..f14a1a8e910 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx @@ -100,7 +100,20 @@ export function BaseCard({ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) const [isTagsModalOpen, setIsTagsModalOpen] = useState(false) const [isDeleting, setIsDeleting] = useState(false) + + /** + * Guards against context menu actions triggering card navigation. + * The card's onClick fires synchronously during the click event bubble phase, + * so the ref is checked before the setTimeout-0 callback resets it. + */ const actionTakenRef = useRef(false) + const withActionGuard = useCallback((fn: () => void) => { + actionTakenRef.current = true + fn() + setTimeout(() => { + actionTakenRef.current = false + }, 0) + }, []) const searchParams = new URLSearchParams({ kbName: title, @@ -131,45 +144,25 @@ export function BaseCard({ ) const handleOpenInNewTab = useCallback(() => { - actionTakenRef.current = true - window.open(href, '_blank') - setTimeout(() => { - actionTakenRef.current = false - }, 0) - }, [href]) + withActionGuard(() => window.open(href, '_blank')) + }, [href, withActionGuard]) const handleViewTags = useCallback(() => { - actionTakenRef.current = true - setIsTagsModalOpen(true) - setTimeout(() => { - actionTakenRef.current = false - }, 0) - }, []) + withActionGuard(() => setIsTagsModalOpen(true)) + }, [withActionGuard]) const handleEdit = useCallback(() => { - actionTakenRef.current = true - setIsEditModalOpen(true) - setTimeout(() => { - actionTakenRef.current = false - }, 0) - }, []) + withActionGuard(() => setIsEditModalOpen(true)) + }, [withActionGuard]) const handleDelete = useCallback(() => { - actionTakenRef.current = true - setIsDeleteModalOpen(true) - setTimeout(() => { - actionTakenRef.current = false - }, 0) - }, []) + withActionGuard(() => setIsDeleteModalOpen(true)) + }, [withActionGuard]) const handleCopyId = useCallback(() => { if (!id) return - actionTakenRef.current = true - navigator.clipboard.writeText(id) - setTimeout(() => { - actionTakenRef.current = false - }, 0) - }, [id]) + withActionGuard(() => navigator.clipboard.writeText(id)) + }, [id, withActionGuard]) const handleConfirmDelete = useCallback(async () => { if (!id || !onDelete) return From 2a82159f036949590e05475a0d68c7182ece3448 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 7 Apr 2026 10:46:30 -0700 Subject: [PATCH 4/4] fix(knowledge): wrap withActionGuard callback in try/finally to prevent stuck ref --- .../knowledge/components/base-card/base-card.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx index f14a1a8e910..bdda246fae3 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx @@ -109,10 +109,13 @@ export function BaseCard({ const actionTakenRef = useRef(false) const withActionGuard = useCallback((fn: () => void) => { actionTakenRef.current = true - fn() - setTimeout(() => { - actionTakenRef.current = false - }, 0) + try { + fn() + } finally { + setTimeout(() => { + actionTakenRef.current = false + }, 0) + } }, []) const searchParams = new URLSearchParams({