From dcf7bca68426dd36ff8b07c47faadbf97def0d43 Mon Sep 17 00:00:00 2001 From: Kris Powers <85710701+KrisPowers@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:08:36 -0400 Subject: [PATCH 1/3] fix(explorer): complete drag-drop move with auto-expand and ancestor guard --- app/frontend/src/fullscreen/FileExplorer.tsx | 36 +++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/app/frontend/src/fullscreen/FileExplorer.tsx b/app/frontend/src/fullscreen/FileExplorer.tsx index a6b936a..d396d10 100644 --- a/app/frontend/src/fullscreen/FileExplorer.tsx +++ b/app/frontend/src/fullscreen/FileExplorer.tsx @@ -64,6 +64,8 @@ export default function FileExplorer({ root, selectedPath, onSelect, onRefresh, const [newItem, setNewItem] = useState<{ kind: 'file' | 'folder'; dir: string } | null>(null) const [filterQuery, setFilterQuery] = useState('') const dragSrc = useRef(null) + const dragExpandTimer = useRef | null>(null) + const dragOverTarget = useRef(null) const scrollRef = useRef(null) const filterInputRef = useRef(null) const installedApps = useInstalledApps() @@ -303,23 +305,45 @@ export default function FileExplorer({ root, selectedPath, onSelect, onRefresh, ], [handleNewFile, handleNewFolder, handleReveal, refreshAll, onRefresh, collapseAll]) // ── Drag and drop ───────────────────────────────────────────────────────── + const clearDragExpand = () => { + if (dragExpandTimer.current) { clearTimeout(dragExpandTimer.current); dragExpandTimer.current = null } + dragOverTarget.current = null + } + const onDragStart = (e: React.DragEvent, node: FileNode) => { dragSrc.current = node.path e.dataTransfer.effectAllowed = 'move' } const onDragOver = (e: React.DragEvent, node: FileNode) => { - if (!node.isDir || node.path === dragSrc.current) return + if (!node.isDir) return + const src = dragSrc.current + // Reject self-drop and dropping a folder into its own descendant + if (!src || node.path === src || node.path.startsWith(src + '/')) return e.preventDefault() e.dataTransfer.dropEffect = 'move' setDragOver(node.path) + // Start auto-expand timer only when we enter a new target + if (dragOverTarget.current !== node.path) { + clearDragExpand() + dragOverTarget.current = node.path + if (!expanded.has(node.path)) { + dragExpandTimer.current = setTimeout(() => { + setExpanded(prev => new Set(prev).add(node.path)) + if (!dirCache.has(node.path)) void loadDir(node.path) + }, 600) + } + } } const onDrop = async (e: React.DragEvent, node: FileNode) => { e.preventDefault() setDragOver(null) - if (!dragSrc.current || !node.isDir || node.path === dragSrc.current) return + clearDragExpand() const src = dragSrc.current + if (!src || !node.isDir) return + // Reject self-drop and ancestor drops (folder into its own subfolder) + if (node.path === src || node.path.startsWith(src + '/')) return const srcName = src.split('/').pop()! await invoke('fs.rename', { from: src, to: `${node.path}/${srcName}` }) dragSrc.current = null @@ -328,7 +352,7 @@ export default function FileExplorer({ root, selectedPath, onSelect, onRefresh, onRefresh() } - const onDragEnd = () => { dragSrc.current = null; setDragOver(null) } + const onDragEnd = () => { dragSrc.current = null; setDragOver(null); clearDragExpand() } // ── Flat tree row ────────────────────────────────────────────────────────── function renderNode(node: FileNode, depth: number, style: React.CSSProperties): React.ReactNode { @@ -363,7 +387,11 @@ export default function FileExplorer({ root, selectedPath, onSelect, onRefresh, onDragOver={e => onDragOver(e, node)} onDrop={e => onDrop(e, node)} onDragEnd={onDragEnd} - onDragLeave={() => setDragOver(null)} + onDragLeave={e => { + if (e.currentTarget.contains(e.relatedTarget as Node)) return + setDragOver(null) + clearDragExpand() + }} > {node.isDir ? ( From 6f3a5916b125927164934acb6074b118f338f0bc Mon Sep 17 00:00:00 2001 From: Kris Powers <85710701+KrisPowers@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:52:28 -0400 Subject: [PATCH 2/3] fix(fullscreen): pin walk return type to void to satisfy no-floating-promises --- app/frontend/src/fullscreen/FileExplorer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/frontend/src/fullscreen/FileExplorer.tsx b/app/frontend/src/fullscreen/FileExplorer.tsx index d396d10..ff61f7c 100644 --- a/app/frontend/src/fullscreen/FileExplorer.tsx +++ b/app/frontend/src/fullscreen/FileExplorer.tsx @@ -128,7 +128,7 @@ export default function FileExplorer({ root, selectedPath, onSelect, onRefresh, // ── flatten the cached tree for rendering — root is the first row ─────────── const flatRows = useMemo(() => { const out: FlatRow[] = [] - function walk(path: string, depth: number) { + function walk(path: string, depth: number): void { const children = dirCache.get(path) if (!children) return for (const child of children) { From 6dfa3af72f4322777273619edc1e05b3ebf3d861 Mon Sep 17 00:00:00 2001 From: Kris Powers <85710701+KrisPowers@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:02:14 -0400 Subject: [PATCH 3/3] fix(fullscreen): void fetchOutline call to satisfy no-floating-promises --- app/frontend/src/fullscreen/StructureView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/frontend/src/fullscreen/StructureView.tsx b/app/frontend/src/fullscreen/StructureView.tsx index 0097119..aabe858 100644 --- a/app/frontend/src/fullscreen/StructureView.tsx +++ b/app/frontend/src/fullscreen/StructureView.tsx @@ -133,7 +133,7 @@ export default function StructureView({ filePath, onGotoLine }: Props) { } } - fetchOutline(4) + void fetchOutline(4) return () => { cancelled = true } }, [filePath])