From c61753f14320a19b2577b2a28e1d3714c023ec05 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 24 Apr 2026 06:19:52 +0700 Subject: [PATCH] chore(lint): no-nested-ternary in app batch 1 (18 files) --- .../app/src/components/agent-stream-view.tsx | 30 +++-- packages/app/src/components/callout-card.tsx | 11 +- .../app/src/components/dictation-controls.tsx | 16 ++- .../app/src/components/download-toast.tsx | 31 ++--- .../app/src/components/explorer-sidebar.tsx | 5 +- packages/app/src/components/message-input.tsx | 106 ++++++++++-------- .../src/components/project-picker-modal.tsx | 10 +- packages/app/src/components/toast-host.tsx | 14 +-- .../app/src/components/ui/dropdown-menu.tsx | 42 ++++--- packages/app/src/components/volume-meter.tsx | 15 ++- .../src/components/web-desktop-scrollbar.tsx | 12 +- .../app/src/screens/startup-splash-screen.tsx | 32 +++--- .../workspace/workspace-desktop-tabs-row.tsx | 25 ++--- .../workspace/workspace-draft-agent-tab.tsx | 14 ++- .../app/src/stores/workspace-tabs-store.ts | 36 +++--- .../src/utils/sidebar-project-row-model.ts | 7 +- .../app/src/utils/test-daemon-connection.ts | 7 +- packages/app/src/voice/voice-runtime.ts | 10 +- 18 files changed, 230 insertions(+), 193 deletions(-) diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index eda768e891..17172af86c 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -649,17 +649,14 @@ const AgentStreamViewComponent = forwardRef [ - stylesheet.listHeaderContent, - boundary.hasLiveHead - ? streamRenderStrategy.getFlatListInverted() - ? { paddingBottom: looseGap } - : { paddingTop: looseGap } - : null, - ], - [boundary.hasLiveHead, streamRenderStrategy, looseGap], - ); + const liveAuxiliaryHeaderStyle = useMemo(() => { + let headerPadding: { paddingBottom: number } | { paddingTop: number } | null; + if (!boundary.hasLiveHead) headerPadding = null; + else if (streamRenderStrategy.getFlatListInverted()) + headerPadding = { paddingBottom: looseGap }; + else headerPadding = { paddingTop: looseGap }; + return [stylesheet.listHeaderContent, headerPadding]; + }, [boundary.hasLiveHead, streamRenderStrategy, looseGap]); const renderLiveAuxiliary = useCallback(() => { if (!auxiliary.pendingPermissions && !auxiliary.workingIndicator) { return null; @@ -1080,12 +1077,11 @@ function PermissionRequestCard({ const textColor = isPrimary ? theme.colors.foreground : theme.colors.foregroundMuted; const iconColor = textColor; const Icon = action.behavior === "allow" ? Check : X; - const testID = - action.behavior === "deny" - ? "permission-request-deny" - : action.id === "accept" || action.id === "implement" - ? "permission-request-accept" - : `permission-request-action-${action.id}`; + let testID: string; + if (action.behavior === "deny") testID = "permission-request-deny"; + else if (action.id === "accept" || action.id === "implement") + testID = "permission-request-accept"; + else testID = `permission-request-action-${action.id}`; return ( ) : null} - {hasDescription ? ( - typeof description === "string" ? ( - {description} - ) : ( - {description} - ) + {hasDescription && typeof description === "string" ? ( + {description} + ) : null} + {hasDescription && typeof description !== "string" ? ( + {description} ) : null} {visibleActions.length > 0 ? ( diff --git a/packages/app/src/components/dictation-controls.tsx b/packages/app/src/components/dictation-controls.tsx index 8cf726bb73..9a382fcc59 100644 --- a/packages/app/src/components/dictation-controls.tsx +++ b/packages/app/src/components/dictation-controls.tsx @@ -98,7 +98,8 @@ export function DictationControls({ - ) : isFailed ? ( + ) : null} + {!actionsDisabled && isFailed ? ( - ) : ( + ) : null} + {!actionsDisabled && !isFailed ? ( <> - )} + ) : null} ); @@ -216,7 +218,8 @@ export function DictationOverlay({ - ) : isFailed ? ( + ) : null} + {!actionsDisabled && isFailed ? ( - ) : ( + ) : null} + {!actionsDisabled && !isFailed ? ( <> - )} + ) : null} ); diff --git a/packages/app/src/components/download-toast.tsx b/packages/app/src/components/download-toast.tsx index ae025201c0..910578aab5 100644 --- a/packages/app/src/components/download-toast.tsx +++ b/packages/app/src/components/download-toast.tsx @@ -3,10 +3,21 @@ import { ActivityIndicator, Pressable, Text, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { Check, X, XCircle } from "lucide-react-native"; -import { useDownloadStore, formatSpeed, formatEta } from "@/stores/download-store"; +import { useDownloadStore, formatSpeed, formatEta, type Download } from "@/stores/download-store"; const AUTO_DISMISS_DELAY = 3000; +function getDownloadStatusText(download: Download): string { + if (download.status === "downloading") { + if (download.progress) { + return `${Math.round(download.progress.percent * 100)}% · ${formatSpeed(download.progress.speed)} · ${formatEta(download.progress.eta)}`; + } + return "Starting..."; + } + if (download.status === "complete") return "Download complete"; + return download.message ?? "Download failed"; +} + export function DownloadToast() { const { theme } = useUnistyles(); const insets = useSafeAreaInsets(); @@ -56,24 +67,18 @@ export function DownloadToast() { {activeDownload.status === "downloading" ? ( - ) : activeDownload.status === "complete" ? ( + ) : null} + {activeDownload.status === "complete" ? ( - ) : ( + ) : null} + {activeDownload.status !== "downloading" && activeDownload.status !== "complete" ? ( - )} + ) : null} {activeDownload.fileName} - - {activeDownload.status === "downloading" - ? activeDownload.progress - ? `${Math.round(activeDownload.progress.percent * 100)}% · ${formatSpeed(activeDownload.progress.speed)} · ${formatEta(activeDownload.progress.eta)}` - : "Starting..." - : activeDownload.status === "complete" - ? "Download complete" - : (activeDownload.message ?? "Download failed")} - + {getDownloadStatusText(activeDownload)} {activeDownload.status === "downloading" && activeDownload.progress && ( diff --git a/packages/app/src/components/explorer-sidebar.tsx b/packages/app/src/components/explorer-sidebar.tsx index 7ca210c96c..fb6479a9e4 100644 --- a/packages/app/src/components/explorer-sidebar.tsx +++ b/packages/app/src/components/explorer-sidebar.tsx @@ -286,7 +286,10 @@ export function ExplorerSidebar({ // Mobile: full-screen overlay with gesture. // On web, keep it interactive only while open so closed sidebars don't eat taps. - const overlayPointerEvents = isWeb ? (isOpen ? "auto" : "none") : "box-none"; + let overlayPointerEvents: "auto" | "none" | "box-none"; + if (!isWeb) overlayPointerEvents = "box-none"; + else if (isOpen) overlayPointerEvents = "auto"; + else overlayPointerEvents = "none"; // Navigation stacks can keep previous screens mounted; hide sidebars for unfocused // screens so only the active screen exposes explorer/terminal surfaces. diff --git a/packages/app/src/components/message-input.tsx b/packages/app/src/components/message-input.tsx index e38d119785..acde47b7c6 100644 --- a/packages/app/src/components/message-input.tsx +++ b/packages/app/src/components/message-input.tsx @@ -183,7 +183,10 @@ function getElementDescriptor(element: HTMLElement | null): string | null { const id = element.id ? `#${element.id}` : ""; const testId = element.getAttribute?.("data-testid"); const label = element.getAttribute?.("aria-label"); - const suffix = testId ? `[data-testid="${testId}"]` : label ? `[aria-label="${label}"]` : ""; + let suffix: string; + if (testId) suffix = `[data-testid="${testId}"]`; + else if (label) suffix = `[aria-label="${label}"]`; + else suffix = ""; return `${tag}${id}${suffix}`; } @@ -576,8 +579,10 @@ export const MessageInput = forwardRef(funct } void voice.startVoice(voiceServerId, voiceAgentId).catch((error) => { console.error("[MessageInput] Failed to start realtime voice", error); - const message = - error instanceof Error ? error.message : typeof error === "string" ? error : null; + let message: string | null; + if (error instanceof Error) message = error.message; + else if (typeof error === "string") message = error; + else message = null; if (message && message.trim().length > 0) { toast.error(message); } @@ -675,11 +680,11 @@ export const MessageInput = forwardRef(funct const getWebElement = useCallback((target: "root" | "wrapper"): HTMLElement | null => { const current = target === "root" ? rootRef.current : inputWrapperRef.current; if (!current) return null; - return current instanceof HTMLElement - ? current - : (current as unknown as { getBoundingClientRect?: () => DOMRect }).getBoundingClientRect - ? (current as unknown as HTMLElement) - : null; + if (current instanceof HTMLElement) return current; + if ((current as unknown as { getBoundingClientRect?: () => DOMRect }).getBoundingClientRect) { + return current as unknown as HTMLElement; + } + return null; }, []); useEffect(() => { @@ -924,16 +929,30 @@ export const MessageInput = forwardRef(funct const isSendButtonDisabled = disabled || (!canPressLoadingButton && (isSubmitDisabled || isSubmitLoading)); const defaultActionQueues = defaultSendBehavior === "queue" && isAgentRunning; - const defaultSubmitAccessibilityLabel = canPressLoadingButton - ? "Interrupt agent" - : defaultActionQueues - ? "Queue message" - : isAgentRunning - ? "Send and interrupt" - : "Send message"; + let defaultSubmitAccessibilityLabel: string; + if (canPressLoadingButton) defaultSubmitAccessibilityLabel = "Interrupt agent"; + else if (defaultActionQueues) defaultSubmitAccessibilityLabel = "Queue message"; + else if (isAgentRunning) defaultSubmitAccessibilityLabel = "Send and interrupt"; + else defaultSubmitAccessibilityLabel = "Send message"; const submitAccessibilityLabel = submitButtonAccessibilityLabel ?? defaultSubmitAccessibilityLabel; + let voiceButtonAccessibilityLabel: string; + if (isRealtimeVoiceForCurrentAgent) { + voiceButtonAccessibilityLabel = voice?.isMuted ? "Unmute Voice mode" : "Mute Voice mode"; + } else if (isDictating) { + voiceButtonAccessibilityLabel = "Stop dictation"; + } else { + voiceButtonAccessibilityLabel = "Start dictation"; + } + + let voiceTooltipText: string; + if (isRealtimeVoiceForCurrentAgent) { + voiceTooltipText = voice?.isMuted ? "Unmute voice" : "Mute voice"; + } else { + voiceTooltipText = "Dictation"; + } + const handleInputChange = useCallback( (nextValue: string) => { markScrollInvestigationEvent(investigationComponentId, "inputChange"); @@ -1107,42 +1126,32 @@ export const MessageInput = forwardRef(funct onPress={handleVoicePress} disabled={!isDictationStartEnabled} accessibilityRole="button" - accessibilityLabel={ - isRealtimeVoiceForCurrentAgent - ? voice?.isMuted - ? "Unmute Voice mode" - : "Mute Voice mode" - : isDictating - ? "Stop dictation" - : "Start dictation" - } + accessibilityLabel={voiceButtonAccessibilityLabel} style={voiceButtonStyle} > - {({ hovered }) => - isDictating ? ( - - ) : isRealtimeVoiceForCurrentAgent && voice?.isMuted ? ( - - ) : ( + {({ hovered }) => { + if (isDictating) { + return ; + } + if (isRealtimeVoiceForCurrentAgent && voice?.isMuted) { + return ( + + ); + } + return ( - ) - } + ); + }} - - {isRealtimeVoiceForCurrentAgent - ? voice?.isMuted - ? "Unmute voice" - : "Mute voice" - : "Dictation"} - + {voiceTooltipText} {(isRealtimeVoiceForCurrentAgent ? voiceMuteToggleKeys : dictationToggleKeys) ? ( (funct accessibilityRole="button" style={sendButtonCombinedStyle} > - {isSubmitLoading ? ( - - ) : submitIcon === "return" ? ( + {isSubmitLoading ? : null} + {!isSubmitLoading && submitIcon === "return" ? ( - ) : ( + ) : null} + {!isSubmitLoading && submitIcon !== "return" ? ( - )} + ) : null} @@ -1204,7 +1213,8 @@ export const MessageInput = forwardRef(funct onRetry={dictationStatus === "failed" ? handleRetryFailedRecording : undefined} onDiscard={dictationStatus === "failed" ? handleDiscardFailedRecording : undefined} /> - ) : showRealtimeOverlay && voice ? ( + ) : null} + {!showDictationOverlay && showRealtimeOverlay && voice ? ( - {isSubmitting ? ( - Opening project... - ) : options.length === 0 && !query.trim() ? ( + {isSubmitting ? Opening project... : null} + {!isSubmitting && options.length === 0 && !query.trim() ? ( Start typing a path - ) : ( + ) : null} + {!isSubmitting && !(options.length === 0 && !query.trim()) ? ( <> {options.map((path, index) => ( ))} - )} + ) : null} diff --git a/packages/app/src/components/toast-host.tsx b/packages/app/src/components/toast-host.tsx index 734cb07bca..7612bb2168 100644 --- a/packages/app/src/components/toast-host.tsx +++ b/packages/app/src/components/toast-host.tsx @@ -242,13 +242,13 @@ export function ToastViewport({ return null; } - const icon = - toast.icon ?? - (toast.variant === "success" ? ( - - ) : toast.variant === "error" ? ( - - ) : null); + let defaultIcon: ReactNode = null; + if (toast.variant === "success") { + defaultIcon = ; + } else if (toast.variant === "error") { + defaultIcon = ; + } + const icon = toast.icon ?? defaultIcon; const content = ( diff --git a/packages/app/src/components/ui/dropdown-menu.tsx b/packages/app/src/components/ui/dropdown-menu.tsx index 6a64ec001a..516558e37a 100644 --- a/packages/app/src/components/ui/dropdown-menu.tsx +++ b/packages/app/src/components/ui/dropdown-menu.tsx @@ -285,8 +285,14 @@ export function DropdownMenuTrigger({ } function getTransformOrigin(placement: Placement, alignment: Alignment): string { - const vertical = placement === "bottom" ? "top" : placement === "top" ? "bottom" : "center"; - const horizontal = alignment === "start" ? "left" : alignment === "end" ? "right" : "center"; + let vertical: string; + if (placement === "bottom") vertical = "top"; + else if (placement === "top") vertical = "bottom"; + else vertical = "center"; + let horizontal: string; + if (alignment === "start") horizontal = "left"; + else if (alignment === "end") horizontal = "right"; + else horizontal = "center"; return `${vertical} ${horizontal}`; } @@ -610,21 +616,23 @@ export function DropdownMenuItem({ }, [isDisabled, selectItem, onSelect, closeOnSelect]); const itemPressableStyle = useCallback( - ({ pressed, hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => [ - styles.item, - selected - ? selectedVariant === "accent" - ? styles.itemSelectedAccent - : styles.itemSelected - : null, - selected && (Boolean(hovered) || pressed) && selectedVariant !== "accent" - ? styles.itemSelectedInteractive - : null, - isDisabled ? styles.itemDisabled : null, - muted && !isDisabled ? styles.itemMuted : null, - Boolean(hovered) && !pressed && !isDisabled ? styles.itemHovered : null, - pressed && !isDisabled ? styles.itemPressed : null, - ], + ({ pressed, hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => { + let selectedStyle: typeof styles.itemSelectedAccent | typeof styles.itemSelected | null; + if (!selected) selectedStyle = null; + else if (selectedVariant === "accent") selectedStyle = styles.itemSelectedAccent; + else selectedStyle = styles.itemSelected; + return [ + styles.item, + selectedStyle, + selected && (Boolean(hovered) || pressed) && selectedVariant !== "accent" + ? styles.itemSelectedInteractive + : null, + isDisabled ? styles.itemDisabled : null, + muted && !isDisabled ? styles.itemMuted : null, + Boolean(hovered) && !pressed && !isDisabled ? styles.itemHovered : null, + pressed && !isDisabled ? styles.itemPressed : null, + ]; + }, [selected, selectedVariant, isDisabled, muted], ); diff --git a/packages/app/src/components/volume-meter.tsx b/packages/app/src/components/volume-meter.tsx index 56b35b5458..422535210d 100644 --- a/packages/app/src/components/volume-meter.tsx +++ b/packages/app/src/components/volume-meter.tsx @@ -119,7 +119,10 @@ export function VolumeMeter({ // Create animated styles unconditionally at top level const line1Style = useAnimatedStyle(() => { const isActive = isSpeaking; - const baseOpacity = isMuted ? 0.3 : isActive ? 0.9 : 0.5; + let baseOpacity: number; + if (isMuted) baseOpacity = 0.3; + else if (isActive) baseOpacity = 0.9; + else baseOpacity = 0.5; const currentVolume = isMuted ? 0 : animatedVolume.value; const currentHeight = MIN_HEIGHT + MAX_HEIGHT * currentVolume * 1.2; const volumeBoost = isMuted || !isActive ? 0 : currentVolume * 0.3; @@ -131,7 +134,10 @@ export function VolumeMeter({ const line2Style = useAnimatedStyle(() => { const isActive = isSpeaking; - const baseOpacity = isMuted ? 0.3 : isActive ? 0.9 : 0.5; + let baseOpacity: number; + if (isMuted) baseOpacity = 0.3; + else if (isActive) baseOpacity = 0.9; + else baseOpacity = 0.5; const currentVolume = isMuted ? 0 : animatedVolume.value; const currentHeight = MIN_HEIGHT + MAX_HEIGHT * currentVolume * 1.05; const volumeBoost = isMuted || !isActive ? 0 : currentVolume * 0.3; @@ -143,7 +149,10 @@ export function VolumeMeter({ const line3Style = useAnimatedStyle(() => { const isActive = isSpeaking; - const baseOpacity = isMuted ? 0.3 : isActive ? 0.9 : 0.5; + let baseOpacity: number; + if (isMuted) baseOpacity = 0.3; + else if (isActive) baseOpacity = 0.9; + else baseOpacity = 0.5; const currentVolume = isMuted ? 0 : animatedVolume.value; const currentHeight = MIN_HEIGHT + MAX_HEIGHT * currentVolume * 0.9; const volumeBoost = isMuted || !isActive ? 0 : currentVolume * 0.3; diff --git a/packages/app/src/components/web-desktop-scrollbar.tsx b/packages/app/src/components/web-desktop-scrollbar.tsx index 5589803e52..4bd1dafe93 100644 --- a/packages/app/src/components/web-desktop-scrollbar.tsx +++ b/packages/app/src/components/web-desktop-scrollbar.tsx @@ -338,13 +338,11 @@ export function WebDesktopScrollbarOverlay({ } const handleVisible = isDragging || isScrollVisible || isHandleHovered; - const handleOpacity = isDragging - ? HANDLE_OPACITY_DRAGGING - : isHandleHovered - ? HANDLE_OPACITY_HOVERED - : isScrollVisible - ? HANDLE_OPACITY_VISIBLE - : 0; + let handleOpacity: number; + if (isDragging) handleOpacity = HANDLE_OPACITY_DRAGGING; + else if (isHandleHovered) handleOpacity = HANDLE_OPACITY_HOVERED; + else if (isScrollVisible) handleOpacity = HANDLE_OPACITY_VISIBLE; + else handleOpacity = 0; const handleWidth = isDragging || isHandleHovered ? HANDLE_WIDTH_ACTIVE : HANDLE_WIDTH_IDLE; const handleColor = theme.colors.scrollbarHandle; const handleCursor = isDragging ? "grabbing" : "grab"; diff --git a/packages/app/src/screens/startup-splash-screen.tsx b/packages/app/src/screens/startup-splash-screen.tsx index bf59c32cf0..1d0f14bdd1 100644 --- a/packages/app/src/screens/startup-splash-screen.tsx +++ b/packages/app/src/screens/startup-splash-screen.tsx @@ -229,22 +229,22 @@ export function StartupSplashScreen({ bootstrapState }: StartupSplashScreenProps }; }, [isError]); - const progressSteps = - phase === "starting-daemon" - ? [{ key: "starting-daemon", label: "Starting local server...", status: "active" as const }] - : phase === "connecting" - ? [ - { key: "starting-daemon", label: "Started local server", status: "complete" as const }, - { - key: "connecting", - label: "Connecting to local server...", - status: "active" as const, - }, - ] - : [ - { key: "starting-daemon", label: "Started local server", status: "complete" as const }, - { key: "connecting", label: "Connected to local server", status: "complete" as const }, - ]; + let progressSteps: { key: string; label: string; status: "active" | "complete" }[]; + if (phase === "starting-daemon") { + progressSteps = [ + { key: "starting-daemon", label: "Starting local server...", status: "active" }, + ]; + } else if (phase === "connecting") { + progressSteps = [ + { key: "starting-daemon", label: "Started local server", status: "complete" }, + { key: "connecting", label: "Connecting to local server...", status: "active" }, + ]; + } else { + progressSteps = [ + { key: "starting-daemon", label: "Started local server", status: "complete" }, + { key: "connecting", label: "Connected to local server", status: "complete" }, + ]; + } const logsText = useMemo(() => { if (isLoadingLogs) { diff --git a/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx b/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx index 64d8966151..7df35baaa4 100644 --- a/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx +++ b/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx @@ -342,19 +342,18 @@ function TabChip({ - {showLabel ? ( - presentation.titleState === "loading" ? ( - - ) : ( - - {presentation.label} - - ) + {showLabel && presentation.titleState === "loading" ? ( + + ) : null} + {showLabel && presentation.titleState !== "loading" ? ( + + {presentation.label} + ) : null} diff --git a/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx b/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx index 13181d2764..123a3b0d82 100644 --- a/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx +++ b/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx @@ -192,14 +192,18 @@ export function WorkspaceDraftAgentTab({ if (!provider) { throw new Error("Select a model"); } + let modeIdOverride: { modeId: string } | Record; + if (autoSubmitConfig?.modeId) { + modeIdOverride = { modeId: autoSubmitConfig.modeId }; + } else if (composerState.modeOptions.length > 0 && composerState.selectedMode !== "") { + modeIdOverride = { modeId: composerState.selectedMode }; + } else { + modeIdOverride = {}; + } const config = buildWorkspaceDraftAgentConfig({ provider, cwd: workspaceDirectory, - ...(autoSubmitConfig?.modeId - ? { modeId: autoSubmitConfig.modeId } - : composerState.modeOptions.length > 0 && composerState.selectedMode !== "" - ? { modeId: composerState.selectedMode } - : {}), + ...modeIdOverride, model: autoSubmitConfig?.model ?? (composerState.effectiveModelId || undefined), thinkingOptionId: autoSubmitConfig?.thinkingOptionId ?? diff --git a/packages/app/src/stores/workspace-tabs-store.ts b/packages/app/src/stores/workspace-tabs-store.ts index dd4b8c8a7e..03990a950e 100644 --- a/packages/app/src/stores/workspace-tabs-store.ts +++ b/packages/app/src/stores/workspace-tabs-store.ts @@ -205,25 +205,25 @@ export const useWorkspaceTabsStore = create()( const currentOrder = state.tabOrderByWorkspace[key] ?? []; const nextOrder = currentOrder.filter((value) => value !== normalizedTabId); - const nextUiTabsByWorkspace = - nextTabs.length === 0 - ? (() => { - const { [key]: _removed, ...rest } = state.uiTabsByWorkspace; - return rest; - })() - : nextTabs.length === currentTabs.length - ? state.uiTabsByWorkspace - : { ...state.uiTabsByWorkspace, [key]: nextTabs }; + let nextUiTabsByWorkspace: typeof state.uiTabsByWorkspace; + if (nextTabs.length === 0) { + const { [key]: _removed, ...rest } = state.uiTabsByWorkspace; + nextUiTabsByWorkspace = rest; + } else if (nextTabs.length === currentTabs.length) { + nextUiTabsByWorkspace = state.uiTabsByWorkspace; + } else { + nextUiTabsByWorkspace = { ...state.uiTabsByWorkspace, [key]: nextTabs }; + } - const nextTabOrderByWorkspace = - nextOrder.length === 0 - ? (() => { - const { [key]: _removed, ...rest } = state.tabOrderByWorkspace; - return rest; - })() - : nextOrder.length === currentOrder.length - ? state.tabOrderByWorkspace - : { ...state.tabOrderByWorkspace, [key]: nextOrder }; + let nextTabOrderByWorkspace: typeof state.tabOrderByWorkspace; + if (nextOrder.length === 0) { + const { [key]: _removed, ...rest } = state.tabOrderByWorkspace; + nextTabOrderByWorkspace = rest; + } else if (nextOrder.length === currentOrder.length) { + nextTabOrderByWorkspace = state.tabOrderByWorkspace; + } else { + nextTabOrderByWorkspace = { ...state.tabOrderByWorkspace, [key]: nextOrder }; + } const currentFocused = state.focusedTabIdByWorkspace[key] ?? null; const nextFocused = diff --git a/packages/app/src/utils/sidebar-project-row-model.ts b/packages/app/src/utils/sidebar-project-row-model.ts index 12764776d2..c280d3d11c 100644 --- a/packages/app/src/utils/sidebar-project-row-model.ts +++ b/packages/app/src/utils/sidebar-project-row-model.ts @@ -43,9 +43,14 @@ export function buildSidebarProjectRowModel(input: { const collapsible = input.project.projectKind === "git" || input.project.workspaces.length > 1; + let chevron: "expand" | "collapse" | null; + if (!collapsible) chevron = null; + else if (input.collapsed) chevron = "expand"; + else chevron = "collapse"; + return { kind: "project_section", - chevron: collapsible ? (input.collapsed ? "expand" : "collapse") : null, + chevron, trailingAction: input.project.projectKind === "git" ? "new_worktree" : "none", }; } diff --git a/packages/app/src/utils/test-daemon-connection.ts b/packages/app/src/utils/test-daemon-connection.ts index 03c3e2a5c6..c0885b3e8d 100644 --- a/packages/app/src/utils/test-daemon-connection.ts +++ b/packages/app/src/utils/test-daemon-connection.ts @@ -59,10 +59,9 @@ export async function buildClientConfig( suppressSendErrors: true, reconnect: { enabled: false }, ...(isDev ? { runtimeMetricsIntervalMs: 10_000 } : {}), - ...(connection.type === "directSocket" || connection.type === "directPipe" - ? localTransportFactory - ? { transportFactory: localTransportFactory } - : {} + ...((connection.type === "directSocket" || connection.type === "directPipe") && + localTransportFactory + ? { transportFactory: localTransportFactory } : {}), }; diff --git a/packages/app/src/voice/voice-runtime.ts b/packages/app/src/voice/voice-runtime.ts index 20e279c3ba..708e405459 100644 --- a/packages/app/src/voice/voice-runtime.ts +++ b/packages/app/src/voice/voice-runtime.ts @@ -271,12 +271,10 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime { bytes: Uint8Array, format: string, ): { arrayBuffer(): Promise; size: number; type: string } { - const mimeType = - format === "pcm" - ? "audio/pcm;rate=24000;bits=16" - : format === "mp3" - ? "audio/mpeg" - : `audio/${format}`; + let mimeType: string; + if (format === "pcm") mimeType = "audio/pcm;rate=24000;bits=16"; + else if (format === "mp3") mimeType = "audio/mpeg"; + else mimeType = `audio/${format}`; return { size: bytes.byteLength,