diff --git a/apps/fabro-web/app/routes/run-sandbox/filesystem-panel.test.tsx b/apps/fabro-web/app/routes/run-sandbox/filesystem-panel.test.tsx index b14b609c8..2424544c4 100644 --- a/apps/fabro-web/app/routes/run-sandbox/filesystem-panel.test.tsx +++ b/apps/fabro-web/app/routes/run-sandbox/filesystem-panel.test.tsx @@ -205,14 +205,15 @@ describe("binary and decoding helpers", () => { }); describe("buildTreeInputs", () => { - test("emits placeholders for directories so they appear in the tree", () => { + test("emits native directory paths without user-visible placeholders", () => { const entries: SandboxFileEntry[] = [ { name: "src", is_dir: true }, { name: "package.json", is_dir: false, size: 200 }, ]; const inputs = buildTreeInputs(entries); + expect(inputs.paths).toContain("src/"); expect(inputs.paths).toContain("package.json"); - expect(inputs.paths.some((path) => path.startsWith("src/"))).toBe(true); + expect(inputs.paths).not.toContain("src/__fabro_dir__"); expect(inputs.directories.has("src")).toBe(true); expect(inputs.fileEntries.get("package.json")?.size).toBe(200); }); @@ -225,9 +226,8 @@ describe("classifySelection", () => { ]; const inputs = buildTreeInputs(entries); - test("recognizes a placeholder selection as a directory", () => { - const placeholder = inputs.paths.find((path) => path.startsWith("src/"))!; - expect(classifySelection(placeholder, inputs.fileEntries, inputs.directories)).toEqual({ + test("recognizes a native directory path selection as a directory", () => { + expect(classifySelection("src/", inputs.fileEntries, inputs.directories)).toEqual({ kind: "dir", relativePath: "src", }); @@ -363,11 +363,11 @@ describe("FilesystemPanel render", () => { }; renderPanel(); expect(lastFilesArgs?.path).toBe("/"); - const placeholder = filesystemPanelModule.buildTreeInputs( + const directoryPath = filesystemPanelModule.buildTreeInputs( filesState.data!.data, - ).paths.find((path) => path.startsWith("src/"))!; + ).paths.find((path) => path === "src/")!; act(() => { - lastTreeOptions?.onSelectionChange?.([placeholder]); + lastTreeOptions?.onSelectionChange?.([directoryPath]); }); expect(lastFilesArgs?.path).toBe("/src"); }); diff --git a/apps/fabro-web/app/routes/run-sandbox/filesystem-panel.tsx b/apps/fabro-web/app/routes/run-sandbox/filesystem-panel.tsx index 835920a19..aa4bd39dd 100644 --- a/apps/fabro-web/app/routes/run-sandbox/filesystem-panel.tsx +++ b/apps/fabro-web/app/routes/run-sandbox/filesystem-panel.tsx @@ -142,9 +142,8 @@ interface BuiltTreeInputs { // The list endpoint returns flat names like `foo` (depth=1) or `foo/bar.ts` // (depth=2). We feed those names to @pierre/trees as relative paths. Every -// directory gets a synthetic placeholder child so empty directories still -// appear; the placeholders are filtered out before selection. -const DIRECTORY_PLACEHOLDER = "__fabro_dir__"; +// directories are represented with the tree library's canonical trailing-slash +// form so empty directories appear without synthetic child rows. export function buildTreeInputs(entries: readonly SandboxFileEntry[]): BuiltTreeInputs { const paths: string[] = []; @@ -154,7 +153,7 @@ export function buildTreeInputs(entries: readonly SandboxFileEntry[]): BuiltTree for (const entry of entries) { if (entry.is_dir) { directories.add(entry.name); - paths.push(`${entry.name}/${DIRECTORY_PLACEHOLDER}`); + paths.push(`${entry.name}/`); } else { fileEntries.set(entry.name, entry); paths.push(entry.name); @@ -164,8 +163,8 @@ export function buildTreeInputs(entries: readonly SandboxFileEntry[]): BuiltTree return { paths, fileEntries, directories }; } -function isPlaceholderPath(path: string): boolean { - return path.endsWith(`/${DIRECTORY_PLACEHOLDER}`) || path === DIRECTORY_PLACEHOLDER; +function normalizeDirectorySelection(path: string): string { + return path.replace(/\/+$/, ""); } export function classifySelection( @@ -173,19 +172,15 @@ export function classifySelection( fileEntries: Map, directories: Set, ): { kind: "file"; entry: SandboxFileEntry } | { kind: "dir"; relativePath: string } | null { - if (isPlaceholderPath(selectedPath)) { - const relPath = selectedPath.substring(0, selectedPath.length - DIRECTORY_PLACEHOLDER.length - 1); - if (relPath) return { kind: "dir", relativePath: relPath }; - return null; - } const fileEntry = fileEntries.get(selectedPath); if (fileEntry) return { kind: "file", entry: fileEntry }; - if (directories.has(selectedPath)) { - return { kind: "dir", relativePath: selectedPath }; + const directoryPath = normalizeDirectorySelection(selectedPath); + if (directories.has(directoryPath)) { + return { kind: "dir", relativePath: directoryPath }; } // Intermediate directory implied by a nested path (depth>1). Treat selection // as navigation into that subdir. - return { kind: "dir", relativePath: selectedPath }; + return { kind: "dir", relativePath: directoryPath }; } interface PreviewState {