diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts
index 664659c44c8..81879fde9e0 100644
--- a/apps/mobile/app.config.ts
+++ b/apps/mobile/app.config.ts
@@ -11,10 +11,15 @@ 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();
+const IOS_BUNDLE_IDENTIFIER_PATTERN = /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/;
-if (isIosPersonalTeamBuild && !personalTeamBundleIdentifier) {
+if (
+ isIosPersonalTeamBuild &&
+ (!personalTeamBundleIdentifier ||
+ !IOS_BUNDLE_IDENTIFIER_PATTERN.test(personalTeamBundleIdentifier))
+) {
throw new Error(
- "T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID is required when T3CODE_IOS_PERSONAL_TEAM=1.",
+ "T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID must be a reverse-DNS identifier such as com.example.t3code when T3CODE_IOS_PERSONAL_TEAM=1.",
);
}
@@ -191,6 +196,8 @@ const config: ExpoConfig = {
},
],
"expo-secure-store",
+ // appleSignIn must be gated here: withoutIosPersonalTeamCapabilities.cjs runs before
+ // plugins earlier in this array, so it cannot strip the entitlement Clerk would add.
["@clerk/expo", { theme: "./clerk-theme.json", appleSignIn: !isIosPersonalTeamBuild }],
"expo-web-browser",
[
@@ -242,6 +249,7 @@ const config: ExpoConfig = {
],
extra: {
appVariant: APP_VARIANT,
+ iosPersonalTeamBuild: isIosPersonalTeamBuild,
relay: {
url: repoEnv.T3CODE_RELAY_URL ?? null,
},
diff --git a/apps/mobile/src/features/agent-awareness/capabilities.ts b/apps/mobile/src/features/agent-awareness/capabilities.ts
new file mode 100644
index 00000000000..d627f365754
--- /dev/null
+++ b/apps/mobile/src/features/agent-awareness/capabilities.ts
@@ -0,0 +1,5 @@
+import Constants from "expo-constants";
+
+export function supportsAgentAwarenessPush() {
+ return Constants.expoConfig?.extra?.iosPersonalTeamBuild !== true;
+}
diff --git a/apps/mobile/src/features/agent-awareness/registrationPayload.ts b/apps/mobile/src/features/agent-awareness/registrationPayload.ts
index cd2e36a403c..5a9d71470a1 100644
--- a/apps/mobile/src/features/agent-awareness/registrationPayload.ts
+++ b/apps/mobile/src/features/agent-awareness/registrationPayload.ts
@@ -1,6 +1,7 @@
import type { RelayDeviceRegistrationRequest } from "@t3tools/contracts/relay";
import type { Preferences } from "../../lib/storage";
+import { supportsAgentAwarenessPush } from "./capabilities";
// Development builds are Xcode-signed and receive sandbox APNs tokens;
// preview and production builds are distribution-signed and use production
@@ -21,7 +22,8 @@ export function makeRelayDeviceRegistrationRequest(input: {
readonly notificationsEnabled: boolean;
readonly preferences: Preferences;
}): RelayDeviceRegistrationRequest {
- const liveActivitiesEnabled = input.preferences.liveActivitiesEnabled !== false;
+ const pushAvailable = supportsAgentAwarenessPush();
+ const liveActivitiesEnabled = pushAvailable && input.preferences.liveActivitiesEnabled !== false;
return {
deviceId: input.deviceId,
label: input.label,
@@ -34,7 +36,7 @@ export function makeRelayDeviceRegistrationRequest(input: {
...(input.pushToStartToken ? { pushToStartToken: input.pushToStartToken } : {}),
preferences: {
liveActivitiesEnabled,
- notificationsEnabled: input.notificationsEnabled,
+ notificationsEnabled: pushAvailable && input.notificationsEnabled,
notifyOnApproval: true,
notifyOnInput: true,
notifyOnCompletion: true,
diff --git a/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts b/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts
index e296825c7ee..0dbf07497c0 100644
--- a/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts
+++ b/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts
@@ -286,6 +286,26 @@ describe("makeRelayDeviceRegistrationRequest", () => {
expect(resolveApsEnvironment(undefined)).toBe("production");
});
+ it("disables push features in Personal Team relay registrations", () => {
+ Constants.expoConfig!.extra = { iosPersonalTeamBuild: true };
+
+ expect(
+ makeRelayDeviceRegistrationRequest({
+ deviceId: "device-1",
+ label: "Julius's iPhone",
+ iosMajorVersion: 18,
+ appVersion: "1.0.0",
+ pushToken: "apns-token",
+ pushToStartToken: "push-to-start-token",
+ notificationsEnabled: true,
+ preferences: {},
+ }).preferences,
+ ).toMatchObject({
+ liveActivitiesEnabled: false,
+ notificationsEnabled: false,
+ });
+ });
+
it("marks notification delivery disabled when APNs permission is unavailable", () => {
expect(
makeRelayDeviceRegistrationRequest({
diff --git a/apps/mobile/src/features/agent-awareness/remoteRegistration.ts b/apps/mobile/src/features/agent-awareness/remoteRegistration.ts
index e9de93c1dcb..e2e2c3b9768 100644
--- a/apps/mobile/src/features/agent-awareness/remoteRegistration.ts
+++ b/apps/mobile/src/features/agent-awareness/remoteRegistration.ts
@@ -30,6 +30,7 @@ import {
} from "../../lib/storage";
import AgentActivity, { type AgentActivityProps } from "../../widgets/AgentActivity";
import { resolveCloudPublicConfig } from "../cloud/publicConfig";
+import { supportsAgentAwarenessPush } from "./capabilities";
import { makeRelayDeviceRegistrationRequest, resolveApsEnvironment } from "./registrationPayload";
const REMOTE_ACTIVITY_REGISTRATION_RETRY_MS = 15_000;
@@ -237,7 +238,7 @@ function iosMajorVersion(): number {
function nativePushTokenRegistration(observedPushToken?: string) {
return Effect.gen(function* () {
- if (!canRegisterRemoteLiveActivities()) {
+ if (!canRegisterRemoteLiveActivities() || !supportsAgentAwarenessPush()) {
return { notificationsEnabled: false, pushToken: null };
}
if (observedPushToken) {
diff --git a/apps/mobile/src/features/settings/SettingsRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsRouteScreen.tsx
index f56b197c14d..1c24a15f384 100644
--- a/apps/mobile/src/features/settings/SettingsRouteScreen.tsx
+++ b/apps/mobile/src/features/settings/SettingsRouteScreen.tsx
@@ -19,6 +19,7 @@ import {
} from "@t3tools/client-runtime/state/runtime";
import { AndroidScreenHeader } from "../../components/AndroidScreenHeader";
import { AppText as Text } from "../../components/AppText";
+import { supportsAgentAwarenessPush } from "../agent-awareness/capabilities";
import { setLiveActivityUpdatesEnabled } from "../agent-awareness/liveActivityPreferences";
import { requestAgentNotificationPermission } from "../agent-awareness/notificationPermissions";
import {
@@ -131,6 +132,7 @@ function LocalSettingsRouteScreen() {
}
function ConfiguredSettingsRouteScreen() {
+ const agentAwarenessPushAvailable = supportsAgentAwarenessPush();
const insets = useSafeAreaInsets();
const navigation = useNavigation();
const { expand: expandClerkSheet } = useClerkSettingsSheetDetent();
@@ -440,22 +442,32 @@ function ConfiguredSettingsRouteScreen() {