Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
24 changes: 24 additions & 0 deletions apps/mobile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,30 @@ Build and run the local iOS dev client:
vp run ios:dev
```

If your Xcode account only has a Personal Team, use a bundle identifier you control and opt into the
reduced-capability local build. Personal Team builds omit the widget extension, push entitlement, and
native Sign in with Apple entitlement; builds without this opt-in are unchanged.

```bash
T3CODE_IOS_PERSONAL_TEAM=1 \
T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.example.t3code.dev \
vp run ios:dev
```

Build and install a self-contained Release app that does not need Metro:

```bash
vp run ios:release
```

The Personal Team equivalent also needs a unique bundle identifier:

```bash
T3CODE_IOS_PERSONAL_TEAM=1 \
T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.example.t3code \
vp run ios:release
```

Build and run the local iOS preview app:

```bash
Expand Down
53 changes: 35 additions & 18 deletions apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ const repoEnv = loadRepoEnv();
Object.assign(process.env, repoEnv);

const APP_VARIANT = resolveAppVariant(repoEnv.APP_VARIANT);
const isIosPersonalTeamBuild = repoEnv.T3CODE_IOS_PERSONAL_TEAM === "1";

const personalTeamBundleIdentifier = repoEnv.T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID?.trim();

if (isIosPersonalTeamBuild && !personalTeamBundleIdentifier) {
throw new Error(
"T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID is required when T3CODE_IOS_PERSONAL_TEAM=1.",
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const VARIANT_CONFIG: Record<
AppVariant,
Expand Down Expand Up @@ -54,6 +63,27 @@ function resolveAppVariant(value: string | undefined): AppVariant {
}

const variant = VARIANT_CONFIG[APP_VARIANT];
const iosBundleIdentifier =
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
isIosPersonalTeamBuild && personalTeamBundleIdentifier
? personalTeamBundleIdentifier
: variant.iosBundleIdentifier;

const widgetsPlugin = [
"expo-widgets",
{
bundleIdentifier: `${iosBundleIdentifier}.widgets`,
groupIdentifier: `group.${iosBundleIdentifier}`,
enablePushNotifications: true,
widgets: [
{
name: "AgentActivity",
displayName: "Agent Activity",
description: "Shows the current state of active T3 Code agents.",
supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"],
},
],
},
] satisfies NonNullable<ExpoConfig["plugins"]>[number];

const config: ExpoConfig = {
name: variant.appName,
Expand All @@ -76,7 +106,7 @@ const config: ExpoConfig = {
ios: {
icon: variant.iosIcon,
supportsTablet: true,
bundleIdentifier: variant.iosBundleIdentifier,
bundleIdentifier: iosBundleIdentifier,
infoPlist: {
NSAppTransportSecurity: {
NSAllowsArbitraryLoads: true,
Expand All @@ -102,9 +132,10 @@ const config: ExpoConfig = {
},
plugins: [
"expo-router",
"expo-asset",
"expo-font",
"expo-secure-store",
["@clerk/expo", { theme: "./clerk-theme.json" }],
["@clerk/expo", { theme: "./clerk-theme.json", appleSignIn: !isIosPersonalTeamBuild }],
"expo-web-browser",
[
"expo-camera",
Expand Down Expand Up @@ -139,23 +170,9 @@ const config: ExpoConfig = {
},
Comment thread
bmdavis419 marked this conversation as resolved.
},
],
[
"expo-widgets",
{
bundleIdentifier: `${variant.iosBundleIdentifier}.widgets`,
groupIdentifier: `group.${variant.iosBundleIdentifier}`,
enablePushNotifications: true,
widgets: [
{
name: "AgentActivity",
displayName: "Agent Activity",
description: "Shows the current state of active T3 Code agents.",
supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"],
},
],
},
],
...(!isIosPersonalTeamBuild ? [widgetsPlugin] : []),
"./plugins/withAndroidCleartextTraffic.cjs",
...(isIosPersonalTeamBuild ? ["./plugins/withoutIosPersonalTeamCapabilities.cjs"] : []),
],
extra: {
appVariant: APP_VARIANT,
Expand Down
40 changes: 21 additions & 19 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"ios:dev": "APP_VARIANT=development EXPO_NO_GIT_STATUS=1 expo prebuild --clean --platform ios && expo run:ios",
"ios:preview": "APP_VARIANT=preview EXPO_NO_GIT_STATUS=1 expo prebuild --clean --platform ios && expo run:ios",
"ios:prod": "APP_VARIANT=production EXPO_NO_GIT_STATUS=1 expo prebuild --clean --platform ios && expo run:ios",
"ios:release": "APP_VARIANT=production EXPO_NO_GIT_STATUS=1 expo prebuild --clean --platform ios && expo run:ios --configuration Release --no-bundler",
"eas:ios:dev": "eas build --profile development -p ios",
"eas:ios:preview": "eas build --profile preview -p ios",
"eas:ios:preview:dev": "eas build --profile preview:dev -p ios",
Expand All @@ -43,7 +44,7 @@
"@clerk/expo": "catalog:",
"@effect/atom-react": "catalog:",
"@expo-google-fonts/dm-sans": "^0.4.2",
"@expo/ui": "~56.0.8",
"@expo/ui": "~56.0.18",
"@legendapp/list": "3.2.0",
"@noble/curves": "catalog:",
"@noble/hashes": "catalog:",
Expand All @@ -59,41 +60,42 @@
"@t3tools/mobile-review-diff-native": "file:./modules/t3-review-diff",
"@t3tools/mobile-terminal-native": "file:./modules/t3-terminal",
"@t3tools/shared": "workspace:*",
"@tabler/icons-react-native": "^3.44.0",
"clsx": "^2.1.1",
"diff": "8.0.3",
"effect": "catalog:",
"expo": "^56.0.0",
"expo-asset": "~56.0.15",
"expo-auth-session": "~56.0.12",
"expo-build-properties": "~56.0.15",
"expo-camera": "~56.0.7",
"expo-clipboard": "~56.0.3",
"expo-constants": "~56.0.16",
"expo": "^56.0.12",
"expo-asset": "~56.0.17",
"expo-auth-session": "~56.0.14",
"expo-build-properties": "~56.0.19",
"expo-camera": "~56.0.8",
"expo-clipboard": "~56.0.4",
"expo-constants": "~56.0.18",
"expo-crypto": "~56.0.4",
"expo-dev-client": "~56.0.16",
"expo-file-system": "~56.0.7",
"expo-font": "~56.0.5",
"expo-dev-client": "~56.0.20",
"expo-file-system": "~56.0.8",
"expo-font": "~56.0.7",
"expo-glass-effect": "~56.0.4",
"expo-haptics": "~56.0.3",
"expo-image-picker": "~56.0.14",
"expo-linking": "~56.0.12",
"expo-image-picker": "~56.0.18",
"expo-linking": "~56.0.14",
"expo-network": "~56.0.5",
"expo-notifications": "~56.0.14",
"expo-notifications": "~56.0.18",
"expo-paste-input": "^0.1.15",
"expo-router": "~56.2.7",
"expo-router": "~56.2.11",
"expo-secure-store": "~56.0.4",
"expo-splash-screen": "~56.0.10",
"expo-symbols": "~56.0.5",
"expo-updates": "~56.0.17",
"expo-symbols": "~56.0.6",
"expo-updates": "~56.0.19",
"expo-web-browser": "~56.0.5",
"expo-widgets": "~56.0.15",
"expo-widgets": "~56.0.19",
"punycode": "^2.3.1",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.85.3",
"react-native-gesture-handler": "~2.31.1",
"react-native-image-viewing": "^0.2.2",
"react-native-keyboard-controller": "1.21.7",
"react-native-keyboard-controller": "1.21.6",
"react-native-nitro-markdown": "^0.5.0",
"react-native-nitro-modules": "0.35.9",
"react-native-reanimated": "4.3.1",
Expand Down
10 changes: 10 additions & 0 deletions apps/mobile/plugins/withoutIosPersonalTeamCapabilities.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { withEntitlementsPlist } = require("expo/config-plugins");

module.exports = function withoutIosPersonalTeamCapabilities(config) {
return withEntitlementsPlist(config, (modConfig) => {
delete modConfig.modResults["aps-environment"];
delete modConfig.modResults["com.apple.developer.applesignin"];
delete modConfig.modResults["com.apple.security.application-groups"];
return modConfig;
});
};
2 changes: 1 addition & 1 deletion apps/mobile/src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DMSans_700Bold,
useFonts,
} from "@expo-google-fonts/dm-sans";
import { RegistryContext } from "@effect/atom-react";
import { usePathname } from "expo-router";
import Stack from "expo-router/stack";
import { useCallback } from "react";
Expand All @@ -18,7 +19,6 @@ import { LoadingScreen } from "../components/LoadingScreen";

import { useWorkspaceState } from "../state/workspace";
import { useThreadOutboxDrain } from "../state/use-thread-outbox-drain";
import { RegistryContext } from "@effect/atom-react";
import { appAtomRegistry } from "../state/atom-registry";
import { CloudAuthProvider } from "../features/cloud/CloudAuthProvider";
import {
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/src/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export default function HomeRouteScreen() {
<>
<HomeHeader
environments={environments}
searchQuery={searchQuery}
selectedEnvironmentId={selectedEnvironmentId}
projectSortOrder={listOptions.projectSortOrder}
threadSortOrder={listOptions.threadSortOrder}
Expand Down
136 changes: 136 additions & 0 deletions apps/mobile/src/components/AppSymbol.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {
IconAdjustmentsHorizontal,
IconAlertTriangle,
IconArchive,
IconArrowBackUp,
IconArrowDownCircle,
IconArrowRightCircle,
IconArrowUp,
IconArrowUpRight,
IconBellRinging,
IconBolt,
IconCamera,
IconCheck,
IconChevronDown,
IconChevronRight,
IconChevronUp,
IconCircleCheck,
IconCircleXFilled,
IconCopy,
IconDeviceDesktop,
IconDots,
IconDotsCircleHorizontal,
IconEdit,
IconExternalLink,
IconEye,
IconFileText,
IconFilter,
IconFolder,
IconFolderPlus,
IconGitBranch,
IconGitMerge,
IconInfoCircle,
IconLayoutColumns,
IconLink,
IconMessage,
IconNetwork,
IconPlayerPlay,
IconPlayerStopFilled,
IconPlus,
IconRefresh,
IconSearch,
IconServer,
IconSettings,
IconTerminal2,
IconTrash,
IconTypography,
IconUserCircle,
IconWifiOff,
IconX,
type Icon,
} from "@tabler/icons-react-native";
import { Platform } from "react-native";
import { SymbolView as ExpoSymbolView, type SFSymbol, type SymbolViewProps } from "expo-symbols";

const ANDROID_ICON_BY_SF_SYMBOL: Partial<Record<SFSymbol, Icon>> = {
"arrow.branch": IconGitBranch,
"arrow.clockwise": IconRefresh,
"arrow.down.circle": IconArrowDownCircle,
"arrow.right.circle": IconArrowRightCircle,
"arrow.triangle.branch": IconGitBranch,
"arrow.turn.left.up": IconArrowBackUp,
"arrow.up": IconArrowUp,
"arrow.up.right": IconArrowUpRight,
archivebox: IconArchive,
"archivebox.fill": IconArchive,
"bell.badge": IconBellRinging,
"bolt.circle": IconBolt,
"bolt.horizontal.circle": IconBolt,
camera: IconCamera,
checkmark: IconCheck,
"checkmark.circle": IconCircleCheck,
"chevron.down": IconChevronDown,
"chevron.right": IconChevronRight,
"chevron.up": IconChevronUp,
desktopcomputer: IconDeviceDesktop,
"doc.on.doc": IconCopy,
"doc.text": IconFileText,
ellipsis: IconDots,
"ellipsis.circle": IconDotsCircleHorizontal,
"exclamationmark.triangle": IconAlertTriangle,
eye: IconEye,
folder: IconFolder,
"folder.badge.plus": IconFolderPlus,
"folder.fill": IconFolder,
gearshape: IconSettings,
"info.circle": IconInfoCircle,
link: IconLink,
"line.3.horizontal.decrease.circle": IconFilter,
"line.3.horizontal.decrease.circle.fill": IconFilter,
magnifyingglass: IconSearch,
"person.crop.circle": IconUserCircle,
play: IconPlayerPlay,
plus: IconPlus,
"point.3.connected.trianglepath.dotted": IconNetwork,
"point.topleft.down.curvedto.point.bottomright.up": IconGitMerge,
safari: IconExternalLink,
"server.rack": IconServer,
"slider.horizontal.3": IconAdjustmentsHorizontal,
"square.and.pencil": IconEdit,
"square.split.2x1": IconLayoutColumns,
"stop.fill": IconPlayerStopFilled,
terminal: IconTerminal2,
"text.bubble": IconMessage,
"textformat.size": IconTypography,
trash: IconTrash,
"wifi.slash": IconWifiOff,
xmark: IconX,
"xmark.circle.fill": IconCircleXFilled,
};

export type { SFSymbol } from "expo-symbols";
export type AppSymbolName = SymbolViewProps["name"];

export function SymbolView(props: SymbolViewProps) {
if (Platform.OS !== "android") {
return <ExpoSymbolView {...props} />;
}

const sfSymbol = typeof props.name === "string" ? props.name : props.name.ios;
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
const AndroidIcon = sfSymbol ? ANDROID_ICON_BY_SF_SYMBOL[sfSymbol] : undefined;

if (!AndroidIcon) {
return props.fallback ?? null;
}

return (
<AndroidIcon
accessibilityLabel={props.accessibilityLabel}
color={props.tintColor}
size={props.size}
strokeWidth={2}
style={props.style}
testID={props.testID}
/>
);
}
2 changes: 1 addition & 1 deletion apps/mobile/src/components/ControlPill.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { MenuView } from "@react-native-menu/menu";
import type { ComponentProps, ReactNode } from "react";
import { Pressable, useColorScheme, View } from "react-native";
import { SymbolView } from "expo-symbols";
import { useThemeColor } from "../lib/useThemeColor";

import { cn } from "../lib/cn";
import { SymbolView } from "./AppSymbol";
import { AppText as Text } from "./AppText";

export function ControlPill(props: {
Expand Down
Loading
Loading