• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

agentic-dev-library / thumbcode / 21118501486

18 Jan 2026 08:58PM UTC coverage: 28.778% (-14.4%) from 43.195%
21118501486

Pull #50

github

web-flow
Merge 9f8429151 into c7c91f5ce
Pull Request #50: feat(components): expand component library to production-ready set

181 of 1093 branches covered (16.56%)

Branch coverage included in aggregate %.

0 of 307 new or added lines in 21 files covered. (0.0%)

495 of 1256 relevant lines covered (39.41%)

1.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/components/code/DiffViewer.tsx
1
/**
2
 * DiffViewer Component
3
 *
4
 * Displays code diffs with line-by-line highlighting.
5
 * Supports unified and split view modes.
6
 */
7

8
import { useState } from 'react';
9
import { Pressable, ScrollView, Text, View } from 'react-native';
10

11
interface DiffLine {
12
  type: 'add' | 'remove' | 'context';
13
  content: string;
14
  oldLineNumber?: number;
15
  newLineNumber?: number;
16
}
17

18
interface DiffViewerProps {
19
  /** Old file content */
20
  oldContent?: string;
21
  /** New file content */
22
  newContent?: string;
23
  /** Pre-parsed diff lines */
24
  diff?: DiffLine[];
25
  /** File path */
26
  filename?: string;
27
  /** View mode */
28
  viewMode?: 'unified' | 'split';
29
  /** Show line numbers */
30
  showLineNumbers?: boolean;
31
}
32

33
function parseDiff(oldContent: string, newContent: string): DiffLine[] {
34
  // Simple line-by-line diff (in production, use a proper diff library)
NEW
35
  const oldLines = oldContent.split('\n');
×
NEW
36
  const newLines = newContent.split('\n');
×
NEW
37
  const result: DiffLine[] = [];
×
38

NEW
39
  let oldIndex = 0;
×
NEW
40
  let newIndex = 0;
×
41

NEW
42
  while (oldIndex < oldLines.length || newIndex < newLines.length) {
×
NEW
43
    if (oldIndex >= oldLines.length) {
×
NEW
44
      result.push({
×
45
        type: 'add',
46
        content: newLines[newIndex],
47
        newLineNumber: newIndex + 1,
48
      });
NEW
49
      newIndex++;
×
NEW
50
    } else if (newIndex >= newLines.length) {
×
NEW
51
      result.push({
×
52
        type: 'remove',
53
        content: oldLines[oldIndex],
54
        oldLineNumber: oldIndex + 1,
55
      });
NEW
56
      oldIndex++;
×
NEW
57
    } else if (oldLines[oldIndex] === newLines[newIndex]) {
×
NEW
58
      result.push({
×
59
        type: 'context',
60
        content: oldLines[oldIndex],
61
        oldLineNumber: oldIndex + 1,
62
        newLineNumber: newIndex + 1,
63
      });
NEW
64
      oldIndex++;
×
NEW
65
      newIndex++;
×
66
    } else {
NEW
67
      result.push({
×
68
        type: 'remove',
69
        content: oldLines[oldIndex],
70
        oldLineNumber: oldIndex + 1,
71
      });
NEW
72
      oldIndex++;
×
73
    }
74
  }
75

NEW
76
  return result;
×
77
}
78

79
export function DiffViewer({
80
  oldContent,
81
  newContent,
82
  diff,
83
  filename,
84
  viewMode: _viewMode = 'unified',
×
85
  showLineNumbers = true,
×
86
}: DiffViewerProps) {
87
  // Note: viewMode 'split' is not yet implemented, only 'unified' is used
NEW
88
  void _viewMode;
×
NEW
89
  const [collapsed, setCollapsed] = useState(false);
×
90

NEW
91
  const lines = diff || (oldContent && newContent ? parseDiff(oldContent, newContent) : []);
×
NEW
92
  const additions = lines.filter((l) => l.type === 'add').length;
×
NEW
93
  const deletions = lines.filter((l) => l.type === 'remove').length;
×
94

NEW
95
  const getLineStyle = (type: DiffLine['type']) => {
×
NEW
96
    switch (type) {
×
97
      case 'add':
NEW
98
        return 'bg-teal-600/20';
×
99
      case 'remove':
NEW
100
        return 'bg-coral-500/20';
×
101
      default:
NEW
102
        return 'bg-transparent';
×
103
    }
104
  };
105

NEW
106
  const getLinePrefix = (type: DiffLine['type']) => {
×
NEW
107
    switch (type) {
×
108
      case 'add':
NEW
109
        return '+';
×
110
      case 'remove':
NEW
111
        return '-';
×
112
      default:
NEW
113
        return ' ';
×
114
    }
115
  };
116

NEW
117
  const getPrefixColor = (type: DiffLine['type']) => {
×
NEW
118
    switch (type) {
×
119
      case 'add':
NEW
120
        return 'text-teal-400';
×
121
      case 'remove':
NEW
122
        return 'text-coral-400';
×
123
      default:
NEW
124
        return 'text-neutral-500';
×
125
    }
126
  };
127

NEW
128
  return (
×
129
    <View className="bg-charcoal overflow-hidden" style={{ borderRadius: '12px 10px 14px 8px' }}>
130
      {/* Header */}
131
      <Pressable
NEW
132
        onPress={() => setCollapsed(!collapsed)}
×
133
        className="flex-row items-center justify-between px-3 py-2 bg-neutral-800 border-b border-neutral-700"
134
      >
135
        <View className="flex-row items-center flex-1">
136
          <Text className="text-neutral-400 mr-2">{collapsed ? '▶' : '▼'}</Text>
×
137
          {filename && (
×
138
            <Text className="font-mono text-sm text-neutral-200" numberOfLines={1}>
139
              {filename}
140
            </Text>
141
          )}
142
        </View>
143
        <View className="flex-row items-center gap-2">
144
          {additions > 0 && <Text className="font-mono text-xs text-teal-400">+{additions}</Text>}
×
145
          {deletions > 0 && <Text className="font-mono text-xs text-coral-400">-{deletions}</Text>}
×
146
        </View>
147
      </Pressable>
148

149
      {/* Content */}
150
      {!collapsed && (
×
151
        <ScrollView horizontal showsHorizontalScrollIndicator={false}>
152
          <View className="min-w-full">
153
            {lines.map((line, index) => (
NEW
154
              <View key={`${line.type}-${index}`} className={`flex-row ${getLineStyle(line.type)}`}>
×
155
                {showLineNumbers && (
×
156
                  <View className="flex-row">
157
                    <Text className="font-mono text-xs text-neutral-600 w-10 text-right px-2 py-1 bg-neutral-900/50">
158
                      {line.oldLineNumber || ''}
×
159
                    </Text>
160
                    <Text className="font-mono text-xs text-neutral-600 w-10 text-right px-2 py-1 bg-neutral-900/50">
161
                      {line.newLineNumber || ''}
×
162
                    </Text>
163
                  </View>
164
                )}
165
                <Text className={`font-mono text-sm w-4 py-1 ${getPrefixColor(line.type)}`}>
166
                  {getLinePrefix(line.type)}
167
                </Text>
168
                <Text
169
                  className="font-mono text-sm text-neutral-200 py-1 pr-4 flex-1"
170
                  numberOfLines={1}
171
                >
172
                  {line.content}
173
                </Text>
174
              </View>
175
            ))}
176
          </View>
177
        </ScrollView>
178
      )}
179
    </View>
180
  );
181
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc