Skip to content
TwinScope0.3.10

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.

Click to enlarge
It commits before you do. The bar names the kind it decided and the engine that will run.

What the bar tells you#

With both sides filled, the detection bar shows:

ElementMeaning
Detected: json → Structural JSON diffThe kind both sides resolved to, and the engine that claims them
manual overrideYou picked the engine yourself; detection is being ignored
Compare Runs it. Disabled when no engine can handle the pair
Auto (…) dropdownThe 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.

  1. A folder is structural. It is known from the filesystem, never sniffed.
  2. Extension next — cheap, and usually right.
  3. 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 unknown and leave the Compare button disabled with no engine to run.
  4. A NUL byte in the first 8 KB of the decoded text means binary.
  5. A JSON parse probe. The content must start and end with {} or [] and parse.
  6. Otherwise it is text.

Known formats outrank the sniff#

ExtensionsKind
json har geojsonjson
yml yamlyaml
csv tsvcsv
md markdownmd
txt logtext
png jpg jpeg gif webp avif bmpimage
ts tsx js jsx mjs cjs css scss html py sql rs go java rb sh toml xmlcode

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 withRead as
EF BB BFUTF-8 with a BOM — the BOM is stripped, so it cannot make line 1 differ from an identical line 1
FF FEUTF-16 LE
FE FFUTF-16 BE
No BOM, but over a quarter of a 1 KB sample is NUL in one byte position and none in the otherUTF-16 LE or BE
Anything elseUTF-8
Bytes that are not valid in their apparent encodingLatin-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.

EngineAcceptsClaim
Binary comparisonbinary + binaryHighest — once both sides are opaque, nothing else has anything to say
File tree difffolder + folder
Visual / pixel diffimage + image
Structural JSON diffjson + json
Text diffAny two of text code json yaml csv mdLowest — the universal fallback

When the two sides are different kinds#

Click to enlarge
An unexpected engine explains itself. Two different kinds, one text diff, and a chip saying so.

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#

Click to enlarge
Detection steps aside. The override chip appears so a surprising result is never a mystery.

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.