A React Native app for the ultimate thinking partner.

refactor(ui): move tool details icon to header row

Moved the tool details info icon from bottom-right of the message to the
header row, positioned to the left of the chevron. This is cleaner and
more intuitive.

Layout is now:
(co recalled) ⓘ ∨

Changes:
- MessageGroupBubble: Added tool details button in header with state
- ToolCallItem: Accepts showToolDetails prop from parent (no local state)
- Removed bottom-right positioning, now controlled by header button

+40 -42
+33 -5
src/components/MessageGroupBubble.tsx
··· 127 127 if (group.type === 'tool_call') { 128 128 const isExpanded = expandedReasoning.has(group.id); 129 129 const label = getMessageLabel(group); 130 + const [showToolDetails, setShowToolDetails] = React.useState(false); 130 131 131 132 return ( 132 133 <View style={styles.messageContainer}> ··· 135 136 <Text style={[styles.messageLabel, { color: theme.colors.text.primary }]}> 136 137 {label} 137 138 </Text> 138 - <InlineReasoningButton 139 - isExpanded={isExpanded} 140 - onToggle={() => toggleReasoning(group.id)} 141 - isDark={isDark} 142 - /> 139 + <View style={styles.headerButtons}> 140 + {/* Tool details button */} 141 + <TouchableOpacity 142 + onPress={() => setShowToolDetails(!showToolDetails)} 143 + style={styles.toolDetailsButton} 144 + activeOpacity={0.7} 145 + > 146 + <Ionicons 147 + name="information-circle-outline" 148 + size={16} 149 + color={theme.colors.text.tertiary} 150 + /> 151 + </TouchableOpacity> 152 + <InlineReasoningButton 153 + isExpanded={isExpanded} 154 + onToggle={() => toggleReasoning(group.id)} 155 + isDark={isDark} 156 + /> 157 + </View> 143 158 </View> 144 159 145 160 {/* Expanded content - only show when chevron is clicked */} ··· 168 183 resultText={group.toolReturn} 169 184 hasResult={!!group.toolReturn} 170 185 showResult={showToolResults} 186 + showToolDetails={showToolDetails} 171 187 isDark={isDark} 172 188 hideHeader={true} // Label already shown in unified header above 173 189 /> ··· 359 375 messageHeader: { 360 376 flexDirection: 'row', 361 377 alignItems: 'center', 378 + justifyContent: 'space-between', 362 379 marginBottom: 8, 363 380 }, 381 + headerButtons: { 382 + flexDirection: 'row', 383 + alignItems: 'center', 384 + gap: 8, 385 + }, 386 + toolDetailsButton: { 387 + padding: 8, 388 + opacity: 0.3, 389 + borderRadius: 4, 390 + }, 364 391 messageLabel: { 365 392 fontSize: 16, 366 393 fontFamily: 'Lexend_500Medium', 394 + flex: 1, 367 395 }, 368 396 // Reasoning expanded content (from ReasoningToggle) 369 397 reasoningExpandedContainer: {
+7 -37
src/components/ToolCallItem.tsx
··· 8 8 resultText?: string; 9 9 hasResult?: boolean; 10 10 showResult?: boolean; // When false, hide the result section entirely 11 + showToolDetails?: boolean; // Controlled by parent - whether to show tool call details 11 12 isDark?: boolean; 12 13 hideHeader?: boolean; // When true, skip rendering the label header (shown by parent) 13 14 } ··· 63 64 return displayNames[toolName] || { present: toolName, past: toolName }; 64 65 }; 65 66 66 - const ToolCallItem: React.FC<ToolCallItemProps> = ({ callText, resultText, hasResult = false, showResult = true, isDark = true, hideHeader = false }) => { 67 + const ToolCallItem: React.FC<ToolCallItemProps> = ({ callText, resultText, hasResult = false, showResult = true, showToolDetails = false, isDark = true, hideHeader = false }) => { 67 68 const theme = isDark ? darkTheme : lightTheme; 68 69 const [expanded, setExpanded] = useState(false); 69 70 const [resultExpanded, setResultExpanded] = useState(false); ··· 201 202 /> 202 203 </TouchableOpacity> 203 204 )} 204 - {hideHeader && ( 205 - <View style={styles.embeddedContainer}> 206 - {/* Details button - bottom right */} 207 - <TouchableOpacity 208 - style={styles.detailsButton} 209 - onPress={() => setExpanded((e) => !e)} 210 - activeOpacity={0.7} 211 - > 212 - <Ionicons 213 - name="information-circle-outline" 214 - size={16} 215 - color={theme.colors.text.tertiary} 216 - /> 217 - </TouchableOpacity> 218 - 219 - {/* Expanded tool call details */} 220 - {expanded && ( 221 - <View style={styles.expandedDetails}> 222 - <Text style={[styles.callText, { color: theme.colors.text.primary }]}> 223 - {prettyCallText} 224 - </Text> 225 - </View> 226 - )} 205 + {hideHeader && showToolDetails && ( 206 + <View style={styles.expandedDetails}> 207 + <Text style={[styles.callText, { color: theme.colors.text.primary }]}> 208 + {prettyCallText} 209 + </Text> 227 210 </View> 228 211 )} 229 212 {showResult && !!resultText && ( ··· 262 245 }, 263 246 chevron: { 264 247 marginLeft: 4, 265 - }, 266 - embeddedContainer: { 267 - position: 'relative', 268 - width: '100%', 269 - }, 270 - detailsButton: { 271 - position: 'absolute', 272 - bottom: 0, 273 - right: 0, 274 - padding: 8, 275 - opacity: 0.3, 276 - borderRadius: 4, 277 - zIndex: 10, 278 248 }, 279 249 expandedDetails: { 280 250 paddingTop: 8,