Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ec7a60b
fix(mobile): keep composer anchored after keyboard dismissal
juliusmarminge Aug 10, 2026
497bd8c
fix(mobile): keep live follow off during user scroll
juliusmarminge Aug 10, 2026
c78d5e0
fix(mobile): ignore programmatic momentum end
juliusmarminge Aug 10, 2026
6495a84
fix(mobile): settle follow after native momentum
juliusmarminge Aug 10, 2026
884cd7d
fix(mobile): preserve follow at drag release
juliusmarminge Aug 10, 2026
b70fee6
fix(mobile): handle long multi-select user input forms
thuongtin Jul 31, 2026
5bada3f
fix(mobile): normalize user input option labels
thuongtin Jul 31, 2026
523bf34
fix(mobile): keep pending input above keyboard
juliusmarminge Aug 10, 2026
b3823ec
fix(mobile): keep pending input actions usable
juliusmarminge Aug 10, 2026
e97b090
feat(mobile): add scroll-to-end button
Kapish14 Aug 9, 2026
8f308a2
revert(mobile): drop follow-state review changes
Kapish14 Aug 10, 2026
73ae4c5
fix(mobile): keep native header buttons stable across updates
juliusmarminge Aug 10, 2026
75748bc
fix(mobile): stabilize settings sheet navigation and updates
juliusmarminge Aug 10, 2026
2cf4d8c
fix rns shit
juliusmarminge Aug 10, 2026
71d5d34
feat(mobile): native menu for thread settings on iOS
juliusmarminge Aug 10, 2026
834d994
fix(mobile): submenu subtitles and multi-pick settings menu
juliusmarminge Aug 10, 2026
3b37c70
fix(mobile): pass keepsMenuPresented through the menu codegen bridge
juliusmarminge Aug 10, 2026
cfaf85d
fix(mobile): drop menu willDisplay override that broke button chrome
juliusmarminge Aug 10, 2026
68a314e
fix(mobile): refresh the visible menu level instead of replacing it
juliusmarminge Aug 10, 2026
825ad66
fix(mobile): close settings menu on nested picks
juliusmarminge Aug 10, 2026
f8d0186
feat(mobile): flag nested settings picks keep-presented for UX compar…
juliusmarminge Aug 10, 2026
2f3bd0a
feat(mobile): drop All Settings escape hatch from the thread menu
juliusmarminge Aug 10, 2026
544e150
feat(mobile): settle on close-on-pick for nested settings menu picks
juliusmarminge Aug 10, 2026
5467150
style(mobile): shrink scroll-to-end button and tuck it near the composer
juliusmarminge Aug 10, 2026
1092d7e
style(mobile): glass scroll-to-end button hugging the composer
juliusmarminge Aug 10, 2026
6b4b5d4
style(mobile): center the chevron inside the glass scroll button
juliusmarminge Aug 10, 2026
cefa7c6
feat(mobile): collapsible questionnaire that replaces the composer
juliusmarminge Aug 10, 2026
dd0cd4f
feat(mobile): questionnaire owns the composer slot in both states
juliusmarminge Aug 10, 2026
b79c450
fix(mobile): crossfade the questionnaire bar-card swap instead of mor…
juliusmarminge Aug 10, 2026
5e57140
fix(mobile): track keyboard animation for the questionnaire height
juliusmarminge Aug 10, 2026
8b96d4a
fix(mobile): reserve keyboard space so the questionnaire never resize…
juliusmarminge Aug 10, 2026
cd9081a
fix(mobile): keep the feed still during questionnaire collapse and ex…
juliusmarminge Aug 10, 2026
eea0156
feat(mobile): keep the end of the chat visible above the expanded que…
juliusmarminge Aug 10, 2026
278ac96
chore(mobile): bump @legendapp/list to 3.3.5
juliusmarminge Aug 10, 2026
faa634c
fix(mobile): snappier questionnaire toggle with deterministic end pin…
juliusmarminge Aug 10, 2026
755add8
fix(mobile): quarantine stale keyboard state after Android resume
juliusmarminge Aug 11, 2026
ce5df72
fix(mobile): in-flow questionnaire card on Android
juliusmarminge Aug 11, 2026
a12d5e6
fix(mobile): keep the Android new-thread composer above the keyboard
juliusmarminge Aug 11, 2026
008d6fa
Merge origin/main into fix-rn-floating-composer-position
juliusmarminge Aug 11, 2026
9e995ae
chore: move @legendapp/list to the pnpm catalog at 3.3.5
juliusmarminge Aug 11, 2026
12f2d97
fix(mobile): restore focus-keyed composer inset on iOS
juliusmarminge Aug 11, 2026
17695d7
perf(mobile): shared-value questionnaire toggle on the UI thread
juliusmarminge Aug 11, 2026
dcf7957
fix(mobile): scope thread feed reset identity by environment
juliusmarminge Aug 11, 2026
17b3820
fix(mobile): harden RNS header item reuse cache and index guards
juliusmarminge Aug 11, 2026
b068fe5
fix(mobile): slide questionnaire collapse behind the bar, not crossfade
juliusmarminge Aug 11, 2026
89675f2
fix(mobile): reuse mail-search toolbar across header updates
juliusmarminge Aug 11, 2026
401afe7
Delete docs/user/threads.md
juliusmarminge Aug 11, 2026
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
2 changes: 1 addition & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@expo-google-fonts/dm-sans": "^0.4.2",
"@expo/metro-runtime": "~56.0.15",
"@expo/ui": "~56.0.18",
"@legendapp/list": "3.3.3",
"@legendapp/list": "catalog:",
"@noble/curves": "catalog:",
"@noble/hashes": "catalog:",
"@pierre/diffs": "catalog:",
Expand Down
33 changes: 20 additions & 13 deletions apps/mobile/src/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { AppText as Text } from "./components/AppText";
import { getCompactBrandHeaderOptions } from "./components/CompactBrandTitle";
import { ArchivedThreadsRouteScreen } from "./features/archive/ArchivedThreadsRouteScreen";
import { useAgentNotificationNavigation } from "./features/agent-awareness/notificationNavigation";
import { ClerkSettingsSheetDetentProvider } from "./features/cloud/ClerkSettingsSheetDetent";
import { ConnectOnboardingRouteScreen } from "./features/cloud/ConnectOnboardingRouteScreen";
import { useConnectOnboardingNavigation } from "./features/cloud/connectOnboardingNavigation";
import { ThreadFilesTreeScreen, ThreadFileScreen } from "./features/files/ThreadFilesRouteScreen";
Expand Down Expand Up @@ -134,7 +133,7 @@ const LEGAL_DOCUMENT_HEADER_OPTIONS: AppScreenOptions = {
presentation: "fullScreenModal",
};

const SettingsSheetStack = createNativeStackNavigator({
const SettingsContentStack = createNativeStackNavigator({
initialRouteName: "Settings",
screenOptions: {
...GLASS_HEADER_OPTIONS,
Expand Down Expand Up @@ -198,20 +197,30 @@ const SettingsSheetStack = createNativeStackNavigator({
title: "Usage",
},
}),
},
});

// The outer stack never owns visible chrome. Settings routes render inside a
// nested stack whose native header remains mounted, while Clerk owns auth chrome.
// Keeping bar visibility invariant avoids iOS 26's headerless-to-headered jump.
const SettingsSheetStack = createNativeStackNavigator({
initialRouteName: "SettingsContent",
screenOptions: {
headerShown: false,
},
screens: {
SettingsContent: createNativeStackScreen({
screen: SettingsContentStack,
linking: "",
}),
SettingsAuth: createNativeStackScreen({
screen: SettingsAuthRouteScreen,
linking: "auth",
options: {
title: "Sign in",
},
}),
SettingsWaitlist: createNativeStackScreen({
// Keep the old deep link working after the Connect GA launch.
screen: SettingsAuthRouteScreen,
linking: "waitlist",
options: {
title: "Sign in",
},
}),
},
});
Expand Down Expand Up @@ -347,11 +356,9 @@ function RootStackLayout(props: {
<HardwareKeyboardCommandProvider pathname={pathname}>
<ThreadOutboxDrainWorker />
<ShowcaseCaptureCoordinator pathname={pathname} />
<ClerkSettingsSheetDetentProvider initiallyExpanded={false}>
<AdaptiveWorkspaceLayout pathname={workspacePathname}>
{props.children}
</AdaptiveWorkspaceLayout>
</ClerkSettingsSheetDetentProvider>
<AdaptiveWorkspaceLayout pathname={workspacePathname}>
{props.children}
</AdaptiveWorkspaceLayout>
</HardwareKeyboardCommandProvider>
);
}
Expand Down
27 changes: 26 additions & 1 deletion apps/mobile/src/components/ControlPill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type ComponentProps,
type ReactElement,
type ReactNode,
useRef,
} from "react";
import { Platform, Pressable, useColorScheme, View } from "react-native";
import { useThemeColor } from "../lib/useThemeColor";
Expand All @@ -21,10 +22,31 @@ export function ControlPill(props: {
readonly label?: string;
readonly accessibilityLabel?: string;
readonly onPress?: () => void;
readonly activateOnPressIn?: boolean;
readonly variant?: "circle" | "pill" | "primary" | "danger";
readonly disabled?: boolean;
readonly className?: string;
}) {
const variant = props.variant ?? "circle";
const activatedOnPressInRef = useRef(false);

const handlePressIn = () => {
activatedOnPressInRef.current = true;
props.onPress?.();
};
const handlePressOut = () => {
// Pressability invokes onPressOut immediately before onPress on release.
// Defer the reset so onPress can identify the same physical gesture.
setTimeout(() => {
activatedOnPressInRef.current = false;
}, 0);
};
const handlePress = () => {
if (activatedOnPressInRef.current) {
return;
}
props.onPress?.();
};

const iconColor = useThemeColor("--color-icon");
const iconSubtle = useThemeColor("--color-icon-subtle");
Expand Down Expand Up @@ -54,6 +76,7 @@ export function ControlPill(props: {
: variant === "danger"
? "bg-danger"
: "bg-subtle",
props.className,
);
const labelClassName = cn(
"text-center text-xs font-t3-bold",
Expand All @@ -68,7 +91,9 @@ export function ControlPill(props: {
<Pressable
accessibilityLabel={props.accessibilityLabel ?? props.label}
accessibilityRole="button"
onPress={props.onPress}
onPress={props.activateOnPressIn ? handlePress : props.onPress}
onPressIn={props.activateOnPressIn ? handlePressIn : undefined}
onPressOut={props.activateOnPressIn ? handlePressOut : undefined}
disabled={props.disabled}
className={containerClassName}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useFocusEffect } from "@react-navigation/native";
import { useCallback, useMemo, useState } from "react";

import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
import { useClerkSettingsSheetDetent } from "../cloud/ClerkSettingsSheetDetent";
import { useArchivedThreadListActions } from "../home/useThreadListActions";
import {
ArchivedThreadsScreen,
Expand All @@ -18,7 +17,6 @@ import {
} from "./useArchivedThreadSnapshots";

export function ArchivedThreadsRouteScreen() {
const { expand } = useClerkSettingsSheetDetent();
const { savedConnectionsById } = useSavedRemoteConnections();
const [searchQuery, setSearchQuery] = useState("");
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<EnvironmentId | null>(null);
Expand Down Expand Up @@ -70,9 +68,8 @@ export function ArchivedThreadsRouteScreen() {

useFocusEffect(
useCallback(() => {
expand();
refresh();
}, [expand, refresh]),
}, [refresh]),
);

return (
Expand Down
44 changes: 0 additions & 44 deletions apps/mobile/src/features/cloud/ClerkSettingsSheetDetent.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions apps/mobile/src/features/cloud/connectOnboardingNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { appAtomRegistry } from "../../state/atom-registry";
import { clearConnectOnboardingRequest, connectOnboardingRequestAtom } from "./connectOnboarding";
import { isConnectOnboardingOptedOut } from "./connectOnboardingOptOut";

// Sign-in happens inside the Settings sheet; give its detent/session-state
// transitions a beat to settle before presenting another formSheet on top.
// Sign-in happens inside the Settings sheet; give its session-state transition
// a beat to settle before presenting another formSheet on top.
const PRESENT_ONBOARDING_DELAY_MS = 600;

/**
Expand Down
29 changes: 24 additions & 5 deletions apps/mobile/src/features/home/HomeRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ export function HomeRouteScreen() {
<NativeStackScreenOptions
options={getConnectionAwareBrandHeaderOptions({
onOpenEnvironments: () =>
navigation.navigate("SettingsSheet", { screen: "SettingsEnvironments" }),
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
params: { screen: "SettingsEnvironments" },
}),
})}
/>
<HomeHeader
Expand All @@ -148,9 +151,17 @@ export function HomeRouteScreen() {
onEnvironmentChange={setSelectedEnvironmentId}
onProjectChange={setSelectedProjectKey}
onOpenEnvironments={() =>
navigation.navigate("SettingsSheet", { screen: "SettingsEnvironments" })
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
params: { screen: "SettingsEnvironments" },
})
}
onOpenSettings={() =>
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
params: { screen: "Settings" },
})
}
onOpenSettings={() => navigation.navigate("SettingsSheet", { screen: "Settings" })}
onProjectSortOrderChange={setProjectSortOrder}
onSearchQueryChange={setSearchQuery}
onStartNewTask={() => navigation.navigate("NewTaskSheet", { screen: "NewTask" })}
Expand All @@ -161,7 +172,10 @@ export function HomeRouteScreen() {
catalogState={catalogState}
environments={environments}
onAddConnection={() =>
navigation.navigate("SettingsSheet", { screen: "SettingsEnvironmentNew" })
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
params: { screen: "SettingsEnvironmentNew" },
})
}
onArchiveThread={archiveThread}
onDeleteThread={confirmDeleteThread}
Expand All @@ -174,7 +188,12 @@ export function HomeRouteScreen() {
onMovePinnedThread={movePinnedThread}
onEnvironmentChange={setSelectedEnvironmentId}
onProjectChange={setSelectedProjectKey}
onOpenSettings={() => navigation.navigate("SettingsSheet", { screen: "Settings" })}
onOpenSettings={() =>
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
params: { screen: "Settings" },
})
}
onProjectSortOrderChange={setProjectSortOrder}
onSearchQueryChange={setSearchQuery}
onSelectThread={(thread) => {
Expand Down
10 changes: 8 additions & 2 deletions apps/mobile/src/features/layout/AdaptiveWorkspaceLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,19 @@ function AdaptiveWorkspaceLayoutContent(
);

const handleOpenSettings = useCallback(() => {
navigation.navigate("SettingsSheet", { screen: "Settings" });
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
params: { screen: "Settings" },
});
}, [navigation]);

// Minted here (root stack navigation) so the sidebar pane stays free of
// navigation hooks — on iOS it renders inside an independent nav tree.
const handleOpenEnvironmentSettings = useCallback(() => {
navigation.navigate("SettingsSheet", { screen: "SettingsEnvironments" });
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
params: { screen: "SettingsEnvironments" },
});
}, [navigation]);

const handleNewThreadInProject = useCallback(
Expand Down
33 changes: 16 additions & 17 deletions apps/mobile/src/features/settings/SettingsAuthRouteScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { useAuth } from "@clerk/expo";
import { AuthView, UserProfileView } from "@clerk/expo/native";
import { StackActions, useNavigation } from "@react-navigation/native";
import { NativeStackScreenOptions } from "../../native/StackHeader";
import { useCallback, useEffect } from "react";
import { useCallback, useLayoutEffect } from "react";
import { View } from "react-native";

import { hasCloudPublicConfig } from "../cloud/publicConfig";

export function SettingsAuthRouteScreen() {
const navigation = useNavigation();

useEffect(() => {
useLayoutEffect(() => {
if (!hasCloudPublicConfig()) {
navigation.dispatch(StackActions.replace("Settings"));
navigation.dispatch(StackActions.replace("SettingsContent"));
}
}, [navigation]);

Expand All @@ -22,20 +21,20 @@ export function SettingsAuthRouteScreen() {
function ConfiguredSettingsAuthRouteScreen() {
const { isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false });
const navigation = useNavigation();
const handleHostBack = useCallback(() => navigation.goBack(), [navigation]);
const handleHostBack = useCallback(
() => navigation.dispatch(StackActions.popTo("SettingsContent")),
[navigation],
);

return (
<>
<NativeStackScreenOptions options={{ headerShown: false }} />
<View collapsable={false} className="flex-1 overflow-hidden bg-sheet">
{isLoaded ? (
isSignedIn ? (
<UserProfileView isDismissible={false} onHostBack={handleHostBack} />
) : (
<AuthView isDismissible={false} onHostBack={handleHostBack} />
)
) : null}
</View>
</>
<View collapsable={false} className="flex-1 overflow-hidden bg-sheet">
{isLoaded ? (
isSignedIn ? (
<UserProfileView isDismissible={false} onHostBack={handleHostBack} />
) : (
<AuthView isDismissible={false} onHostBack={handleHostBack} />
)
) : null}
</View>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ export function SettingsEnvironmentsRouteScreen() {
accessibilityLabel: "Add environment",
icon: "plus",
onPress: () =>
navigation.navigate("SettingsSheet", { screen: "SettingsEnvironmentNew" }),
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
params: { screen: "SettingsEnvironmentNew" },
}),
},
]}
/>
Expand All @@ -108,7 +111,10 @@ export function SettingsEnvironmentsRouteScreen() {
<NativeHeaderToolbar.Button
icon="plus"
onPress={() =>
navigation.navigate("SettingsSheet", { screen: "SettingsEnvironmentNew" })
navigation.navigate("SettingsSheet", {
screen: "SettingsContent",
params: { screen: "SettingsEnvironmentNew" },
})
}
separateBackground
tintColor={headerIconColor}
Expand Down
Loading
Loading