Refine appearance settings and syntax highlighting

Keep parsed chat and tool detail content in sync with appearance token changes. Hide font-family controls on native, use One as the default syntax theme, add Swift highlighting, and give Nord a distinct light palette.
This commit is contained in:
Mohamed Boudra
2026-05-31 21:40:38 +07:00
parent 4cc72a84e8
commit 9f88b4e69c
21 changed files with 310 additions and 53 deletions
@@ -0,0 +1,66 @@
import { Fragment, type ReactNode } from "react";
import { withUnistyles } from "react-native-unistyles";
import type { Theme } from "@/styles/theme";
interface AppearanceStyleBoundaryProps {
appearanceKey?: string;
children: ReactNode;
}
function AppearanceStyleBoundaryBase({ appearanceKey, children }: AppearanceStyleBoundaryProps) {
return <Fragment key={appearanceKey}>{children}</Fragment>;
}
const appearanceStyleBoundaryMapping = (theme: Theme): Partial<AppearanceStyleBoundaryProps> => ({
appearanceKey: [
theme.fontFamily.ui,
theme.fontFamily.mono,
theme.fontSize.xs,
theme.fontSize.sm,
theme.fontSize.base,
theme.fontSize.lg,
theme.fontSize.xl,
theme.fontSize["2xl"],
theme.fontSize["3xl"],
theme.fontSize["4xl"],
theme.fontSize.code,
theme.lineHeight.diff,
theme.colors.foreground,
theme.colors.foregroundMuted,
theme.colors.mutedForeground,
theme.colors.surface1,
theme.colors.surface2,
theme.colors.border,
theme.colors.accentBright,
theme.colors.syntax.keyword,
theme.colors.syntax.comment,
theme.colors.syntax.string,
theme.colors.syntax.number,
theme.colors.syntax.literal,
theme.colors.syntax.function,
theme.colors.syntax.definition,
theme.colors.syntax.class,
theme.colors.syntax.type,
theme.colors.syntax.tag,
theme.colors.syntax.attribute,
theme.colors.syntax.property,
theme.colors.syntax.variable,
theme.colors.syntax.operator,
theme.colors.syntax.punctuation,
theme.colors.syntax.regexp,
theme.colors.syntax.escape,
theme.colors.syntax.meta,
theme.colors.syntax.heading,
theme.colors.syntax.link,
].join("\u0000"),
});
const ThemedAppearanceStyleBoundary = withUnistyles(AppearanceStyleBoundaryBase);
export function AppearanceStyleBoundary({ children }: { children: ReactNode }) {
return (
<ThemedAppearanceStyleBoundary uniProps={appearanceStyleBoundaryMapping}>
{children}
</ThemedAppearanceStyleBoundary>
);
}
@@ -3,6 +3,7 @@ import { Text, View, type StyleProp, type TextStyle, type ViewStyle } from "reac
interface MarkdownTextSpanProps {
style?: StyleProp<TextStyle>;
monoSurface?: boolean;
children: ReactNode;
}
@@ -4,6 +4,7 @@ import { UITextView } from "react-native-uitextview";
interface MarkdownTextSpanProps {
style?: StyleProp<TextStyle>;
monoSurface?: boolean;
children: ReactNode;
}
@@ -1,8 +1,10 @@
import { useMemo, type ReactNode } from "react";
import { Text, View, type StyleProp, type TextStyle, type ViewStyle } from "react-native";
import { CODE_SURFACE_DATASET } from "@/styles/code-surface";
interface MarkdownTextSpanProps {
style?: StyleProp<TextStyle>;
monoSurface?: boolean;
children: ReactNode;
}
@@ -11,8 +13,12 @@ interface MarkdownTextSpanProps {
// react-native-uitextview: its transitive import of codegenNativeComponent
// pulls in setUpReactDevTools, which doesn't resolve under Metro's web
// target in dev mode.
export function MarkdownTextSpan({ style, children }: MarkdownTextSpanProps) {
return <Text style={style}>{children}</Text>;
export function MarkdownTextSpan({ style, monoSurface, children }: MarkdownTextSpanProps) {
return (
<Text dataSet={monoSurface ? CODE_SURFACE_DATASET : undefined} style={style}>
{children}
</Text>
);
}
interface MarkdownParagraphViewProps {
+21 -11
View File
@@ -11,6 +11,7 @@ import {
type TextStyle,
} from "react-native";
import { MarkdownParagraphView, MarkdownTextSpan } from "@/components/markdown-text";
import { AppearanceStyleBoundary } from "@/components/appearance-style-boundary";
import * as React from "react";
import {
useState,
@@ -1496,16 +1497,18 @@ const MemoizedMarkdownBlock = React.memo(function MemoizedMarkdownBlock({
onLinkPress,
}: MemoizedMarkdownBlockProps) {
return (
<ThemedMarkdown
uniProps={markdownStyleMapping}
rules={rules}
markdownit={parser}
onLinkPress={onLinkPress}
allowedImageHandlers={MARKDOWN_ALLOWED_IMAGE_HANDLERS}
topLevelMaxExceededItem={MARKDOWN_TOP_LEVEL_MAX_EXCEEDED_ITEM}
>
{text}
</ThemedMarkdown>
<AppearanceStyleBoundary>
<ThemedMarkdown
uniProps={markdownStyleMapping}
rules={rules}
markdownit={parser}
onLinkPress={onLinkPress}
allowedImageHandlers={MARKDOWN_ALLOWED_IMAGE_HANDLERS}
topLevelMaxExceededItem={MARKDOWN_TOP_LEVEL_MAX_EXCEEDED_ITEM}
>
{text}
</ThemedMarkdown>
</AppearanceStyleBoundary>
);
});
@@ -1513,6 +1516,7 @@ interface MarkdownInheritedTextProps {
inheritedStyles: TextStyle;
textStyle: TextStyle;
style?: StyleProp<TextStyle>;
monoSurface?: boolean;
children: ReactNode;
}
@@ -1520,13 +1524,18 @@ function MarkdownInheritedText({
inheritedStyles,
textStyle,
style: overrideStyle,
monoSurface,
children,
}: MarkdownInheritedTextProps) {
const style = useMemo(
() => [inheritedStyles, textStyle, overrideStyle],
[inheritedStyles, textStyle, overrideStyle],
);
return <MarkdownTextSpan style={style}>{children}</MarkdownTextSpan>;
return (
<MarkdownTextSpan monoSurface={monoSurface} style={style}>
{children}
</MarkdownTextSpan>
);
}
interface MarkdownListItemContentProps {
@@ -1696,6 +1705,7 @@ export const AssistantMessage = memo(function AssistantMessage({
key={node.key}
inheritedStyles={inheritedStyles}
textStyle={styles.code_inline}
monoSurface
>
{content}
</MarkdownInheritedText>
@@ -8,6 +8,7 @@ import {
} from "react-native";
import { ScrollView as GHScrollView } from "react-native-gesture-handler";
import { StyleSheet } from "react-native-unistyles";
import { AppearanceStyleBoundary } from "@/components/appearance-style-boundary";
import type { ToolCallDetail } from "@getpaseo/protocol/agent-types";
import { buildLineDiff, parseUnifiedDiff, type DiffLine } from "@/utils/tool-call-parsers";
import { highlightDiffLines } from "@/utils/diff-highlight";
@@ -756,7 +757,15 @@ function LoadingSkeleton({ containerStyle }: { containerStyle: StyleProp<ViewSty
);
}
export function ToolCallDetailsContent({
export function ToolCallDetailsContent({ ...props }: ToolCallDetailsContentProps) {
return (
<AppearanceStyleBoundary>
<ToolCallDetailsContentInner {...props} />
</AppearanceStyleBoundary>
);
}
function ToolCallDetailsContentInner({
detail,
errorText,
maxHeight,