How Archify Computes Diffs Between Two Architecture Snapshots
Archify computes diffs between two architecture snapshots using a three-stage structural algorithm that indexes JSON representations into hash maps for O(1) lookups, detects added and removed nodes and edges by comparing identifiers, and performs deep property comparisons to identify attribute changes, all executed client-side in O(N+M) time.
Archify is an open-source architecture visualization tool (tt-a1i/archify) that tracks system evolution by comparing plain-JSON snapshots of your diagrams. When you trigger a comparison—typically by selecting "Show changes" in the interface—the tool executes a structural diff routine that identifies exactly what moved, what was deleted, and what properties changed between two points in time.
Understanding Archify's Snapshot Format
Archify serializes every diagram into a JSON object containing three core collections. The nodeIds property holds an ordered list of node identifiers, while edges stores an array of edge descriptors defined by source and target node indices. Additional metadata—including source, target, hops, direction, and depths—provides semantic context for reachability and routing views.
This structure enables the diff algorithm to treat snapshots as immutable data structures, making it possible to calculate deltas using simple set operations and property traversals.
The Three-Stage Structural Diff Algorithm
When Archify computes diffs between two architecture snapshots, it executes a deterministic three-stage process that runs in linear time relative to the number of nodes and edges.
Stage 1: Indexing Snapshots for O(1) Lookups
Before comparison, both snapshots are converted into fast-lookup data structures. The algorithm builds Map objects mapping nodeId to array index (nodeMapA and nodeMapB) and Set objects containing stringified edge tuples (edgeSetA and edgeSetB).
According to the source code in generated/maka-regenerated.workflow.html (lines 5795–5830), this indexing step transforms the linear arrays into hash maps, ensuring that subsequent existence checks operate in constant time.
Stage 2: Detecting Added and Removed Elements
The algorithm iterates through snapshotA.nodeIds to identify removed nodes—any identifier not present in nodeMapB is recorded as deleted. Conversely, iterating through snapshotB.nodeIds identifies added nodes by checking against nodeMapA.
For edges, the routine generates unique keys using the tuple pattern <fromId, toId>. By checking membership in edgeSetA versus edgeSetB, Archify classifies edges as added or removed without deep traversal of the graph structure.
Stage 3: Detecting Attribute Changes
For nodes existing in both snapshots, the algorithm performs a property-by-property comparison of the node payload—including labels, types, and custom fields—recording any mismatch where snapshotA[node].prop !== snapshotB[node].prop.
Edges undergo similar scrutiny: the routine compares properties like depth, label, and custom data for edges that share the same source and target identifiers across both snapshots.
Diff Output Structure
The resulting diff object follows a strict schema that separates structural changes from attribute mutations:
{
"addedNodes": ["n12", "n13"],
"removedNodes": ["n05"],
"addedEdges": [{"from":"n01","to":"n12"}],
"removedEdges": [{"from":"n03","to":"n04"}],
"changedNodes": {
"n07": {"label": {"old":"Auth", "new":"Authorization"}}
},
"changedEdges": {
"e14": {"depth": {"old":1, "new":2}}
}
}
This format allows the UI layer to apply distinct visual treatments—green halos for additions, red strikethroughs for deletions, and yellow badges for modifications—without reparsing the raw snapshot data.
Implementation Details and Performance
Several implementation choices in tt-a1i/archify ensure the diff computation remains efficient even for large architectures.
Snapshot Validation: Before diffing, the validation guard at generated/maka-regenerated.workflow.html (lines 5796–5810) verifies that snapshots contain required arrays, consistent lengths, and matching source/target identifiers.
Linear Time Complexity: By utilizing Map and Set structures, the algorithm achieves O(N+M) performance where N represents node count and M edge count. This eliminates the need for nested iteration.
Deterministic Ordering: Because nodeIds is an ordered array, Archify can detect layout-only changes—such as node reordering—by comparing index positions, enabling "layout diff" views that highlight structural rearrangements without semantic changes.
Computing Diffs in the Browser
You can programmatically compute diffs between two snapshots obtained from Archify.focus.reachabilitySnapshot() or similar APIs using this implementation pattern:
// Assume `snapA` and `snapB` are plain JSON snapshots
function computeArchifyDiff(snapA, snapB) {
// Build fast lookup tables
const nodeMapA = new Map(snapA.nodeIds.map((id, i) => [id, i]));
const nodeMapB = new Map(snapB.nodeIds.map((id, i) => [id, i]));
const edgeKey = (e) => `${e.from}-${e.to}`;
const edgeSetA = new Set(snapA.edges.map(edgeKey));
const edgeSetB = new Set(snapB.edges.map(edgeKey));
const diff = {
addedNodes: [], removedNodes: [], addedEdges: [], removedEdges: [],
changedNodes: {}, changedEdges: {}
};
// Detect removed and added nodes
for (const id of snapA.nodeIds) {
if (!nodeMapB.has(id)) diff.removedNodes.push(id);
}
for (const id of snapB.nodeIds) {
if (!nodeMapA.has(id)) diff.addedNodes.push(id);
}
// Detect attribute changes for existing nodes
for (const id of snapA.nodeIds) {
if (nodeMapB.has(id)) {
const a = snapA.nodes?.[id] ?? {};
const b = snapB.nodes?.[id] ?? {};
const changes = {};
for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
if (a[key] !== b[key]) changes[key] = { old: a[key], new: b[key] };
}
if (Object.keys(changes).length) diff.changedNodes[id] = changes;
}
}
// Detect edge changes
for (const e of snapA.edges) {
if (!edgeSetB.has(edgeKey(e))) diff.removedEdges.push(e);
}
for (const e of snapB.edges) {
if (!edgeSetA.has(edgeKey(e))) diff.addedEdges.push(e);
}
// Detect attribute changes for existing edges
for (const e of snapA.edges) {
const key = edgeKey(e);
if (edgeSetB.has(key)) {
const a = snapA.edgeData?.[key] ?? {};
const b = snapB.edgeData?.[key] ?? {};
const changes = {};
for (const k of new Set([...Object.keys(a), ...Object.keys(b)])) {
if (a[k] !== b[k]) changes[k] = { old: a[k], new: b[k] };
}
if (Object.keys(changes).length) diff.changedEdges[key] = changes;
}
}
return diff;
}
Applying Visual Diff Highlights
Once computed, the diff object drives the visual layer. The following pattern demonstrates how to decorate DOM elements based on the computed delta:
function highlightDiff(diff) {
diff.addedNodes.forEach(id =>
document.querySelector(`[data-node="${id}"]`)?.classList.add('added')
);
diff.removedNodes.forEach(id =>
document.querySelector(`[data-node="${id}"]`)?.classList.add('removed')
);
diff.addedEdges.forEach(e => {
const el = document.querySelector(`[data-edge="${e.from}-${e.to}"]`);
if (el) el.classList.add('added');
});
diff.removedEdges.forEach(e => {
const el = document.querySelector(`[data-edge="${e.from}-${e.to}"]`);
if (el) el.classList.add('removed');
});
}
Summary
- Archify represents architecture states as plain-JSON snapshots containing
nodeIds,edges, andmetadata. - The diff algorithm runs in three stages: indexing into hash maps, detecting structural additions and removals, and comparing attributes.
- Implementation in
generated/maka-regenerated.workflow.htmlachieves O(N+M) time complexity usingMapandSetstructures. - Validation guards at lines 5796–5810 ensure snapshot integrity before comparison.
- The resulting diff object separates
addedNodes,removedNodes,addedEdges,removedEdges, and attribute changes for both nodes and edges. - Because the routine operates on JSON, it executes entirely in the browser without server dependencies.
Frequently Asked Questions
What is the time complexity of Archify's diff algorithm?
The algorithm operates in O(N+M) time, where N is the number of nodes and M is the number of edges. By converting snapshots into Map and Set data structures during the indexing phase, all lookup operations become constant time, avoiding the O(N²) penalty of nested array searches.
Can Archify detect changes in node ordering or layout?
Yes. Because the nodeIds array preserves order, the diff routine can identify when a node changes position in the sequence even if its properties remain identical. This enables "layout-only" diffs that highlight structural rearrangements in the diagram without semantic modifications.
Where does the snapshot validation logic reside?
Snapshot validation occurs in generated/maka-regenerated.workflow.html between lines 5796–5810. This guard verifies that snapshots contain the required nodeIds and edges arrays, that array lengths are consistent, and that edge source/target references match existing node identifiers before the diff computation begins.
Does Archify require a server to compute diffs?
No. The diff computation runs entirely client-side because it operates on plain-JSON snapshot objects. You can capture snapshots using the helper defined in scripts/start-template.html, store them as JSON files (like examples/checkout-platform-delta.receipt.json or examples/rag-pipeline.architecture.json), and compare them in the browser without network latency.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →