litvc 1.3.0

Lit - The agentic-first distributed version control system. A complete Git replacement designed for AI agents first and humans second.
Documentation
<script lang="ts">
  import type { FileDiff } from "../types";

  let { file }: { file: FileDiff | null } = $props();

  function lineNumber(n: number) {
    return String(n).padStart(4, " ");
  }
</script>

<div class="diff-viewer">
  {#if file}
    <div class="diff-header">
      <span class="diff-path mono">{file.path}</span>
      <span class="diff-stats">
        {#if file.additions > 0}
          <span class="text-safe">+{file.additions}</span>
        {/if}
        {#if file.deletions > 0}
          <span class="text-danger">-{file.deletions}</span>
        {/if}
      </span>
    </div>

    {#if file.is_binary}
      <div class="binary-notice">Binary file — cannot display diff</div>
    {:else if file.hunks.length === 0}
      <div class="no-diff">No diff available — file may be untracked or unchanged</div>
    {:else}
      <div class="diff-content">
        {#each file.hunks as hunk, i}
          <div class="hunk-header">
            <span class="mono">@@ -{hunk.old_start},{hunk.old_count} +{hunk.new_start},{hunk.new_count} @@</span>
          </div>
          {#each hunk.lines as line}
            <div
              class="diff-line"
              class:add={line.kind === "add"}
              class:remove={line.kind === "remove"}
              class:context={line.kind === "context"}
            >
              <span class="line-gutter">
                {#if line.kind === "remove" || line.kind === "context"}
                  <span class="line-num">{""}</span>
                {:else}
                  <span class="line-num"></span>
                {/if}
                {#if line.kind === "add" || line.kind === "context"}
                  <span class="line-num">{""}</span>
                {:else}
                  <span class="line-num"></span>
                {/if}
              </span>
              <span class="line-indicator">
                {#if line.kind === "add"}+{:else if line.kind === "remove"}-{:else}{" "}{/if}
              </span>
              <span class="line-content">{line.content}</span>
            </div>
          {/each}
          {#if i < file.hunks.length - 1}
            <div class="hunk-separator"></div>
          {/if}
        {/each}
      </div>
    {/if}
  {:else}
    <div class="no-selection">
      <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--text-mute)" stroke-width="0.8" stroke-linecap="round">
        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
        <polyline points="14 2 14 8 20 8"/>
        <line x1="16" y1="13" x2="8" y2="13"/>
        <line x1="16" y1="17" x2="8" y2="17"/>
        <polyline points="10 9 9 9 8 9"/>
      </svg>
      <span>Select a file to view diff</span>
    </div>
  {/if}
</div>

<style>
  .diff-viewer {
    height: 100%;
    display: flex;
    flex-direction: column;
    background: var(--bg-0);
    overflow: hidden;
  }

  .diff-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-8) var(--space-16);
    background: var(--bg-1);
    border-bottom: 1px solid var(--border-1);
    flex-shrink: 0;
  }

  .diff-path {
    font-size: var(--text-sm);
    color: var(--text-1);
    font-weight: 500;
  }

  .diff-stats {
    display: flex;
    gap: var(--space-8);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
  }

  .diff-content {
    flex: 1;
    overflow: auto;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1.5;
  }

  .hunk-header {
    padding: var(--space-4) var(--space-16);
    background: var(--diff-hunk-bg);
    color: var(--text-3);
    font-size: var(--text-xs);
    border-top: 1px solid var(--border-1);
    border-bottom: 1px solid var(--border-1);
  }

  .hunk-separator {
    height: var(--space-8);
    background: var(--bg-0);
  }

  .diff-line {
    display: flex;
    white-space: pre;
    min-height: 20px;
    align-items: stretch;
  }
  .diff-line.add {
    background: var(--diff-add-bg);
  }
  .diff-line.remove {
    background: var(--diff-del-bg);
  }

  .line-gutter {
    display: flex;
    flex-shrink: 0;
    width: 72px;
    user-select: none;
    border-right: 1px solid var(--border-1);
  }
  .line-num {
    width: 36px;
    padding: 0 var(--space-4);
    text-align: right;
    color: var(--text-mute);
    font-size: var(--text-xs);
  }

  .line-indicator {
    width: 20px;
    text-align: center;
    flex-shrink: 0;
    user-select: none;
  }
  .diff-line.add .line-indicator {
    color: var(--diff-add-text);
    font-weight: 700;
  }
  .diff-line.remove .line-indicator {
    color: var(--diff-del-text);
    font-weight: 700;
  }

  .line-content {
    flex: 1;
    padding-right: var(--space-16);
  }
  .diff-line.add .line-content {
    color: var(--diff-add-text);
  }
  .diff-line.remove .line-content {
    color: var(--diff-del-text);
  }

  .binary-notice, .no-diff {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1;
    color: var(--text-mute);
    font-size: var(--text-sm);
  }

  .no-selection {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-12);
    flex: 1;
    color: var(--text-mute);
    font-size: var(--text-sm);
  }
</style>