A line diff paints the whole line. This marks the one token.
Diff Poster turns a before/after snippet into a clean shareable image, diffing at the token level so a renamed variable reads clearly instead of the whole line going red.
Rewrite return 'hi ' + name; as return `hello, ${name}!`; and a normal diff has one thing
to say: this line changed. The whole thing goes red, a copy goes green underneath, and the
reader has to eyeball two lines to find the actual edit. But the edit is small. You swapped a
concatenation for a template literal. That's the interesting part, and a line diff buries it.
Diff Poster is a tool for sharing exactly that kind of change: paste the old code, paste the new code, get a single clean image of just the diff, sized for a tweet or a Slack message. No login, no export dialog, nothing uploaded. It exists because the two normal options both fight you. A raw editor screenshot drags in gutters, minimaps, line numbers, and whatever window chrome was open. A tool like Carbon or Snappify is gorgeous but built for a bigger job, themes and gradients and multi-slide decks, when all you wanted was to quote-tweet one function.
Diffing at the token, not the line
The whole product rides on one decision: the diff runs over tokens, not lines. Instead of
comparing the before and after as two lists of strings, it first tokenizes each side into the
actual lexical pieces, identifiers, operators, string literals, comments kept atomic, and then
diffs those two streams. So in the example above it marks 'hi ' + name as removed and
`hello, ${name}!` as added, and leaves return and the semicolon untouched, because they
survived the edit. The reader sees the substitution, not "somewhere on this line, something."
The diff itself is a textbook longest-common-subsequence pass over the token streams, the same
algorithm behind git diff and diff(1). LCS finds the longest run of tokens that appears in
both versions in order; anything not on that shared subsequence is what got removed or added.
It's simple, well understood, and plenty fast for a function-sized snippet. It is deliberately
not built for diffing whole files: a wholesale rewrite, a for-loop turned into a reduce,
interleaves so many removed and added tokens that the inline view gets noisy, exactly the way
GitHub's word-diff does on a big hunk. The tool is tuned for the localized change, which is
also the change most worth sharing.
Why it draws to a canvas
The output is not styled HTML that gets screenshotted. It's rendered directly onto a
<canvas>: the window chrome, the line numbers, the syntax coloring, and the red/green diff
tint are all drawn by hand, pixel by pixel. The lazy path would have been to build a nice DOM
diff view and rasterize it with something like html2canvas, but DOM-to-image libraries are
notorious for font-metrics and layout drift, the exported PNG never quite matches what you saw.
Drawing to canvas trades that convenience for exact control: the image is a fixed 1200×675, the
16:9 frame Twitter, Bluesky, and Slack all crop link previews to, rendered at your screen's
pixel density so it stays crisp on a retina display.
Everything runs client-side. Tokenizing, diffing, laying out the rows, coloring, and exporting
the PNG all happen in your browser, which is why there's no account, no backend, and no
question about where your snippet went. It's a static Vite app, no framework, a handful of pure
functions chained end to end: tokenize to diffTokens to the row-fitting math to
classifyToken to the canvas renderer to a PNG blob. The whole thing is on
GitHub if you'd rather read it than trust it.
One honest limitation worth naming: copy-to-clipboard for images isn't universal. Where the
browser can write a PNG to the clipboard, Copy does it in one click; where it can't, Copy is
clearly disabled and Download always works. The downloaded file is named
diff-poster-YYYYMMDD-HHMMSS.png so a folder of them sorts itself by time.
Try it
Open Diff Poster, drop your original code in the Before pane and the changed version in After, pick the language for syntax coloring (JavaScript, Python, or plain text), and hit Generate image. Then Copy or Download, no dialog either way, and paste it wherever you were going to describe the change in words.
This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.
Loading comments…