clawdex-mobile 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.github/workflows/pages.yml +41 -0
  2. package/AGENTS.md +263 -110
  3. package/README.md +11 -0
  4. package/apps/mobile/.env.example +2 -2
  5. package/apps/mobile/App.tsx +175 -14
  6. package/apps/mobile/app.json +27 -9
  7. package/apps/mobile/eas.json +14 -4
  8. package/apps/mobile/package.json +13 -13
  9. package/apps/mobile/src/api/__tests__/chatMapping.test.ts +219 -0
  10. package/apps/mobile/src/api/__tests__/client.test.ts +579 -6
  11. package/apps/mobile/src/api/__tests__/ws.test.ts +27 -0
  12. package/apps/mobile/src/api/account.ts +47 -0
  13. package/apps/mobile/src/api/chatMapping.ts +435 -18
  14. package/apps/mobile/src/api/client.ts +296 -36
  15. package/apps/mobile/src/api/rateLimits.ts +143 -0
  16. package/apps/mobile/src/api/types.ts +106 -0
  17. package/apps/mobile/src/api/ws.ts +10 -1
  18. package/apps/mobile/src/components/ChatHeader.tsx +12 -12
  19. package/apps/mobile/src/components/ChatInput.tsx +154 -88
  20. package/apps/mobile/src/components/ChatMessage.tsx +548 -93
  21. package/apps/mobile/src/components/ComposerUsageLimits.tsx +167 -0
  22. package/apps/mobile/src/components/SelectionSheet.tsx +466 -0
  23. package/apps/mobile/src/components/ToolBlock.tsx +17 -15
  24. package/apps/mobile/src/components/VoiceRecordingWaveform.tsx +181 -0
  25. package/apps/mobile/src/components/WorkspacePickerModal.tsx +572 -0
  26. package/apps/mobile/src/components/__tests__/chat-input-layout.test.ts +35 -0
  27. package/apps/mobile/src/components/__tests__/chatImageSource.test.ts +44 -0
  28. package/apps/mobile/src/components/__tests__/composerUsageLimits.test.ts +138 -0
  29. package/apps/mobile/src/components/__tests__/voiceWaveform.test.ts +31 -0
  30. package/apps/mobile/src/components/chat-input-layout.ts +59 -0
  31. package/apps/mobile/src/components/chatImageSource.ts +86 -0
  32. package/apps/mobile/src/components/usageLimitBadges.ts +109 -0
  33. package/apps/mobile/src/components/voiceWaveform.ts +46 -0
  34. package/apps/mobile/src/config.ts +9 -2
  35. package/apps/mobile/src/hooks/useVoiceRecorder.ts +8 -1
  36. package/apps/mobile/src/navigation/DrawerContent.tsx +607 -457
  37. package/apps/mobile/src/navigation/__tests__/chatThreadTree.test.ts +89 -0
  38. package/apps/mobile/src/navigation/__tests__/drawerChats.test.ts +65 -0
  39. package/apps/mobile/src/navigation/chatThreadTree.ts +191 -0
  40. package/apps/mobile/src/navigation/drawerChats.ts +9 -0
  41. package/apps/mobile/src/screens/GitScreen.tsx +2 -0
  42. package/apps/mobile/src/screens/MainScreen.tsx +4244 -1237
  43. package/apps/mobile/src/screens/OnboardingScreen.tsx +2 -0
  44. package/apps/mobile/src/screens/SettingsScreen.tsx +256 -226
  45. package/apps/mobile/src/screens/TerminalScreen.tsx +2 -5
  46. package/apps/mobile/src/screens/__tests__/agentThreadDisplay.test.ts +80 -0
  47. package/apps/mobile/src/screens/__tests__/agentThreads.test.ts +170 -0
  48. package/apps/mobile/src/screens/__tests__/planCardState.test.ts +88 -0
  49. package/apps/mobile/src/screens/__tests__/subAgentTranscript.test.ts +102 -0
  50. package/apps/mobile/src/screens/__tests__/transcriptMessages.test.ts +97 -0
  51. package/apps/mobile/src/screens/agentThreadDisplay.ts +261 -0
  52. package/apps/mobile/src/screens/agentThreads.ts +167 -0
  53. package/apps/mobile/src/screens/planCardState.ts +40 -0
  54. package/apps/mobile/src/screens/subAgentTranscript.ts +149 -0
  55. package/apps/mobile/src/screens/transcriptMessages.ts +102 -0
  56. package/apps/mobile/src/theme.ts +6 -12
  57. package/docs/codex-app-server-cli-gap-tracker.md +14 -5
  58. package/docs/privacy-policy.md +54 -0
  59. package/docs/setup-and-operations.md +4 -3
  60. package/docs/terms-of-service.md +33 -0
  61. package/package.json +3 -3
  62. package/services/mac-bridge/package.json +6 -6
  63. package/services/rust-bridge/Cargo.lock +58 -363
  64. package/services/rust-bridge/Cargo.toml +2 -2
  65. package/services/rust-bridge/package.json +1 -1
  66. package/services/rust-bridge/src/main.rs +507 -9
  67. package/site/index.html +54 -0
  68. package/site/privacy/index.html +80 -0
  69. package/site/styles.css +135 -0
  70. package/site/support/index.html +51 -0
  71. package/site/terms/index.html +68 -0
@@ -0,0 +1,167 @@
1
+ import { StyleSheet, Text, View } from 'react-native';
2
+
3
+ import type { ComposerUsageLimitBadgeModel } from './usageLimitBadges';
4
+ import { spacing, typography } from '../theme';
5
+
6
+ const BATTERY_BODY_WIDTH = 16;
7
+
8
+ const toneColors = {
9
+ neutral: {
10
+ border: '#34C759',
11
+ background: 'rgba(52, 199, 89, 0.16)',
12
+ text: '#A7F3C1',
13
+ label: '#7EE2A8',
14
+ },
15
+ warning: {
16
+ border: '#F59E0B',
17
+ background: 'rgba(245, 158, 11, 0.16)',
18
+ text: '#FFE2A8',
19
+ label: '#F8C76B',
20
+ },
21
+ critical: {
22
+ border: '#EF4444',
23
+ background: 'rgba(239, 68, 68, 0.16)',
24
+ text: '#FFC1C1',
25
+ label: '#FF9C9C',
26
+ },
27
+ } as const;
28
+
29
+ interface ComposerUsageLimitsProps {
30
+ limits: ComposerUsageLimitBadgeModel[];
31
+ }
32
+
33
+ export function ComposerUsageLimits({ limits }: ComposerUsageLimitsProps) {
34
+ if (limits.length === 0) {
35
+ return null;
36
+ }
37
+
38
+ return (
39
+ <View style={styles.row}>
40
+ {limits.map((limit) => (
41
+ <View key={limit.id} style={styles.badge}>
42
+ <Text
43
+ style={[
44
+ styles.labelText,
45
+ {
46
+ color: toneColors[limit.tone].label,
47
+ },
48
+ ]}
49
+ >
50
+ {limit.label}
51
+ </Text>
52
+ <View style={styles.meterRow}>
53
+ <View style={styles.batteryWrap}>
54
+ <View
55
+ style={[
56
+ styles.batteryBody,
57
+ {
58
+ borderColor: toneColors[limit.tone].border,
59
+ backgroundColor: toneColors[limit.tone].background,
60
+ },
61
+ ]}
62
+ >
63
+ <View
64
+ style={[
65
+ styles.batteryFill,
66
+ {
67
+ width: Math.max(
68
+ 0,
69
+ Math.round((BATTERY_BODY_WIDTH - 2) * (limit.remainingPercent / 100))
70
+ ),
71
+ backgroundColor: toneColors[limit.tone].border,
72
+ },
73
+ ]}
74
+ />
75
+ </View>
76
+ <View
77
+ style={[
78
+ styles.batteryCap,
79
+ {
80
+ backgroundColor: toneColors[limit.tone].border,
81
+ },
82
+ ]}
83
+ />
84
+ </View>
85
+ <Text
86
+ style={[
87
+ styles.valueText,
88
+ {
89
+ color: toneColors[limit.tone].text,
90
+ },
91
+ ]}
92
+ >
93
+ {formatPercent(limit.remainingPercent)}
94
+ </Text>
95
+ </View>
96
+ </View>
97
+ ))}
98
+ </View>
99
+ );
100
+ }
101
+
102
+ function formatPercent(value: number): string {
103
+ if (value >= 100) {
104
+ return '100';
105
+ }
106
+
107
+ if (value <= 0) {
108
+ return '0';
109
+ }
110
+
111
+ return `${String(value)}%`;
112
+ }
113
+
114
+ const styles = StyleSheet.create({
115
+ row: {
116
+ flexDirection: 'row',
117
+ justifyContent: 'flex-start',
118
+ gap: spacing.xs + 2,
119
+ },
120
+ badge: {
121
+ flexDirection: 'row',
122
+ alignItems: 'center',
123
+ gap: 4,
124
+ },
125
+ meterRow: {
126
+ flexDirection: 'row',
127
+ alignItems: 'center',
128
+ gap: 3,
129
+ },
130
+ batteryWrap: {
131
+ flexDirection: 'row',
132
+ alignItems: 'center',
133
+ },
134
+ batteryBody: {
135
+ width: BATTERY_BODY_WIDTH,
136
+ height: 8,
137
+ borderRadius: 2,
138
+ borderWidth: 1,
139
+ padding: 1,
140
+ overflow: 'hidden',
141
+ },
142
+ batteryFill: {
143
+ height: '100%',
144
+ borderRadius: 1,
145
+ },
146
+ batteryCap: {
147
+ width: 2,
148
+ height: 4,
149
+ borderRadius: 1,
150
+ marginLeft: 1,
151
+ },
152
+ labelText: {
153
+ ...typography.caption,
154
+ fontWeight: '600',
155
+ fontVariant: ['tabular-nums'],
156
+ fontSize: 8,
157
+ lineHeight: 9,
158
+ textTransform: 'lowercase',
159
+ },
160
+ valueText: {
161
+ ...typography.caption,
162
+ fontWeight: '700',
163
+ fontVariant: ['tabular-nums'],
164
+ fontSize: 8,
165
+ lineHeight: 9,
166
+ },
167
+ });
@@ -0,0 +1,466 @@
1
+ import { Ionicons } from '@expo/vector-icons';
2
+ import { type ComponentProps } from 'react';
3
+ import {
4
+ ActivityIndicator,
5
+ Modal,
6
+ Pressable,
7
+ ScrollView,
8
+ StyleSheet,
9
+ Text,
10
+ useWindowDimensions,
11
+ View,
12
+ } from 'react-native';
13
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
14
+ import { colors, spacing, typography } from '../theme';
15
+
16
+ type IoniconName = ComponentProps<typeof Ionicons>['name'];
17
+
18
+ type OptionTone = 'default' | 'accent' | 'danger';
19
+ type SelectionSheetPresentation = 'default' | 'expanded';
20
+
21
+ export interface SelectionSheetOption {
22
+ key: string;
23
+ title: string;
24
+ description?: string;
25
+ badge?: string;
26
+ meta?: string;
27
+ icon?: IoniconName;
28
+ titleColor?: string;
29
+ descriptionColor?: string;
30
+ badgeBackgroundColor?: string;
31
+ badgeTextColor?: string;
32
+ metaColor?: string;
33
+ iconColor?: string;
34
+ selected?: boolean;
35
+ disabled?: boolean;
36
+ tone?: OptionTone;
37
+ onPress: () => void;
38
+ }
39
+
40
+ interface SelectionSheetProps {
41
+ visible: boolean;
42
+ title: string;
43
+ subtitle?: string;
44
+ eyebrow?: string;
45
+ options: SelectionSheetOption[];
46
+ onClose: () => void;
47
+ closeLabel?: string;
48
+ loading?: boolean;
49
+ loadingLabel?: string;
50
+ emptyLabel?: string;
51
+ presentation?: SelectionSheetPresentation;
52
+ }
53
+
54
+ export function SelectionSheet({
55
+ visible,
56
+ title,
57
+ subtitle,
58
+ eyebrow,
59
+ options,
60
+ onClose,
61
+ closeLabel = 'Close',
62
+ loading = false,
63
+ loadingLabel = 'Loading…',
64
+ emptyLabel = 'No options available.',
65
+ presentation = 'expanded',
66
+ }: SelectionSheetProps) {
67
+ const insets = useSafeAreaInsets();
68
+ const { height: windowHeight } = useWindowDimensions();
69
+ const expanded = presentation === 'expanded';
70
+ const expandedTopInset = Math.max(insets.top + spacing.xl + 20, 112);
71
+ const expandedBottomInset = Math.max(insets.bottom + spacing.xl + 28, 118);
72
+ const expandedCardHeight = Math.min(
73
+ Math.max(360, Math.round(windowHeight * 0.62)),
74
+ windowHeight - expandedTopInset - expandedBottomInset
75
+ );
76
+
77
+ return (
78
+ <Modal
79
+ visible={visible}
80
+ transparent
81
+ animationType="fade"
82
+ presentationStyle="overFullScreen"
83
+ onRequestClose={onClose}
84
+ >
85
+ <View style={styles.backdrop}>
86
+ <Pressable style={StyleSheet.absoluteFill} onPress={onClose} />
87
+ <View
88
+ style={[
89
+ styles.sheetOuter,
90
+ expanded && styles.sheetOuterExpanded,
91
+ {
92
+ paddingBottom: expanded ? expandedBottomInset : Math.max(insets.bottom, spacing.md),
93
+ paddingTop: expanded ? expandedTopInset : undefined,
94
+ },
95
+ ]}
96
+ >
97
+ <View
98
+ style={[
99
+ styles.sheetCard,
100
+ expanded && styles.sheetCardExpanded,
101
+ expanded ? { height: expandedCardHeight } : null,
102
+ ]}
103
+ >
104
+ <View style={styles.handle} />
105
+
106
+ <View style={styles.header}>
107
+ {eyebrow ? <Text style={styles.eyebrow}>{eyebrow}</Text> : null}
108
+ <Text style={styles.title}>{title}</Text>
109
+ {subtitle ? (
110
+ <Text style={styles.subtitle} numberOfLines={expanded ? 3 : 2}>
111
+ {subtitle}
112
+ </Text>
113
+ ) : null}
114
+ </View>
115
+
116
+ {loading ? (
117
+ <View style={styles.loadingState}>
118
+ <ActivityIndicator color={colors.textPrimary} />
119
+ <Text style={styles.loadingLabel}>{loadingLabel}</Text>
120
+ </View>
121
+ ) : options.length > 0 ? (
122
+ <ScrollView
123
+ style={[styles.list, expanded && styles.listExpanded]}
124
+ contentContainerStyle={[
125
+ styles.listContent,
126
+ expanded && styles.listContentExpanded,
127
+ ]}
128
+ showsVerticalScrollIndicator={false}
129
+ >
130
+ {options.map((option) => {
131
+ const tone = option.tone ?? 'default';
132
+ const iconColor =
133
+ option.iconColor ??
134
+ (tone === 'danger'
135
+ ? '#FF8A8A'
136
+ : option.selected || tone === 'accent'
137
+ ? colors.textPrimary
138
+ : colors.textMuted);
139
+ const titleColor = option.titleColor ?? colors.textPrimary;
140
+ const descriptionColor = option.descriptionColor ?? colors.textMuted;
141
+ const metaColor = option.metaColor ?? colors.textMuted;
142
+ const badgeBackgroundColor =
143
+ option.badgeBackgroundColor ?? styles.badge.backgroundColor;
144
+ const badgeTextColor =
145
+ option.badgeTextColor ?? styles.badgeText.color;
146
+
147
+ return (
148
+ <Pressable
149
+ key={option.key}
150
+ disabled={option.disabled}
151
+ onPress={option.onPress}
152
+ style={({ pressed }) => [
153
+ styles.option,
154
+ option.selected && styles.optionSelected,
155
+ option.disabled && styles.optionDisabled,
156
+ pressed && !option.disabled && styles.optionPressed,
157
+ ]}
158
+ >
159
+ <View style={styles.optionMain}>
160
+ {option.icon ? (
161
+ <View
162
+ style={[
163
+ styles.iconWrap,
164
+ option.selected && styles.iconWrapSelected,
165
+ tone === 'danger' && styles.iconWrapDanger,
166
+ ]}
167
+ >
168
+ <Ionicons name={option.icon} size={15} color={iconColor} />
169
+ </View>
170
+ ) : null}
171
+
172
+ <View style={styles.copy}>
173
+ <View style={styles.titleRow}>
174
+ <Text
175
+ style={[
176
+ styles.optionTitle,
177
+ option.selected && styles.optionTitleSelected,
178
+ { color: titleColor },
179
+ ]}
180
+ numberOfLines={2}
181
+ >
182
+ {option.title}
183
+ </Text>
184
+ {option.badge ? (
185
+ <View
186
+ style={[
187
+ styles.badge,
188
+ { backgroundColor: badgeBackgroundColor },
189
+ ]}
190
+ >
191
+ <Text style={[styles.badgeText, { color: badgeTextColor }]}>
192
+ {option.badge}
193
+ </Text>
194
+ </View>
195
+ ) : null}
196
+ </View>
197
+ {option.description ? (
198
+ <Text
199
+ style={[styles.optionDescription, { color: descriptionColor }]}
200
+ numberOfLines={2}
201
+ >
202
+ {option.description}
203
+ </Text>
204
+ ) : null}
205
+ </View>
206
+ </View>
207
+
208
+ <View style={styles.accessory}>
209
+ {option.meta ? (
210
+ <Text
211
+ style={[styles.meta, { color: metaColor }]}
212
+ numberOfLines={1}
213
+ >
214
+ {option.meta}
215
+ </Text>
216
+ ) : null}
217
+ {option.selected ? (
218
+ <Ionicons
219
+ name="checkmark-circle"
220
+ size={18}
221
+ color={colors.textPrimary}
222
+ />
223
+ ) : null}
224
+ </View>
225
+ </Pressable>
226
+ );
227
+ })}
228
+ </ScrollView>
229
+ ) : (
230
+ <View style={styles.loadingState}>
231
+ <Text style={styles.loadingLabel}>{emptyLabel}</Text>
232
+ </View>
233
+ )}
234
+
235
+ <View style={styles.footer}>
236
+ <Pressable
237
+ onPress={onClose}
238
+ style={({ pressed }) => [
239
+ styles.closeButton,
240
+ pressed && styles.closeButtonPressed,
241
+ ]}
242
+ >
243
+ <Text style={styles.closeText}>{closeLabel}</Text>
244
+ </Pressable>
245
+ </View>
246
+ </View>
247
+ </View>
248
+ </View>
249
+ </Modal>
250
+ );
251
+ }
252
+
253
+ const styles = StyleSheet.create({
254
+ backdrop: {
255
+ flex: 1,
256
+ justifyContent: 'flex-end',
257
+ backgroundColor: 'rgba(0, 0, 0, 0.52)',
258
+ },
259
+ sheetOuter: {
260
+ paddingHorizontal: spacing.md,
261
+ paddingTop: spacing.xl,
262
+ },
263
+ sheetOuterExpanded: {
264
+ paddingHorizontal: spacing.md,
265
+ },
266
+ sheetCard: {
267
+ maxHeight: '82%',
268
+ borderRadius: 24,
269
+ borderCurve: 'continuous',
270
+ borderWidth: 1,
271
+ borderColor: 'rgba(255, 255, 255, 0.09)',
272
+ backgroundColor: '#07090C',
273
+ paddingHorizontal: spacing.lg,
274
+ paddingTop: spacing.sm,
275
+ paddingBottom: spacing.lg,
276
+ gap: spacing.md,
277
+ boxShadow: '0 -10px 34px rgba(0, 0, 0, 0.42)',
278
+ },
279
+ sheetCardExpanded: {
280
+ maxHeight: undefined,
281
+ minHeight: undefined,
282
+ borderRadius: 28,
283
+ },
284
+ handle: {
285
+ alignSelf: 'center',
286
+ width: 38,
287
+ height: 4,
288
+ borderRadius: 999,
289
+ backgroundColor: 'rgba(255, 255, 255, 0.18)',
290
+ },
291
+ header: {
292
+ gap: 4,
293
+ },
294
+ eyebrow: {
295
+ ...typography.caption,
296
+ color: 'rgba(232, 236, 244, 0.58)',
297
+ fontSize: 10,
298
+ lineHeight: 12,
299
+ fontWeight: '700',
300
+ textTransform: 'uppercase',
301
+ letterSpacing: 1,
302
+ },
303
+ title: {
304
+ ...typography.headline,
305
+ color: colors.textPrimary,
306
+ fontSize: 18,
307
+ lineHeight: 22,
308
+ fontWeight: '700',
309
+ },
310
+ subtitle: {
311
+ ...typography.caption,
312
+ color: colors.textMuted,
313
+ fontSize: 12,
314
+ lineHeight: 16,
315
+ },
316
+ list: {
317
+ maxHeight: 420,
318
+ },
319
+ listExpanded: {
320
+ flex: 1,
321
+ maxHeight: undefined,
322
+ },
323
+ listContent: {
324
+ gap: spacing.sm,
325
+ },
326
+ listContentExpanded: {
327
+ paddingBottom: spacing.xs,
328
+ },
329
+ loadingState: {
330
+ minHeight: 120,
331
+ alignItems: 'center',
332
+ justifyContent: 'center',
333
+ gap: spacing.sm,
334
+ },
335
+ loadingLabel: {
336
+ ...typography.caption,
337
+ color: colors.textMuted,
338
+ textAlign: 'center',
339
+ },
340
+ option: {
341
+ minHeight: 64,
342
+ borderRadius: 18,
343
+ borderCurve: 'continuous',
344
+ borderWidth: 1,
345
+ borderColor: 'rgba(255, 255, 255, 0.08)',
346
+ backgroundColor: '#0D1014',
347
+ paddingHorizontal: spacing.md,
348
+ paddingVertical: spacing.sm + 2,
349
+ flexDirection: 'row',
350
+ alignItems: 'center',
351
+ justifyContent: 'space-between',
352
+ gap: spacing.md,
353
+ },
354
+ optionSelected: {
355
+ borderColor: 'rgba(255, 255, 255, 0.22)',
356
+ backgroundColor: '#141920',
357
+ },
358
+ optionDisabled: {
359
+ opacity: 0.56,
360
+ },
361
+ optionPressed: {
362
+ opacity: 0.88,
363
+ },
364
+ optionMain: {
365
+ flex: 1,
366
+ minWidth: 0,
367
+ flexDirection: 'row',
368
+ alignItems: 'center',
369
+ gap: spacing.sm,
370
+ },
371
+ iconWrap: {
372
+ width: 30,
373
+ height: 30,
374
+ borderRadius: 10,
375
+ alignItems: 'center',
376
+ justifyContent: 'center',
377
+ backgroundColor: '#171C22',
378
+ borderWidth: 1,
379
+ borderColor: 'rgba(255, 255, 255, 0.08)',
380
+ },
381
+ iconWrapSelected: {
382
+ backgroundColor: '#1C232C',
383
+ borderColor: 'rgba(255, 255, 255, 0.12)',
384
+ },
385
+ iconWrapDanger: {
386
+ backgroundColor: 'rgba(239, 68, 68, 0.12)',
387
+ borderColor: 'rgba(239, 68, 68, 0.24)',
388
+ },
389
+ copy: {
390
+ flex: 1,
391
+ minWidth: 0,
392
+ gap: 3,
393
+ },
394
+ titleRow: {
395
+ flexDirection: 'row',
396
+ alignItems: 'center',
397
+ gap: spacing.xs + 2,
398
+ },
399
+ optionTitle: {
400
+ ...typography.body,
401
+ flex: 1,
402
+ color: colors.textSecondary,
403
+ fontWeight: '600',
404
+ lineHeight: 18,
405
+ },
406
+ optionTitleSelected: {
407
+ color: colors.textPrimary,
408
+ },
409
+ optionDescription: {
410
+ ...typography.caption,
411
+ color: 'rgba(232, 236, 244, 0.62)',
412
+ lineHeight: 15,
413
+ },
414
+ badge: {
415
+ borderRadius: 999,
416
+ borderWidth: 1,
417
+ borderColor: 'rgba(255, 255, 255, 0.1)',
418
+ backgroundColor: '#11151A',
419
+ paddingHorizontal: spacing.xs + 4,
420
+ paddingVertical: 2,
421
+ },
422
+ badgeText: {
423
+ ...typography.caption,
424
+ color: colors.textMuted,
425
+ fontSize: 10,
426
+ lineHeight: 12,
427
+ fontWeight: '700',
428
+ textTransform: 'uppercase',
429
+ letterSpacing: 0.6,
430
+ },
431
+ accessory: {
432
+ flexShrink: 0,
433
+ alignItems: 'flex-end',
434
+ gap: 6,
435
+ },
436
+ meta: {
437
+ ...typography.caption,
438
+ color: colors.textMuted,
439
+ fontSize: 11,
440
+ lineHeight: 14,
441
+ fontWeight: '600',
442
+ },
443
+ footer: {
444
+ alignItems: 'flex-end',
445
+ },
446
+ closeButton: {
447
+ minWidth: 88,
448
+ borderRadius: 14,
449
+ borderCurve: 'continuous',
450
+ borderWidth: 1,
451
+ borderColor: 'rgba(255, 255, 255, 0.1)',
452
+ backgroundColor: '#101318',
453
+ paddingHorizontal: spacing.lg,
454
+ paddingVertical: spacing.sm + 2,
455
+ alignItems: 'center',
456
+ justifyContent: 'center',
457
+ },
458
+ closeButtonPressed: {
459
+ opacity: 0.86,
460
+ },
461
+ closeText: {
462
+ ...typography.body,
463
+ color: colors.textPrimary,
464
+ fontWeight: '600',
465
+ },
466
+ });
@@ -1,15 +1,19 @@
1
1
  import { Ionicons } from '@expo/vector-icons';
2
2
  import { ActivityIndicator, Platform, StyleSheet, Text, View } from 'react-native';
3
- import Animated, { FadeInUp } from 'react-native-reanimated';
4
3
 
5
4
  import { colors, radius, spacing } from '../theme';
6
5
 
7
6
  interface ToolBlockProps {
8
7
  command: string;
9
8
  status: 'running' | 'complete' | 'error';
9
+ icon?: keyof typeof Ionicons.glyphMap;
10
10
  }
11
11
 
12
- export function ToolBlock({ command, status }: ToolBlockProps) {
12
+ export function ToolBlock({
13
+ command,
14
+ status,
15
+ icon = 'terminal-outline',
16
+ }: ToolBlockProps) {
13
17
  const statusIcon: keyof typeof Ionicons.glyphMap | null =
14
18
  status === 'running'
15
19
  ? null
@@ -24,19 +28,17 @@ export function ToolBlock({ command, status }: ToolBlockProps) {
24
28
  : colors.statusError;
25
29
 
26
30
  return (
27
- <Animated.View entering={FadeInUp.duration(300)}>
28
- <View style={styles.container}>
29
- <Ionicons name="terminal-outline" size={14} color={colors.textSecondary} />
30
- <Text style={styles.command} numberOfLines={1}>
31
- {command}
32
- </Text>
33
- {status === 'running' ? (
34
- <ActivityIndicator size="small" color={statusColor} />
35
- ) : statusIcon ? (
36
- <Ionicons name={statusIcon} size={14} color={statusColor} />
37
- ) : null}
38
- </View>
39
- </Animated.View>
31
+ <View style={styles.container}>
32
+ <Ionicons name={icon} size={14} color={colors.textSecondary} />
33
+ <Text style={styles.command} numberOfLines={1}>
34
+ {command}
35
+ </Text>
36
+ {status === 'running' ? (
37
+ <ActivityIndicator size="small" color={statusColor} />
38
+ ) : statusIcon ? (
39
+ <Ionicons name={statusIcon} size={14} color={statusColor} />
40
+ ) : null}
41
+ </View>
40
42
  );
41
43
  }
42
44