Skip to content

Bug: Trees presorted phantom self-nested dir #755

@theopak

Description

@theopak

Prerequisites

Describe the issue

preparePresortedFileTreeInput produces a phantom, self-nested directory when the presorted input contains a directory row (trailing slash) immediately followed by one of its descendants. The shared-prefix cache inside appendPresortedPaths records the parent's prefix paired with the leaf's depth (off by one), so the next path that hits the cache fast-path re-creates the leaf directory as a child of itself.

Reproduction

import { preparePresortedFileTreeInput } from '@pierre/trees';

// Presorted, canonical-order directory paths (trailing slashes = directories).
const input = preparePresortedFileTreeInput([
  '/home/user/photos/',
  '/home/user/photos/raw/',
]);
// Build a tree from `input` (e.g. new PathStore({ preparedInput: input }),
// or a FileTree) and enumerate the directory nodes.

Expected directories:

/home/  →  /home/user/  →  /home/user/photos/  →  /home/user/photos/raw/

Actual — an extra phantom branch appears:

/home/user/photos/photos/
/home/user/photos/photos/raw/

(Any parent dir followed by a deeper descendant sharing its prefix triggers it; deeper inputs like ['/a/b/', '/a/b/c/', '/a/b/c/d/'] compound it into /a/b/b/, /a/b/b/c/c/, …. prepareFileTreeInput is unaffected — only the presorted path.)

Suggested fix

In appendPresortedPaths (path-store/src/builder.js), the trailing-slash leaf branch creates the leaf directory node and increments currentDepth, but never advances segmentStart. The subsequent cachedDirPrefix/cachedDirDepth write then stores the parent prefix at the leaf's depth — inconsistent by one level. Advancing segmentStart past the leaf restores the invariant:

--- a/path-store/src/builder.js
+++ b/path-store/src/builder.js
@@ -236,6 +236,9 @@ appendPresortedPaths(paths, containsDirectories = null) {
 						});
 						stackTop++;
 						dirStack[stackTop] = nodeId;
+						// Advance past the leaf dir so the cachedDirPrefix/cachedDirDepth
+						// write below pairs the leaf's full path with the leaf's depth,
+						// not the parent prefix at the leaf depth (off by one).
+						segmentStart = endIndex + 1;
 					}
 					const directoryId = dirStack[stackTop];
 					if (directoryId === void 0) throw new Error(`Unable to resolve directory node for "${path}"`);

This makes the cache record the leaf directory's full path at the leaf's depth, matching the file-path branch's existing (correct) behavior.

What browser(s) are you seeing the problem on?

Chrome

What version of @pierre/diffs are you using?

1.0.0-beta.4


AI disclosure: issue written with assistance from Claude Opus 4.8 based on usage in my private codebase.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions