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.
Prerequisites
Describe the issue
preparePresortedFileTreeInputproduces 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 insideappendPresortedPathsrecords 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
Expected directories:
Actual — an extra phantom branch appears:
(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/, ….prepareFileTreeInputis 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 incrementscurrentDepth, but never advancessegmentStart. The subsequentcachedDirPrefix/cachedDirDepthwrite then stores the parent prefix at the leaf's depth — inconsistent by one level. AdvancingsegmentStartpast the leaf restores the invariant: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.