diff --git a/apps/web/src/components/files/FileBrowserPanel.tsx b/apps/web/src/components/files/FileBrowserPanel.tsx index 49894db3c8cf..1803d327290f 100644 --- a/apps/web/src/components/files/FileBrowserPanel.tsx +++ b/apps/web/src/components/files/FileBrowserPanel.tsx @@ -30,7 +30,7 @@ interface FileBrowserPanelProps { environmentId: EnvironmentId; cwd: string; projectName: string; - /** File currently open in the preview pane; revealed and selected in the tree. */ + /** Entry currently open in the surface; revealed and selected in the tree. A directory is expanded. */ selectedPath: string | null; /** Bumped when the same path should be revealed again (e.g. re-opened from search). */ selectedPathRevealId: number; @@ -299,8 +299,13 @@ export default function FileBrowserPanel({ ) { return; } - if (entryKinds.get(selectedPath) !== "file") return; - const selectedItem = model.getItem(selectedPath); + const selectedKind = entryKinds.get(selectedPath); + if (selectedKind === undefined) return; + // Directory rows are registered with a trailing slash (see treePath). + const selectedItem = + selectedKind === "directory" + ? model.getItem(`${selectedPath}/`) + : model.getItem(selectedPath); if (!selectedItem) return; // A selection that originated inside the tree (clicking a row, possibly @@ -334,8 +339,12 @@ export default function FileBrowserPanel({ if (item && "expand" in item) item.expand(); } + if (selectedKind === "directory" && "expand" in selectedItem) selectedItem.expand(); selectedItem.select(); - model.scrollToPath(selectedPath, { focus: true, offset: "center" }); + model.scrollToPath(selectedKind === "directory" ? `${selectedPath}/` : selectedPath, { + focus: true, + offset: "center", + }); queueMicrotask(() => { syncingSelectionRef.current = false; }); diff --git a/apps/web/src/components/files/FilePreviewPanel.tsx b/apps/web/src/components/files/FilePreviewPanel.tsx index b739d120da63..25bb82b8ad79 100644 --- a/apps/web/src/components/files/FilePreviewPanel.tsx +++ b/apps/web/src/components/files/FilePreviewPanel.tsx @@ -995,12 +995,20 @@ export default function FilePreviewPanel({ relativePath, attachment === undefined && !isMedia && !isPdf, ); + // A chat link cannot tell a folder from a file, so a folder arrives here as + // a file surface and the read fails. Keep the breadcrumbs, drop the preview + // pane, and let the tree fill the surface with the folder revealed. Mutation + // refresh stays on so the surface notices if the path becomes a file. A host + // path cannot be revealed in the workspace tree, so it keeps the read error. + const isDirectory = file.isNotFile && !isHostFile; const [explorerOpen, setExplorerOpen] = useState(initialExplorerOpen); - const showExplorer = shouldShowFileExplorer({ - relativePath, - explorerOpen, - attachmentOpen: attachment !== undefined, - }); + const showExplorer = + isDirectory || + shouldShowFileExplorer({ + relativePath, + explorerOpen, + attachmentOpen: attachment !== undefined, + }); // Reading markdown rendered is a preference, not a property of one file. Keeping // it on the panel meant a thread switch dropped it and forced source back. const [renderMarkdownPreferred, setRenderMarkdownPreferred] = useLocalStorage( @@ -1027,12 +1035,13 @@ export default function FilePreviewPanel({ (handledReveal?.path === relativePath && handledReveal.requestId === revealRequestId); const renderMarkdown = isMarkdown && renderMarkdownPreferred && revealHandled; const renderBrowserFile = isPdf || (isHtml && renderBrowserFilePreferred && revealHandled); - const canToggleRendered = attachment === undefined && (isMarkdown || isHtml); + const canToggleRendered = !isDirectory && attachment === undefined && (isMarkdown || isHtml); const rendered = isMarkdown ? renderMarkdown : renderBrowserFile; const setRenderedPreferred = isMarkdown ? setRenderMarkdownPreferred : setRenderBrowserFilePreferred; const canOpenInBrowser = + !isDirectory && relativePath !== null && attachment === undefined && !isVideo && @@ -1190,7 +1199,7 @@ export default function FilePreviewPanel({ Open file in preview browser ) : null} - {!isHostFile ? ( + {!isHostFile && !isDirectory ? ( {relativePath && attachment ? ( @@ -1332,7 +1341,7 @@ export default function FilePreviewPanel({