Skip to main content

THE CENTER

The Git Diff Viewer and Branch Diagram are critical interfaces for Human ◈ AI collaboration. They provide transparency through:
  • Fresh Diff Computation: No cached data, always current state
  • Dual View Modes: Unified (quick overview) vs Split (detailed comparison)
  • OKLab Gradients: Perceptually uniform color transitions reduce cognitive load
  • SVG Layering: Proper visual hierarchy shows Git relationships clearly
  • Interactive Filters: Focus on relevant branches for efficient review
┌─────────────────────────────────────────────────────────────────┐
│  AI Agent                        Human Developer                │
│  generates                       reviews & validates             │
│  code changes                    through diff view               │
│       │                                ▲                         │
│       │                                │                         │
│       ▼                                │                         │
│  ┌─────────────────────────────────────┴────────────────┐       │
│  │              GitDiffViewer + Branch Diagram          │       │
│  │  ┌──────────────┬──────────────┬─────────────────┐   │       │
│  │  │ Green = ADD  │ Red = DELETE │ Context Lines   │   │       │
│  │  └──────────────┴──────────────┴─────────────────┘   │       │
│  └──────────────────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────────────────┘

Diff Viewer Architecture

Fresh Diff Computation

Every diff request computes fresh from Git trees - no caching.
User requests diff

GitDiffViewer.init(container, repoPath, options)
    ↓ IPC invoke()
Rust Backend (git_viewers.rs)
    ├── Repository::open(path)           ← FRESH open, no cache
    ├── repo.diff_tree_to_tree()         ← Compute diff from Git trees
    └── Parse diff → DiffResult struct

TypeScript Processing
    ├── Organize changes by file
    ├── Parse unified diff format into hunks
    └── Extract line-by-line changes

Render based on viewMode
    ├── "unified" → renderUnifiedDiff()
    └── "split"   → renderSplitDiff()
Stateless Design:
  • Repository::open() called fresh each time
  • No static cache, no lazy_static!, no OnceCell
  • Pure function: returns Result<GitCommitDetail>
  • No persistent state between calls

Diff Data Flow

// git_viewers.rs - Fresh open
let repo = Repository::open(path).map_err(|e| e.to_string())?;

// Direct tree comparison
let diff = repo.diff_tree_to_tree(
    parent_tree.as_ref(),
    Some(&commit_tree),
    None,
).map_err(|e| e.to_string())?;

// Count additions/deletions by iterating diff
diff.print(git2::DiffFormat::Patch, |_, _, line| {
    match line.origin() {
        '+' => total_additions += 1,
        '-' => total_deletions += 1,
        _ => {}
    }
    true
}).ok();

// Return fresh result - no side effects
Ok(GitCommitDetail { ... })
Why this matters:
  • Human always sees current state
  • No stale diffs from previous sessions
  • AI changes immediately visible
  • Trust through transparency

Diff Parsing Pipeline

Unified Diff Format

diff --git a/src/main.rs b/src/main.rs          ← File boundary
index abc123..def456 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,6 +10,7 @@ fn main() {                      ← Hunk header
     println!("Hello");                          ← Context (space)
+    println!("World");                          ← Addition (+)
     return 0;                                   ← Context (space)

Parsing State Machine

Raw diff string

┌─────────────────────────────────────────────────────────────────┐
│  STATE: SCANNING (looking for "diff --git" or "@@")            │
├─────────────────────────────────────────────────────────────────┤
│  line.startsWith("diff --git")                                 │
│      ↓                                                          │
│  STATE: NEW_FILE                                                │
│      ├── Extract oldPath, newPath via regex                    │
│      └── Create ParsedDiff { hunks: [], additions: 0 }         │
│                                                                 │
│  line.startsWith("@@")                                          │
│      ↓                                                          │
│  STATE: NEW_HUNK                                                │
│      ├── Extract oldStart, oldLines, newStart, newLines        │
│      └── Create DiffHunk { lines: [] }                         │
│                                                                 │
│  STATE: PARSING_LINES                                           │
│      ├── line[0] === '+' → type = 'add', additions++           │
│      ├── line[0] === '-' → type = 'del', deletions++           │
│      └── line[0] === ' ' → type = 'ctx' (context)              │
└─────────────────────────────────────────────────────────────────┘

ParsedDiff[] { oldPath, newPath, additions, deletions, hunks }

Line Type Classification

// git-diff-viewer.ts
while (i < lines.length && !lines[i].startsWith('@@') && !lines[i].startsWith('diff')) {
  const hunkLine = lines[i];
  const type = hunkLine[0] === '+' ? 'add' :
               hunkLine[0] === '-' ? 'del' : 'ctx';

  hunk.lines.push({
    type,
    content: hunkLine.slice(1),  // Remove prefix character
  });

  if (type === 'add') currentFile.additions++;
  if (type === 'del') currentFile.deletions++;

  i++;
}

View Modes

Unified View

Single column, interleaved additions and deletions.
@@ -10,3 +10,4 @@ fn main() {
┌───────┬───────┬───────┬────────────────────────────────────┐
│  OLD  │  NEW  │ TYPE  │  CONTENT                           │
├───────┼───────┼───────┼────────────────────────────────────┤
│  10   │  10   │  ctx  │  println!("Hello");                │
│       │  11   │  +    │  println!("World");    ← GREEN     │
│  11   │  12   │  ctx  │  return 0;                         │
└───────┴───────┴───────┴────────────────────────────────────┘

Characteristics:
├── Single content column
├── Two line number columns (old, new)
├── Prefix shows +/- for change type
└── Best for: reviewing small, focused changes

Split View

Side-by-side, old on left, new on right.
@@ -10,3 +10,4 @@ fn main() {
┌─────────────────────────────┬─────────────────────────────────┐
│     OLD (Left)              │     NEW (Right)                 │
├─────────────────────────────┼─────────────────────────────────┤
│ 10 │ println!("Hello");     │ 10 │ println!("Hello");         │
│    │ [empty placeholder]    │ 11 │ println!("World"); ← GREEN │
│ 11 │ return 0;              │ 12 │ return 0;                  │
└─────────────────────────────┴─────────────────────────────────┘

Characteristics:
├── Two content columns (grid-template-columns: 1fr 1fr)
├── Empty placeholders for added/deleted lines
├── Visual alignment of corresponding lines
└── Best for: comparing large refactors, side-by-side analysis

Color Coding (Both Views)

// git-diff-viewer.ts
const bgColor = line.type === 'add' ? 'var(--color-success-bg)' :
                line.type === 'del' ? 'var(--color-error-bg)' :
                'transparent';
const textColor = line.type === 'add' ? 'var(--color-success)' :
                  line.type === 'del' ? 'var(--color-error)' :
                  'var(--color-text)';
TypeBackgroundText
addLight greenDark green
delLight redDark red
ctxTransparentDefault

Branch Diagram Architecture

OKLab Color System

Perceptually uniform color interpolation for merge line gradients. Why OKLab over RGB:
  • RGB interpolation produces muddy midpoints
  • HSL has hue discontinuities
  • OKLab is scientifically proven to be perceptually uniform

Color Conversion Pipeline

Input: "#FF6B6B" (coral red)

┌────────────────────────────────────────────────────────────────┐
│  hexToRgb()           → RGB {r, g, b}                          │
│  rgbToOklab()         → OKLab {L, a, b}                        │
│  interpolateOklab()   → OKLab (t = 0.0 to 1.0)                 │
│  oklabToRgb()         → RGB                                    │
│  rgbToHex()           → "#XXXXXX"                              │
└────────────────────────────────────────────────────────────────┘

Gradient Stops (5 steps)

SVG <linearGradient id="merge-gradient-0">
  <stop offset="0%" stop-color="#FF6B6B" />
  <stop offset="20%" stop-color="#XX..." />
  <stop offset="40%" stop-color="#XX..." />
  ...
</linearGradient>

OKLab Transformation Matrices

// git-branch-diagram-viewer.ts
function rgbToOklab(rgb: RGB): OKLab {
  // Step 1: sRGB → linear RGB
  const r = srgbToLinear(rgb.r);
  const g = srgbToLinear(rgb.g);
  const b = srgbToLinear(rgb.b);

  // Step 2: sRGB → LMS (cone response)
  const l_ = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
  const m_ = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
  const s_ = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;

  // Step 3: LMS → L'M'S' (cube root)
  const l = Math.cbrt(l_);
  const m = Math.cbrt(m_);
  const s = Math.cbrt(s_);

  // Step 4: L'M'S' → OKLab
  return {
    L: 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
    a: 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
    b: 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
  };
}
Matrix coefficients verified against official OKLab specification

SVG Layered Rendering

Painter’s Algorithm

SVG elements are rendered in DOM order - first in DOM = behind other elements.
<svg>
  <!-- LAYER 1: Timeline (first in DOM = bottom) -->
  <line
    x1="..."
    y1="70"
    x2="..."
    y2="70"
    stroke="#6366f1"
    stroke-opacity="0.4"
  />

  <!-- LAYER 2: Merge Lines (second in DOM = middle) -->
  <g class="merge-lines-group">
    <defs>
      <linearGradient id="merge-gradient-0">...</linearGradient>
    </defs>
    <path d="..." stroke="url(#merge-gradient-0)" />
  </g>

  <!-- LAYER 3: Branch Nodes (last in DOM = top) -->
  <g class="branch-nodes-group">
    <rect ... />
    <text>main</text>
  </g>
</svg>

Visual Hierarchy

┌─────────────────────────────────────────────────────────────────┐
│                        SVG Canvas                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  LAYER 1: Timeline line (bottom)                        │    │
│  │  ════════════════════════════════════════════════       │    │
│  └─────────────────────────────────────────────────────────┘    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  LAYER 2: Merge lines (middle)                          │    │
│  │       ╭────────────────────────────╮                    │    │
│  │       ╰──────────────╮             │                    │    │
│  │                      ╰─────────────╯                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  LAYER 3: Branch nodes (top)                            │    │
│  │     ┌────────┐       ┌────────┐       ┌────────┐        │    │
│  │     │ main   │       │ develop│       │ feature│        │    │
│  │     └────────┘       └────────┘       └────────┘        │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘
No CSS z-index used - relies entirely on SVG painter’s model for correct layering.

Interactive Filter System

Two Filter Modes

MODE 1: 'all-related'
─────────────────────
Shows:
├── Selected branches
├── Branches that MERGED INTO selected
└── Branches that selected MERGED INTO

Logic: OR between endpoints (at least one related)

Example: Select "main"
├── Shows: main
├── Shows: feature/A (merged into main)
└── Shows: develop (main merged into it)

MODE 2: 'between-selected'
────────────────────────
Shows:
├── ONLY selected branches
└── Hides all unselected branches

Logic: AND between endpoints (both must be selected)

Example: Select "main" and "develop"
├── Shows: main, develop
├── Shows: merge lines between main and develop
└── Hides: everything else

Bidirectional Merge Relationship Check

// git-branch-diagram-viewer.ts
private isBranchRelated(branchName: string): boolean {
  if (this.selectedBranches.size === 0) return true;
  if (this.selectedBranches.has(branchName)) return true;

  if (this.filterMode === 'between-selected') {
    return false; // Only selected branches shown
  }

  // 'all-related' mode
  const branch = this.data?.branches.find(b => b.name === branchName);
  if (!branch) return false;

  for (const selected of this.selectedBranches) {
    // CHECK 1: This branch merged INTO selected
    if (branch.merge_targets.includes(selected)) return true;

    // CHECK 2: Selected merged INTO this branch
    if (branch.merged_from.includes(selected)) return true;

    // CHECK 3 & 4: Reverse checks for symmetry
    const selectedBranch = this.data?.branches.find(b => b.name === selected);
    if (selectedBranch) {
      if (selectedBranch.merge_targets.includes(branchName)) return true;
      if (selectedBranch.merged_from.includes(branchName)) return true;
    }
  }

  return false;
}
Symmetry ensures relationship discovered from either direction

Opacity-Based Visual Feedback

// Filtered (not related)
const nodeOpacity = isFiltered ? '0.15' : '1';
const lineOpacity = isFiltered ? '0.1' : '0.8';

// Applied to SVG elements
<g class="branch-node" style="opacity: ${nodeOpacity};">
<path stroke-opacity="${lineOpacity}" />
StateNode OpacityLine OpacityVisibility
Filtered15%10%Ghosted, faded
Active100%80%Fully solid, prominent
Contrast ratio: 6.67x for nodes, 8x for lines - clear visual distinction.

Integration Summary

How Claims Work Together

[Filter System]
    ↓ User selects branches
Visibility Determination
    ├── isBranchRelated()
    └── isMergeLineBetweenSelected()

┌────────────────┬─────────────────────┬─────────────────────┐
│ Timeline Layer │ Merge Lines Layer   │ Branch Nodes Layer  │
│ (always vis.)  │ (OKLab gradients)   │ (opacity filtered)  │
└────────────────┴─────────────────────┴─────────────────────┘

Final Rendered SVG
├── Filtered elements = 15% opacity
├── Active elements = 100% opacity
└── Gradients = OKLab smooth transitions

Data Flow

User Interaction

Filter Selection

selectedBranches Set → isBranchRelated() → Visible/Filtered

renderMergeLines() + renderBranchNodes()
    ├── OKLab gradients applied
    └── Opacity based on filter state

SVG DOM Structure
    ├── <line> (timeline)
    ├── <g> merge-lines-group
    └── <g> branch-nodes-group

Key Insight

Fresh computation + dual view modes + OKLab gradients + SVG layering = complete transparency in Git history visualization. Human and AI see the same clear representation, enabling efficient collaboration and trust.