From 5f620a36d65b3b6b7ae1f097e5e7ade071b7aa52 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 24 Apr 2026 00:25:02 +0700 Subject: [PATCH] chore(lint): hoist inline callbacks in app (jsx-no-new-function-as-prop) --- packages/app/src/app/pair-scan.tsx | 9 +- .../app/src/components/add-host-modal.tsx | 20 ++- packages/app/src/components/agent-list.tsx | 25 ++-- .../app/src/components/branch-switcher.tsx | 46 +++--- packages/app/src/components/composer.test.tsx | 22 +-- .../components/draggable-list.web.test.tsx | 20 ++- .../app/src/components/pair-link-modal.tsx | 14 +- .../src/components/project-picker-modal.tsx | 93 +++++++----- .../sortable-inline-list.web.test.tsx | 20 ++- packages/app/src/components/ui/button.tsx | 37 +++-- packages/app/src/components/ui/combobox.tsx | 105 ++++++++------ .../app/src/components/ui/dropdown-menu.tsx | 76 ++++++---- .../src/components/workspace-hover-card.tsx | 125 +++++++++------- .../contexts/sidebar-callout-context.test.tsx | 13 +- .../components/integrations-section.tsx | 12 +- .../components/pair-device-section.tsx | 14 +- .../src/hooks/use-hover-safe-zone.test.tsx | 26 ++-- .../app/src/screens/new-workspace-screen.tsx | 40 ++++-- .../app/src/screens/open-project-screen.tsx | 15 +- .../screens/settings/providers-section.tsx | 133 ++++++++++-------- .../app/src/screens/startup-splash-screen.tsx | 18 ++- 21 files changed, 560 insertions(+), 323 deletions(-) diff --git a/packages/app/src/app/pair-scan.tsx b/packages/app/src/app/pair-scan.tsx index 562658082..6d828ddfe 100644 --- a/packages/app/src/app/pair-scan.tsx +++ b/packages/app/src/app/pair-scan.tsx @@ -198,10 +198,15 @@ export default function PairScanScreen() { [isPairing, navigateToPairedHost, upsertDaemonFromOfferUrl], ); + const handleRouterBack = useCallback(() => router.back(), [router]); + const handleRequestPermission = useCallback(() => { + void requestPermission(); + }, [requestPermission]); + if (isWeb) { return ( - router.back()} /> + Not available on web @@ -230,7 +235,7 @@ export default function PairScanScreen() { Allow camera access to scan the pairing QR code from your daemon. - void requestPermission()}> + Grant permission diff --git a/packages/app/src/components/add-host-modal.tsx b/packages/app/src/components/add-host-modal.tsx index 5877a8d02..d49f0b232 100644 --- a/packages/app/src/components/add-host-modal.tsx +++ b/packages/app/src/components/add-host-modal.tsx @@ -231,6 +231,18 @@ export function AddHostModal({ visible, onClose, onCancel, onSaved }: AddHostMod } }, [daemons, handleClose, isMobile, isSaving, onSaved, upsertDirectConnection]); + const handleChangeEndpoint = useCallback((next: string) => { + endpointRawRef.current = next; + }, []); + + const handleSubmitEditing = useCallback(() => { + void handleSave(); + }, [handleSave]); + + const handleSavePress = useCallback(() => { + void handleSave(); + }, [handleSave]); + return ( { - endpointRawRef.current = next; - }} + onChangeText={handleChangeEndpoint} placeholder="hostname:port" placeholderTextColor={theme.colors.foregroundMuted} style={styles.input} @@ -258,7 +268,7 @@ export function AddHostModal({ visible, onClose, onCancel, onSaved }: AddHostMod keyboardType="url" editable={!isSaving} returnKeyType="done" - onSubmitEditing={() => void handleSave()} + onSubmitEditing={handleSubmitEditing} /> {errorMessage ? {errorMessage} : null} @@ -270,7 +280,7 @@ export function AddHostModal({ visible, onClose, onCancel, onSaved }: AddHostMod @@ -79,7 +87,7 @@ export function PairDeviceSection() { variant="outline" size="sm" leftIcon={} - onPress={() => void pairingQuery.refetch()} + onPress={handleRefetch} > Retry @@ -118,7 +126,7 @@ export function PairDeviceSection() { ) } - onPress={() => void handleCopyLink()} + onPress={handleCopyPress} > {copied ? "Copied" : "Copy"} diff --git a/packages/app/src/hooks/use-hover-safe-zone.test.tsx b/packages/app/src/hooks/use-hover-safe-zone.test.tsx index 1899b61ad..3c4d31c9d 100644 --- a/packages/app/src/hooks/use-hover-safe-zone.test.tsx +++ b/packages/app/src/hooks/use-hover-safe-zone.test.tsx @@ -1,7 +1,7 @@ /** * @vitest-environment jsdom */ -import React, { useRef, type RefObject } from "react"; +import React, { useCallback, useRef, type RefObject } from "react"; import { act } from "react"; import { createRoot, type Root } from "react-dom/client"; import type { View } from "react-native"; @@ -73,20 +73,20 @@ function Harness({ onLeaveSafeZone, }); + const handleTriggerRef = useCallback((node: HTMLDivElement | null) => { + triggerRef.current = node; + installRect(node, { left: 0, right: 100, top: 20, bottom: 60 }); + }, []); + + const handleContentRef = useCallback((node: HTMLDivElement | null) => { + contentRef.current = node; + installRect(node, { left: 120, right: 240, top: 20, bottom: 120 }); + }, []); + return ( <> -
{ - triggerRef.current = node; - installRect(node, { left: 0, right: 100, top: 20, bottom: 60 }); - }} - /> -
{ - contentRef.current = node; - installRect(node, { left: 120, right: 240, top: 20, bottom: 120 }); - }} - /> +
+
); } diff --git a/packages/app/src/screens/new-workspace-screen.tsx b/packages/app/src/screens/new-workspace-screen.tsx index 14c1ec072..6afe2d31b 100644 --- a/packages/app/src/screens/new-workspace-screen.tsx +++ b/packages/app/src/screens/new-workspace-screen.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Pressable, Text, View } from "react-native"; +import type { PressableStateCallbackType } from "react-native"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import Animated from "react-native-reanimated"; @@ -284,6 +285,27 @@ export function NewWorkspaceScreen({ setPickerOpen(true); }, []); + const handleClearDraft = useCallback(() => { + // No-op: screen navigates away on success, text should stay for retry on error + }, []); + + const badgePressableStyle = useCallback( + ({ pressed, hovered }: PressableStateCallbackType & { hovered?: boolean }) => [ + styles.badge, + Boolean(hovered) && !isPending && styles.badgeHovered, + pressed && !isPending && styles.badgePressed, + isPending && styles.badgeDisabled, + ], + [isPending], + ); + + const handlePickerOpenChange = useCallback((nextOpen: boolean) => { + setPickerOpen(nextOpen); + if (!nextOpen) { + setPickerSearchQuery(""); + } + }, []); + const buildCreateWorktreeInput = useCallback( (input: { cwd: string; attachments: AgentAttachment[] }) => { const checkoutRequest = pickerItemToCheckoutRequest(selectedItem); @@ -496,9 +518,7 @@ export function NewWorkspaceScreen({ attachments={chatDraft.attachments} onChangeAttachments={chatDraft.setAttachments} cwd={chatDraft.cwd} - clearDraft={() => { - // No-op: screen navigates away on success, text should stay for retry on error - }} + clearDraft={handleClearDraft} autoFocus commandDraftConfig={composerState?.commandDraftConfig} statusControls={ @@ -520,12 +540,7 @@ export function NewWorkspaceScreen({ testID="new-workspace-ref-picker-trigger" onPress={openPicker} disabled={isPending} - style={({ pressed, hovered }) => [ - styles.badge, - hovered && !isPending && styles.badgeHovered, - pressed && !isPending && styles.badgePressed, - isPending && styles.badgeDisabled, - ]} + style={badgePressableStyle} accessibilityRole="button" accessibilityLabel="Starting ref" > @@ -557,12 +572,7 @@ export function NewWorkspaceScreen({ searchPlaceholder="Search branches and PRs" title="Start from" open={pickerOpen} - onOpenChange={(nextOpen) => { - setPickerOpen(nextOpen); - if (!nextOpen) { - setPickerSearchQuery(""); - } - }} + onOpenChange={handlePickerOpenChange} onSearchQueryChange={setPickerSearchQuery} desktopPlacement="bottom-start" anchorRef={pickerAnchorRef} diff --git a/packages/app/src/screens/open-project-screen.tsx b/packages/app/src/screens/open-project-screen.tsx index 67207fa25..13c4ba777 100644 --- a/packages/app/src/screens/open-project-screen.tsx +++ b/packages/app/src/screens/open-project-screen.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { View, Text } from "react-native"; import { StyleSheet } from "react-native-unistyles"; import { FolderOpen, Smartphone } from "lucide-react-native"; @@ -35,6 +35,13 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) { } }, [isCompactLayout, openDesktopAgentList]); + const handleOpenPicker = useCallback(() => { + void openProjectPicker(); + }, [openProjectPicker]); + + const handleOpenPairDevice = useCallback(() => setIsPairDeviceOpen(true), []); + const handleClosePairDevice = useCallback(() => setIsPairDeviceOpen(false), []); + return ( @@ -55,7 +62,7 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) {