Volume 3: Human-System Collaboration

Pattern 19: Version Control

Part II: Interaction Patterns - Temporal Patterns


Opening Scenario: The Contract That Needed a Do-Over

Brian was finalizing a major software licensing contract worth $500,000. He'd been negotiating with the client for three weeks. The current version looked perfect.

His manager, Karen, reviewed it and said: "This is good, but I think we gave up too much on the liability clause. What did Version 4 say about that?"

Brian checked the system:

Contract_Final.docx
Last modified: December 12, 2024

That was it. One file. No versions. No history.

"I don't know what Version 4 said," Brian admitted. "I've been overwriting the same file each time."

Karen's eyes widened. "You don't have the previous versions?"

"No. I just saved changes as I went."

Three weeks of negotiation. Twelve rounds of revisions. All lost except the final version.

Karen remembered: "Version 4 had better liability terms. We need to get those back and combine them with the pricing from Version 7."

They couldn't. The intermediate versions were gone forever.

Brian had to restart negotiations from scratch. Two more weeks. The client threatened to walk away. The deal nearly died.


The legal department's IT manager, Susan, heard about this disaster. She investigated how contracts were being managed:

// Bad: Overwrites previous versions
function saveContract(contractId, content) {
  database.update(contractId, {
    content: content,
    lastModified: new Date()
  });

  // Previous content is lost forever
  // Can't undo changes
  // Can't compare versions
  // Can't restore previous state
}

She found more horror stories:

Accidental deletion: - Lawyer deleted critical paragraph - Saved file - Realized mistake next day - Paragraph gone forever

Concurrent editing: - Two lawyers editing same contract - Both saved changes - Second save overwrote first lawyer's work - Hours of work lost

"What did we agree to?": - Client: "You changed this clause after we approved it" - Company: "No we didn't" - No way to prove who was right - Trust broken, relationship damaged


Susan built a comprehensive version control system:

class VersionControlSystem {
  saveVersion(document, content, metadata) {
    const version = {
      versionNumber: this.getNextVersionNumber(document.id),
      documentId: document.id,
      content: content,
      timestamp: new Date().toISOString(),
      author: metadata.userId,
      authorName: metadata.userName,
      comment: metadata.comment,
      significant: metadata.significant || false,
      tags: metadata.tags || [],
      parentVersion: this.getCurrentVersion(document.id),
      hash: this.computeHash(content)
    };

    // Store version permanently
    this.versionStore.insert(version);

    // Update current pointer
    this.updateCurrentVersion(document.id, version.versionNumber);

    return version;
  }

  getVersion(documentId, versionNumber) {
    return this.versionStore.query({
      documentId: documentId,
      versionNumber: versionNumber
    });
  }

  getAllVersions(documentId) {
    return this.versionStore.query({
      documentId: documentId,
      orderBy: 'versionNumber DESC'
    });
  }

  compareVersions(documentId, version1, version2) {
    const v1 = this.getVersion(documentId, version1);
    const v2 = this.getVersion(documentId, version2);

    return this.diff(v1.content, v2.content);
  }

  restoreVersion(documentId, versionNumber, metadata) {
    const version = this.getVersion(documentId, versionNumber);

    // Create new version with old content
    return this.saveVersion(
      { id: documentId },
      version.content,
      {
        ...metadata,
        comment: `Restored from version ${versionNumber}: ${version.comment}`
      }
    );
  }
}

Now when Brian worked on contracts:

Contract #5847 - Software Licensing Agreement

Current Version: 12
Last Modified: Dec 12, 2024 3:47 PM by Brian Wilson

[Version History]
┌────────────────────────────────────────────────────┐
│ v12 Dec 12, 3:47 PM - Brian Wilson               │
│     "Final version with approved pricing"         │
│                                                    │
│ v11 Dec 11, 4:23 PM - Brian Wilson               │
│     "Revised liability clause per legal review"   │
│                                                    │
│ v10 Dec 11, 2:15 PM - Karen Chen (Manager)       │
│     "Adjusted payment terms"                       │
│                                                    │
│ v7  Dec 9, 11:30 AM - Brian Wilson ⭐            │
│     "Best pricing agreed - TAG: pricing-approved" │
│                                                    │
│ v4  Dec 6, 2:00 PM - Brian Wilson ⭐             │
│     "Liability terms acceptable - TAG: legal-ok"  │
└────────────────────────────────────────────────────┘

[Compare] [Restore] [Tag Version]

When Karen asked about Version 4:

[Comparing Version 4 vs Version 12]

Section 7: Liability Limitations

Version 4 (Dec 6):
  "Company's total liability shall not exceed the greater of 
   (i) amount paid under this Agreement or (ii) $50,000."

Version 12 (Dec 12):
  "Company's total liability shall not exceed the amount 
   paid under this Agreement in the 12 months preceding 
   the claim."

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ANALYSIS: Version 4 had $50,000 floor. Version 12 removed it.

[Restore Version 4 Language] [Create Hybrid Version]

Brian could see exactly what changed. He created Version 13 combining: - Liability language from Version 4 (better protection) - Pricing from Version 7 (agreed terms) - Final edits from Version 12 (polish)

Do-overs made possible. Contract saved. Deal closed. Everyone happy.

Context

Version Control applies when:

Documents evolve over time: Multiple rounds of editing and revision

Mistakes happen: Need ability to undo changes

Collaboration required: Multiple people editing same document

Comparison needed: "What changed between these two versions?"

Compliance mandates: Must track all changes for audit

Experimentation wanted: Try changes without fear of breaking things

Historical reference: Need to see "what did we agree to in March?"

Rollback capability: Return to previous working state

Problem Statement

Most systems lack proper version control, causing data loss and workflow problems:

Overwriting data:

// Bad: Destroys previous versions
UPDATE documents 
SET content = 'New content'
WHERE id = 123;

// Old content is gone
// Can't undo
// Can't compare
// Can't restore

No version history:

// Bad: Only current state
{
  id: 123,
  content: "Current content",
  lastModified: "2024-12-12"
}

// How many times edited?
// Who made previous changes?
// What did it look like yesterday?
// Unknown!

Lost intermediate work:

Version 1: Original draft
Version 2: Manager edits (lost)
Version 3: Legal review (lost)
Version 4: Client feedback (lost)
Version 5: Final (only this exists)

// All the intermediate wisdom is gone
// Can't see evolution of thinking
// Can't learn from process

No comparison capability:

// Bad: Can't see what changed
function whatChanged(oldDoc, newDoc) {
  return "Something changed, but we don't know what";
}

// Users confused about differences
// Can't review changes effectively
// Disputes over "who changed what"

Concurrent edit conflicts:

User A: Opens document at 2:00 PM
User B: Opens document at 2:05 PM
User A: Saves changes at 2:30 PM
User B: Saves changes at 2:35 PM

Result: User A's work is lost
        Only User B's changes survive

No rollback:

// Bad: Can't undo bulk changes
function deleteAllComments() {
  document.comments = [];
  save(document);
}

// Comments deleted
// No way to get them back
// Irreversible mistake

We need comprehensive version control that preserves history, enables comparison, supports collaboration, and allows safe experimentation.

Forces

Storage vs History

  • Complete version history uses storage
  • But history is valuable
  • Balance retention with costs

Granularity vs Usability

  • Version every keystroke?
  • Or only manual saves?
  • Balance detail with practicality

Automatic vs Manual

  • Auto-version provides safety net
  • But creates version spam
  • Balance protection with clarity

Linear vs Branching

  • Linear history is simple
  • But collaboration needs branches
  • Balance simplicity with power

Performance vs Completeness

  • Storing full copies is slow
  • But diffs can be complex
  • Balance speed with reliability

Solution

Implement comprehensive version control system that automatically preserves every save as immutable version, enables comparison between any versions, supports restoration of previous states, and provides clear visualization of document evolution.

The pattern has seven key strategies:

1. Immutable Version Storage

Store every version permanently:

class ImmutableVersionStore {
  constructor() {
    this.versions = new Map();
    this.currentPointers = new Map();
  }

  saveVersion(documentId, content, metadata) {
    const versionNumber = this.getNextVersionNumber(documentId);

    const version = {
      id: this.generateVersionId(),
      versionNumber: versionNumber,
      documentId: documentId,

      // Content (immutable)
      content: Object.freeze(this.deepClone(content)),
      contentHash: this.hashContent(content),
      contentSize: JSON.stringify(content).length,

      // Metadata
      timestamp: new Date().toISOString(),
      author: metadata.author,
      authorName: metadata.authorName,
      authorEmail: metadata.authorEmail,

      // Version info
      comment: metadata.comment || '',
      significant: metadata.significant || false,
      tags: metadata.tags || [],

      // Lineage
      parentVersion: this.getCurrentVersion(documentId),
      childVersions: [],

      // Storage optimization
      storageType: this.determineStorageType(documentId, content),
      delta: null,  // For delta storage
      baseVersion: null  // For delta storage
    };

    // Update parent's children list
    if (version.parentVersion) {
      const parent = this.getVersion(documentId, version.parentVersion);
      parent.childVersions.push(versionNumber);
    }

    // Store based on strategy
    if (version.storageType === 'full') {
      this.storeFullVersion(version);
    } else {
      this.storeDeltaVersion(version, content);
    }

    // Update current pointer
    this.currentPointers.set(documentId, versionNumber);

    return version;
  }

  determineStorageType(documentId, content) {
    const versions = this.getVersions(documentId);

    // Store full version every 10 versions
    if (versions.length % 10 === 0) {
      return 'full';
    }

    // Store full if content dramatically different
    const current = this.getCurrentVersionContent(documentId);
    if (current) {
      const similarity = this.calculateSimilarity(current, content);
      if (similarity < 0.3) {
        return 'full';  // Less than 30% similar
      }
    }

    return 'delta';  // Store as delta
  }

  storeFullVersion(version) {
    this.versions.set(version.id, version);
  }

  storeDeltaVersion(version, content) {
    const parent = this.getVersion(
      version.documentId, 
      version.parentVersion
    );

    // Compute delta from parent
    const delta = this.computeDelta(parent.content, content);

    version.delta = delta;
    version.baseVersion = version.parentVersion;
    version.content = null;  // Don't store full content

    this.versions.set(version.id, version);
  }

  getVersion(documentId, versionNumber) {
    const versions = Array.from(this.versions.values()).filter(v =>
      v.documentId === documentId && v.versionNumber === versionNumber
    );

    if (versions.length === 0) {
      return null;
    }

    const version = versions[0];

    // If delta-stored, reconstruct content
    if (version.storageType === 'delta') {
      version.content = this.reconstructContent(version);
    }

    return version;
  }

  reconstructContent(version) {
    // Get base version
    const base = this.getVersion(version.documentId, version.baseVersion);

    // Apply delta
    return this.applyDelta(base.content, version.delta);
  }

  computeDelta(oldContent, newContent) {
    // Simplified - real implementation would use sophisticated diff
    return {
      type: 'json-patch',
      patches: this.jsonDiff(oldContent, newContent)
    };
  }

  applyDelta(baseContent, delta) {
    // Apply patches to reconstruct content
    let content = this.deepClone(baseContent);

    delta.patches.forEach(patch => {
      this.applyPatch(content, patch);
    });

    return content;
  }
}

2. Version Comparison

Compare any two versions:

class VersionComparison {
  compareVersions(version1, version2) {
    const comparison = {
      version1: {
        number: version1.versionNumber,
        timestamp: version1.timestamp,
        author: version1.authorName
      },
      version2: {
        number: version2.versionNumber,
        timestamp: version2.timestamp,
        author: version2.authorName
      },
      differences: this.findDifferences(version1.content, version2.content),
      statistics: this.calculateStatistics(version1.content, version2.content)
    };

    return comparison;
  }

  findDifferences(content1, content2) {
    const diffs = [];

    // Text comparison
    if (typeof content1 === 'string' && typeof content2 === 'string') {
      return this.textDiff(content1, content2);
    }

    // Object comparison
    if (typeof content1 === 'object' && typeof content2 === 'object') {
      return this.objectDiff(content1, content2);
    }

    return [{
      type: 'type-change',
      from: typeof content1,
      to: typeof content2
    }];
  }

  textDiff(text1, text2) {
    // Use diff algorithm (Myers, Hunt-McIlroy, etc.)
    const lines1 = text1.split('\n');
    const lines2 = text2.split('\n');

    const changes = [];
    let i = 0, j = 0;

    while (i < lines1.length || j < lines2.length) {
      if (i >= lines1.length) {
        // Addition
        changes.push({
          type: 'addition',
          lineNumber: j + 1,
          content: lines2[j]
        });
        j++;
      } else if (j >= lines2.length) {
        // Deletion
        changes.push({
          type: 'deletion',
          lineNumber: i + 1,
          content: lines1[i]
        });
        i++;
      } else if (lines1[i] === lines2[j]) {
        // No change
        i++;
        j++;
      } else {
        // Modification
        changes.push({
          type: 'modification',
          lineNumber: i + 1,
          oldContent: lines1[i],
          newContent: lines2[j]
        });
        i++;
        j++;
      }
    }

    return changes;
  }

  objectDiff(obj1, obj2) {
    const changes = [];

    // Check all keys in obj1
    Object.keys(obj1).forEach(key => {
      if (!(key in obj2)) {
        changes.push({
          type: 'field-removed',
          field: key,
          oldValue: obj1[key]
        });
      } else if (obj1[key] !== obj2[key]) {
        changes.push({
          type: 'field-modified',
          field: key,
          oldValue: obj1[key],
          newValue: obj2[key]
        });
      }
    });

    // Check for new keys in obj2
    Object.keys(obj2).forEach(key => {
      if (!(key in obj1)) {
        changes.push({
          type: 'field-added',
          field: key,
          newValue: obj2[key]
        });
      }
    });

    return changes;
  }

  calculateStatistics(content1, content2) {
    const stats = {
      additions: 0,
      deletions: 0,
      modifications: 0
    };

    const diffs = this.findDifferences(content1, content2);

    diffs.forEach(diff => {
      if (diff.type === 'addition' || diff.type === 'field-added') {
        stats.additions++;
      } else if (diff.type === 'deletion' || diff.type === 'field-removed') {
        stats.deletions++;
      } else if (diff.type === 'modification' || diff.type === 'field-modified') {
        stats.modifications++;
      }
    });

    stats.totalChanges = stats.additions + stats.deletions + stats.modifications;

    return stats;
  }

  renderDiff(comparison) {
    let html = '<div class="version-comparison">';

    html += `
      <div class="comparison-header">
        <div class="version-info">
          <strong>Version ${comparison.version1.number}</strong>
          ${comparison.version1.timestamp}
          by ${comparison.version1.author}
        </div>
        <div class="comparison-arrow">→</div>
        <div class="version-info">
          <strong>Version ${comparison.version2.number}</strong>
          ${comparison.version2.timestamp}
          by ${comparison.version2.author}
        </div>
      </div>
    `;

    html += '<div class="statistics">';
    html += `<span class="additions">+${comparison.statistics.additions}</span>`;
    html += `<span class="deletions">-${comparison.statistics.deletions}</span>`;
    html += `<span class="modifications">~${comparison.statistics.modifications}</span>`;
    html += '</div>';

    html += '<div class="differences">';
    comparison.differences.forEach(diff => {
      html += this.renderDifference(diff);
    });
    html += '</div>';

    html += '</div>';
    return html;
  }

  renderDifference(diff) {
    if (diff.type === 'addition') {
      return `
        <div class="diff-line addition">
          <span class="line-number">+${diff.lineNumber}</span>
          <span class="content">${diff.content}</span>
        </div>
      `;
    }

    if (diff.type === 'deletion') {
      return `
        <div class="diff-line deletion">
          <span class="line-number">-${diff.lineNumber}</span>
          <span class="content">${diff.content}</span>
        </div>
      `;
    }

    if (diff.type === 'modification') {
      return `
        <div class="diff-line modification">
          <div class="old">
            <span class="line-number">${diff.lineNumber}</span>
            <del>${diff.oldContent}</del>
          </div>
          <div class="new">
            <span class="line-number">${diff.lineNumber}</span>
            <ins>${diff.newContent}</ins>
          </div>
        </div>
      `;
    }
  }
}

3. Version Restoration

Restore any previous version:

class VersionRestoration {
  constructor(versionStore) {
    this.versionStore = versionStore;
  }

  restoreVersion(documentId, targetVersion, metadata) {
    // Get the version to restore
    const version = this.versionStore.getVersion(documentId, targetVersion);

    if (!version) {
      throw new Error(`Version ${targetVersion} not found`);
    }

    // Create new version with restored content
    const restoredVersion = this.versionStore.saveVersion(
      documentId,
      version.content,
      {
        author: metadata.userId,
        authorName: metadata.userName,
        authorEmail: metadata.userEmail,
        comment: `Restored from version ${targetVersion}: ${version.comment}`,
        significant: true,
        tags: ['restored', `from-v${targetVersion}`]
      }
    );

    return {
      restoredVersion: restoredVersion.versionNumber,
      restoredFrom: targetVersion,
      restoredContent: version.content,
      restoredBy: metadata.userName,
      restoredAt: new Date()
    };
  }

  createHybridVersion(documentId, sourceVersions, metadata) {
    // Combine content from multiple versions
    const versions = sourceVersions.map(vNum =>
      this.versionStore.getVersion(documentId, vNum)
    );

    // Merge content (domain-specific logic)
    const hybridContent = this.mergeVersions(versions, metadata.mergeStrategy);

    // Save as new version
    const hybridVersion = this.versionStore.saveVersion(
      documentId,
      hybridContent,
      {
        ...metadata,
        comment: `Hybrid version from v${sourceVersions.join(', v')}`,
        significant: true,
        tags: ['hybrid', ...sourceVersions.map(v => `from-v${v}`)]
      }
    );

    return hybridVersion;
  }

  mergeVersions(versions, strategy) {
    if (strategy === 'field-by-field') {
      return this.mergeFieldByField(versions);
    }

    if (strategy === 'section-by-section') {
      return this.mergeSectionBySection(versions);
    }

    // Default: use most recent non-null values
    return this.mergeLatestValues(versions);
  }

  mergeFieldByField(versions) {
    const merged = {};

    // For each field, take value from specified version
    // (metadata would specify which version for each field)

    return merged;
  }

  revertChanges(documentId, fromVersion, toVersion, metadata) {
    // Revert specific changes made between versions
    const from = this.versionStore.getVersion(documentId, fromVersion);
    const to = this.versionStore.getVersion(documentId, toVersion);

    // Find what changed
    const changes = this.findChanges(from.content, to.content);

    // Revert those changes from current version
    const current = this.versionStore.getCurrentVersionContent(documentId);
    const reverted = this.applyRevert(current, changes);

    return this.versionStore.saveVersion(
      documentId,
      reverted,
      {
        ...metadata,
        comment: `Reverted changes from v${fromVersion} to v${toVersion}`,
        tags: ['revert']
      }
    );
  }
}

4. Version Tagging

Mark significant versions:

class VersionTagging {
  constructor(versionStore) {
    this.versionStore = versionStore;
    this.tags = new Map();
  }

  tagVersion(documentId, versionNumber, tag, metadata) {
    const version = this.versionStore.getVersion(documentId, versionNumber);

    if (!version.tags) {
      version.tags = [];
    }

    const tagInfo = {
      tag: tag,
      taggedBy: metadata.userId,
      taggedByName: metadata.userName,
      taggedAt: new Date().toISOString(),
      description: metadata.description || ''
    };

    version.tags.push(tagInfo);

    // Store in tag index for fast lookup
    const tagKey = `${documentId}:${tag}`;
    this.tags.set(tagKey, versionNumber);

    return tagInfo;
  }

  getTaggedVersion(documentId, tag) {
    const tagKey = `${documentId}:${tag}`;
    const versionNumber = this.tags.get(tagKey);

    if (!versionNumber) {
      return null;
    }

    return this.versionStore.getVersion(documentId, versionNumber);
  }

  // Common tags
  tagAsApproved(documentId, versionNumber, approver) {
    return this.tagVersion(documentId, versionNumber, 'approved', {
      userId: approver.id,
      userName: approver.name,
      description: 'Officially approved version'
    });
  }

  tagAsRelease(documentId, versionNumber, releaseInfo) {
    return this.tagVersion(documentId, versionNumber, `release-${releaseInfo.version}`, {
      userId: releaseInfo.releasedBy.id,
      userName: releaseInfo.releasedBy.name,
      description: `Release ${releaseInfo.version}: ${releaseInfo.notes}`
    });
  }

  tagAsMilestone(documentId, versionNumber, milestone) {
    return this.tagVersion(documentId, versionNumber, milestone, {
      userId: milestone.userId,
      userName: milestone.userName,
      description: milestone.description
    });
  }

  getAllTags(documentId) {
    const versions = this.versionStore.getAllVersions(documentId);
    const allTags = [];

    versions.forEach(version => {
      if (version.tags && version.tags.length > 0) {
        version.tags.forEach(tag => {
          allTags.push({
            versionNumber: version.versionNumber,
            tag: tag.tag,
            taggedBy: tag.taggedByName,
            taggedAt: tag.taggedAt,
            description: tag.description
          });
        });
      }
    });

    return allTags;
  }
}

5. Branching and Merging

Support parallel development:

class VersionBranching {
  constructor(versionStore) {
    this.versionStore = versionStore;
    this.branches = new Map();
  }

  createBranch(documentId, fromVersion, branchName, metadata) {
    const branch = {
      id: this.generateBranchId(),
      name: branchName,
      documentId: documentId,
      baseVersion: fromVersion,
      createdBy: metadata.userId,
      createdAt: new Date().toISOString(),
      description: metadata.description || '',
      headVersion: fromVersion  // Latest version in this branch
    };

    this.branches.set(branch.id, branch);

    return branch;
  }

  saveVersionToBranch(branchId, content, metadata) {
    const branch = this.branches.get(branchId);

    // Save version with branch reference
    const version = this.versionStore.saveVersion(
      branch.documentId,
      content,
      {
        ...metadata,
        branch: branchId,
        branchName: branch.name
      }
    );

    // Update branch head
    branch.headVersion = version.versionNumber;

    return version;
  }

  mergeBranch(sourceBranchId, targetBranchId, metadata) {
    const sourceBranch = this.branches.get(sourceBranchId);
    const targetBranch = this.branches.get(targetBranchId);

    // Get head versions of both branches
    const sourceHead = this.versionStore.getVersion(
      sourceBranch.documentId,
      sourceBranch.headVersion
    );

    const targetHead = this.versionStore.getVersion(
      targetBranch.documentId,
      targetBranch.headVersion
    );

    // Find common ancestor
    const ancestor = this.findCommonAncestor(sourceHead, targetHead);

    // Perform three-way merge
    const merged = this.threeWayMerge(
      ancestor.content,
      sourceHead.content,
      targetHead.content
    );

    if (merged.conflicts.length > 0) {
      return {
        status: 'conflicts',
        conflicts: merged.conflicts,
        mergedContent: merged.content
      };
    }

    // Save merged version to target branch
    const mergedVersion = this.saveVersionToBranch(
      targetBranchId,
      merged.content,
      {
        ...metadata,
        comment: `Merged ${sourceBranch.name} into ${targetBranch.name}`,
        tags: ['merge'],
        mergedFrom: sourceBranchId
      }
    );

    return {
      status: 'success',
      mergedVersion: mergedVersion.versionNumber,
      conflicts: []
    };
  }

  threeWayMerge(base, source, target) {
    const conflicts = [];
    const merged = this.deepClone(base);

    // Find changes in source
    const sourceChanges = this.findChanges(base, source);

    // Find changes in target
    const targetChanges = this.findChanges(base, target);

    // Apply non-conflicting changes
    sourceChanges.forEach(change => {
      const conflict = this.findConflict(change, targetChanges);

      if (conflict) {
        conflicts.push({
          field: change.field,
          baseValue: base[change.field],
          sourceValue: source[change.field],
          targetValue: target[change.field]
        });
      } else {
        merged[change.field] = source[change.field];
      }
    });

    targetChanges.forEach(change => {
      if (!this.wasApplied(change, sourceChanges)) {
        merged[change.field] = target[change.field];
      }
    });

    return {
      content: merged,
      conflicts: conflicts
    };
  }

  findCommonAncestor(version1, version2) {
    // Walk back through version history to find common ancestor
    const ancestors1 = this.getAncestors(version1);
    const ancestors2 = this.getAncestors(version2);

    // Find first common version
    for (const v1 of ancestors1) {
      if (ancestors2.some(v2 => v2.versionNumber === v1.versionNumber)) {
        return v1;
      }
    }

    return null;
  }

  getAncestors(version) {
    const ancestors = [];
    let current = version;

    while (current.parentVersion) {
      current = this.versionStore.getVersion(
        current.documentId,
        current.parentVersion
      );
      ancestors.push(current);
    }

    return ancestors;
  }
}

6. Automatic Versioning

Auto-save versions based on rules:

class AutomaticVersioning {
  constructor(versionStore) {
    this.versionStore = versionStore;
    this.config = {
      autoSaveInterval: 5 * 60 * 1000,  // 5 minutes
      significantChangeThreshold: 100,   // 100 chars
      maxAutoVersions: 10
    };
  }

  setupAutoSave(documentId, editor, metadata) {
    let lastSaved = Date.now();
    let lastContent = editor.getContent();

    setInterval(() => {
      const currentContent = editor.getContent();

      // Check if significant change
      if (this.isSignificantChange(lastContent, currentContent)) {
        // Auto-save version
        this.versionStore.saveVersion(
          documentId,
          currentContent,
          {
            ...metadata,
            comment: 'Auto-saved',
            significant: false,
            tags: ['auto-save']
          }
        );

        lastSaved = Date.now();
        lastContent = currentContent;

        // Cleanup old auto-save versions
        this.cleanupAutoVersions(documentId);
      }
    }, this.config.autoSaveInterval);
  }

  isSignificantChange(oldContent, newContent) {
    const oldStr = JSON.stringify(oldContent);
    const newStr = JSON.stringify(newContent);

    const diff = Math.abs(oldStr.length - newStr.length);

    return diff >= this.config.significantChangeThreshold;
  }

  cleanupAutoVersions(documentId) {
    const versions = this.versionStore.getAllVersions(documentId);

    const autoVersions = versions
      .filter(v => v.tags && v.tags.includes('auto-save'))
      .sort((a, b) => b.versionNumber - a.versionNumber);

    // Keep only last N auto-save versions
    const toDelete = autoVersions.slice(this.config.maxAutoVersions);

    toDelete.forEach(version => {
      // Mark as archived or delete if safe
      this.archiveVersion(version);
    });
  }

  versionBeforeCriticalOperation(documentId, operation, metadata) {
    // Create checkpoint before risky operations
    const current = this.versionStore.getCurrentVersionContent(documentId);

    return this.versionStore.saveVersion(
      documentId,
      current,
      {
        ...metadata,
        comment: `Before ${operation}`,
        significant: true,
        tags: ['checkpoint', `before-${operation}`]
      }
    );
  }
}

7. Version History Visualization

Show version timeline:

class VersionVisualization {
  renderVersionHistory(documentId) {
    const versions = this.versionStore.getAllVersions(documentId);

    return `
      <div class="version-history">
        <div class="history-header">
          <h3>Version History</h3>
          <div class="history-stats">
            <span>${versions.length} versions</span>
            <span>${this.getUniqueAuthors(versions)} contributors</span>
          </div>
        </div>

        <div class="version-timeline">
          ${versions.map(v => this.renderVersionNode(v)).join('')}
        </div>
      </div>
    `;
  }

  renderVersionNode(version) {
    return `
      <div class="version-node ${version.significant ? 'significant' : 'auto'}">
        <div class="version-header">
          <span class="version-number">v${version.versionNumber}</span>
          ${version.tags.map(t => `<span class="tag">${t}</span>`).join('')}
        </div>

        <div class="version-meta">
          <span class="author">${version.authorName}</span>
          <span class="timestamp">${this.formatTime(version.timestamp)}</span>
        </div>

        ${version.comment ? `
          <div class="version-comment">${version.comment}</div>
        ` : ''}

        <div class="version-actions">
          <button onclick="viewVersion(${version.versionNumber})">View</button>
          <button onclick="compareVersion(${version.versionNumber})">Compare</button>
          <button onclick="restoreVersion(${version.versionNumber})">Restore</button>
        </div>
      </div>
    `;
  }

  renderBranchDiagram(documentId) {
    const branches = this.getAllBranches(documentId);

    // Draw branch diagram (like git log --graph)
    return this.drawBranchGraph(branches);
  }
}

Implementation Details

Complete Version Control System

class ComprehensiveVersionControl {
  constructor() {
    this.versionStore = new ImmutableVersionStore();
    this.comparison = new VersionComparison();
    this.restoration = new VersionRestoration(this.versionStore);
    this.tagging = new VersionTagging(this.versionStore);
    this.branching = new VersionBranching(this.versionStore);
    this.autoVersion = new AutomaticVersioning(this.versionStore);
    this.visualization = new VersionVisualization();
  }

  // Save new version
  save(documentId, content, metadata) {
    return this.versionStore.saveVersion(documentId, content, metadata);
  }

  // Get specific version
  get(documentId, versionNumber) {
    return this.versionStore.getVersion(documentId, versionNumber);
  }

  // Compare two versions
  compare(documentId, version1, version2) {
    const v1 = this.get(documentId, version1);
    const v2 = this.get(documentId, version2);
    return this.comparison.compareVersions(v1, v2);
  }

  // Restore previous version
  restore(documentId, versionNumber, metadata) {
    return this.restoration.restoreVersion(documentId, versionNumber, metadata);
  }

  // Tag important version
  tag(documentId, versionNumber, tag, metadata) {
    return this.tagging.tagVersion(documentId, versionNumber, tag, metadata);
  }

  // List all versions
  history(documentId) {
    return this.versionStore.getAllVersions(documentId);
  }
}

// Usage
const versionControl = new ComprehensiveVersionControl();

// Save version
versionControl.save(
  'contract-5847',
  contractContent,
  {
    author: user.id,
    authorName: user.name,
    authorEmail: user.email,
    comment: 'Revised liability clause per legal review',
    significant: true
  }
);

// Compare versions
const comparison = versionControl.compare('contract-5847', 4, 12);
console.log(`${comparison.statistics.totalChanges} changes found`);

// Restore version
versionControl.restore(
  'contract-5847',
  7,  // Version 7 had best pricing
  {
    userId: user.id,
    userName: user.name,
    userEmail: user.email
  }
);

// Tag approved version
versionControl.tag(
  'contract-5847',
  15,
  'approved',
  {
    userId: manager.id,
    userName: manager.name,
    description: 'Final approved version'
  }
);

Consequences

Benefits

Safety Net: - Every change preserved - Can always undo - Experimentation safe

Collaboration: - Multiple people can edit - Changes tracked per person - Merge conflicts resolved

Accountability: - Who made which changes - When were they made - Why were they made

Comparison: - See exactly what changed - Understand evolution - Learn from process

Recovery: - Restore any previous state - Recover from mistakes - Find working version

Liabilities

Storage Costs: - Every version uses space - Can grow large - Need archival strategy

Complexity: - More complex than simple save - Branching/merging advanced - Learning curve exists

Performance: - Storing versions takes time - Comparison can be slow - Need optimization

Version Proliferation: - Too many versions confuse - Auto-save creates clutter - Need cleanup strategies

Merge Conflicts: - Concurrent edits conflict - Manual resolution needed - Can be frustrating

Domain Examples

// Track contract negotiations
versionControl.save(contractId, content, {
  comment: 'Initial draft based on standard template',
  tags: ['draft', 'template-based']
});

versionControl.save(contractId, content, {
  comment: 'Client requested changes to payment terms',
  tags: ['client-revision']
});

versionControl.save(contractId, content, {
  comment: 'Legal review - liability clause updated',
  tags: ['legal-review'],
  significant: true
});

versionControl.tag(contractId, 12, 'final-approved', {
  description: 'Version sent to client for signature'
});

Software: Code Versioning

// Development workflow
versionControl.createBranch(codeId, 10, 'feature-authentication', {
  description: 'Adding OAuth authentication'
});

versionControl.saveVersionToBranch(branchId, code, {
  comment: 'Implemented OAuth login flow'
});

versionControl.mergeBranch(featureBranch, mainBranch, {
  comment: 'Merging authentication feature'
});

Content: Article Editing

// Editorial process
versionControl.save(articleId, content, {
  comment: 'First draft',
  tags: ['draft']
});

versionControl.save(articleId, content, {
  comment: 'Editor revisions - structure improved',
  tags: ['editorial-review']
});

versionControl.save(articleId, content, {
  comment: 'Final copy edit - typos fixed',
  tags: ['copy-edit']
});

versionControl.tag(articleId, 8, 'published', {
  description: 'Published on website'
});

Prerequisites: - Volume 3, Pattern 17: State-Aware Behavior (versions have states) - Volume 3, Pattern 18: Audit Trail (versions are audit points)

Synergies: - Volume 3, Pattern 11: Cascading Updates (version cascades) - All patterns (versioning applies to all form data)

Conflicts: - Real-time collaboration (too many versions) - Performance-critical apps (overhead high)

Alternatives: - Event sourcing (store events not versions) - Database timestamps (simple audit) - Manual backups (periodic snapshots)

Known Uses

Git: Source code version control

Google Docs: Document revision history

Microsoft Word: Track Changes and version history

Confluence: Page version history

SharePoint: Document versions

Salesforce: Field history tracking

GitHub: Pull requests and commit history

Dropbox: File version history


Further Reading

Academic Foundations

  • Version Control Theory: Zeller, A. (2009). Why Programs Fail: A Guide to Systematic Debugging (2nd ed.). Morgan Kaufmann. ISBN: 978-0123745156
  • Diff Algorithms: Myers, E.W. (1986). "An O(ND) Difference Algorithm and Its Variations." Algorithmica 1(2): 251-266.
  • Three-Way Merge: Khanna, S., Kunal, K., & Pierce, B.C. (2007). "A Formal Investigation of Diff3." FSTTCS '07.

Practical Implementation

Standards & Specifications

  • Pattern 23: Audit Trail - Continuous audit vs discrete versions
  • Pattern 22: State-Aware Behavior - State transitions create versions
  • Pattern 5: Form State Tracking - Track which version user is editing
  • Volume 2, Pattern 26: Feedback Loop Implementation - Detect version differences
  • Volume 1, Chapter 13: Lessons Learned and Best Practices - Document versioning architecture

Tools & Services

Implementation Examples