chore(lint): hoist/memoize inline arrays and objects in app JSX

This commit is contained in:
Mohamed Boudra
2026-04-24 01:41:00 +07:00
parent 6465edc01f
commit 01338d2a55
2 changed files with 129 additions and 112 deletions

View File

@@ -55,20 +55,69 @@ export function ToolCallDetailsContent({
detail?.type === "sub_agent");
const codeBlockStyle = isFullBleed ? styles.fullBleedBlock : styles.diffContainer;
const sectionFillStyle = useMemo(
() => [styles.section, shouldFill && styles.fillHeight],
[shouldFill],
);
const codeBlockFillStyle = useMemo(
() => [codeBlockStyle, shouldFill && styles.fillHeight],
[codeBlockStyle, shouldFill],
);
const codeVerticalScrollStyle = useMemo(
() => [
styles.codeVerticalScroll,
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
shouldFill && styles.fillHeight,
webScrollbarStyle,
],
[resolvedMaxHeight, shouldFill, webScrollbarStyle],
);
const scrollAreaFillStyle = useMemo(
() => [
styles.scrollArea,
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
shouldFill && styles.fillHeight,
webScrollbarStyle,
],
[resolvedMaxHeight, shouldFill, webScrollbarStyle],
);
const scrollAreaStyle = useMemo(
() => [
styles.scrollArea,
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
webScrollbarStyle,
],
[resolvedMaxHeight, webScrollbarStyle],
);
const jsonScrollCombined = useMemo(
() => [styles.jsonScroll, webScrollbarStyle],
[webScrollbarStyle],
);
const jsonScrollErrorCombined = useMemo(
() => [styles.jsonScroll, styles.jsonScrollError, webScrollbarStyle],
[webScrollbarStyle],
);
const fullBleedContainerStyle = useMemo(
() => [
isFullBleed ? styles.fullBleedContainer : styles.paddedContainer,
shouldFill && styles.fillHeight,
],
[isFullBleed, shouldFill],
);
const loadingContainerStyle = useMemo(
() => [styles.loadingContainer, fillAvailableHeight && styles.fillHeight],
[fillAvailableHeight],
);
if (detail?.type === "shell") {
const command = detail.command.replace(/\n+$/, "");
const commandOutput = (detail.output ?? "").replace(/^\n+/, "");
const hasOutput = commandOutput.length > 0;
sections.push(
<View key="shell" style={[styles.section, shouldFill && styles.fillHeight]}>
<View style={[codeBlockStyle, shouldFill && styles.fillHeight]}>
<View key="shell" style={sectionFillStyle}>
<View style={codeBlockFillStyle}>
<ScrollView
style={[
styles.codeVerticalScroll,
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
shouldFill && styles.fillHeight,
webScrollbarStyle,
]}
style={codeVerticalScrollStyle}
contentContainerStyle={styles.codeVerticalContent}
nestedScrollEnabled
showsVerticalScrollIndicator
@@ -96,15 +145,10 @@ export function ToolCallDetailsContent({
const setupLog = detail.log.replace(/^\n+/, "");
const hasLog = setupLog.length > 0;
sections.push(
<View key="worktree-setup" style={[styles.section, shouldFill && styles.fillHeight]}>
<View style={[codeBlockStyle, shouldFill && styles.fillHeight]}>
<View key="worktree-setup" style={sectionFillStyle}>
<View style={codeBlockFillStyle}>
<ScrollView
style={[
styles.codeVerticalScroll,
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
shouldFill && styles.fillHeight,
webScrollbarStyle,
]}
style={codeVerticalScrollStyle}
contentContainerStyle={styles.codeVerticalContent}
nestedScrollEnabled
showsVerticalScrollIndicator
@@ -136,15 +180,10 @@ export function ToolCallDetailsContent({
? `${detail.subAgentType}: ${detail.description}`
: (detail.subAgentType ?? detail.description ?? "Sub-agent activity");
sections.push(
<View key="sub-agent" style={[styles.section, shouldFill && styles.fillHeight]}>
<View style={[codeBlockStyle, shouldFill && styles.fillHeight]}>
<View key="sub-agent" style={sectionFillStyle}>
<View style={codeBlockFillStyle}>
<ScrollView
style={[
styles.codeVerticalScroll,
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
shouldFill && styles.fillHeight,
webScrollbarStyle,
]}
style={codeVerticalScrollStyle}
contentContainerStyle={styles.codeVerticalContent}
nestedScrollEnabled
showsVerticalScrollIndicator
@@ -168,9 +207,9 @@ export function ToolCallDetailsContent({
);
} else if (detail?.type === "edit") {
sections.push(
<View key="edit" style={[styles.section, shouldFill && styles.fillHeight]}>
<View key="edit" style={sectionFillStyle}>
{diffLines ? (
<View style={[codeBlockStyle, shouldFill && styles.fillHeight]}>
<View style={codeBlockFillStyle}>
<DiffViewer
diffLines={diffLines}
maxHeight={resolvedMaxHeight}
@@ -182,15 +221,10 @@ export function ToolCallDetailsContent({
);
} else if (detail?.type === "write") {
sections.push(
<View key="write" style={[styles.section, shouldFill && styles.fillHeight]}>
<View key="write" style={sectionFillStyle}>
{detail.content ? (
<ScrollView
style={[
styles.scrollArea,
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
shouldFill && styles.fillHeight,
webScrollbarStyle,
]}
style={scrollAreaFillStyle}
contentContainerStyle={styles.scrollContent}
nestedScrollEnabled
showsVerticalScrollIndicator={true}
@@ -212,14 +246,9 @@ export function ToolCallDetailsContent({
} else if (detail?.type === "read") {
if (detail.content) {
sections.push(
<View key="read" style={[styles.section, shouldFill && styles.fillHeight]}>
<View key="read" style={sectionFillStyle}>
<ScrollView
style={[
styles.scrollArea,
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
shouldFill && styles.fillHeight,
webScrollbarStyle,
]}
style={scrollAreaFillStyle}
contentContainerStyle={styles.scrollContent}
nestedScrollEnabled
showsVerticalScrollIndicator={true}
@@ -253,11 +282,7 @@ export function ToolCallDetailsContent({
searchSections.push(
<View key="search-content" style={styles.section}>
<ScrollView
style={[
styles.scrollArea,
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
webScrollbarStyle,
]}
style={scrollAreaStyle}
contentContainerStyle={styles.scrollContent}
nestedScrollEnabled
showsVerticalScrollIndicator
@@ -306,14 +331,9 @@ export function ToolCallDetailsContent({
sections.push(...searchSections);
} else if (detail?.type === "fetch") {
sections.push(
<View key="fetch" style={[styles.section, shouldFill && styles.fillHeight]}>
<View key="fetch" style={sectionFillStyle}>
<ScrollView
style={[
styles.scrollArea,
resolvedMaxHeight !== undefined && { maxHeight: resolvedMaxHeight },
shouldFill && styles.fillHeight,
webScrollbarStyle,
]}
style={scrollAreaFillStyle}
contentContainerStyle={styles.scrollContent}
nestedScrollEnabled
showsVerticalScrollIndicator
@@ -388,7 +408,7 @@ export function ToolCallDetailsContent({
<ScrollView
horizontal
nestedScrollEnabled
style={[styles.jsonScroll, webScrollbarStyle]}
style={jsonScrollCombined}
contentContainerStyle={styles.jsonContent}
showsHorizontalScrollIndicator={true}
>
@@ -406,15 +426,15 @@ export function ToolCallDetailsContent({
if (errorText) {
sections.push(
<View key="error" style={styles.section}>
<Text style={[styles.sectionTitle, styles.errorText]}>Error</Text>
<Text style={SECTION_TITLE_ERROR_STYLE}>Error</Text>
<ScrollView
horizontal
nestedScrollEnabled
style={[styles.jsonScroll, styles.jsonScrollError, webScrollbarStyle]}
style={jsonScrollErrorCombined}
contentContainerStyle={styles.jsonContent}
showsHorizontalScrollIndicator={true}
>
<Text selectable style={[styles.scrollText, styles.errorText]}>
<Text selectable style={SCROLL_TEXT_ERROR_STYLE}>
{errorText}
</Text>
</ScrollView>
@@ -425,7 +445,7 @@ export function ToolCallDetailsContent({
if (sections.length === 0) {
if (showLoadingSkeleton) {
return (
<View style={[styles.loadingContainer, fillAvailableHeight && styles.fillHeight]}>
<View style={loadingContainerStyle}>
<View style={styles.loadingLineWide} />
<View style={styles.loadingLineMedium} />
<View style={styles.loadingLineShort} />
@@ -435,16 +455,7 @@ export function ToolCallDetailsContent({
return <Text style={styles.emptyStateText}>No additional details available</Text>;
}
return (
<View
style={[
isFullBleed ? styles.fullBleedContainer : styles.paddedContainer,
shouldFill && styles.fillHeight,
]}
>
{sections}
</View>
);
return <View style={fullBleedContainerStyle}>{sections}</View>;
}
// ---- Styles ----
@@ -598,3 +609,6 @@ const styles = StyleSheet.create((theme) => {
},
};
});
const SECTION_TITLE_ERROR_STYLE = [styles.sectionTitle, styles.errorText];
const SCROLL_TEXT_ERROR_STYLE = [styles.scrollText, styles.errorText];

View File

@@ -126,18 +126,18 @@ function ThemeIcon({
}
function ThemeSwatch({ color, size }: { color: string; size: number }) {
return (
<View
style={{
width: size,
height: size,
borderRadius: size / 2,
backgroundColor: color,
borderWidth: 1,
borderColor: "rgba(255,255,255,0.15)",
}}
/>
const swatchStyle = useMemo(
() => ({
width: size,
height: size,
borderRadius: size / 2,
backgroundColor: color,
borderWidth: 1,
borderColor: "rgba(255,255,255,0.15)",
}),
[color, size],
);
return <View style={swatchStyle} />;
}
function themeTriggerStyle({ pressed }: PressableStateCallbackType) {
@@ -166,6 +166,18 @@ const THEME_LABELS: Record<AppSettings["theme"], string> = {
auto: "System",
};
const ROW_WITH_BORDER_STYLE = [settingsStyles.row, settingsStyles.rowBorder];
const SEND_BEHAVIOR_OPTIONS = [
{ value: "interrupt" as const, label: "Interrupt" },
{ value: "queue" as const, label: "Queue" },
];
const RELEASE_CHANNEL_OPTIONS = [
{ value: "stable" as const, label: "Stable" },
{ value: "beta" as const, label: "Beta" },
];
// ---------------------------------------------------------------------------
// Section components
// ---------------------------------------------------------------------------
@@ -252,7 +264,7 @@ function GeneralSection({
</DropdownMenuContent>
</DropdownMenu>
</View>
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
<View style={ROW_WITH_BORDER_STYLE}>
<View style={settingsStyles.rowContent}>
<Text style={settingsStyles.rowTitle}>Default send</Text>
<Text style={settingsStyles.rowHint}>
@@ -263,10 +275,7 @@ function GeneralSection({
size="sm"
value={settings.sendBehavior}
onValueChange={handleSendBehaviorChange}
options={[
{ value: "interrupt", label: "Interrupt" },
{ value: "queue", label: "Queue" },
]}
options={SEND_BEHAVIOR_OPTIONS}
/>
</View>
</View>
@@ -401,7 +410,7 @@ function DesktopAppUpdateRow() {
return (
<>
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
<View style={ROW_WITH_BORDER_STYLE}>
<View style={settingsStyles.rowContent}>
<Text style={settingsStyles.rowTitle}>Release channel</Text>
<Text style={settingsStyles.rowHint}>
@@ -412,13 +421,10 @@ function DesktopAppUpdateRow() {
size="sm"
value={settings.releaseChannel}
onValueChange={handleReleaseChannelChange}
options={[
{ value: "stable", label: "Stable" },
{ value: "beta", label: "Beta" },
]}
options={RELEASE_CHANNEL_OPTIONS}
/>
</View>
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
<View style={ROW_WITH_BORDER_STYLE}>
<View style={settingsStyles.rowContent}>
<Text style={settingsStyles.rowTitle}>App updates</Text>
<Text style={settingsStyles.rowHint}>{statusText}</Text>
@@ -503,10 +509,15 @@ function SidebarSectionButton({
const handlePress = useCallback(() => {
onSelect(itemId);
}, [onSelect, itemId]);
const accessibilityState = useMemo(() => ({ selected: isSelected }), [isSelected]);
const labelStyle = useMemo(
() => [sidebarStyles.label, isSelected && { color: theme.colors.foreground }],
[isSelected, theme.colors.foreground],
);
return (
<Pressable
accessibilityRole="button"
accessibilityState={{ selected: isSelected }}
accessibilityState={accessibilityState}
onPress={handlePress}
style={isSelected ? selectedSidebarItemStyle : sidebarItemStyle}
>
@@ -514,10 +525,7 @@ function SidebarSectionButton({
size={theme.iconSize.md}
color={isSelected ? theme.colors.foreground : theme.colors.foregroundMuted}
/>
<Text
style={[sidebarStyles.label, isSelected && { color: theme.colors.foreground }]}
numberOfLines={1}
>
<Text style={labelStyle} numberOfLines={1}>
{label}
</Text>
</Pressable>
@@ -537,10 +545,15 @@ function SidebarHostItem({ serverId, label, isSelected, isLocal, onSelect }: Sid
const handlePress = useCallback(() => {
onSelect(serverId);
}, [onSelect, serverId]);
const accessibilityState = useMemo(() => ({ selected: isSelected }), [isSelected]);
const labelStyle = useMemo(
() => [sidebarStyles.label, isSelected && { color: theme.colors.foreground }],
[isSelected, theme.colors.foreground],
);
return (
<Pressable
accessibilityRole="button"
accessibilityState={{ selected: isSelected }}
accessibilityState={accessibilityState}
onPress={handlePress}
testID={`settings-host-entry-${serverId}`}
style={isSelected ? selectedSidebarItemStyle : sidebarItemStyle}
@@ -549,10 +562,7 @@ function SidebarHostItem({ serverId, label, isSelected, isLocal, onSelect }: Sid
size={theme.iconSize.md}
color={isSelected ? theme.colors.foreground : theme.colors.foregroundMuted}
/>
<Text
style={[sidebarStyles.label, isSelected && { color: theme.colors.foreground }]}
numberOfLines={1}
>
<Text style={labelStyle} numberOfLines={1}>
{label}
</Text>
{isLocal ? (
@@ -604,13 +614,14 @@ function SettingsSidebar({
const containerStyle = isDesktop ? sidebarStyles.desktopContainer : sidebarStyles.mobileContainer;
const selectedSectionId = view.kind === "section" ? view.section : null;
const selectedServerId = view.kind === "host" ? view.serverId : null;
const paddingTopStyle = useMemo(() => ({ height: padding.top }), [padding.top]);
return (
<View style={containerStyle} testID="settings-sidebar">
{isDesktop ? (
<>
<TitlebarDragRegion />
{padding.top > 0 ? <View style={{ height: padding.top }} /> : null}
{padding.top > 0 ? <View style={paddingTopStyle} /> : null}
</>
) : null}
{isDesktop ? (
@@ -685,6 +696,7 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
const appVersionText = formatVersionWithPrefix(appVersion);
const isCompactLayout = useIsCompactFormFactor();
const insets = useSafeAreaInsets();
const insetBottomStyle = useMemo(() => ({ paddingBottom: insets.bottom }), [insets.bottom]);
const hosts = useHosts();
const hostServerIds = useMemo(() => hosts.map((host) => host.serverId), [hosts]);
const anyOnlineServerId = useAnyOnlineHostServerId(hostServerIds);
@@ -935,10 +947,7 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
return (
<View style={styles.container}>
<BackHeader title="Settings" onBack={handleBackToWorkspace} />
<ScrollView
style={styles.scrollView}
contentContainerStyle={{ paddingBottom: insets.bottom }}
>
<ScrollView style={styles.scrollView} contentContainerStyle={insetBottomStyle}>
<SettingsSidebar
view={view}
onSelectSection={handleSelectSection}
@@ -962,10 +971,7 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
titleAccessory={detailHeader?.titleAccessory}
onBack={handleBackToRoot}
/>
<ScrollView
style={styles.scrollView}
contentContainerStyle={{ paddingBottom: insets.bottom }}
>
<ScrollView style={styles.scrollView} contentContainerStyle={insetBottomStyle}>
<View style={styles.content}>{content}</View>
</ScrollView>
{addHostModals}
@@ -1009,10 +1015,7 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
}
leftStyle={desktopStyles.detailLeft}
/>
<ScrollView
style={styles.scrollView}
contentContainerStyle={{ paddingBottom: insets.bottom }}
>
<ScrollView style={styles.scrollView} contentContainerStyle={insetBottomStyle}>
<View style={styles.content}>{content}</View>
</ScrollView>
</View>