Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/mobile/src/app/automation/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {
ScrollView,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Text } from "@/components/text";
import { TaskAutomationValidationError } from "@/features/tasks/api";
import { AutomationForm } from "@/features/tasks/components/AutomationForm";
import { useCreateTaskAutomation } from "@/features/tasks/hooks/useAutomations";
import { useSkillStoreSkill } from "@/features/tasks/skills/hooks";
import { formatSkillTemplateId } from "@/features/tasks/skills/skillTemplateIds";
import { useScreenInsets } from "@/hooks/useScreenInsets";
import { useThemeColors } from "@/lib/theme";

// Reserved space below the scrolling form content. Tall enough that the
Expand All @@ -35,7 +35,7 @@ export default function CreateAutomationScreen() {
: skillNameParam;
const router = useRouter();
const themeColors = useThemeColors();
const insets = useSafeAreaInsets();
const { insets, bottom } = useScreenInsets();
const createAutomation = useCreateTaskAutomation();
const defaultTimezone = useMemo(
() => getCalendars()[0]?.timeZone ?? "UTC",
Expand Down Expand Up @@ -209,7 +209,7 @@ export default function CreateAutomationScreen() {
{formMounted && (
<View
className="absolute inset-x-0 bottom-0 border-gray-6 border-t bg-background px-4 pt-3"
style={{ paddingBottom: insets.bottom + 12 }}
style={{ paddingBottom: bottom("compact") }}
>
<Pressable
onPress={handleCreate}
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/app/mcp-servers/add-custom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import {
TextInput,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { FloatingMcpHeader } from "@/features/mcp/components/FloatingMcpHeader";
import { useMcpInstallations } from "@/features/mcp/hooks";
import { installCustomWithOAuth } from "@/features/mcp/oauth";
import type { McpAuthType } from "@/features/mcp/types";
import { useScreenInsets } from "@/hooks/useScreenInsets";
import { logger } from "@/lib/logger";
import { useThemeColors } from "@/lib/theme";

Expand All @@ -29,7 +29,7 @@ const AUTH_OPTIONS: { value: McpAuthType; label: string }[] = [

export default function AddCustomMcpServerScreen() {
const themeColors = useThemeColors();
const insets = useSafeAreaInsets();
const { insets, bottom } = useScreenInsets();
const installations = useMcpInstallations();

const [name, setName] = useState("");
Expand Down Expand Up @@ -85,7 +85,7 @@ export default function AddCustomMcpServerScreen() {
className="flex-1"
contentContainerStyle={{
paddingTop: insets.top + 60,
paddingBottom: insets.bottom + 24,
paddingBottom: bottom("default"),
paddingHorizontal: 16,
}}
keyboardShouldPersistTaps="handled"
Expand Down
8 changes: 4 additions & 4 deletions apps/mobile/src/app/mcp-servers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
TextInput,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { FloatingMcpHeader } from "@/features/mcp/components/FloatingMcpHeader";
import {
installationToRowProps,
Expand All @@ -22,13 +21,14 @@ import type {
McpRecommendedServer,
McpServerInstallation,
} from "@/features/mcp/types";
import { useScreenInsets } from "@/hooks/useScreenInsets";
import { useThemeColors } from "@/lib/theme";

type Tab = "installed" | "marketplace";

export default function McpServersScreen() {
const themeColors = useThemeColors();
const insets = useSafeAreaInsets();
const { insets, bottom } = useScreenInsets();
const router = useRouter();
const [tab, setTab] = useState<Tab>("installed");
const [search, setSearch] = useState("");
Expand Down Expand Up @@ -149,7 +149,7 @@ export default function McpServersScreen() {
tintColor={themeColors.accent[9]}
/>
}
contentContainerStyle={{ paddingBottom: insets.bottom + 24 }}
contentContainerStyle={{ paddingBottom: bottom("default") }}
/>
) : (
<FlatList
Expand All @@ -172,7 +172,7 @@ export default function McpServersScreen() {
tintColor={themeColors.accent[9]}
/>
}
contentContainerStyle={{ paddingBottom: insets.bottom + 24 }}
contentContainerStyle={{ paddingBottom: bottom("default") }}
/>
)}
</View>
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/app/mcp-servers/installation/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
Switch,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { FloatingMcpHeader } from "@/features/mcp/components/FloatingMcpHeader";
import { ServerIcon } from "@/features/mcp/components/ServerIcon";
import {
Expand All @@ -31,6 +30,7 @@ import { reauthorizeInstallation } from "@/features/mcp/oauth";
import { getMcpConnectionManager } from "@/features/mcp/service";
import type { McpApprovalState } from "@/features/mcp/types";
import { isStdioServer } from "@/features/mcp/types";
import { useScreenInsets } from "@/hooks/useScreenInsets";
import { logger } from "@/lib/logger";
import { useThemeColors } from "@/lib/theme";

Expand All @@ -39,7 +39,7 @@ const log = logger.scope("mcp-installation-detail");
export default function McpInstallationDetailScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const themeColors = useThemeColors();
const insets = useSafeAreaInsets();
const { insets, bottom } = useScreenInsets();

const installations = useMcpInstallations();
const installation = useMemo(
Expand Down Expand Up @@ -142,7 +142,7 @@ export default function McpInstallationDetailScreen() {
className="flex-1"
contentContainerStyle={{
paddingTop: insets.top + 60,
paddingBottom: insets.bottom + 24,
paddingBottom: bottom("default"),
paddingHorizontal: 16,
}}
refreshControl={
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/app/mcp-servers/template/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
TextInput,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { FloatingMcpHeader } from "@/features/mcp/components/FloatingMcpHeader";
import { ServerIcon } from "@/features/mcp/components/ServerIcon";
import {
Expand All @@ -20,6 +19,7 @@ import {
} from "@/features/mcp/hooks";
import { installTemplateWithOAuth } from "@/features/mcp/oauth";
import { isStdioServer } from "@/features/mcp/types";
import { useScreenInsets } from "@/hooks/useScreenInsets";
import { logger } from "@/lib/logger";
import { useThemeColors } from "@/lib/theme";

Expand All @@ -28,7 +28,7 @@ const log = logger.scope("mcp-template-detail");
export default function McpTemplateDetailScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const themeColors = useThemeColors();
const insets = useSafeAreaInsets();
const { insets, bottom } = useScreenInsets();

const marketplace = useMcpMarketplace();
const installations = useMcpInstallations();
Expand Down Expand Up @@ -116,7 +116,7 @@ export default function McpTemplateDetailScreen() {
className="flex-1"
contentContainerStyle={{
paddingTop: insets.top + 60,
paddingBottom: insets.bottom + 24,
paddingBottom: bottom("default"),
paddingHorizontal: 16,
}}
>
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/app/pr-diff.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Text } from "@components/text";
import { useLocalSearchParams } from "expo-router";
import { ActivityIndicator, FlatList, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { FileDiff } from "@/features/tasks/components/FileDiff";
import {
type ChangedFile,
usePrChangedFiles,
} from "@/features/tasks/hooks/usePrChangedFiles";
import { useScreenInsets } from "@/hooks/useScreenInsets";
import { useThemeColors } from "@/lib/theme";

export default function PrDiffScreen() {
const { prUrl } = useLocalSearchParams<{ prUrl?: string }>();
const themeColors = useThemeColors();
const insets = useSafeAreaInsets();
const { bottom } = useScreenInsets();

const { data: files, isLoading } = usePrChangedFiles(prUrl ?? null);

Expand Down Expand Up @@ -47,7 +47,7 @@ export default function PrDiffScreen() {
contentContainerStyle={{
paddingHorizontal: 12,
paddingTop: 8,
paddingBottom: insets.bottom + 16,
paddingBottom: bottom("default"),
}}
ListHeaderComponent={
<View className="mb-2 flex-row items-center justify-between px-1 py-1">
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/app/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { router } from "expo-router";
import { ArrowSquareOut, CaretRight, SpeakerHigh } from "phosphor-react-native";
import { useState } from "react";
import { Linking, Pressable, ScrollView, Switch, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useAuthStore, useProjectsQuery, useUserQuery } from "@/features/auth";
import { useDismissedReportsStore } from "@/features/inbox/stores/dismissedReportsStore";
import { usePushTokenStore } from "@/features/notifications/stores/pushTokenStore";
Expand All @@ -19,6 +18,7 @@ import { SettingsRow } from "@/features/settings/components/SettingsRow";
import { SettingsSection } from "@/features/settings/components/SettingsSection";
import { SelectSheet } from "@/features/tasks/composer/SelectSheet";
import { playCompletionSound } from "@/features/tasks/utils/sounds";
import { useScreenInsets } from "@/hooks/useScreenInsets";
import { logger } from "@/lib/logger";
import { useThemeColors } from "@/lib/theme";

Expand Down Expand Up @@ -79,7 +79,7 @@ function taskModeLabel(mode: InitialTaskMode): string {

export default function SettingsScreen() {
const themeColors = useThemeColors();
const insets = useSafeAreaInsets();
const { insets, bottom } = useScreenInsets();

const {
logout,
Expand Down Expand Up @@ -170,7 +170,7 @@ export default function SettingsScreen() {
// padding clears the home indicator and gives breathing room past the last
// row so it never hides behind it.
const contentPaddingTop = insets.top + 60;
const contentPaddingBottom = insets.bottom + 32;
const contentPaddingBottom = bottom("default");

return (
<View className="flex-1 bg-background">
Expand Down
8 changes: 4 additions & 4 deletions apps/mobile/src/app/task/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
} from "react-native";
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
import Animated, { useAnimatedStyle } from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { FloatingBackButton } from "@/components/FloatingBackButton";
import { getTask, runTaskInCloud } from "@/features/tasks/api";
import { FloatingTaskHeader } from "@/features/tasks/components/FloatingTaskHeader";
Expand All @@ -36,6 +35,7 @@ import { useTaskSessionStore } from "@/features/tasks/stores/taskSessionStore";
import { useTaskStore } from "@/features/tasks/stores/taskStore";
import type { Task } from "@/features/tasks/types";
import { getSessionActivityPhase } from "@/features/tasks/utils/sessionActivity";
import { useScreenInsets } from "@/hooks/useScreenInsets";
import { logger } from "@/lib/logger";
import { useThemeColors } from "@/lib/theme";

Expand All @@ -59,7 +59,7 @@ export default function TaskDetailScreen() {
}>();
const router = useRouter();
const queryClient = useQueryClient();
const insets = useSafeAreaInsets();
const { insets, composerBottom } = useScreenInsets();
const themeColors = useThemeColors();
Comment on lines +62 to 63
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 composerBottom is a plain JS function from useMemo, not a Reanimated worklet. useAnimatedStyle callbacks that read a SharedValue (height.value) are serialized and executed on the UI thread, where calling non-worklet JS functions throws a runtime error. The sibling file task/index.tsx avoids this correctly by pre-computing restingBottom = bottom("compact") outside the animated style and capturing the primitive number in the worklet closure. The same pattern should be applied here.

Suggested change
const { insets, composerBottom } = useScreenInsets();
const themeColors = useThemeColors();
const { insets, composerBottom } = useScreenInsets();
const composerBottomValue = composerBottom();
const themeColors = useThemeColors();
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/mobile/src/app/task/[id].tsx
Line: 62-63

Comment:
`composerBottom` is a plain JS function from `useMemo`, not a Reanimated worklet. `useAnimatedStyle` callbacks that read a `SharedValue` (`height.value`) are serialized and executed on the UI thread, where calling non-worklet JS functions throws a runtime error. The sibling file `task/index.tsx` avoids this correctly by pre-computing `restingBottom = bottom("compact")` outside the animated style and capturing the primitive number in the worklet closure. The same pattern should be applied here.

```suggestion
  const { insets, composerBottom } = useScreenInsets();
  const composerBottomValue = composerBottom();
  const themeColors = useThemeColors();
```

How can I resolve this? If you propose a fix, please make it concise.

const [task, setTask] = useState<Task | null>(null);
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -123,9 +123,9 @@ export default function TaskDetailScreen() {
// height, so the composer sits at the keyboard top — no extra gap needed
// when open. Closed state keeps a comfortable bottom inset.
return {
marginBottom: height.value < 0 ? 0 : Math.max(insets.bottom, 50),
marginBottom: height.value < 0 ? 0 : composerBottom(),
};
}, [insets.bottom]);
}, [composerBottom]);
Comment on lines 125 to +128
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Once the value is pre-computed outside the worklet, use the scalar directly and update the dependency array accordingly.

Suggested change
return {
marginBottom: height.value < 0 ? 0 : Math.max(insets.bottom, 50),
marginBottom: height.value < 0 ? 0 : composerBottom(),
};
}, [insets.bottom]);
}, [composerBottom]);
return {
marginBottom: height.value < 0 ? 0 : composerBottomValue,
};
}, [composerBottomValue]);
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/mobile/src/app/task/[id].tsx
Line: 125-128

Comment:
Once the value is pre-computed outside the worklet, use the scalar directly and update the dependency array accordingly.

```suggestion
    return {
      marginBottom: height.value < 0 ? 0 : composerBottomValue,
    };
  }, [composerBottomValue]);
```

How can I resolve this? If you propose a fix, please make it concise.


useEffect(() => {
if (!taskId) return;
Expand Down
7 changes: 3 additions & 4 deletions apps/mobile/src/app/task/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ import {
useReanimatedKeyboardAnimation,
} from "react-native-keyboard-controller";
import Animated, { runOnJS, useAnimatedStyle } from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";

import { useVoiceRecording } from "@/features/chat";
import { usePreferencesStore } from "@/features/preferences/stores/preferencesStore";
import { createTask, runTaskInCloud } from "@/features/tasks/api";
Expand Down Expand Up @@ -74,6 +72,7 @@ import {
isRepositorySelectionComplete,
toRepositorySelection,
} from "@/features/tasks/utils/repositorySelection";
import { useScreenInsets } from "@/hooks/useScreenInsets";
import { logger } from "@/lib/logger";
import { toRgba, useThemeColors } from "@/lib/theme";

Expand Down Expand Up @@ -110,9 +109,9 @@ export default function NewTaskScreen() {
}>();
const router = useRouter();
const themeColors = useThemeColors();
const insets = useSafeAreaInsets();
const { insets, bottom } = useScreenInsets();
const keyboard = useReanimatedKeyboardAnimation();
const restingBottom = insets.bottom + 12;
const restingBottom = bottom("compact");
const {
error,
hasGithubIntegration,
Expand Down
66 changes: 66 additions & 0 deletions apps/mobile/src/components/SheetContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { ReactNode } from "react";
import { Modal, Pressable, View } from "react-native";
import {
type BottomGapVariant,
useScreenInsets,
} from "@/hooks/useScreenInsets";

const SHEET_SHADOW = {
shadowColor: "#000",
shadowOpacity: 0.15,
shadowRadius: 20,
shadowOffset: { width: 0, height: -4 },
elevation: 12,
} as const;

interface SheetContainerProps {
open: boolean;
onClose: () => void;
children: ReactNode;
/** Bottom padding gap above the safe-area inset. Defaults to "compact". */
bottomGap?: BottomGapVariant;
/** Extra classes for the sheet panel. */
className?: string;
}

/**
* Bottom-sheet shell: a dimmed backdrop, a panel pinned to the bottom edge with
* the standard rounded top, border, shadow, drag handle, and safe-area-aware
* bottom padding. Tapping the backdrop closes; taps inside the panel don't.
*
* Use this instead of re-implementing the `mt-auto rounded-t-2xl …` markup so
* every sheet shares one shape and one inset policy.
*/
export function SheetContainer({
open,
onClose,
children,
bottomGap = "compact",
className = "",
}: SheetContainerProps) {
const { bottom } = useScreenInsets();

return (
<Modal
visible={open}
transparent
animationType="slide"
onRequestClose={onClose}
statusBarTranslucent
>
<Pressable className="flex-1 bg-black/40" onPress={onClose}>
<Pressable
onPress={() => {}}
className={`mt-auto rounded-t-2xl border-gray-6 border-t bg-background ${className}`}
style={{ paddingBottom: bottom(bottomGap), ...SHEET_SHADOW }}
>
{/* Drag handle */}
<View className="items-center pt-2 pb-1">
<View className="h-1 w-10 rounded-full bg-gray-6" />
</View>
{children}
</Pressable>
</Pressable>
</Modal>
);
}
Loading
Loading