Type detection
Never make someone choose an engine you can work out for yourself. Detection runs in a fixed order, says what it concluded before anything runs, and steps aside when you disagree.
What the bar tells you#
With both sides filled, the detection bar shows:
| Element | Meaning |
|---|---|
| Detected: json → Structural JSON diff | The kind both sides resolved to, and the engine that claims them |
| manual override | You picked the engine yourself; detection is being ignored |
| Compare ⏎ | Runs it. Disabled when no engine can handle the pair |
| Auto (…) dropdown | The engine override, listing all five engines |
Before both sides are filled it says which side it is still waiting for, so an input that silently failed to load is visible rather than mysterious.
The order a kind is decided#
Order matters here, and every step in it exists because the obvious order got something wrong.
- A folder is structural. It is known from the filesystem, never sniffed.
- Extension next — cheap, and usually right.
- Nothing to sniff? Keep what is known. A large binary travels as a path with no text attached;
re-deciding at that point would turn a known binary back into
unknownand leave the Compare button disabled with no engine to run. - A NUL byte in the first 8 KB of the decoded text means binary.
- A JSON parse probe. The content must start and end with
{}or[]and parse. - Otherwise it is text.
Known formats outrank the sniff#
| Extensions | Kind |
|---|---|
json har geojson | json |
yml yaml | yaml |
csv tsv | csv |
md markdown | md |
txt log | text |
png jpg jpeg gif webp avif bmp | image |
ts tsx js jsx mjs cjs css scss html py sql rs go java rb sh toml xml | code |
Anything not in that list falls through to the content checks. The same module maps those extensions
to the language id used for syntax highlighting, so the two can never disagree
about what a .tsx file is.
Text is decoded before it is sniffed#
| What the bytes start with | Read as |
|---|---|
EF BB BF | UTF-8 with a BOM — the BOM is stripped, so it cannot make line 1 differ from an identical line 1 |
FF FE | UTF-16 LE |
FE FF | UTF-16 BE |
| No BOM, but over a quarter of a 1 KB sample is NUL in one byte position and none in the other | UTF-16 LE or BE |
| Anything else | UTF-8 |
| Bytes that are not valid in their apparent encoding | Latin-1, flagged as lossy — every byte maps to a character, so the diff stays byte-accurate even when it is not readable |
The alternating-NUL heuristic needs at least 32 bytes to fire. Below that a six-byte executable header reads as UTF-16 BE convincingly enough to be line-diffed as text, and calling a tiny unmarked UTF-16 file binary is the cheaper mistake — a BOM would have caught it anyway.
Line endings are detected at the same time (LF, CRLF, CR or none) and reported in the status bar, because "why does every line differ?" usually has a line-ending answer.
From kind to engine#
Each engine declares which pairs it accepts and how strongly it claims them. The highest claim wins.
| Engine | Accepts | Claim |
|---|---|---|
| Binary comparison | binary + binary | Highest — once both sides are opaque, nothing else has anything to say |
| File tree diff | folder + folder | |
| Visual / pixel diff | image + image | |
| Structural JSON diff | json + json | |
| Text diff | Any two of text code json yaml csv md | Lowest — the universal fallback |
When the two sides are different kinds#
A .json against a .yml is a real thing to want to compare. Rather than refuse it, the pair falls
through to the text engine — and because that choice is surprising, the bar adds a chip saying the
kinds differ and that it is comparing as text. The Detected: chip names both kinds in that case.
If no engine accepts the pair at all — a folder against a file — the bar reads no engine and the
Compare button is disabled. Overriding the engine will enable it, but the engine you chose gets
to refuse: pick the folder engine for a file pair and it fails with Folder comparison needs two
folders on disk, which is a clearer answer than a comparison that pretends to work.
Overriding the engine#
The dropdown at the end of the bar lists every engine, with Auto (…) naming what detection would
have chosen. Picking one:
- adds the manual override chip,
- clears any finished result and the options that belonged to it, because a different engine's options are unrelated,
- and sticks for that pair until you change an input or set it back to
Auto.
Reopening a comparison from history sets the engine explicitly too — the stored row names the engine that ran, so a reopened comparison cannot quietly change engine because a file was renamed since.