diff --git a/packages/app/src/attachments/local-file-attachment-store.ts b/packages/app/src/attachments/local-file-attachment-store.ts index 181771eaf..98111d812 100644 --- a/packages/app/src/attachments/local-file-attachment-store.ts +++ b/packages/app/src/attachments/local-file-attachment-store.ts @@ -114,12 +114,14 @@ export function createLocalFileAttachmentStore(params: { await ensureDirectory(baseDirectory); const id = input.id ?? generateAttachmentId(); - const mimeTypeFromSource = - input.source.kind === "data_url" - ? parseDataUrl(input.source.dataUrl).mimeType - : input.source.kind === "blob" - ? input.source.blob.type - : undefined; + let mimeTypeFromSource: string | undefined; + if (input.source.kind === "data_url") { + mimeTypeFromSource = parseDataUrl(input.source.dataUrl).mimeType; + } else if (input.source.kind === "blob") { + mimeTypeFromSource = input.source.blob.type; + } else { + mimeTypeFromSource = undefined; + } const mimeType = normalizeMimeType(input.mimeType ?? mimeTypeFromSource); const fileName = input.fileName ?? null; const extension = extensionForAttachment({ fileName, mimeType }); diff --git a/packages/app/src/components/add-host-modal.tsx b/packages/app/src/components/add-host-modal.tsx index 554750dad..5c81e79ba 100644 --- a/packages/app/src/components/add-host-modal.tsx +++ b/packages/app/src/components/add-host-modal.tsx @@ -222,12 +222,14 @@ export function AddHostModal({ visible, onClose, onCancel, onSaved }: AddHostMod handleClose(); } catch (error) { const { title, detail, raw: rawDetail } = buildConnectionFailureCopy(endpoint, error); - const combined = - rawDetail && detail && rawDetail !== detail - ? `${title}\n${detail}\nDetails: ${rawDetail}` - : detail - ? `${title}\n${detail}` - : title; + let combined: string; + if (rawDetail && detail && rawDetail !== detail) { + combined = `${title}\n${detail}\nDetails: ${rawDetail}`; + } else if (detail) { + combined = `${title}\n${detail}`; + } else { + combined = title; + } setErrorMessage(combined); if (!isMobile) { // Desktop/web: also surface it as a dialog for quick visibility. diff --git a/packages/app/src/components/code-insets.ts b/packages/app/src/components/code-insets.ts index fb2e17a4d..62756602a 100644 --- a/packages/app/src/components/code-insets.ts +++ b/packages/app/src/components/code-insets.ts @@ -8,12 +8,10 @@ export function lineNumberGutterWidth(maxLineNumber: number): number { } export function getCodeInsets(theme: any) { - const padding = - typeof theme.spacing?.[3] === "number" - ? theme.spacing[3] - : typeof theme.spacing?.[4] === "number" - ? theme.spacing[4] - : 12; + let padding: number; + if (typeof theme.spacing?.[3] === "number") padding = theme.spacing[3]; + else if (typeof theme.spacing?.[4] === "number") padding = theme.spacing[4]; + else padding = 12; const extraRight = theme.spacing[4]; const extraBottom = theme.spacing[3]; diff --git a/packages/app/src/components/combined-model-selector.tsx b/packages/app/src/components/combined-model-selector.tsx index 863d30575..054fbfb08 100644 --- a/packages/app/src/components/combined-model-selector.tsx +++ b/packages/app/src/components/combined-model-selector.tsx @@ -217,19 +217,19 @@ function ModelRow({ accessibilityLabel={isFavorite ? "Unfavorite model" : "Favorite model"} testID={`favorite-model-${row.provider}-${row.modelId}`} > - {({ hovered }) => ( - - )} + {({ hovered }) => { + let starColor: string; + if (isFavorite) starColor = theme.colors.palette.amber[500]; + else if (hovered) starColor = theme.colors.foregroundMuted; + else starColor = theme.colors.border; + return ( + + ); + }} ) : null, [ diff --git a/packages/app/src/components/command-center.tsx b/packages/app/src/components/command-center.tsx index 306443af2..6664bb8bd 100644 --- a/packages/app/src/components/command-center.tsx +++ b/packages/app/src/components/command-center.tsx @@ -101,12 +101,12 @@ function CommandCenterActionRow({ const { theme } = useUnistyles(); const handlePress = useCallback(() => onSelect(item), [onSelect, item]); const action = item.action; - const actionIcon = - action.icon === "plus" ? ( - - ) : action.icon === "settings" ? ( - - ) : null; + let actionIcon: React.ReactNode = null; + if (action.icon === "plus") { + actionIcon = ; + } else if (action.icon === "settings") { + actionIcon = ; + } const titleStyle = useMemo( () => [styles.title, { color: theme.colors.foreground }], [theme.colors.foreground], diff --git a/packages/app/src/components/composer.tsx b/packages/app/src/components/composer.tsx index 899a4e7a7..8748ad29b 100644 --- a/packages/app/src/components/composer.tsx +++ b/packages/app/src/components/composer.tsx @@ -782,8 +782,10 @@ export function Composer({ } void voice.startVoice(serverId, agentId).catch((error) => { console.error("[Composer] Failed to start voice mode", 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) { toastErrorRef.current(message); } diff --git a/packages/app/src/components/git-diff-pane.tsx b/packages/app/src/components/git-diff-pane.tsx index 0653e570b..78e76be28 100644 --- a/packages/app/src/components/git-diff-pane.tsx +++ b/packages/app/src/components/git-diff-pane.tsx @@ -1238,12 +1238,14 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi const hasChanges = files.length > 0; const diffErrorMessage = diffPayloadError?.message ?? null; const prErrorMessage = githubFeaturesEnabled ? (prPayloadError?.message ?? null) : null; - const branchLabel = - gitStatus?.currentBranch && gitStatus.currentBranch !== "HEAD" - ? gitStatus.currentBranch - : notGit - ? "Not a git repository" - : "Unknown"; + let branchLabel: string; + if (gitStatus?.currentBranch && gitStatus.currentBranch !== "HEAD") { + branchLabel = gitStatus.currentBranch; + } else if (notGit) { + branchLabel = "Not a git repository"; + } else { + branchLabel = "Unknown"; + } const actionsDisabled = !isGit || Boolean(status?.error) || isStatusLoading; const aheadCount = gitStatus?.aheadBehind?.ahead ?? 0; const behindBaseCount = gitStatus?.aheadBehind?.behind ?? 0; @@ -1313,15 +1315,17 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi ); } else if (!hasChanges) { + let emptyMessage: string; + if (changesPreferences.hideWhitespace) { + emptyMessage = "No visible changes after hiding whitespace"; + } else if (diffMode === "uncommitted") { + emptyMessage = "No uncommitted changes"; + } else { + emptyMessage = `No changes vs ${baseRefLabel}`; + } bodyContent = ( - - {changesPreferences.hideWhitespace - ? "No visible changes after hiding whitespace" - : diffMode === "uncommitted" - ? "No uncommitted changes" - : `No changes vs ${baseRefLabel}`} - + {emptyMessage} ); } else { diff --git a/packages/app/src/components/left-sidebar.tsx b/packages/app/src/components/left-sidebar.tsx index 7a2150f08..ed6647d71 100644 --- a/packages/app/src/components/left-sidebar.tsx +++ b/packages/app/src/components/left-sidebar.tsx @@ -145,12 +145,11 @@ export const LeftSidebar = memo(function LeftSidebar({ const activeHostStatus = activeServerId ? (activeHostSnapshot?.connectionStatus ?? "connecting") : "idle"; - const activeHostStatusColor = - activeHostStatus === "online" - ? theme.colors.palette.green[400] - : activeHostStatus === "connecting" - ? theme.colors.palette.amber[500] - : theme.colors.palette.red[500]; + let activeHostStatusColor: string; + if (activeHostStatus === "online") activeHostStatusColor = theme.colors.palette.green[400]; + else if (activeHostStatus === "connecting") + activeHostStatusColor = theme.colors.palette.amber[500]; + else activeHostStatusColor = theme.colors.palette.red[500]; const hostOptions = useMemo( () => daemons.map((daemon) => ({ @@ -523,7 +522,10 @@ function MobileSidebar({ pointerEvents: backdropOpacity.value > 0.01 ? "auto" : "none", })); - 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"; const backdropStyle = useMemo( () => [staticStyles.backdrop, backdropAnimatedStyle], diff --git a/packages/app/src/components/provider-diagnostic-sheet.tsx b/packages/app/src/components/provider-diagnostic-sheet.tsx index 4b748bece..1eb87490a 100644 --- a/packages/app/src/components/provider-diagnostic-sheet.tsx +++ b/packages/app/src/components/provider-diagnostic-sheet.tsx @@ -215,7 +215,8 @@ export function ProviderDiagnosticSheet({ Running diagnostic… - ) : diagnostic ? ( + ) : null} + {!(loading && !diagnostic) && diagnostic ? ( - ) : ( + ) : null} + {!(loading && !diagnostic) && !diagnostic ? ( No diagnostic available. - )} + ) : null} diff --git a/packages/app/src/components/sidebar-workspace-list.tsx b/packages/app/src/components/sidebar-workspace-list.tsx index a2de544aa..d9e001948 100644 --- a/packages/app/src/components/sidebar-workspace-list.tsx +++ b/packages/app/src/components/sidebar-workspace-list.tsx @@ -196,6 +196,16 @@ interface WorkspaceRowInnerProps { archiveShortcutKeys?: ShortcutKey[][] | null; } +function getWorkspaceArchiveStatus( + isWorktree: boolean, + archiveStatus: "idle" | "pending" | "success", + isArchivingWorkspace: boolean, +): "idle" | "pending" | "success" { + if (isWorktree) return archiveStatus; + if (isArchivingWorkspace) return "pending"; + return "idle"; +} + function useSidebarWorkspaceEntry( serverId: string | null, workspaceId: string | null, @@ -369,8 +379,10 @@ function WorkspaceStatusIndicator({ ); } - const KindIcon = - workspaceKind === "local_checkout" ? Monitor : workspaceKind === "worktree" ? FolderGit2 : null; + let KindIcon: typeof Monitor | typeof FolderGit2 | null; + if (workspaceKind === "local_checkout") KindIcon = Monitor; + else if (workspaceKind === "worktree") KindIcon = FolderGit2; + else KindIcon = null; if (!KindIcon) return null; const dotColor = getStatusDotColor({ theme, bucket, showDoneAsInactive: false }); @@ -1187,7 +1199,8 @@ function WorkspaceRowInner({ - ) : workspace.diffStat ? ( + ) : null} + {!(onArchive && (isHovered || isTouchPlatform)) && workspace.diffStat ? ( ({ const DND_MODIFIERS: Modifier[] = [restrictToHorizontalAxis]; +function computeDragOpacity(hasExternalContext: boolean, isDragging: boolean): number { + if (!isDragging) return 1; + return hasExternalContext ? 0.3 : 0.9; +} + function SortableItem({ id, item, @@ -77,7 +82,7 @@ function SortableItem({ () => ({ transform: combinedTransform || undefined, transition, - opacity: externalDndContext && isDragging ? 0.3 : isDragging ? 0.9 : 1, + opacity: computeDragOpacity(Boolean(externalDndContext), isDragging), zIndex: isDragging ? 1000 : 1, }), [combinedTransform, transition, externalDndContext, isDragging], diff --git a/packages/app/src/components/terminal-emulator.tsx b/packages/app/src/components/terminal-emulator.tsx index 14f05215d..c596256b6 100644 --- a/packages/app/src/components/terminal-emulator.tsx +++ b/packages/app/src/components/terminal-emulator.tsx @@ -583,13 +583,11 @@ export default function TerminalEmulator({ const handleVisible = scrollbarGeometry.isVisible && (isDraggingScrollbar || isScrollVisible || isHandleHovered); - const handleOpacity = isDraggingScrollbar - ? SCROLLBAR_HANDLE_OPACITY_DRAGGING - : isHandleHovered - ? SCROLLBAR_HANDLE_OPACITY_HOVERED - : isScrollVisible - ? SCROLLBAR_HANDLE_OPACITY_VISIBLE - : 0; + let handleOpacity: number; + if (isDraggingScrollbar) handleOpacity = SCROLLBAR_HANDLE_OPACITY_DRAGGING; + else if (isHandleHovered) handleOpacity = SCROLLBAR_HANDLE_OPACITY_HOVERED; + else if (isScrollVisible) handleOpacity = SCROLLBAR_HANDLE_OPACITY_VISIBLE; + else handleOpacity = 0; const handleWidth = isDraggingScrollbar || isHandleHovered ? SCROLLBAR_HANDLE_WIDTH_ACTIVE diff --git a/packages/app/src/components/ui/combobox.tsx b/packages/app/src/components/ui/combobox.tsx index 9549b8c82..56f774ecc 100644 --- a/packages/app/src/components/ui/combobox.tsx +++ b/packages/app/src/components/ui/combobox.tsx @@ -200,17 +200,22 @@ export function ComboboxItem({ }: ComboboxItemProps): ReactElement { const { theme } = useUnistyles(); - const leadingContent = leadingSlot ? ( - {leadingSlot} - ) : kind === "directory" || kind === "file" ? ( - - {kind === "directory" ? ( + let leadingContent: ReactElement | null = null; + if (leadingSlot) { + leadingContent = {leadingSlot}; + } else if (kind === "directory") { + leadingContent = ( + - ) : ( + + ); + } else if (kind === "file") { + leadingContent = ( + - )} - - ) : null; + + ); + } const itemPressableStyle = useCallback( ({ pressed, hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => [ @@ -482,30 +487,36 @@ export function Combobox({ const measuredTopStartBottom = useMeasuredTopStartPosition ? Math.max(windowHeight - referenceTop + 5, collisionPadding) : null; + let resolvedPositionReady: boolean; + if (isDesktopAboveSearch) { + resolvedPositionReady = floatingLeft !== null && desktopAboveSearchBottom !== null; + } else if (useMeasuredTopStartPosition) { + resolvedPositionReady = clampedMeasuredTopStartLeft !== null && measuredTopStartBottom !== null; + } else { + resolvedPositionReady = + floatingLeft !== null && + floatingTop !== null && + (hasNonZeroFloatingPosition || referenceAtOrigin); + } const hasResolvedDesktopPosition = - referenceWidth !== null && - referenceWidth > 0 && - (isDesktopAboveSearch - ? floatingLeft !== null && desktopAboveSearchBottom !== null - : useMeasuredTopStartPosition - ? clampedMeasuredTopStartLeft !== null && measuredTopStartBottom !== null - : floatingLeft !== null && - floatingTop !== null && - (hasNonZeroFloatingPosition || referenceAtOrigin)); + referenceWidth !== null && referenceWidth > 0 && resolvedPositionReady; const shouldHideDesktopContent = desktopPreventInitialFlash && !hasResolvedDesktopPosition; const shouldUseDesktopFade = !desktopPreventInitialFlash; - const desktopPositionStyle = isDesktopAboveSearch - ? { - left: floatingLeft ?? 0, - bottom: desktopAboveSearchBottom ?? 0, - } - : useMeasuredTopStartPosition - ? { - left: clampedMeasuredTopStartLeft ?? 0, - bottom: measuredTopStartBottom ?? 0, - } - : floatingStyles; + let desktopPositionStyle: { left: number; bottom: number } | typeof floatingStyles; + if (isDesktopAboveSearch) { + desktopPositionStyle = { + left: floatingLeft ?? 0, + bottom: desktopAboveSearchBottom ?? 0, + }; + } else if (useMeasuredTopStartPosition) { + desktopPositionStyle = { + left: clampedMeasuredTopStartLeft ?? 0, + bottom: measuredTopStartBottom ?? 0, + }; + } else { + desktopPositionStyle = floatingStyles; + } const { sheetRef: bottomSheetRef, handleSheetChange } = useIsolatedBottomSheetVisibility({ visible: isOpen, diff --git a/packages/app/src/components/ui/context-menu.tsx b/packages/app/src/components/ui/context-menu.tsx index 1deb9b3e3..449ad141d 100644 --- a/packages/app/src/components/ui/context-menu.tsx +++ b/packages/app/src/components/ui/context-menu.tsx @@ -695,20 +695,22 @@ export function ContextMenuItem({ }, [isDisabled, closeOnSelect, setOpen, onSelect]); const itemPressableStyle = useCallback( - ({ pressed, hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => [ - styles.item, - selected - ? selectedVariant === "accent" - ? styles.itemSelectedAccent - : styles.itemSelected - : null, - selected && (hovered || pressed) && selectedVariant !== "accent" - ? styles.itemSelectedInteractive - : null, - isDisabled ? styles.itemDisabled : null, - 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 && (hovered || pressed) && selectedVariant !== "accent" + ? styles.itemSelectedInteractive + : null, + isDisabled ? styles.itemDisabled : null, + hovered && !pressed && !isDisabled ? styles.itemHovered : null, + pressed && !isDisabled ? styles.itemPressed : null, + ]; + }, [selected, selectedVariant, isDisabled], ); diff --git a/packages/app/src/contexts/session-context.tsx b/packages/app/src/contexts/session-context.tsx index a22ff9811..9a0e3dc93 100644 --- a/packages/app/src/contexts/session-context.tsx +++ b/packages/app/src/contexts/session-context.tsx @@ -112,12 +112,10 @@ function buildAudioPlaybackSource(chunks: BufferedAudioChunk[]): AudioPlaybackSo } const format = chunks[0]?.format ?? "pcm"; - 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}`; const bytes = output.slice(); return { diff --git a/packages/app/src/desktop/components/desktop-updates-section.tsx b/packages/app/src/desktop/components/desktop-updates-section.tsx index 6fc8a254b..49d5c8178 100644 --- a/packages/app/src/desktop/components/desktop-updates-section.tsx +++ b/packages/app/src/desktop/components/desktop-updates-section.tsx @@ -21,6 +21,20 @@ import { import { useDaemonStatus } from "@/desktop/hooks/use-daemon-status"; import { resolveAppVersion } from "@/utils/app-version"; +function getDaemonManagementButtonLabel(isUpdating: boolean, isPaused: boolean): string { + if (isUpdating) return isPaused ? "Resuming..." : "Pausing..."; + return isPaused ? "Resume" : "Pause"; +} + +function getDaemonRestartButtonLabel( + isRestarting: boolean, + daemonStatus: string | undefined, + actionLabel: string, +): string { + if (!isRestarting) return actionLabel; + return daemonStatus === "running" ? "Restarting..." : "Starting..."; +} + export function LocalDaemonSection() { const { theme } = useUnistyles(); const showSection = shouldUseDesktopDaemon(); @@ -338,13 +352,10 @@ export function LocalDaemonSection() { onPress={handleToggleDaemonManagement} disabled={isUpdatingDaemonManagement} > - {isUpdatingDaemonManagement - ? isDaemonManagementPaused - ? "Resuming..." - : "Pausing..." - : isDaemonManagementPaused - ? "Resume" - : "Pause"} + {getDaemonManagementButtonLabel( + isUpdatingDaemonManagement, + isDaemonManagementPaused, + )} @@ -360,11 +371,11 @@ export function LocalDaemonSection() { onPress={handleUpdateLocalDaemon} disabled={isRestartingDaemon} > - {isRestartingDaemon - ? daemonStatus?.status === "running" - ? "Restarting..." - : "Starting..." - : daemonActionLabel} + {getDaemonRestartButtonLabel( + isRestartingDaemon, + daemonStatus?.status, + daemonActionLabel, + )} diff --git a/packages/app/src/desktop/components/pair-device-section.tsx b/packages/app/src/desktop/components/pair-device-section.tsx index e098aa46d..03b9ce0fb 100644 --- a/packages/app/src/desktop/components/pair-device-section.tsx +++ b/packages/app/src/desktop/components/pair-device-section.tsx @@ -79,7 +79,8 @@ export function PairDeviceSection() { Loading pairing offer… - ) : pairingQuery.isError ? ( + ) : null} + {!pairingQuery.isPending && pairingQuery.isError ? ( {pairingQuery.error instanceof Error @@ -90,7 +91,8 @@ export function PairDeviceSection() { Retry - ) : !pairingQuery.data?.url ? ( + ) : null} + {!pairingQuery.isPending && !pairingQuery.isError && !pairingQuery.data?.url ? ( {pairingQuery.data?.relayEnabled === false @@ -101,7 +103,8 @@ export function PairDeviceSection() { Retry - ) : ( + ) : null} + {!pairingQuery.isPending && !pairingQuery.isError && pairingQuery.data?.url ? ( Scan this QR code with Paseo on your phone, or copy the link below. @@ -109,11 +112,11 @@ export function PairDeviceSection() { {qrImageSource ? ( - ) : qrQuery.isError ? ( + ) : null} + {!qrImageSource && qrQuery.isError ? ( QR code unavailable. - ) : ( - - )} + ) : null} + {!qrImageSource && !qrQuery.isError ? : null} @@ -135,7 +138,7 @@ export function PairDeviceSection() { - )} + ) : null} ); diff --git a/packages/app/src/hooks/use-agent-screen-state-machine.ts b/packages/app/src/hooks/use-agent-screen-state-machine.ts index e9b33d76f..f4ac29386 100644 --- a/packages/app/src/hooks/use-agent-screen-state-machine.ts +++ b/packages/app/src/hooks/use-agent-screen-state-machine.ts @@ -158,13 +158,11 @@ export function deriveAgentScreenViewState({ }; } - const source: "authoritative" | "optimistic" | "stale" = useOptimisticCreateFlowAgent - ? "optimistic" - : input.agent - ? "authoritative" - : input.shouldUseOptimisticStream - ? "optimistic" - : "stale"; + let source: "authoritative" | "optimistic" | "stale"; + if (useOptimisticCreateFlowAgent) source = "optimistic"; + else if (input.agent) source = "authoritative"; + else if (input.shouldUseOptimisticStream) source = "optimistic"; + else source = "stale"; let sync: AgentScreenReadySyncState; if (!input.isConnected) { diff --git a/packages/app/src/hooks/use-checkout-pr-status-query.ts b/packages/app/src/hooks/use-checkout-pr-status-query.ts index 18a7d4f36..97e4d8ed0 100644 --- a/packages/app/src/hooks/use-checkout-pr-status-query.ts +++ b/packages/app/src/hooks/use-checkout-pr-status-query.ts @@ -50,15 +50,15 @@ function selectWorkspacePrHint(payload: CheckoutPrStatusPayload): PrHint | null return null; } + let state: "merged" | "open" | "closed"; + if (status.isMerged || status.state === "merged") state = "merged"; + else if (status.state === "open") state = "open"; + else state = "closed"; + return { url: status.url, number, - state: - status.isMerged || status.state === "merged" - ? "merged" - : status.state === "open" - ? "open" - : "closed", + state, checks: status.checks, checksStatus: status.checksStatus as PrHint["checksStatus"], reviewDecision: status.reviewDecision as PrHint["reviewDecision"], diff --git a/packages/app/src/hooks/use-git-actions.ts b/packages/app/src/hooks/use-git-actions.ts index c70b6c093..7c9f7626b 100644 --- a/packages/app/src/hooks/use-git-actions.ts +++ b/packages/app/src/hooks/use-git-actions.ts @@ -280,12 +280,14 @@ export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): Use const pushDisabled = actionsDisabled || pushStatus === "pending"; const archiveDisabled = actionsDisabled || archiveStatus === "pending"; - const branchLabel = - gitStatus?.currentBranch && gitStatus.currentBranch !== "HEAD" - ? gitStatus.currentBranch - : notGit - ? "Not a git repository" - : "Unknown"; + let branchLabel: string; + if (gitStatus?.currentBranch && gitStatus.currentBranch !== "HEAD") { + branchLabel = gitStatus.currentBranch; + } else if (notGit) { + branchLabel = "Not a git repository"; + } else { + branchLabel = "Unknown"; + } // Build actions const gitActions: GitActions = useMemo(() => { diff --git a/packages/app/src/panels/setup-panel.tsx b/packages/app/src/panels/setup-panel.tsx index e8e3a2a4b..b257a92d5 100644 --- a/packages/app/src/panels/setup-panel.tsx +++ b/packages/app/src/panels/setup-panel.tsx @@ -171,14 +171,11 @@ function SetupPanel() { return null; })(); - const statusLabel = - snapshot?.status === "running" - ? "Running" - : snapshot?.status === "completed" - ? "Completed" - : snapshot?.status === "failed" - ? "Failed" - : "Waiting for setup output"; + let statusLabel: string; + if (snapshot?.status === "running") statusLabel = "Running"; + else if (snapshot?.status === "completed") statusLabel = "Completed"; + else if (snapshot?.status === "failed") statusLabel = "Failed"; + else statusLabel = "Waiting for setup output"; return ( Setting up workspace... - ) : hasNoSetupCommands ? ( + ) : null} + {!isWaiting && hasNoSetupCommands ? ( - ) : ( + ) : null} + {!isWaiting && !hasNoSetupCommands ? ( {commands.map((command) => { const isExpanded = expandedIndices.has(command.index); @@ -275,7 +274,7 @@ function SetupPanel() { ) : null} - )} + ) : null} ); } diff --git a/packages/app/src/screens/sessions-screen.tsx b/packages/app/src/screens/sessions-screen.tsx index f47f1250c..aa1e2d650 100644 --- a/packages/app/src/screens/sessions-screen.tsx +++ b/packages/app/src/screens/sessions-screen.tsx @@ -70,14 +70,16 @@ function SessionsScreenContent({ serverId }: { serverId: string }) { - ) : sortedAgents.length === 0 ? ( + ) : null} + {!isInitialLoad && sortedAgents.length === 0 ? ( No sessions yet - ) : ( + ) : null} + {!isInitialLoad && sortedAgents.length > 0 ? ( - )} + ) : null} ); } diff --git a/packages/app/src/screens/settings-screen.tsx b/packages/app/src/screens/settings-screen.tsx index f1e2df9ae..0eae159d6 100644 --- a/packages/app/src/screens/settings-screen.tsx +++ b/packages/app/src/screens/settings-screen.tsx @@ -344,6 +344,15 @@ function AboutSection({ appVersionText, isDesktopApp }: AboutSectionProps) { ); } +function getUpdateButtonLabel( + isInstalling: boolean, + latestVersion: string | null | undefined, +): string { + if (isInstalling) return "Installing..."; + if (latestVersion) return `Update to ${formatVersionWithPrefix(latestVersion)}`; + return "Update"; +} + function DesktopAppUpdateRow() { const { settings, updateSettings } = useAppSettings(); const { @@ -451,11 +460,7 @@ function DesktopAppUpdateRow() { onPress={handleInstallUpdate} disabled={isChecking || isInstalling || !availableUpdate} > - {isInstalling - ? "Installing..." - : availableUpdate?.latestVersion - ? `Update to ${formatVersionWithPrefix(availableUpdate.latestVersion)}` - : "Update"} + {getUpdateButtonLabel(isInstalling, availableUpdate?.latestVersion)} diff --git a/packages/app/src/screens/settings/providers-section.tsx b/packages/app/src/screens/settings/providers-section.tsx index 5befca7ea..b309f9abc 100644 --- a/packages/app/src/screens/settings/providers-section.tsx +++ b/packages/app/src/screens/settings/providers-section.tsx @@ -140,11 +140,13 @@ export function ProvidersSection({ serverId }: ProvidersSectionProps) { Connect to this host to see providers - ) : isLoading ? ( + ) : null} + {hasServer && isConnected && isLoading ? ( Loading... - ) : ( + ) : null} + {hasServer && isConnected && !isLoading ? ( {providerDefinitions.map((def, index) => ( ))} - )} + ) : null} {diagnosticProvider ? ( diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index 79a3ccaa2..f592ef228 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -2183,33 +2183,41 @@ function WorkspaceScreenContent({ }, [buildPaneContentModel], ); - const content = shouldRenderMissingWorkspaceDescriptor({ + const showMissingWorkspaceDescriptor = shouldRenderMissingWorkspaceDescriptor({ workspace: workspaceDescriptor, hasHydratedWorkspaces, - }) ? ( - - - - ) : isMissingWorkspaceExecutionAuthority ? ( - - - Workspace execution directory is missing. Reload workspace data before opening tabs. - - - ) : !activeTabDescriptor ? ( - !hasHydratedAgents ? ( + }); + let content: React.ReactNode; + if (showMissingWorkspaceDescriptor) { + content = ( - ) : ( + ); + } else if (isMissingWorkspaceExecutionAuthority) { + content = ( + + + Workspace execution directory is missing. Reload workspace data before opening tabs. + + + ); + } else if (!activeTabDescriptor && !hasHydratedAgents) { + content = ( + + + + ); + } else if (!activeTabDescriptor) { + content = ( No tabs are available yet. Use New tab to create an agent or terminal. - ) - ) : ( - mountedFocusedPaneTabIds.map((tabId) => { + ); + } else { + content = mountedFocusedPaneTabIds.map((tabId) => { const tabDescriptor = focusedPaneTabDescriptorMap.get(tabId); if (!tabDescriptor) { return null; @@ -2226,8 +2234,8 @@ function WorkspaceScreenContent({ buildPaneContentModel={buildMobilePaneContentModel} /> ); - }) - ); + }); + } const buildDesktopPaneContentModel = useCallback( function buildDesktopPaneContentModel(input: { paneId: string; tab: WorkspaceTabDescriptor }) { diff --git a/packages/app/src/stores/download-store.ts b/packages/app/src/stores/download-store.ts index c72f7c9dd..c2cd51c65 100644 --- a/packages/app/src/stores/download-store.ts +++ b/packages/app/src/stores/download-store.ts @@ -216,11 +216,10 @@ export const useDownloadStore = create()((set, get) => ({ updated.delete(id); } } - const newActiveId = state.activeDownloadId - ? updated.has(state.activeDownloadId) - ? state.activeDownloadId - : findMostRecentDownloadId(updated) - : null; + let newActiveId: string | null; + if (!state.activeDownloadId) newActiveId = null; + else if (updated.has(state.activeDownloadId)) newActiveId = state.activeDownloadId; + else newActiveId = findMostRecentDownloadId(updated); return { downloads: updated, activeDownloadId: newActiveId }; }); },