Fix markdown list text wrapping on Android

- Add custom list rendering rules in message.tsx for proper flex layout
- Remove flexWrap from list content styles (causes Android clipping)
- Use flex: 1 and flexShrink: 1 for natural text wrapping
- Add minWidth: 0 to content containers for proper shrinking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-12-24 04:41:37 +00:00
co-authored by Claude Opus 4.5
parent 09f130408b
commit 37da13a38b
4 changed files with 240 additions and 45 deletions
@@ -599,6 +599,111 @@ function PermissionRequestCard({
[theme]
);
const markdownRules = useMemo(() => {
return {
text: (
node: any,
_children: React.ReactNode[],
_parent: any,
styles: any,
inheritedStyles: any = {}
) => (
<Text key={node.key} style={[inheritedStyles, styles.text]} selectable>
{node.content}
</Text>
),
textgroup: (
node: any,
children: React.ReactNode[],
_parent: any,
styles: any,
inheritedStyles: any = {}
) => (
<Text key={node.key} style={[inheritedStyles, styles.textgroup]} selectable>
{children}
</Text>
),
code_block: (
node: any,
_children: React.ReactNode[],
_parent: any,
styles: any,
inheritedStyles: any = {}
) => (
<Text key={node.key} style={[inheritedStyles, styles.code_block]} selectable>
{node.content}
</Text>
),
fence: (
node: any,
_children: React.ReactNode[],
_parent: any,
styles: any,
inheritedStyles: any = {}
) => (
<Text key={node.key} style={[inheritedStyles, styles.fence]} selectable>
{node.content}
</Text>
),
code_inline: (
node: any,
_children: React.ReactNode[],
_parent: any,
styles: any,
inheritedStyles: any = {}
) => (
<Text key={node.key} style={[inheritedStyles, styles.code_inline]} selectable>
{node.content}
</Text>
),
bullet_list: (
node: any,
children: React.ReactNode[],
_parent: any,
styles: any
) => (
<View key={node.key} style={styles.bullet_list}>
{children}
</View>
),
ordered_list: (
node: any,
children: React.ReactNode[],
_parent: any,
styles: any
) => (
<View key={node.key} style={styles.ordered_list}>
{children}
</View>
),
list_item: (
node: any,
children: React.ReactNode[],
parent: any,
styles: any
) => {
const isOrdered = parent?.type === "ordered_list";
const index = parent?.children?.indexOf(node) ?? 0;
const bullet = isOrdered ? `${index + 1}.` : "•";
const iconStyle = isOrdered
? styles.ordered_list_icon
: styles.bullet_list_icon;
const contentStyle = isOrdered
? styles.ordered_list_content
: styles.bullet_list_content;
return (
<View key={node.key} style={[styles.list_item, { flexShrink: 0 }]}>
<Text style={iconStyle}>{bullet}</Text>
<Text style={[contentStyle, { flex: 1, flexShrink: 1, minWidth: 0 }]} selectable>
{children}
</Text>
</View>
);
},
};
}, []);
const permissionResponse = useDaemonRequest<
{ agentId: string; requestId: string; response: AgentPermissionResponse },
{ agentId: string; requestId: string },
@@ -675,7 +780,7 @@ function PermissionRequestCard({
},
]}
>
<Markdown style={markdownStyles}>{planMarkdown}</Markdown>
<Markdown style={markdownStyles} rules={markdownRules}>{planMarkdown}</Markdown>
</View>
</View>
) : null}
@@ -986,6 +1091,8 @@ const permissionStyles = StyleSheet.create((theme) => ({
padding: theme.spacing[3],
borderRadius: theme.borderRadius.lg,
borderWidth: theme.borderWidth[1],
flexShrink: 1,
minWidth: 0,
},
metadataRow: {
marginBottom: theme.spacing[2],
+92
View File
@@ -234,6 +234,8 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
backgroundColor: theme.colors.background,
padding: theme.spacing[2],
gap: theme.spacing[2],
flexShrink: 1,
minWidth: 0,
},
}));
@@ -417,6 +419,51 @@ export const AssistantMessage = memo(function AssistantMessage({
</Text>
);
},
bullet_list: (
node: any,
children: ReactNode[],
_parent: any,
styles: any
) => (
<View key={node.key} style={styles.bullet_list}>
{children}
</View>
),
ordered_list: (
node: any,
children: ReactNode[],
_parent: any,
styles: any
) => (
<View key={node.key} style={styles.ordered_list}>
{children}
</View>
),
list_item: (
node: any,
children: ReactNode[],
parent: any,
styles: any
) => {
const isOrdered = parent?.type === "ordered_list";
const index = parent?.children?.indexOf(node) ?? 0;
const bullet = isOrdered ? `${index + 1}.` : "•";
const iconStyle = isOrdered
? styles.ordered_list_icon
: styles.bullet_list_icon;
const contentStyle = isOrdered
? styles.ordered_list_content
: styles.bullet_list_content;
return (
<View key={node.key} style={styles.list_item}>
<Text style={iconStyle}>{bullet}</Text>
<View style={[contentStyle, { flex: 1, flexShrink: 1, minWidth: 0 }]}>
{children}
</View>
</View>
);
},
};
}, [onInlinePathPress]);
@@ -1007,6 +1054,51 @@ export function AgentThoughtMessage({ message, status = "ready" }: AgentThoughtM
{node.content}
</Text>
),
bullet_list: (
node: any,
children: ReactNode[],
_parent: any,
styles: any
) => (
<View key={node.key} style={styles.bullet_list}>
{children}
</View>
),
ordered_list: (
node: any,
children: ReactNode[],
_parent: any,
styles: any
) => (
<View key={node.key} style={styles.ordered_list}>
{children}
</View>
),
list_item: (
node: any,
children: ReactNode[],
parent: any,
styles: any
) => {
const isOrdered = parent?.type === "ordered_list";
const index = parent?.children?.indexOf(node) ?? 0;
const bullet = isOrdered ? `${index + 1}.` : "•";
const iconStyle = isOrdered
? styles.ordered_list_icon
: styles.bullet_list_icon;
const contentStyle = isOrdered
? styles.ordered_list_content
: styles.bullet_list_content;
return (
<View key={node.key} style={styles.list_item}>
<Text style={iconStyle}>{bullet}</Text>
<View style={[contentStyle, { flex: 1, flexShrink: 1, minWidth: 0 }]}>
{children}
</View>
</View>
);
},
};
}, []);
+5 -8
View File
@@ -215,13 +215,13 @@ export function createMarkdownStyles(theme: Theme) {
bullet_list: {
marginVertical: theme.spacing[2],
paddingLeft: theme.spacing[1],
paddingLeft: 0,
width: "100%" as const,
},
ordered_list: {
marginVertical: theme.spacing[2],
paddingLeft: theme.spacing[1],
paddingLeft: 0,
width: "100%" as const,
},
@@ -229,36 +229,33 @@ export function createMarkdownStyles(theme: Theme) {
marginBottom: theme.spacing[1],
flexDirection: "row" as const,
alignItems: "flex-start" as const,
width: "100%" as const,
flexShrink: 1,
},
bullet_list_content: {
flex: 1,
flexShrink: 1,
flexWrap: "wrap" as const,
},
ordered_list_content: {
flex: 1,
flexShrink: 1,
flexWrap: "wrap" as const,
},
bullet_list_icon: {
color: theme.colors.mutedForeground,
marginRight: theme.spacing[2],
marginRight: 4,
fontSize: theme.fontSize.base,
lineHeight: 22,
},
ordered_list_icon: {
color: theme.colors.mutedForeground,
marginRight: theme.spacing[2],
marginRight: 4,
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.semibold,
lineHeight: 22,
minWidth: 20,
minWidth: 12,
},
// =========================================================================