Typos fixed all over a line jump unchanged↔changed too many times to read inline, so it safely whole-blocks instead of scrambling.
before
teh cat aet teh fihs and teh dog rann home
the cat ate the fish and the dog ran home
after
teh cat aet teh fihs and teh dog rann home
the cat ate the fish and the dog ran home
One character added to a word #character-add
character · word · add
Inserting one letter (cat → cart). It renders identically to a character delete/edit, because htmldiff is word-level so any single-character change re-marks the whole word. The visual shows that expected sameness.
before
the catcart sat on the mat
after
the catcart sat on the mat
One character removed from a word #character-delete
character · word · delete
Removing one letter (cart → cat). The mirror of the add, and — by the same word-level mechanism — the same whole-word re-mark. The behaviour is uniform at the character level, and the visual shows it.
before
the cartcat sat on the mat
after
the cartcat sat on the mat
Word
A whole-word swap is marked inline; surrounding words are untouched.
One word swapped inside bold text #word-swap
word · sentence · edit
workspace → project, inside **bold** — only the word is marked.
before
assumes the workspaceproject root here
after
assumes the workspaceproject root here
A word added to a sentence #word-add
word · sentence · add
Inserting a word marks just the new word inline; the rest of the sentence is untouched.
before
the black cat sat on the mat
after
the black cat sat on the mat
A word removed from a sentence #word-delete
word · sentence · delete
Removing a word marks just that word inline; the rest stays clean.
before
the black cat sat on the mat
after
the black cat sat on the mat
Phrase
A reworded phrase keeps its identical lead-in unmarked even when the new text is much longer.
Reworded phrase keeps its shared opening #phrase-edit-common-leadin
phrase · paragraph · edit · consecutive
The bold label and path prefix stay unmarked; only the divergent tail is shown.
before
Assumed directory — ~/dev/awesomele/yi-yi-kitchen-strategy/strategy (the project root). The server bun scripts live in delivery/server; baked in with --cwd.
after
Assumed directory — ~/dev/awesomele/yi-yi-kitchen-strategy/strategy (the project root). The server bun scripts live in delivery/server; baked in with --cwd.
When almost every word changes, mddiff replaces the whole paragraph instead of scrambling it.
before
Every path inside command_body MUST be relative to the workspace root. No cd is ever permitted, and absolute paths to interpreters are forbidden everywhere.
It MUST be runnable as-is from the project root, with the location baked in so the operator never has to cd. There are exactly two cases for how it finds its work.
after
Every path inside command_body MUST be relative to the workspace root. No cd is ever permitted, and absolute paths to interpreters are forbidden everywhere.
It MUST be runnable as-is from the project root, with the location baked in so the operator never has to cd. There are exactly two cases for how it finds its work.
Edited middle, unchanged start and end #paragraph-edit-prefix-suffix
paragraph · page · edit
A heavily edited paragraph that shares a long prefix and suffix keeps both ends unmarked.
before
Tell the operator, in plain language, that the command assumes the workspace root as the working directory. Do NOT bake the absolute path into the command — keep all paths relative to the workspace root soproject root as the working directory. The command must run as-is from there with the location baked in — a script path relative to the root or the runner option, per Inputs. Never put a cd before it; the shell stays there after it finishes.
after
Tell the operator, in plain language, that the command assumes the workspace root as the working directory. Do NOT bake the absolute path into the command — keep all paths relative to the workspace root soproject root as the working directory. The command must run as-is from there with the location baked in — a script path relative to the root or the runner option, per Inputs. Never put a cd before it; the shell stays there after it finishes.
Added paragraph leaves an aligned gap #paragraph-add-gap
paragraph · page · add
An inserted paragraph is a gap (no anchor); the shared heading below stays row-aligned across columns.
before
H
intro
INSERTED
Steps
after
H
intro
INSERTED
Steps
Deleted paragraph: struck out, with an aligned gap #paragraph-delete-gap
paragraph · page · delete
A removed paragraph renders struck-through on the left and leaves an empty slot on the right.
before
keep
DELETE THIS whole paragraph
tail
after
keep
DELETE THIS whole paragraph
tail
Hyperlink
A known exception. A link has two parts — displayed text and embedded URL — and node-htmldiff keys links by URL, so we cannot diff them elegantly. We fall back to a safe whole-block/whole-item replace so nothing is ever silently lost.
Link text changed, URL unchanged (in prose) #hyperlink-text-change-paragraph
hyperlink · paragraph · edit
Same href, new visible text: htmldiff returns no marks, so the safe floor whole-blocks it — the old text survives instead of being silently replaced.
URL changed, link text unchanged #hyperlink-url-change
hyperlink · paragraph · edit
The honest limit: the link is flagged (struck + re-added), but because only the invisible href changed, both sides read identically — the change is signalled, not legible.
Code is literal, so changes are line-level: an edited, added, or removed line word-diffs in place (unchanged lines stay clean), and a whole code block added/removed is a clean block change.
One code line changed #code-line-edit
word · codeblock · edit
Only the changed token on the changed line is marked; the other lines stay clean.
before
const a = 1;
const b = 2;3;
after
const a = 1;
const b = 2;3;
A line added to a code block #code-line-add
phrase · codeblock · add
A new line is marked inline inside the code block; existing lines stay clean.
before
const a = 1;
const b = 2;
after
const a = 1;
const b = 2;
A line removed from a code block #code-line-delete
phrase · codeblock · delete
A removed line is struck inline inside the code block; remaining lines stay clean.
before
const a = 1;
const b = 2;
after
const a = 1;
const b = 2;
A whole code block added #code-block-add
block · codeblock · add
A new fenced block is a whole-block addition; surrounding paragraphs stay aligned.
before
Run it:
npm start
after
after
Run it:
npm start
after
A whole code block removed #code-block-delete
block · codeblock · delete
A removed fenced block renders struck-through and leaves an aligned gap on the right.
before
Run it:
npm start
after
after
Run it:
npm start
after
One character changed on a code line #code-character-edit
character · codeblock · edit · consecutive
A single-digit change is word-diffed in place; the rest of the line stays clean.
before
port = 80808081
after
port = 80808081
One character added to a code token #code-character-add
character · codeblock · add
Inserting a letter (x → xs) re-marks the whole token; the rest of the line stays clean.
before
let xxs = 1;
after
let xxs = 1;
One character removed from a code token #code-character-delete
character · codeblock · delete
The mirror (xs → x) — the same whole-token re-mark.
before
let xsx = 1;
after
let xsx = 1;
A token added to a code line #code-word-add
word · codeblock · add
A trailing comment appended to the line is marked added; the code before it stays clean.
before
const a = 1; // ok
after
const a = 1; // ok
A token removed from a code line #code-word-delete
word · codeblock · delete
A dropped trailing comment is struck; the code before it stays clean.
before
const a = 1;
// ok
after
const a = 1;
// ok
A phrase reworked within a code line #code-phrase-edit
phrase · codeblock · edit · consecutive
Several tokens of a string literal change at once; each changed run is word-diffed in place, the line stays inline.
before
const url = "http:https://a.b.example/v1v2";
after
const url = "http:https://a.b.example/v1v2";
List
Items diff at item level: a light edit stays inline, a heavy rewrite splits cleanly, and an added/removed item leaves an aligned gap with numbering preserved.
Small edit to a list item stays inline #list-item-edit-light
word · list · edit
A small wording change is a word-diff inside the bullet, not a removed + added pair.
before
Use the workspaceproject root here
after
Use the workspaceproject root here
Heavily rewritten item → shown as removed + added #list-item-rewrite-scramble
paragraph · list · edit
Scattered shared vocabulary would scramble a word-diff, so the item becomes a clean removed + added pair; neighbours survive.
before
alpha
Every path inside command_body MUST be relative to the workspace root and no cd is ever permitted there
It MUST be runnable as-is from the project root with the location baked in so cd is never needed at all
omega
after
alpha
Every path inside command_body MUST be relative to the workspace root and no cd is ever permitted there
It MUST be runnable as-is from the project root with the location baked in so cd is never needed at all
omega
Expanded item keeps its shared opening (stays one bullet) #list-item-expand-prefix
phrase · list · edit
An item that roughly tripled in length keeps its shared lead-in unmarked and stays a single bullet.
before
alpha
command_body (required) — the actual command path, kept relative to the workspace root with no absolute paths allowed anywhere.which must run as-is from the project root with its location baked in, using either a relative path or the runner cwd option, where absolute paths stay forbidden because they break portability across machines, and there are exactly two distinct mechanisms to choose between here.
omega
after
alpha
command_body (required) — the actual command path, kept relative to the workspace root with no absolute paths allowed anywhere.which must run as-is from the project root with its location baked in, using either a relative path or the runner cwd option, where absolute paths stay forbidden because they break portability across machines, and there are exactly two distinct mechanisms to choose between here.
omega
Removed item leaves an aligned gap; numbering preserved #list-item-delete-gap
paragraph · list · delete
A removed bullet shows an empty slot at the matching row in the new column; the ordered list still reads 1, 2.
before
keep one
DROP ME
keep two
after
keep one
DROP ME
keep two
Added item leaves an aligned gap on the left #list-item-add-gap
paragraph · list · add
A new bullet shows on the right; the old column gets an aligned empty slot. Mirror of the deletion case, numbering preserved.
before
keep one
NEW MIDDLE
keep two
after
keep one
NEW MIDDLE
keep two
One character changed in a list item #list-character-edit
character · list · edit · consecutive
A single-letter fix word-diffs in place inside the bullet.
before
the colourcolor swatch here
after
the colourcolor swatch here
One character added in a list item #list-character-add
character · list · add
Inserting a letter (cat → cart) re-marks the whole word; the bullet stays one item.
before
the catcart naps here
after
the catcart naps here
One character removed in a list item #list-character-delete
character · list · delete
The mirror (cart → cat) — same whole-word re-mark.
before
the cartcat naps here
after
the cartcat naps here
A word added to a list item #list-word-add
word · list · add
A new word is marked inline inside the bullet; the rest stays clean.
before
Use the project root here
after
Use the project root here
A word removed from a list item #list-word-delete
word · list · delete
A dropped word is struck inline; the rest of the bullet stays clean.
before
Use the project root here
after
Use the project root here
A phrase appended to a list item #list-phrase-add
phrase · list · add
A trailing phrase is marked inline; the item stays one bullet.
before
Keep all paths relative to the project root
after
Keep all paths relative to the project root
A phrase removed from a list item #list-phrase-delete
phrase · list · delete
A trailing phrase struck inline; the kept text stays clean.
before
Keep all paths relative to the project root
after
Keep all paths relative to the project root
One sentence changed in a list item #list-sentence-edit
sentence · list · edit
The first sentence stays clean; only the second sentence’s tail is marked.
before
The server runs locally. Deploys happen nightly.on every merge.
after
The server runs locally. Deploys happen nightly.on every merge.
A sentence added to a list item #list-sentence-add
sentence · list · add
A new trailing sentence is marked inline; the existing sentence stays clean.
before
Deploys run nightly. Rollbacks are automatic.
after
Deploys run nightly. Rollbacks are automatic.
A sentence removed from a list item #list-sentence-delete
sentence · list · delete
A dropped trailing sentence struck inline; the remaining sentence stays clean.
before
Deploys run nightly. Rollbacks are automatic.
after
Deploys run nightly. Rollbacks are automatic.
A link in a list item changed (text + URL) #list-hyperlink-edit
hyperlink · list · edit
Both the text and the href change, so htmldiff marks the link cleanly inside the bullet — it stays one item.
An honest limitation: mddiff does not detect a move, so swapping two entries shows as each position’s link changing in place rather than a clean reorder.
Tables diff structurally (columns / rows / cells). Each projected side-by-side column stays rectangular — no phantom empty cells, the way a naive HTML word-diff produces.