567 lines · 21.1 KB · TypeScript
RawDownload
import {  createPathConfigForStaticNavigation,  getPathFromState,  NavigationState,  StackActions,  useNavigation,} from "@react-navigation/native";import {  createNativeStackNavigator,  createNativeStackScreen,  type NativeStackNavigationOptions,} from "@react-navigation/native-stack";import { useEffect, useRef } from "react";import { DynamicColorIOS, Platform, Pressable, ScrollView, StyleSheet } from "react-native";import { useResolveClassNames } from "uniwind"; import { AppText as Text } from "./components/AppText";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";import { AdaptiveWorkspaceLayout } from "./features/layout/AdaptiveWorkspaceLayout";import { HardwareKeyboardCommandProvider } from "./features/keyboard/HardwareKeyboardCommandProvider";import { ReviewCommentComposerSheet } from "./features/review/ReviewCommentComposerSheet";import { ReviewSheet } from "./features/review/ReviewSheet";import { ThreadTerminalRouteScreen } from "./features/terminal/ThreadTerminalRouteScreen";import { GitBranchesSheet } from "./features/threads/git/GitBranchesSheet";import { GitCommitSheet } from "./features/threads/git/GitCommitSheet";import { GitConfirmSheet } from "./features/threads/git/GitConfirmSheet";import { GitOverviewSheet } from "./features/threads/git/GitOverviewSheet";import { ThreadRouteScreen } from "./features/threads/ThreadRouteScreen";import { ConnectionsRouteScreen } from "./features/connection/ConnectionsRouteScreen";import { ConnectionsNewRouteScreen } from "./features/connection/ConnectionsNewRouteScreen";import { HomeRouteScreen } from "./features/home/HomeRouteScreen";import { AddProjectDestinationRoute } from "./features/projects/AddProjectDestinationRoute";import { AddProjectLocalRoute } from "./features/projects/AddProjectLocalRoute";import { AddProjectRepositoryRoute } from "./features/projects/AddProjectRepositoryRoute";import { AddProjectSourceRoute } from "./features/projects/AddProjectSourceRoute";import { NewTaskDraftRouteScreen } from "./features/threads/NewTaskDraftRouteScreen";import { NewTaskFlowProvider } from "./features/threads/new-task-flow-provider";import { NewTaskRouteScreen } from "./features/threads/NewTaskRouteScreen";import { SettingsAppearanceRouteScreen } from "./features/settings/SettingsAppearanceRouteScreen";import { SettingsClientStorageRouteScreen } from "./features/settings/SettingsClientStorageRouteScreen";import { SettingsAuthRouteScreen } from "./features/settings/SettingsAuthRouteScreen";import { SettingsEnvironmentsRouteScreen } from "./features/settings/SettingsEnvironmentsRouteScreen";import { SettingsLegalRouteScreen } from "./features/settings/SettingsLegalRouteScreen";import { SettingsRouteScreen } from "./features/settings/SettingsRouteScreen";import { SettingsWaitlistRouteScreen } from "./features/settings/SettingsWaitlistRouteScreen";import { ShowcaseCaptureCoordinator } from "./features/showcase/ShowcaseCaptureCoordinator";import {  SettingsLegalDocumentCloseHeaderButton,  SettingsLegalDocumentExternalHeaderButton,} from "./features/settings/components/SettingsLegalDocumentRouteScreen";import { useAppShortcuts } from "./features/shortcuts/useAppShortcuts";import { useIncomingShare } from "./features/sharing/IncomingShareProvider";import {  EMPTY_INCOMING_SHARE_PRESENTATION_STATE,  transitionIncomingSharePresentation,} from "./features/sharing/incoming-share-presentation";import { NATIVE_LIQUID_GLASS_SUPPORTED } from "./native/native-glass";import { nativeHeaderScrollEdgeEffects } from "./native/StackHeader";import { useThreadOutboxDrain } from "./state/use-thread-outbox-drain"; const HEADER_SCROLL_EDGE_EFFECTS = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version); // Matches --color-sheet in global.css (light/dark). DynamicColorIOS lets the header// background stay STATIC config while still adapting to appearance changes.const SHEET_BACKGROUND_COLOR =  Platform.OS === "ios"    ? DynamicColorIOS({ light: "rgba(242, 242, 247, 0.98)", dark: "rgba(14, 14, 14, 0.98)" })    : undefined; type AppScreenOptions = NativeStackNavigationOptions & {  readonly unstable_navigationItemStyle?: "editor";}; // Shared header presets. Screens only override genuinely dynamic values (titles,// subtitles, toolbar items, search callbacks) via NativeStackScreenOptions.//// GLASS: transparent header over the screen's primary scroll view on supported// iOS versions. Pre-glass iOS gets the same solid material as internal-scroll// surfaces so content is laid out below the bar instead of underlapping it.const GLASS_HEADER_OPTIONS: AppScreenOptions = {  headerBackButtonDisplayMode: "minimal",  headerBackTitle: "",  headerLargeTitle: false,  headerShadowVisible: false,  headerShown: true,  headerStyle: NATIVE_LIQUID_GLASS_SUPPORTED    ? { backgroundColor: "transparent" }    : SHEET_BACKGROUND_COLOR !== undefined      ? { backgroundColor: SHEET_BACKGROUND_COLOR as unknown as string }      : undefined,  headerTitleStyle: { fontSize: 18, fontWeight: "800" },  headerTransparent: NATIVE_LIQUID_GLASS_SUPPORTED,  scrollEdgeEffects: NATIVE_LIQUID_GLASS_SUPPORTED ? HEADER_SCROLL_EDGE_EFFECTS : undefined,  unstable_navigationItemStyle: NATIVE_LIQUID_GLASS_SUPPORTED ? "editor" : undefined,}; // SOLID: opaque sheet-colored header for surfaces whose content scrolls internally// (file viewer, terminal, review) — there is nothing for glass to sample there.const SOLID_HEADER_OPTIONS: AppScreenOptions = {  headerBackButtonDisplayMode: "minimal",  headerBackTitle: "",  headerLargeTitle: false,  headerShadowVisible: false,  headerShown: true,  headerStyle:    SHEET_BACKGROUND_COLOR !== undefined      ? // native-stack types this as `string`, but the native side accepts any        // ColorValue including DynamicColorIOS.        { backgroundColor: SHEET_BACKGROUND_COLOR as unknown as string }      : undefined,  headerTitleStyle: { fontSize: 18, fontWeight: "800" },  headerTransparent: false,  unstable_navigationItemStyle: Platform.OS === "ios" ? "editor" : undefined,}; // Solid header variant for screens inside sheets (centered title, no editor style).const SHEET_SOLID_HEADER_OPTIONS: AppScreenOptions = {  ...SOLID_HEADER_OPTIONS,  unstable_navigationItemStyle: undefined,}; const LEGAL_DOCUMENT_HEADER_OPTIONS: AppScreenOptions = {  ...SHEET_SOLID_HEADER_OPTIONS,  headerBackVisible: false,  headerLeft: SettingsLegalDocumentCloseHeaderButton,  headerRight: () => <SettingsLegalDocumentExternalHeaderButton />,  presentation: "fullScreenModal",}; const SettingsSheetStack = createNativeStackNavigator({  initialRouteName: "Settings",  screenOptions: {    ...GLASS_HEADER_OPTIONS,    // Sheets read better with the iOS-default centered title (no editor style).    unstable_navigationItemStyle: undefined,  },  screens: {    Settings: createNativeStackScreen({      screen: SettingsRouteScreen,      linking: "",      options: {        title: "Settings",      },    }),    SettingsEnvironments: createNativeStackScreen({      screen: SettingsEnvironmentsRouteScreen,      linking: "environments",      options: {        title: "Environments",      },    }),    SettingsEnvironmentNew: createNativeStackScreen({      screen: ConnectionsNewRouteScreen,      linking: "environment-new",      options: {        title: "Add Environment",      },    }),    SettingsArchive: createNativeStackScreen({      screen: ArchivedThreadsRouteScreen,      linking: "archive",      options: {        title: "Archived Threads",      },    }),    SettingsAppearance: createNativeStackScreen({      screen: SettingsAppearanceRouteScreen,      linking: "appearance",      options: {        title: "Appearance",      },    }),    SettingsClientStorage: createNativeStackScreen({      screen: SettingsClientStorageRouteScreen,      linking: "client-storage",      options: {        title: "Client Storage",      },    }),    SettingsAuth: createNativeStackScreen({      screen: SettingsAuthRouteScreen,      linking: "auth",      options: {        title: "Sign in",      },    }),    SettingsWaitlist: createNativeStackScreen({      screen: SettingsWaitlistRouteScreen,      linking: "waitlist",      options: {        title: "Join the waitlist",      },    }),  },}); // Thread routes live FLAT in the root stack (not in a nested navigator). A nested// stack means a second UINavigationController with its own UINavigationBar, which// breaks iOS 26's shared-header morphing between Home and Thread (each pair inside// one bar morphs; across two bars the whole screen slides). Flat linking paths keep// the same deep-link URLs the nested config produced.const THREAD_LINKING_PREFIX = "threads/:environmentId/:threadId"; // New-task / add-project flow: nested navigator inside the formSheet (Settings-sheet// pattern — a plain formSheet screen cannot render a stack header; the header and// in-sheet pushes come from this nested stack).const NewTaskSheetStack = createNativeStackNavigator({  initialRouteName: "NewTask",  screenOptions: {    ...GLASS_HEADER_OPTIONS,    // Sheets read better with the iOS-default centered title (no editor style).    unstable_navigationItemStyle: undefined,  },  screens: {    NewTask: createNativeStackScreen({      screen: NewTaskRouteScreen,      linking: "",      options: {        title: "Choose project",      },    }),    NewTaskDraft: createNativeStackScreen({      screen: NewTaskDraftRouteScreen,      linking: "draft",      // The draft composer has no scroll view for glass to sample; a solid      // header also lays the content out below the bar (no manual inset).      options: SHEET_SOLID_HEADER_OPTIONS,    }),    AddProject: createNativeStackScreen({      screen: AddProjectSourceRoute,      linking: "add-project",      options: {        title: "Add Project",      },    }),    AddProjectRepository: createNativeStackScreen({      screen: AddProjectRepositoryRoute,      linking: "add-project/repository",    }),    AddProjectDestination: createNativeStackScreen({      screen: AddProjectDestinationRoute,      linking: "add-project/destination",    }),    AddProjectLocal: createNativeStackScreen({      screen: AddProjectLocalRoute,      linking: "add-project/local",    }),  },}); // Routes presented as sheets/overlays ON TOP of the workspace. They must not// influence the adaptive workspace layout: opening Settings over Home should// not flip the sidebar in or change the active thread.const WORKSPACE_OVERLAY_ROUTES = new Set([  "ConnectOnboarding",  "Connections",  "ConnectionsNew",  "GitBranches",  "GitCommit",  "GitConfirm",  "GitOverview",  "NewTaskSheet",  "SettingsLegal",  "SettingsSheet",  "ThreadReviewComment",]); /** * Pathname of the topmost NON-overlay route — the screen the workspace is * actually "on", regardless of any sheets floating above it. */function workspacePathFromState(state: NavigationState): string {  const routes = state.routes.filter((route) => !WORKSPACE_OVERLAY_ROUTES.has(route.name));  const effectiveState =    routes.length > 0 && routes.length !== state.routes.length      ? ({ ...state, routes, index: routes.length - 1 } as NavigationState)      : state;  const path = getPathFromState(effectiveState, navigationPathConfig);  return path.startsWith("/") ? path : `/${path}`;} function RootStackLayout(props: {  readonly children: React.ReactNode;  readonly state: NavigationState;}) {  const navigation = useNavigation();  const { pendingShare } = useIncomingShare();  const sharePresentationRef = useRef(EMPTY_INCOMING_SHARE_PRESENTATION_STATE);  useAgentNotificationNavigation();  useThreadOutboxDrain();  // Presents the T3 Connect onboarding sheet after an in-session sign-in.  useConnectOnboardingNavigation();  // Launcher app shortcuts: routes shortcut taps and tracks opened threads.  useAppShortcuts(props.state);  useEffect(() => {    const topRouteName = props.state.routes[props.state.index]?.name;    const transition = transitionIncomingSharePresentation(sharePresentationRef.current, {      isShareSheetPresented: topRouteName === "NewTaskSheet",      pendingShareId: pendingShare?.id ?? null,    });    sharePresentationRef.current = transition.state;    if (!transition.shareIdToPresent) {      return;    }    navigation.navigate("NewTaskSheet", {      screen: "NewTask",      params: { incomingShareId: transition.shareIdToPresent },    });  }, [navigation, pendingShare, props.state]);  // Full pathname (sheets included) for keyboard-command scoping; the  // workspace layout only reacts to the underlying non-overlay route.  const path = getPathFromState(props.state, navigationPathConfig);  const pathname = path.startsWith("/") ? path : `/${path}`;  const workspacePathname = workspacePathFromState(props.state);   return (    <HardwareKeyboardCommandProvider pathname={pathname}>      <ShowcaseCaptureCoordinator pathname={pathname} />      <ClerkSettingsSheetDetentProvider initiallyExpanded={false}>        <AdaptiveWorkspaceLayout pathname={workspacePathname}>          {props.children}        </AdaptiveWorkspaceLayout>      </ClerkSettingsSheetDetentProvider>    </HardwareKeyboardCommandProvider>  );} function NotFoundScreen() {  const navigation = useNavigation();  const screenBgStyle = StyleSheet.flatten(useResolveClassNames("bg-screen"));  const primaryBgStyle = StyleSheet.flatten(useResolveClassNames("bg-primary"));  const returnHomeButtonStyle = StyleSheet.flatten([    {      borderRadius: 999,      paddingHorizontal: 20,      paddingVertical: 14,    },    primaryBgStyle,  ]);   return (    <ScrollView      contentInsetAdjustmentBehavior="automatic"      contentContainerStyle={{        flexGrow: 1,        alignItems: "center",        justifyContent: "center",        gap: 16,        paddingHorizontal: 24,        paddingVertical: 32,      }}      style={[{ flex: 1 }, screenBgStyle]}    >      <Text className="text-3xl font-t3-bold text-foreground" selectable>        Route not found      </Text>      <Pressable        style={returnHomeButtonStyle}        onPress={() => navigation.dispatch(StackActions.replace("Home"))}      >        <Text className="text-base font-t3-bold text-primary-foreground">Return home</Text>      </Pressable>    </ScrollView>  );} export const RootStack = createNativeStackNavigator({  initialRouteName: "Home",  layout: RootStackLayout,  screenOptions: {    headerShown: false,  },  screens: {    Home: createNativeStackScreen({      screen: HomeRouteScreen,      linking: "",      options: {        ...GLASS_HEADER_OPTIONS,        contentStyle: { backgroundColor: "transparent" },        headerBackVisible: false,        title: "Threads",      },    }),    Thread: createNativeStackScreen({      screen: ThreadRouteScreen,      linking: THREAD_LINKING_PREFIX,      options: GLASS_HEADER_OPTIONS,    }),    ThreadTerminal: createNativeStackScreen({      screen: ThreadTerminalRouteScreen,      linking: `${THREAD_LINKING_PREFIX}/terminal`,      options: SOLID_HEADER_OPTIONS,    }),    ThreadReview: createNativeStackScreen({      screen: ReviewSheet,      linking: `${THREAD_LINKING_PREFIX}/review`,      options: SOLID_HEADER_OPTIONS,    }),    ThreadReviewComment: createNativeStackScreen({      screen: ReviewCommentComposerSheet,      linking: `${THREAD_LINKING_PREFIX}/review-comment`,      options: {        // Android cannot host the keyboard-driven comment composer inside a        // formSheet; use a full-screen modal there instead.        presentation: Platform.OS === "android" ? "fullScreenModal" : "formSheet",        sheetAllowedDetents: Platform.OS === "android" ? undefined : [0.55, 0.92],        sheetGrabberVisible: Platform.OS !== "android",      },    }),    ThreadFiles: createNativeStackScreen({      screen: ThreadFilesTreeScreen,      linking: `${THREAD_LINKING_PREFIX}/files`,      options: {        ...GLASS_HEADER_OPTIONS,        contentStyle:          SHEET_BACKGROUND_COLOR !== undefined            ? { backgroundColor: SHEET_BACKGROUND_COLOR }            : undefined,        title: "Files",      },    }),    ThreadFile: createNativeStackScreen({      screen: ThreadFileScreen,      linking: `${THREAD_LINKING_PREFIX}/files/:path*`,      options: SOLID_HEADER_OPTIONS,    }),    GitOverview: createNativeStackScreen({      screen: GitOverviewSheet,      linking: `${THREAD_LINKING_PREFIX}/git`,      options: {        presentation: "formSheet",        sheetAllowedDetents: [0.55, 0.92],        sheetGrabberVisible: true,      },    }),    GitCommit: createNativeStackScreen({      screen: GitCommitSheet,      linking: `${THREAD_LINKING_PREFIX}/git/commit`,      options: {        presentation: "formSheet",        sheetAllowedDetents: [0.55, 0.92],        sheetGrabberVisible: true,      },    }),    GitBranches: createNativeStackScreen({      screen: GitBranchesSheet,      linking: `${THREAD_LINKING_PREFIX}/git/branches`,      options: {        presentation: "formSheet",        sheetAllowedDetents: [0.55, 0.92],        sheetGrabberVisible: true,      },    }),    GitConfirm: createNativeStackScreen({      screen: GitConfirmSheet,      linking: `${THREAD_LINKING_PREFIX}/git-confirm`,      options: {        presentation: "formSheet",        sheetAllowedDetents: [0.45, 0.7],        sheetGrabberVisible: true,      },    }),    SettingsSheet: createNativeStackScreen({      screen: SettingsSheetStack,      linking: "settings",      options: {        gestureEnabled: true,        headerShown: false,        // Android pushes settings as a regular full page with an in-screen        // back header; iOS keeps the detented form sheet.        ...(Platform.OS === "android"          ? { presentation: "card" as const }          : {              presentation: "formSheet" as const,              sheetAllowedDetents: [0.7, 0.92],              sheetGrabberVisible: true,            }),      },    }),    SettingsLegal: createNativeStackScreen({      screen: SettingsLegalRouteScreen,      linking: "settings/legal",      options: {        ...LEGAL_DOCUMENT_HEADER_OPTIONS,        title: "Legal",      },    }),    ConnectOnboarding: createNativeStackScreen({      screen: ConnectOnboardingRouteScreen,      linking: "connect-onboarding",      options: {        // A root-level Android formSheet does not host the native stack bar;        // the route renders an embedded AndroidSheetHeader instead.        ...(Platform.OS === "android" ? { headerShown: false } : SHEET_SOLID_HEADER_OPTIONS),        title: "Set up T3 Connect",

Lines 1–500 of 567.