From 2f69f0c667491f576013897a661d31153d965dcc Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Fri, 27 Mar 2026 21:52:17 -0500 Subject: [PATCH] Enable dragging workspace files into chat context - Make file tree rows draggable with a custom tree-path payload - Accept tree-path drops in the composer and insert them as @mentions - Update the drag overlay copy for files vs. tree paths --- apps/web/src/components/ChatView.tsx | 46 ++++++++++++++++--- apps/web/src/components/WorkspaceFileTree.tsx | 10 ++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index a2d8d3566..5258f55fa 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -91,6 +91,7 @@ import PlanSidebar from "./PlanSidebar"; import ThreadTerminalDrawer from "./ThreadTerminalDrawer"; import { SpotifyPlayerDrawer } from "./SpotifyPlayer"; import { + AtSignIcon, BotIcon, ChevronDownIcon, ChevronLeftIcon, @@ -345,6 +346,7 @@ export default function ChatView({ threadId }: ChatViewProps) { const promptRef = useRef(prompt); const [showScrollToBottom, setShowScrollToBottom] = useState(false); const [isDragOverComposer, setIsDragOverComposer] = useState(false); + const [dragOverType, setDragOverType] = useState<"files" | "tree-path">("files"); const [expandedImage, setExpandedImage] = useState(null); const [optimisticUserMessages, setOptimisticUserMessages] = useState([]); const optimisticUserMessagesRef = useRef(optimisticUserMessages); @@ -2444,17 +2446,25 @@ export default function ChatView({ threadId }: ChatViewProps) { addComposerImages(imageFiles); }; + const isAcceptedDragType = (dataTransfer: DataTransfer) => + dataTransfer.types.includes("Files") || + dataTransfer.types.includes("application/x-okcode-tree-path"); + + const isDragTreePath = (dataTransfer: DataTransfer) => + dataTransfer.types.includes("application/x-okcode-tree-path"); + const onComposerDragEnter = (event: React.DragEvent) => { - if (!event.dataTransfer.types.includes("Files")) { + if (!isAcceptedDragType(event.dataTransfer)) { return; } event.preventDefault(); dragDepthRef.current += 1; + setDragOverType(isDragTreePath(event.dataTransfer) ? "tree-path" : "files"); setIsDragOverComposer(true); }; const onComposerDragOver = (event: React.DragEvent) => { - if (!event.dataTransfer.types.includes("Files")) { + if (!isAcceptedDragType(event.dataTransfer)) { return; } event.preventDefault(); @@ -2463,7 +2473,7 @@ export default function ChatView({ threadId }: ChatViewProps) { }; const onComposerDragLeave = (event: React.DragEvent) => { - if (!event.dataTransfer.types.includes("Files")) { + if (!isAcceptedDragType(event.dataTransfer)) { return; } event.preventDefault(); @@ -2478,12 +2488,27 @@ export default function ChatView({ threadId }: ChatViewProps) { }; const onComposerDrop = (event: React.DragEvent) => { - if (!event.dataTransfer.types.includes("Files")) { + if (!isAcceptedDragType(event.dataTransfer)) { return; } event.preventDefault(); dragDepthRef.current = 0; setIsDragOverComposer(false); + + // Handle file tree path drops — insert as @mention context + if (isDragTreePath(event.dataTransfer)) { + const treePath = event.dataTransfer.getData("application/x-okcode-tree-path"); + if (treePath) { + const snapshot = readComposerSnapshot(); + const mention = `@${treePath} `; + // Insert at the current cursor position + applyPromptReplacement(snapshot.cursor, snapshot.cursor, mention); + } + focusComposer(); + return; + } + + // Handle image file drops const files = Array.from(event.dataTransfer.files); addComposerImages(files); focusComposer(); @@ -4007,8 +4032,17 @@ export default function ChatView({ threadId }: ChatViewProps) { {isDragOverComposer && (
- - Drop images to attach + {dragOverType === "tree-path" ? ( + <> + + Drop to add as context + + ) : ( + <> + + Drop images to attach + + )}
)} diff --git a/apps/web/src/components/WorkspaceFileTree.tsx b/apps/web/src/components/WorkspaceFileTree.tsx index 9048fa7e4..6f662adee 100644 --- a/apps/web/src/components/WorkspaceFileTree.tsx +++ b/apps/web/src/components/WorkspaceFileTree.tsx @@ -172,6 +172,11 @@ const WorkspaceDirectoryRow = memo(function WorkspaceDirectoryRow(props: { return (