A React Native app for the ultimate thinking partner.

fix(messages): increase initialNumToRender to 100 to show all recent messages

Previously only the first 50 of the 100 most recent messages were rendered on initial load.

+59 -15
+59 -15
App.tsx
··· 18 18 Image, 19 19 KeyboardAvoidingView, 20 20 ScrollView, 21 + Keyboard, 21 22 } from 'react-native'; 22 23 import { Ionicons } from '@expo/vector-icons'; 23 24 import { StatusBar } from 'expo-status-bar'; 24 25 import * as Clipboard from 'expo-clipboard'; 25 26 import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'; 27 + import * as SystemUI from 'expo-system-ui'; 26 28 import Markdown from '@ronradtke/react-native-markdown-display'; 27 29 import * as ImagePicker from 'expo-image-picker'; 28 30 import { useFonts, Lexend_300Light, Lexend_400Regular, Lexend_500Medium, Lexend_600SemiBold, Lexend_700Bold } from '@expo-google-fonts/lexend'; ··· 52 54 const insets = useSafeAreaInsets(); 53 55 const systemColorScheme = useColorScheme(); 54 56 const [colorScheme, setColorScheme] = useState<'light' | 'dark'>(systemColorScheme || 'dark'); 57 + 58 + // Set Android system UI colors 59 + useEffect(() => { 60 + if (Platform.OS === 'android') { 61 + SystemUI.setBackgroundColorAsync(darkTheme.colors.background.primary); 62 + } 63 + }, []); 64 + 65 + // Keyboard height tracking for Android 66 + const [keyboardHeight, setKeyboardHeight] = useState(0); 67 + 68 + useEffect(() => { 69 + if (Platform.OS !== 'android') return; 70 + 71 + const showSubscription = Keyboard.addListener('keyboardDidShow', (e) => { 72 + setKeyboardHeight(e.endCoordinates.height); 73 + }); 74 + const hideSubscription = Keyboard.addListener('keyboardDidHide', () => { 75 + setKeyboardHeight(0); 76 + }); 77 + 78 + return () => { 79 + showSubscription.remove(); 80 + hideSubscription.remove(); 81 + }; 82 + }, []); 55 83 56 84 const [fontsLoaded] = useFonts({ 57 85 Lexend_300Light, ··· 1536 1564 messageId={msg.id} 1537 1565 isExpanded={isReasoningExpanded} 1538 1566 onToggle={() => toggleReasoning(msg.id)} 1567 + isDark={colorScheme === 'dark'} 1539 1568 /> 1540 1569 </View> 1541 1570 ); ··· 1555 1584 resultText={toolReturn?.content} 1556 1585 reasoning={msg.reasoning} 1557 1586 hasResult={!!toolReturn} 1587 + isDark={colorScheme === 'dark'} 1558 1588 /> 1559 1589 </View> 1560 1590 ); ··· 1743 1773 messageId={msg.id} 1744 1774 isExpanded={isReasoningExpanded} 1745 1775 onToggle={() => toggleReasoning(msg.id)} 1776 + isDark={colorScheme === 'dark'} 1746 1777 /> 1747 1778 )} 1779 + <Text style={[styles.assistantLabel, { color: theme.colors.text.primary }]}>(co said)</Text> 1748 1780 <View style={{ position: 'relative' }}> 1749 1781 <ExpandableMessageContent 1750 1782 content={msg.content} ··· 1924 1956 }} 1925 1957 > 1926 1958 <Ionicons name="library-outline" size={24} color={theme.colors.text.primary} /> 1927 - <Text style={[styles.menuItemText, { color: theme.colors.text.primary }]}>Knowledge</Text> 1959 + <Text style={[styles.menuItemText, { color: theme.colors.text.primary }]}>Memory</Text> 1928 1960 </TouchableOpacity> 1929 1961 1930 1962 <TouchableOpacity ··· 2134 2166 <Text style={[ 2135 2167 styles.viewSwitcherText, 2136 2168 { color: currentView === 'knowledge' ? theme.colors.text.primary : theme.colors.text.tertiary } 2137 - ]}>Knowledge</Text> 2169 + ]}>Memory</Text> 2138 2170 </TouchableOpacity> 2139 2171 </View> 2140 2172 )} 2141 2173 2142 2174 {/* View Content */} 2143 2175 <KeyboardAvoidingView 2144 - behavior={Platform.OS === 'ios' ? 'padding' : 'height'} 2176 + behavior={Platform.OS === 'ios' ? 'padding' : undefined} 2145 2177 style={styles.chatRow} 2146 2178 keyboardVerticalOffset={Platform.OS === 'ios' ? insets.top + 60 : 0} 2147 2179 > 2148 2180 {/* You View */} 2149 - <View style={[styles.memoryViewContainer, { display: currentView === 'you' ? 'flex' : 'none' }]}> 2181 + <View style={[styles.memoryViewContainer, { display: currentView === 'you' ? 'flex' : 'none', backgroundColor: theme.colors.background.primary }]}> 2150 2182 {!hasCheckedYouBlock ? ( 2151 2183 /* Loading state - checking for You block */ 2152 2184 <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> ··· 2223 2255 removeClippedSubviews={false} 2224 2256 maxToRenderPerBatch={20} 2225 2257 updateCellsBatchingPeriod={50} 2226 - initialNumToRender={50} 2258 + initialNumToRender={100} 2227 2259 contentContainerStyle={[ 2228 2260 styles.messagesList, 2229 2261 displayMessages.length === 0 && { flexGrow: 1 } ··· 2250 2282 if (block.type === 'reasoning') { 2251 2283 return ( 2252 2284 <React.Fragment key={`completed-${index}`}> 2253 - <LiveStatusIndicator status="thought" /> 2285 + <LiveStatusIndicator status="thought" isDark={colorScheme === 'dark'} /> 2254 2286 <View style={styles.reasoningStreamingContainer}> 2255 2287 <Text style={styles.reasoningStreamingText}>{block.content}</Text> 2256 2288 </View> ··· 2259 2291 } else if (block.type === 'assistant_message') { 2260 2292 return ( 2261 2293 <React.Fragment key={`completed-${index}`}> 2262 - <LiveStatusIndicator status="saying" /> 2294 + <LiveStatusIndicator status="saying" isDark={colorScheme === 'dark'} /> 2263 2295 <View style={{ flex: 1 }}> 2264 2296 <MessageContent 2265 2297 content={block.content} ··· 2290 2322 <ToolCallItem 2291 2323 callText={toolCall.args} 2292 2324 hasResult={false} 2325 + isDark={colorScheme === 'dark'} 2293 2326 /> 2294 2327 </View> 2295 2328 ))} ··· 2298 2331 {currentStream.assistantMessage && ( 2299 2332 <> 2300 2333 <View style={currentStream.toolCalls.length > 0 ? { marginTop: 16 } : undefined}> 2301 - <LiveStatusIndicator status="saying" /> 2334 + <LiveStatusIndicator status="saying" isDark={colorScheme === 'dark'} /> 2302 2335 </View> 2303 2336 <View style={{ flex: 1 }}> 2304 2337 <MessageContent ··· 2313 2346 2314 2347 {/* Show thinking indicator if nothing else to show */} 2315 2348 {completedStreamBlocks.length === 0 && !currentStream.reasoning && !currentStream.assistantMessage && currentStream.toolCalls.length === 0 && ( 2316 - <LiveStatusIndicator status="thinking" /> 2349 + <LiveStatusIndicator status="thinking" isDark={colorScheme === 'dark'} /> 2317 2350 )} 2318 2351 </Animated.View> 2319 2352 )} ··· 2344 2377 <View 2345 2378 style={[ 2346 2379 styles.inputContainer, 2347 - { paddingBottom: Math.max(insets.bottom, 16) }, 2380 + { 2381 + paddingBottom: Platform.OS === 'android' 2382 + ? (keyboardHeight > 0 ? keyboardHeight + 40 : 40) 2383 + : Math.max(insets.bottom, 16) 2384 + }, 2348 2385 displayMessages.length === 0 && styles.inputContainerCentered 2349 2386 ]} 2350 2387 onLayout={handleInputLayout} ··· 2448 2485 </View> 2449 2486 2450 2487 {/* Knowledge View */} 2451 - <View style={[styles.memoryViewContainer, { display: currentView === 'knowledge' ? 'flex' : 'none' }]}> 2488 + <View style={[styles.memoryViewContainer, { display: currentView === 'knowledge' ? 'flex' : 'none', backgroundColor: theme.colors.background.primary }]}> 2452 2489 {/* Knowledge Tabs */} 2453 2490 <View style={[styles.knowledgeTabs, { backgroundColor: theme.colors.background.secondary, borderBottomColor: theme.colors.border.primary }]}> 2454 2491 <TouchableOpacity ··· 2749 2786 </View> 2750 2787 2751 2788 {/* Settings View */} 2752 - <View style={[styles.memoryViewContainer, { display: currentView === 'settings' ? 'flex' : 'none' }]}> 2789 + <View style={[styles.memoryViewContainer, { display: currentView === 'settings' ? 'flex' : 'none', backgroundColor: theme.colors.background.primary }]}> 2753 2790 <View style={[styles.settingsHeader, { backgroundColor: theme.colors.background.secondary, borderBottomColor: theme.colors.border.primary }]}> 2754 2791 <Text style={[styles.settingsTitle, { color: theme.colors.text.primary }]}>Settings</Text> 2755 2792 </View> ··· 2963 3000 2964 3001 export default function App() { 2965 3002 return ( 2966 - <SafeAreaProvider> 2967 - <CoApp /> 2968 - </SafeAreaProvider> 3003 + <View style={{ flex: 1, backgroundColor: darkTheme.colors.background.primary }}> 3004 + <SafeAreaProvider style={{ flex: 1, backgroundColor: darkTheme.colors.background.primary }}> 3005 + <CoApp /> 3006 + </SafeAreaProvider> 3007 + </View> 2969 3008 ); 2970 3009 } 2971 3010 ··· 3074 3113 paddingHorizontal: 18, 3075 3114 paddingVertical: 16, 3076 3115 width: '100%', 3116 + }, 3117 + assistantLabel: { 3118 + fontSize: 16, 3119 + fontFamily: 'Lexend_500Medium', 3120 + marginBottom: 8, 3077 3121 }, 3078 3122 messageBubble: { 3079 3123 maxWidth: 600,