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

agentic-dev-library / thumbcode / 21933635344

12 Feb 2026 04:31AM UTC coverage: 28.282% (-0.09%) from 28.372%
21933635344

Pull #120

github

web-flow
Merge 85853f9b5 into 82c88cdf1
Pull Request #120: fix(quality): SonarCloud bug, code smells, Readonly props

388 of 2123 branches covered (18.28%)

Branch coverage included in aggregate %.

1 of 40 new or added lines in 9 files covered. (2.5%)

2 existing lines in 2 files now uncovered.

1038 of 2919 relevant lines covered (35.56%)

8.06 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
import { ChevronDownIcon } from '@/components/icons';
11
import { organicBorderRadius } from '@/lib/organic-styles';
12

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

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

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

41
  let oldIndex = 0;
×
42
  let newIndex = 0;
×
43

44
  while (oldIndex < oldLines.length || newIndex < newLines.length) {
×
45
    if (oldIndex >= oldLines.length) {
×
46
      result.push({
×
47
        type: 'add',
48
        content: newLines[newIndex],
49
        newLineNumber: newIndex + 1,
50
      });
51
      newIndex++;
×
52
    } else if (newIndex >= newLines.length) {
×
53
      result.push({
×
54
        type: 'remove',
55
        content: oldLines[oldIndex],
56
        oldLineNumber: oldIndex + 1,
57
      });
58
      oldIndex++;
×
59
    } else if (oldLines[oldIndex] === newLines[newIndex]) {
×
60
      result.push({
×
61
        type: 'context',
62
        content: oldLines[oldIndex],
63
        oldLineNumber: oldIndex + 1,
64
        newLineNumber: newIndex + 1,
65
      });
66
      oldIndex++;
×
67
      newIndex++;
×
68
    } else {
69
      // Lines differ - emit both remove and add
70
      result.push({
×
71
        type: 'remove',
72
        content: oldLines[oldIndex],
73
        oldLineNumber: oldIndex + 1,
74
      });
75
      result.push({
×
76
        type: 'add',
77
        content: newLines[newIndex],
78
        newLineNumber: newIndex + 1,
79
      });
80
      oldIndex++;
×
81
      newIndex++;
×
82
    }
83
  }
84

85
  return result;
×
86
}
87

88
export function DiffViewer({
89
  oldContent,
90
  newContent,
91
  diff,
92
  filename,
93
  viewMode: _viewMode = 'unified',
×
94
  showLineNumbers = true,
×
95
}: Readonly<DiffViewerProps>) {
UNCOV
96
  const [collapsed, setCollapsed] = useState(false);
×
97

98
  const lines = diff || (oldContent && newContent ? parseDiff(oldContent, newContent) : []);
×
99
  const additions = lines.filter((l) => l.type === 'add').length;
×
100
  const deletions = lines.filter((l) => l.type === 'remove').length;
×
101

102
  const getLineStyle = (type: DiffLine['type']) => {
×
103
    switch (type) {
×
104
      case 'add':
105
        return 'bg-teal-600/20';
×
106
      case 'remove':
107
        return 'bg-coral-500/20';
×
108
      default:
109
        return 'bg-transparent';
×
110
    }
111
  };
112

113
  const getLinePrefix = (type: DiffLine['type']) => {
×
114
    switch (type) {
×
115
      case 'add':
116
        return '+';
×
117
      case 'remove':
118
        return '-';
×
119
      default:
120
        return ' ';
×
121
    }
122
  };
123

124
  const getPrefixColor = (type: DiffLine['type']) => {
×
125
    switch (type) {
×
126
      case 'add':
127
        return 'text-teal-400';
×
128
      case 'remove':
129
        return 'text-coral-400';
×
130
      default:
131
        return 'text-neutral-500';
×
132
    }
133
  };
134

135
  return (
×
136
    <View className="bg-charcoal overflow-hidden" style={organicBorderRadius.card}>
137
      {/* Header */}
138
      <Pressable
139
        onPress={() => setCollapsed(!collapsed)}
×
140
        className="flex-row items-center justify-between px-3 py-2 bg-neutral-800 border-b border-neutral-700"
141
        accessibilityRole="button"
142
        accessibilityLabel={`${filename || 'file'}, ${additions} additions, ${deletions} deletions`}
×
143
        accessibilityHint={collapsed ? 'Expand the diff' : 'Collapse the diff'}
×
144
        accessibilityState={{ expanded: !collapsed }}
145
      >
146
        <View className="flex-row items-center flex-1">
147
          <View className="mr-2" style={{ transform: [{ rotate: collapsed ? '-90deg' : '0deg' }] }}>
×
148
            <ChevronDownIcon size={14} color="warmGray" turbulence={0.12} />
149
          </View>
150
          {filename && (
×
151
            <Text className="font-mono text-sm text-neutral-200" numberOfLines={1}>
152
              {filename}
153
            </Text>
154
          )}
155
        </View>
156
        <View className="flex-row items-center gap-2">
157
          {additions > 0 && <Text className="font-mono text-xs text-teal-400">+{additions}</Text>}
×
158
          {deletions > 0 && <Text className="font-mono text-xs text-coral-400">-{deletions}</Text>}
×
159
        </View>
160
      </Pressable>
161

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