Skip to content

Visualize cumulative changes across multiple text revisions with intensity-based highlighting

License

Notifications You must be signed in to change notification settings

rossshannon/deep-diffs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Deep Diffs Header

Deep Diffs

Visualise cumulative changes across multiple text revisions. Regions, words or characters that have been edited multiple times are highlighted with increasing intensity.

Deep diff example

Unlike normal diff tools that compare two versions, deep-diffs can visualise where changes have been made in a document throughout multiple revisions over time — giving you a “heatmap” of editorial activity, particularly where details are being worked onor revised repeatedly.

Use Cases

  • Legal document review — see which clauses have been contentious across redlining rounds
  • Collaborative writing — identify paragraphs that have already been finessed
  • Wikipedia-style editing — spot edit-war hotspots
  • Code review — find code blocks that have been tweaked repeatedly

Installation

npm install @rossshannon/deep-diffs

Quick Start

import { deepDiffHtml, getDefaultStyles } from '@rossshannon/deep-diffs';

// A contract clause being refined over multiple revisions
const revisions = [
  'The client shall pay the invoice.',
  'The client shall pay the invoice within 30 days.',
  'The client shall pay the invoice in full within 30 days.',
  'The client shall pay the invoice in full within 30 business days.',
];

const html = deepDiffHtml(revisions);
const css = getDefaultStyles();

Output:

The client shall pay the invoice<ins class="deep-diff"> <ins class="deep-diff">in full
</ins>within 30 <ins class="deep-diff">business </ins>days</ins>.

Notice the nested <ins> tags — “within 30 days” was added first, then “in full” was inserted inside that region, then “business” was added. The nesting depth indicates how many times a region has been edited. The default CSS styles these with increasing background intensity, creating a visual heatmap of editorial activity. After multiple rounds of editing, a richer picture of the editorial process emerges.

API

computeDeepDiff(revisions, options?)

Computes diff markers without rendering.

import { computeDeepDiff } from '@rossshannon/deep-diffs';

const { text, markers } = computeDeepDiff(revisions);

// text: the final revision text
// markers: array of { start, end, enabled } marker objects

Options:

  • skipEmpty (boolean, default true) — skip empty revisions (useful for filtering vandalism)
  • timeout (number, default 1) — diff computation timeout in seconds

renderWithMarkers(text, markers, options?)

Renders text with markers as HTML.

import { computeDeepDiff, renderWithMarkers } from '@rossshannon/deep-diffs';

const { text, markers } = computeDeepDiff(revisions);
const html = renderWithMarkers(text, markers, {
  tagName: 'mark',
  className: 'changed'
});

Options:

  • tagName (string, default 'ins') — HTML tag for markers
  • className (string, default 'deep-diff') — CSS class for tags

deepDiffHtml(revisions, options?)

Convenience function combining computeDeepDiff and renderWithMarkers.

import { deepDiffHtml } from '@rossshannon/deep-diffs';

const html = deepDiffHtml(revisions, { skipEmpty: true });

getDefaultStyles(maxDepth?)

Generates CSS for nested marker intensity.

import { getDefaultStyles } from '@rossshannon/deep-diffs';

const css = getDefaultStyles(5);
// Returns CSS with increasingly intense backgrounds for nested .deep-diff elements

How It Works

  1. Chain diffs — compute diffs between each consecutive revision pair
  2. Track markers — maintain a list of changed regions with [start, end] positions
  3. Transform markers — as new edits occur, existing markers shift, expand, or contract:
    • Insert before marker → shift right
    • Insert within marker → expand
    • Delete before marker → shift left
    • Delete within marker → contract (disable if fully subsumed)
  4. Accumulate — new insertions add new markers; nesting depth = change frequency
  5. Render — interleave tags at marker boundaries

This is essentially a simplified form of operational transformation — the same conceptual framework that powers real-time collaboration in Google Docs.

Browser Usage

<script type="module">
  import { deepDiffHtml, getDefaultStyles } from 'https://unpkg.com/@rossshannon/deep-diffs';

  const style = document.createElement('style');
  style.textContent = getDefaultStyles();
  document.head.appendChild(style);

  document.getElementById('output').innerHTML = deepDiffHtml(myRevisions);
</script>

TypeScript

Full TypeScript support with bundled type definitions:

import {
  deepDiffHtml,
  computeDeepDiff,
  type Marker,
  type DeepDiffResult
} from '@rossshannon/deep-diffs';

const result: DeepDiffResult = computeDeepDiff(['v1', 'v2', 'v3']);
console.log(result.text);           // string
console.log(result.markers);        // Marker[]

const html: string = deepDiffHtml(['v1', 'v2'], {
  tagName: 'mark',
  className: 'highlight'
});

Limitations

  • Character-indexed markers — This techique is tuned for tracking how details change in text over time. Large structural refactors (e.g., replacing or moving paragraphs) will lose some of the necessary context.
  • No move detection — if text is cut and pasted elsewhere, it's treated as delete + insert, not a move.

Prior Art & Inspiration

History

Original algorithm by Ross Shannon (2010). Modernised and published 2026.

License

MIT

About

Visualize cumulative changes across multiple text revisions with intensity-based highlighting

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •