Folders
Two recursive scans, then one decision per path. Per-file status with a glyph as well as a colour, renames paired, and any file pair one double-click away — with the tree still there when you come back.
Two passes, not one#
The engine walks each side into a flat map of relative paths first, and only then decides a status per path.
Keeping the walk and the comparison separate is what makes "one side could not be read" a per-entry problem instead of an aborted scan. A folder containing one protected subdirectory still produces a useful comparison: that subdirectory becomes a single error row, and everything else is compared normally.
Per-file status#
| Status | Glyph | Meaning |
|---|---|---|
| Added | + | Present only on the after side |
| Deleted | ✕ | Present only on the before side |
| Modified | ● | Present on both sides, different content |
| Identical | · | Present on both sides, same content |
| Unreadable | ! | One side could not be read; the row carries the reason |
Rows are aligned pairs rather than two independent trees: a file that exists on one side only lines up against a visible gap, which is the only way the eye can tell what happened. Directories inherit their subtree — a folder counts as modified when anything inside it is, so a collapsed tree still tells you where to look.
How "modified" is decided#
Cheapest check first, and content is only read when nothing cheaper can answer:
- Sizes differ → modified. One
statper side settles it. - Same size, same timestamp → identical.
- Same size, different timestamp → the only ambiguous case, and the only one that costs a read: both sides are hashed with SHA-256 and compared.
That ordering is why a ten-thousand-file tree stays fast. The notes for the comparison record how many files actually needed hashing, so the cost is visible rather than mysterious.
Renames#
An unmatched deletion and an unmatched addition are often one file that moved. They pair up: both rows keep their status, each gains a note — renamed from old-name on one side, renamed to new-name on the other — and the count appears in the summary strip. Scoring uses three signals, in falling order of certainty:
- Identical content. A hash match is not a guess.
- The same basename elsewhere.
src/a.ts→src/lib/a.tsis a move even when the content also changed — so a cross-directory rename is found, not just one within a folder. - Similar content. Chunk hashes over a sample, plus size proximity, for a file that was renamed and edited.
Filters#
Two filters, and they compose:
- Status — All, Added, Deleted, Modified, Identical.
- Name — the toolbar box.
*.tsbehaves like a shell glob (*and?both work); anything without a wildcard is a plain case-insensitive substring, because that is what typing a few letters into a filter box means.
A directory survives whenever any of its children does, so filtering never leaves a file floating without its folder. When nothing survives, the pane says "No files match this filter" rather than going blank.
Drill in to any file pair#
Double-click a row — or select it and press ⏎ — to open that one file pair as its own comparison, detected and routed like any other. Only files present on both sides can be opened; there is nothing to compare a deletion against.
The toolbar then shows a breadcrumb back to the folder comparison. Pressing it restores the tree without re-running anything: the parent comparison is stashed whole, because a ten-thousand-file scan must not repeat just because someone opened a file.
What is skipped, and said#
| Skipped | Why |
|---|---|
.git, node_modules, .DS_Store | The ignore list. Nobody is comparing two object stores by accident |
| Symlinks | Counted and skipped rather than followed: following them risks a cycle, and comparing a link target is a different question from comparing files |
Both are named in the notes for the comparison — including the number of symlinks skipped — and those notes travel into any report you export.
Limits#
| Limit | Value | What happens past it |
|---|---|---|
| Entries per side | 50,000 | The scan stops, the summary is marked a partial scan, and a note says so |
| Depth | 40 levels | The walk stops descending and records it — a tree this deep is generated, or a symlink loop we failed to spot |
Alongside the added / removed / modified counts, the strip carries this engine's own vocabulary: identical files, renames, unreadable entries, and the net size change across the whole tree.