chore(lint): no-nested-ternary in app batch 2 (26 files)

This commit is contained in:
Mohamed Boudra
2026-04-24 06:27:10 +07:00
parent ed15dcec3a
commit 2e95806126
26 changed files with 276 additions and 208 deletions

View File

@@ -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 });

View File

@@ -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.

View File

@@ -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];

View File

@@ -217,19 +217,19 @@ function ModelRow({
accessibilityLabel={isFavorite ? "Unfavorite model" : "Favorite model"}
testID={`favorite-model-${row.provider}-${row.modelId}`}
>
{({ hovered }) => (
<Star
size={16}
color={
isFavorite
? theme.colors.palette.amber[500]
: hovered
? theme.colors.foregroundMuted
: theme.colors.border
}
fill={isFavorite ? theme.colors.palette.amber[500] : "transparent"}
/>
)}
{({ 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 (
<Star
size={16}
color={starColor}
fill={isFavorite ? theme.colors.palette.amber[500] : "transparent"}
/>
);
}}
</Pressable>
) : null,
[

View File

@@ -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" ? (
<Plus size={16} strokeWidth={2.4} color={theme.colors.foregroundMuted} />
) : action.icon === "settings" ? (
<Settings size={16} strokeWidth={2.2} color={theme.colors.foregroundMuted} />
) : null;
let actionIcon: React.ReactNode = null;
if (action.icon === "plus") {
actionIcon = <Plus size={16} strokeWidth={2.4} color={theme.colors.foregroundMuted} />;
} else if (action.icon === "settings") {
actionIcon = <Settings size={16} strokeWidth={2.2} color={theme.colors.foregroundMuted} />;
}
const titleStyle = useMemo(
() => [styles.title, { color: theme.colors.foreground }],
[theme.colors.foreground],

View File

@@ -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);
}

View File

@@ -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
</View>
);
} 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 = (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>
{changesPreferences.hideWhitespace
? "No visible changes after hiding whitespace"
: diffMode === "uncommitted"
? "No uncommitted changes"
: `No changes vs ${baseRefLabel}`}
</Text>
<Text style={styles.emptyText}>{emptyMessage}</Text>
</View>
);
} else {

View File

@@ -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],

View File

@@ -215,7 +215,8 @@ export function ProviderDiagnosticSheet({
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
<Text style={sheetStyles.mutedText}>Running diagnostic</Text>
</View>
) : diagnostic ? (
) : null}
{!(loading && !diagnostic) && diagnostic ? (
<ScrollView
style={sheetStyles.codeScroll}
contentContainerStyle={sheetStyles.codeContent}
@@ -227,11 +228,12 @@ export function ProviderDiagnosticSheet({
</Text>
</ScrollView>
</ScrollView>
) : (
) : null}
{!(loading && !diagnostic) && !diagnostic ? (
<View style={sheetStyles.codeBlockLoading}>
<Text style={sheetStyles.mutedText}>No diagnostic available.</Text>
</View>
)}
) : null}
</View>
</View>

View File

@@ -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({
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : workspace.diffStat ? (
) : null}
{!(onArchive && (isHovered || isTouchPlatform)) && workspace.diffStat ? (
<DiffStat
additions={workspace.diffStat.additions}
deletions={workspace.diffStat.deletions}
@@ -1413,7 +1426,7 @@ function WorkspaceRowWithMenu({
dragHandleProps={dragHandleProps}
menuController={null}
archiveLabel={isWorktree ? "Archive worktree" : "Hide from sidebar"}
archiveStatus={isWorktree ? archiveStatus : isArchivingWorkspace ? "pending" : "idle"}
archiveStatus={getWorkspaceArchiveStatus(isWorktree, archiveStatus, isArchivingWorkspace)}
archivePendingLabel={isWorktree ? "Archiving..." : "Hiding..."}
onArchive={isWorktree ? handleArchiveWorktree : handleArchiveWorkspace}
onCopyBranchName={canCopyBranchName ? handleCopyBranchName : undefined}

View File

@@ -27,6 +27,11 @@ const restrictToHorizontalAxis: Modifier = ({ transform }) => ({
const DND_MODIFIERS: Modifier[] = [restrictToHorizontalAxis];
function computeDragOpacity(hasExternalContext: boolean, isDragging: boolean): number {
if (!isDragging) return 1;
return hasExternalContext ? 0.3 : 0.9;
}
function SortableItem<T>({
id,
item,
@@ -77,7 +82,7 @@ function SortableItem<T>({
() => ({
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],

View File

@@ -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

View File

@@ -200,17 +200,22 @@ export function ComboboxItem({
}: ComboboxItemProps): ReactElement {
const { theme } = useUnistyles();
const leadingContent = leadingSlot ? (
<View style={styles.comboboxItemLeadingSlot}>{leadingSlot}</View>
) : kind === "directory" || kind === "file" ? (
<View style={styles.comboboxItemLeadingSlot}>
{kind === "directory" ? (
let leadingContent: ReactElement | null = null;
if (leadingSlot) {
leadingContent = <View style={styles.comboboxItemLeadingSlot}>{leadingSlot}</View>;
} else if (kind === "directory") {
leadingContent = (
<View style={styles.comboboxItemLeadingSlot}>
<Folder size={16} color={theme.colors.foregroundMuted} />
) : (
</View>
);
} else if (kind === "file") {
leadingContent = (
<View style={styles.comboboxItemLeadingSlot}>
<File size={16} color={theme.colors.foregroundMuted} />
)}
</View>
) : null;
</View>
);
}
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,

View File

@@ -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],
);

View File

@@ -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 {

View File

@@ -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,
)}
</Button>
</View>
<View style={ROW_WITH_BORDER_STYLE}>
@@ -360,11 +371,11 @@ export function LocalDaemonSection() {
onPress={handleUpdateLocalDaemon}
disabled={isRestartingDaemon}
>
{isRestartingDaemon
? daemonStatus?.status === "running"
? "Restarting..."
: "Starting..."
: daemonActionLabel}
{getDaemonRestartButtonLabel(
isRestartingDaemon,
daemonStatus?.status,
daemonActionLabel,
)}
</Button>
</View>
<View style={ROW_WITH_BORDER_STYLE}>

View File

@@ -79,7 +79,8 @@ export function PairDeviceSection() {
<ActivityIndicator size="small" />
<Text style={styles.hint}>Loading pairing offer</Text>
</View>
) : pairingQuery.isError ? (
) : null}
{!pairingQuery.isPending && pairingQuery.isError ? (
<View style={styles.centered}>
<Text style={styles.hint}>
{pairingQuery.error instanceof Error
@@ -90,7 +91,8 @@ export function PairDeviceSection() {
Retry
</Button>
</View>
) : !pairingQuery.data?.url ? (
) : null}
{!pairingQuery.isPending && !pairingQuery.isError && !pairingQuery.data?.url ? (
<View style={styles.centered}>
<Text style={styles.hint}>
{pairingQuery.data?.relayEnabled === false
@@ -101,7 +103,8 @@ export function PairDeviceSection() {
Retry
</Button>
</View>
) : (
) : null}
{!pairingQuery.isPending && !pairingQuery.isError && pairingQuery.data?.url ? (
<View style={styles.content}>
<Text style={styles.hint}>
Scan this QR code with Paseo on your phone, or copy the link below.
@@ -109,11 +112,11 @@ export function PairDeviceSection() {
<View style={styles.qrContainer}>
{qrImageSource ? (
<Image source={qrImageSource} style={styles.qrImage} resizeMode="contain" />
) : qrQuery.isError ? (
) : null}
{!qrImageSource && qrQuery.isError ? (
<Text style={styles.hint}>QR code unavailable.</Text>
) : (
<ActivityIndicator size="small" />
)}
) : null}
{!qrImageSource && !qrQuery.isError ? <ActivityIndicator size="small" /> : null}
</View>
<View style={styles.linkRow}>
<View style={styles.inputWrapper}>
@@ -135,7 +138,7 @@ export function PairDeviceSection() {
</Button>
</View>
</View>
)}
) : null}
</View>
</View>
);

View File

@@ -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) {

View File

@@ -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"],

View File

@@ -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(() => {

View File

@@ -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 (
<ScrollView
@@ -196,7 +193,8 @@ function SetupPanel() {
<ActivityIndicator size="large" color={theme.colors.foregroundMuted} />
<Text style={styles.waitingText}>Setting up workspace...</Text>
</View>
) : hasNoSetupCommands ? (
) : null}
{!isWaiting && hasNoSetupCommands ? (
<View style={styles.emptyContainer}>
<Text
style={styles.emptyText}
@@ -206,7 +204,8 @@ function SetupPanel() {
No setup commands ran for this workspace.
</Text>
</View>
) : (
) : null}
{!isWaiting && !hasNoSetupCommands ? (
<View style={styles.commandList}>
{commands.map((command) => {
const isExpanded = expandedIndices.has(command.index);
@@ -275,7 +274,7 @@ function SetupPanel() {
</View>
) : null}
</View>
)}
) : null}
</ScrollView>
);
}

View File

@@ -70,14 +70,16 @@ function SessionsScreenContent({ serverId }: { serverId: string }) {
<View style={styles.loadingContainer}>
<LoadingSpinner size="large" color={theme.colors.foregroundMuted} />
</View>
) : sortedAgents.length === 0 ? (
) : null}
{!isInitialLoad && sortedAgents.length === 0 ? (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>No sessions yet</Text>
<Button variant="ghost" leftIcon={ChevronLeft} onPress={handleBack}>
Back
</Button>
</View>
) : (
) : null}
{!isInitialLoad && sortedAgents.length > 0 ? (
<AgentList
agents={sortedAgents}
showCheckoutInfo={false}
@@ -86,7 +88,7 @@ function SessionsScreenContent({ serverId }: { serverId: string }) {
listFooterComponent={listFooterComponent}
showAttentionIndicator={false}
/>
)}
) : null}
</View>
);
}

View File

@@ -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)}
</Button>
</View>
</View>

View File

@@ -140,11 +140,13 @@ export function ProvidersSection({ serverId }: ProvidersSectionProps) {
<View style={EMPTY_CARD_STYLE}>
<Text style={styles.emptyText}>Connect to this host to see providers</Text>
</View>
) : isLoading ? (
) : null}
{hasServer && isConnected && isLoading ? (
<View style={EMPTY_CARD_STYLE}>
<Text style={styles.emptyText}>Loading...</Text>
</View>
) : (
) : null}
{hasServer && isConnected && !isLoading ? (
<View style={settingsStyles.card}>
{providerDefinitions.map((def, index) => (
<ProviderRow
@@ -156,7 +158,7 @@ export function ProvidersSection({ serverId }: ProvidersSectionProps) {
/>
))}
</View>
)}
) : null}
</SettingsSection>
{diagnosticProvider ? (

View File

@@ -2183,33 +2183,41 @@ function WorkspaceScreenContent({
},
[buildPaneContentModel],
);
const content = shouldRenderMissingWorkspaceDescriptor({
const showMissingWorkspaceDescriptor = shouldRenderMissingWorkspaceDescriptor({
workspace: workspaceDescriptor,
hasHydratedWorkspaces,
}) ? (
<View style={styles.emptyState}>
<ActivityIndicator color={theme.colors.foregroundMuted} />
</View>
) : isMissingWorkspaceExecutionAuthority ? (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>
Workspace execution directory is missing. Reload workspace data before opening tabs.
</Text>
</View>
) : !activeTabDescriptor ? (
!hasHydratedAgents ? (
});
let content: React.ReactNode;
if (showMissingWorkspaceDescriptor) {
content = (
<View style={styles.emptyState}>
<ActivityIndicator color={theme.colors.foregroundMuted} />
</View>
) : (
);
} else if (isMissingWorkspaceExecutionAuthority) {
content = (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>
Workspace execution directory is missing. Reload workspace data before opening tabs.
</Text>
</View>
);
} else if (!activeTabDescriptor && !hasHydratedAgents) {
content = (
<View style={styles.emptyState}>
<ActivityIndicator color={theme.colors.foregroundMuted} />
</View>
);
} else if (!activeTabDescriptor) {
content = (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>
No tabs are available yet. Use New tab to create an agent or terminal.
</Text>
</View>
)
) : (
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 }) {

View File

@@ -216,11 +216,10 @@ export const useDownloadStore = create<DownloadState>()((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 };
});
},