Merge branch 'fix/diff-padding'

This commit is contained in:
Mohamed Boudra
2026-02-04 12:58:26 +07:00
5 changed files with 176 additions and 152 deletions

View File

@@ -0,0 +1,9 @@
export function getCodeInsets(theme: any) {
const paddingX = theme.spacing[3] + theme.spacing[2];
const paddingY = theme.spacing[1];
const extraRight = theme.spacing[4];
const extraBottom = theme.spacing[3];
return { paddingX, paddingY, extraRight, extraBottom };
}

View File

@@ -4,6 +4,7 @@ import { ScrollView } from "react-native-gesture-handler";
import { StyleSheet } from "react-native-unistyles";
import { Fonts } from "@/constants/theme";
import type { DiffLine, DiffSegment } from "@/utils/tool-call-parsers";
import { getCodeInsets } from "./code-insets";
interface DiffViewerProps {
diffLines: DiffLine[];
@@ -86,64 +87,70 @@ export function DiffViewer({ diffLines, maxHeight = 280, emptyLabel = "No change
);
}
const styles = StyleSheet.create((theme) => ({
verticalScroll: {},
verticalContent: {
flexGrow: 1,
},
horizontalContent: {
flexDirection: "column" as const,
},
linesContainer: {
alignSelf: "flex-start",
},
line: {
minWidth: "100%",
paddingHorizontal: theme.spacing[3],
paddingVertical: theme.spacing[1],
},
lineText: {
fontFamily: Fonts.mono,
fontSize: theme.fontSize.xs,
color: theme.colors.foreground,
},
headerLine: {
backgroundColor: theme.colors.surface2,
},
headerText: {
color: theme.colors.foregroundMuted,
},
addLine: {
backgroundColor: "rgba(46, 160, 67, 0.15)",
},
addText: {
color: theme.colors.foreground,
},
removeLine: {
backgroundColor: "rgba(248, 81, 73, 0.1)",
},
removeText: {
color: theme.colors.foreground,
},
addHighlight: {
backgroundColor: "rgba(46, 160, 67, 0.4)",
},
removeHighlight: {
backgroundColor: "rgba(248, 81, 73, 0.35)",
},
contextLine: {
backgroundColor: theme.colors.surface2,
},
contextText: {
color: theme.colors.foregroundMuted,
},
emptyState: {
padding: theme.spacing[4],
alignItems: "center" as const,
justifyContent: "center" as const,
},
emptyText: {
fontSize: theme.fontSize.sm,
color: theme.colors.foregroundMuted,
},
}));
const styles = StyleSheet.create((theme) => {
const insets = getCodeInsets(theme);
return {
verticalScroll: {},
verticalContent: {
flexGrow: 1,
paddingBottom: insets.extraBottom,
},
horizontalContent: {
flexDirection: "column" as const,
paddingRight: insets.extraRight,
},
linesContainer: {
alignSelf: "flex-start",
},
line: {
minWidth: "100%",
paddingHorizontal: insets.paddingX,
paddingVertical: insets.paddingY,
},
lineText: {
fontFamily: Fonts.mono,
fontSize: theme.fontSize.xs,
color: theme.colors.foreground,
},
headerLine: {
backgroundColor: theme.colors.surface2,
},
headerText: {
color: theme.colors.foregroundMuted,
},
addLine: {
backgroundColor: "rgba(46, 160, 67, 0.15)",
},
addText: {
color: theme.colors.foreground,
},
removeLine: {
backgroundColor: "rgba(248, 81, 73, 0.1)",
},
removeText: {
color: theme.colors.foreground,
},
addHighlight: {
backgroundColor: "rgba(46, 160, 67, 0.4)",
},
removeHighlight: {
backgroundColor: "rgba(248, 81, 73, 0.35)",
},
contextLine: {
backgroundColor: theme.colors.surface2,
},
contextText: {
color: theme.colors.foregroundMuted,
},
emptyState: {
padding: theme.spacing[4],
alignItems: "center" as const,
justifyContent: "center" as const,
},
emptyText: {
fontSize: theme.fontSize.sm,
color: theme.colors.foregroundMuted,
},
};
});

View File

@@ -389,8 +389,8 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
borderTopWidth: 0,
borderColor: theme.colors.borderAccent,
backgroundColor: theme.colors.surface0,
padding: theme.spacing[2],
gap: theme.spacing[2],
padding: 0,
gap: 0,
flexShrink: 1,
minWidth: 0,
},
@@ -781,6 +781,9 @@ interface TodoListCardProps {
}
const todoListCardStylesheet = StyleSheet.create((theme) => ({
detailsWrapper: {
padding: theme.spacing[2],
},
list: {
gap: theme.spacing[1],
},
@@ -832,26 +835,28 @@ export const TodoListCard = memo(function TodoListCard({
const renderDetails = useCallback(() => {
return (
<View style={todoListCardStylesheet.list}>
{items.length === 0 ? (
<Text style={todoListCardStylesheet.emptyText}>No tasks yet.</Text>
) : (
items.map((item, idx) => (
<View key={`${item.text}-${idx}`} style={todoListCardStylesheet.itemRow}>
<View style={todoListCardStylesheet.radioOuter}>
{item.completed ? <View style={todoListCardStylesheet.radioInner} /> : null}
<View style={todoListCardStylesheet.detailsWrapper}>
<View style={todoListCardStylesheet.list}>
{items.length === 0 ? (
<Text style={todoListCardStylesheet.emptyText}>No tasks yet.</Text>
) : (
items.map((item, idx) => (
<View key={`${item.text}-${idx}`} style={todoListCardStylesheet.itemRow}>
<View style={todoListCardStylesheet.radioOuter}>
{item.completed ? <View style={todoListCardStylesheet.radioInner} /> : null}
</View>
<Text
style={[
todoListCardStylesheet.itemText,
item.completed && todoListCardStylesheet.itemTextCompleted,
]}
>
{item.text}
</Text>
</View>
<Text
style={[
todoListCardStylesheet.itemText,
item.completed && todoListCardStylesheet.itemTextCompleted,
]}
>
{item.text}
</Text>
</View>
))
)}
))
)}
</View>
</View>
);
}, [items]);
@@ -1099,12 +1104,13 @@ export const ToolCall = memo(function ToolCall({
args,
result,
error,
cwd,
});
} else {
// Desktop: toggle inline expansion
setIsExpanded((prev) => !prev);
}
}, [isMobile, openToolCall, toolName, kind, status, args, result, error]);
}, [isMobile, openToolCall, toolName, kind, status, args, result, error, cwd]);
useEffect(() => {
if (isMobile || !isPerfLoggingEnabled()) {
@@ -1136,15 +1142,7 @@ export const ToolCall = memo(function ToolCall({
// Render inline details for desktop
const renderDetails = useCallback(() => {
if (isMobile) return null;
return (
<View style={toolCallInlineStyles.detailsContainer}>
<ToolCallDetailsContent
display={display}
errorText={errorText}
maxHeight={400}
/>
</View>
);
return <ToolCallDetailsContent display={display} errorText={errorText} maxHeight={400} />;
}, [isMobile, display, errorText]);
return (
@@ -1169,10 +1167,3 @@ export const ToolCall = memo(function ToolCall({
/>
);
});
const toolCallInlineStyles = StyleSheet.create((theme) => ({
detailsContainer: {
paddingTop: theme.spacing[3],
paddingBottom: theme.spacing[2],
},
}));

View File

@@ -11,6 +11,7 @@ import {
type ToolCallDisplay,
} from "@/utils/tool-call-parsers";
import { DiffViewer } from "./diff-viewer";
import { getCodeInsets } from "./code-insets";
// ---- Types ----
@@ -157,46 +158,37 @@ export function ToolCallDetailsContent({
}, [display]);
const sections: ReactNode[] = [];
const isFullBleed = display.type === "edit" || display.type === "shell";
if (display.type === "shell") {
const combinedText = `$ ${display.command}${display.output ? `\n${display.output}` : ""}`;
sections.push(
<View key="shell" style={styles.section}>
<Text style={styles.sectionTitle}>Command</Text>
<ScrollView
horizontal
nestedScrollEnabled
style={styles.jsonScroll}
contentContainerStyle={styles.jsonContent}
showsHorizontalScrollIndicator={true}
>
<Text selectable style={styles.scrollText}>{display.command}</Text>
</ScrollView>
{display.output ? (
<View style={styles.diffContainer}>
<ScrollView
style={[styles.scrollArea, { maxHeight }]}
contentContainerStyle={styles.scrollContent}
style={[styles.codeVerticalScroll, { maxHeight }]}
contentContainerStyle={styles.codeVerticalContent}
nestedScrollEnabled
showsVerticalScrollIndicator={true}
showsVerticalScrollIndicator
>
<ScrollView
horizontal
nestedScrollEnabled
showsHorizontalScrollIndicator={true}
showsHorizontalScrollIndicator
contentContainerStyle={styles.codeHorizontalContent}
>
<Text selectable style={styles.scrollText}>{display.output}</Text>
<View style={styles.codeLine}>
<Text selectable style={styles.scrollText}>{combinedText}</Text>
</View>
</ScrollView>
</ScrollView>
) : null}
</View>
</View>
);
} else if (display.type === "edit") {
sections.push(
<View key="edit" style={styles.section}>
<Text style={styles.sectionTitle}>File</Text>
<View style={styles.fileBadge}>
<Text style={styles.fileBadgeText}>{display.filePath}</Text>
</View>
{diffLines && diffLines.length > 0 ? (
{diffLines ? (
<View style={styles.diffContainer}>
<DiffViewer diffLines={diffLines} maxHeight={maxHeight} />
</View>
@@ -206,10 +198,6 @@ export function ToolCallDetailsContent({
} else if (display.type === "read") {
sections.push(
<View key="read" style={styles.section}>
<Text style={styles.sectionTitle}>File</Text>
<View style={styles.fileBadge}>
<Text style={styles.fileBadgeText}>{display.filePath}</Text>
</View>
{(display.offset !== undefined || display.limit !== undefined) ? (
<Text style={styles.rangeText}>
{display.offset !== undefined ? `Offset: ${display.offset}` : ""}
@@ -326,7 +314,11 @@ export function ToolCallDetailsContent({
);
}
return <View style={styles.container}>{sections}</View>;
return (
<View style={isFullBleed ? styles.fullBleedContainer : styles.paddedContainer}>
{sections}
</View>
);
}
// ---- Hook for parsing tool call data ----
@@ -362,10 +354,18 @@ export function useToolCallDetails(data: ToolCallDetailsData) {
// ---- Styles ----
const styles = StyleSheet.create((theme) => ({
container: {
gap: theme.spacing[4],
},
const styles = StyleSheet.create((theme) => {
const insets = getCodeInsets(theme);
return {
paddedContainer: {
gap: theme.spacing[4],
padding: theme.spacing[2],
},
fullBleedContainer: {
gap: theme.spacing[2],
padding: 0,
},
groupHeader: {
flexDirection: "row",
alignItems: "center",
@@ -391,20 +391,6 @@ const styles = StyleSheet.create((theme) => ({
textTransform: "uppercase",
letterSpacing: 0.5,
},
fileBadge: {
alignSelf: "flex-start",
paddingHorizontal: theme.spacing[2],
paddingVertical: theme.spacing[1],
borderRadius: theme.borderRadius.base,
borderWidth: theme.borderWidth[1],
borderColor: theme.colors.border,
backgroundColor: theme.colors.surface2,
},
fileBadgeText: {
color: theme.colors.foreground,
fontFamily: Fonts.mono,
fontSize: theme.fontSize.xs,
},
rangeText: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.xs,
@@ -416,6 +402,19 @@ const styles = StyleSheet.create((theme) => ({
overflow: "hidden",
backgroundColor: theme.colors.surface2,
},
codeVerticalScroll: {},
codeVerticalContent: {
flexGrow: 1,
paddingBottom: insets.extraBottom,
},
codeHorizontalContent: {
paddingRight: insets.extraRight,
},
codeLine: {
minWidth: "100%",
paddingHorizontal: insets.paddingX,
paddingVertical: insets.paddingY,
},
scrollArea: {
borderWidth: theme.borderWidth[1],
borderColor: theme.colors.border,
@@ -451,4 +450,5 @@ const styles = StyleSheet.create((theme) => ({
fontSize: theme.fontSize.sm,
fontStyle: "italic",
},
}));
};
});

View File

@@ -16,6 +16,7 @@ import {
BottomSheetBackgroundProps,
} from "@gorhom/bottom-sheet";
import { Pencil, Eye, SquareTerminal, Search, Wrench, X } from "lucide-react-native";
import { extractPrincipalParam } from "@/utils/tool-call-parsers";
import { ToolCallDetailsContent, useToolCallDetails } from "./tool-call-details";
// ----- Types -----
@@ -24,6 +25,7 @@ export interface ToolCallSheetData {
toolName: string;
kind?: string;
status?: "executing" | "completed" | "failed";
cwd?: string;
args?: unknown;
result?: unknown;
error?: unknown;
@@ -138,13 +140,14 @@ interface ToolCallSheetContentProps {
}
function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
const { toolName, kind, args, result, error } = data;
const { toolName, kind, cwd, args, result, error } = data;
const IconComponent = kind
? toolKindIcons[kind.toLowerCase()] || Wrench
: Wrench;
const { display, errorText } = useToolCallDetails({ toolName, args, result, error });
const principalParam = useMemo(() => extractPrincipalParam(args, cwd), [args, cwd]);
return (
<View style={styles.container}>
@@ -152,9 +155,16 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
<View style={styles.header}>
<View style={styles.headerLeft}>
<IconComponent size={20} color={styles.headerIcon.color} />
<Text style={styles.headerTitle} numberOfLines={1}>
{display.toolName}
</Text>
<View style={styles.headerTextColumn}>
<Text style={styles.headerTitle} numberOfLines={1}>
{display.toolName}
</Text>
{principalParam ? (
<Text style={styles.headerSubtitle} numberOfLines={1}>
{principalParam}
</Text>
) : null}
</View>
</View>
<Pressable onPress={onClose} style={styles.closeButton}>
<X size={20} color={styles.closeIcon.color} />
@@ -200,6 +210,10 @@ const styles = StyleSheet.create((theme) => ({
gap: theme.spacing[2],
flex: 1,
},
headerTextColumn: {
flex: 1,
minWidth: 0,
},
headerIcon: {
color: theme.colors.foreground,
},
@@ -209,6 +223,11 @@ const styles = StyleSheet.create((theme) => ({
color: theme.colors.foreground,
flex: 1,
},
headerSubtitle: {
marginTop: theme.spacing[1],
fontSize: theme.fontSize.sm,
color: theme.colors.foregroundMuted,
},
closeButton: {
padding: theme.spacing[2],
},
@@ -220,8 +239,6 @@ const styles = StyleSheet.create((theme) => ({
backgroundColor: theme.colors.surface2,
},
contentContainer: {
paddingHorizontal: theme.spacing[4],
paddingTop: theme.spacing[4],
paddingBottom: theme.spacing[8],
padding: 0,
},
}));