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

agentic-dev-library / thumbcode / 21118624864

18 Jan 2026 09:07PM UTC coverage: 28.754% (-14.4%) from 43.195%
21118624864

Pull #50

github

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

181 of 1095 branches covered (16.53%)

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

157
      {/* Content */}
158
      {!collapsed && (
×
159
        <ScrollView horizontal showsHorizontalScrollIndicator={false}>
160
          <View className="min-w-full">
161
            {lines.map((line, index) => (
NEW
162
              <View key={`${line.type}-${index}`} className={`flex-row ${getLineStyle(line.type)}`}>
×
163
                {showLineNumbers && (
×
164
                  <View className="flex-row">
165
                    <Text className="font-mono text-xs text-neutral-600 w-10 text-right px-2 py-1 bg-neutral-900/50">
166
                      {line.oldLineNumber || ''}
×
167
                    </Text>
168
                    <Text className="font-mono text-xs text-neutral-600 w-10 text-right px-2 py-1 bg-neutral-900/50">
169
                      {line.newLineNumber || ''}
×
170
                    </Text>
171
                  </View>
172
                )}
173
                <Text className={`font-mono text-sm w-4 py-1 ${getPrefixColor(line.type)}`}>
174
                  {getLinePrefix(line.type)}
175
                </Text>
176
                <Text
177
                  className="font-mono text-sm text-neutral-200 py-1 pr-4 flex-1"
178
                  numberOfLines={1}
179
                >
180
                  {line.content}
181
                </Text>
182
              </View>
183
            ))}
184
          </View>
185
        </ScrollView>
186
      )}
187
    </View>
188
  );
189
}
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