authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-14 15:05:40-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-14 15:05:40-07:00
log0f21fcbb6dbabb6d2185a73edf6b865fd64c6019
tree1bfaf895e6e5880304c03dc68c74d7a9da74d852
parentbcc20249cc0a888430f2006d61c11de176d531cd
signature Signed by PGP key B5690EEEBB952194

feat(mobile): add a T3 Connect page to the Clerk profile (#11765)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

6 files changed, 374 insertions(+), 12 deletions(-)

apps/mobile/global.css+25
...@@ -233,6 +233,31 @@...@@ -233,6 +233,31 @@
233 }233 }
234}234}
235235
236/* ─── Clerk native profile ──────────────────────────────────────────── */
237/* Fixed palette for custom pages inside Clerk's native user profile. Mirrors
238 clerk-theme.json, which themes the SDK's own screens, so ours match them.
239 Kept out of the runtime palette above on purpose: custom themes must not
240 restyle Clerk's chrome. Keep in sync with clerk-theme.json. */
241@layer theme {
242 :root {
243 @variant light {
244 --color-clerk-page: #f2f2f7;
245 --color-clerk-foreground: #262626;
246 --color-clerk-foreground-muted: #737373;
247 --color-clerk-border: rgba(229, 229, 234, 0.06);
248 --color-clerk-danger: #dc2626;
249 }
250
251 @variant dark {
252 --color-clerk-page: #0e0e0e;
253 --color-clerk-foreground: #f5f5f5;
254 --color-clerk-foreground-muted: #a3a3a3;
255 --color-clerk-border: rgba(42, 42, 42, 0.06);
256 --color-clerk-danger: #fca5a5;
257 }
258 }
259}
260
236/* ─── Typography ────────────────────────────────────────────────────── */261/* ─── Typography ────────────────────────────────────────────────────── */
237@theme {262@theme {
238 /* Keep these native family names aligned with app.config.ts. */263 /* Keep these native family names aligned with app.config.ts. */
apps/mobile/src/features/cloud/T3ConnectProfilePage.tsx created+273
...@@ -0,0 +1,273 @@
1import { findErrorTraceId } from "@t3tools/client-runtime/errors";
2import {
3 isAtomCommandInterrupted,
4 squashAtomCommandFailure,
5} from "@t3tools/client-runtime/state/runtime";
6import type { MenuAction } from "@react-native-menu/menu";
7import type { EnvironmentId } from "@t3tools/contracts";
8import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay";
9import { type ReactNode, useRef, useState } from "react";
10import {
11 ActivityIndicator,
12 Alert,
13 Pressable,
14 RefreshControl,
15 ScrollView,
16 Text,
17 View,
18} from "react-native";
19
20import { SymbolView } from "../../components/AppSymbol";
21import { showConfirmDialog } from "../../components/ConfirmDialogHost";
22import { ControlPillMenu } from "../../components/ControlPill";
23import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic";
24import { useAtomCommand } from "../../state/use-atom-command";
25import {
26 deregisterManagedRelayEnvironmentCommand,
27 useManagedRelayEnvironments,
28} from "./managedRelayState";
29
30const linkedAtFormatter = new Intl.DateTimeFormat(undefined, { dateStyle: "medium" });
31
32function linkedAtLabel(value: string): string {
33 const linkedAt = new Date(value);
34 return Number.isNaN(linkedAt.getTime())
35 ? "Link date unavailable"
36 : `Linked ${linkedAtFormatter.format(linkedAt)}`;
37}
38
39function endpointLabel(environment: RelayClientEnvironmentRecord): string {
40 return environment.endpoint.providerKind === "cloudflare_tunnel"
41 ? "Managed tunnel"
42 : "Activity publishing only";
43}
44
45function confirmDeregister(environment: RelayClientEnvironmentRecord, onConfirm: () => void) {
46 const title = "Deregister server?";
47 const message = `“${environment.label}” will be removed from this account. T3 Connect access will be revoked, any managed tunnel will be removed, and a host space will become available. Local connections on your devices are not changed.`;
48 if (process.env.EXPO_OS === "ios") {
49 Alert.alert(title, message, [
50 { text: "Cancel", style: "cancel" },
51 { text: "Deregister", style: "destructive", onPress: onConfirm },
52 ]);
53 return;
54 }
55 showConfirmDialog({ title, message, confirmText: "Deregister", destructive: true, onConfirm });
56}
57
58/**
59 * The "T3 Connect" custom page inside Clerk's native user profile: every
60 * environment registered to the signed-in account, with account-level
61 * deregistration. Mirrors the web UserButton page; connections on this device
62 * are managed in Settings instead.
63 */
64export function T3ConnectProfilePage() {
65 const environmentsState = useManagedRelayEnvironments();
66 const deregisterEnvironment = useAtomCommand(deregisterManagedRelayEnvironmentCommand, {
67 reportFailure: false,
68 });
69 const [deregisteringEnvironmentId, setDeregisteringEnvironmentId] =
70 useState<EnvironmentId | null>(null);
71 const mutationPendingRef = useRef(false);
72 // Deregistered rows stay in the cached list until the refresh lands, so hide
73 // them by the linkedAt they had. A re-link produces a new linkedAt and shows again.
74 const [removedEnvironments, setRemovedEnvironments] = useState<{
75 readonly accountId: string | null;
76 readonly linkedAtById: ReadonlyMap<EnvironmentId, string>;
77 }>({ accountId: null, linkedAtById: new Map() });
78
79 const handleDeregister = async (environment: RelayClientEnvironmentRecord) => {
80 const accountId = environmentsState.accountId;
81 if (!accountId || mutationPendingRef.current) return;
82
83 mutationPendingRef.current = true;
84 setDeregisteringEnvironmentId(environment.environmentId);
85 const result = await deregisterEnvironment({
86 accountId,
87 environmentId: environment.environmentId,
88 });
89 mutationPendingRef.current = false;
90 setDeregisteringEnvironmentId(null);
91
92 if (result._tag === "Success") {
93 setRemovedEnvironments((current) => {
94 const linkedAtById = new Map(current.accountId === accountId ? current.linkedAtById : []);
95 linkedAtById.set(environment.environmentId, environment.linkedAt);
96 return { accountId, linkedAtById };
97 });
98 environmentsState.refresh();
99 return;
100 }
101 if (isAtomCommandInterrupted(result)) return;
102
103 const cause = squashAtomCommandFailure(result);
104 const message = cause instanceof Error ? cause.message : "Could not deregister the server.";
105 const traceId = findErrorTraceId(cause);
106 console.error("[t3-connect] Could not deregister environment", {
107 environmentId: environment.environmentId,
108 message,
109 traceId,
110 cause,
111 });
112 Alert.alert(
113 "Could not deregister server",
114 traceId ? `${message}\n\nTrace ID: ${traceId}` : message,
115 traceId
116 ? [
117 {
118 text: "Copy trace ID",
119 onPress: () => copyTextWithHaptic(traceId, { target: "connection-trace-id" }),
120 },
121 { text: "OK", style: "cancel" },
122 ]
123 : undefined,
124 );
125 };
126
127 const removedEnvironmentLinkedAt =
128 removedEnvironments.accountId === environmentsState.accountId
129 ? removedEnvironments.linkedAtById
130 : new Map<EnvironmentId, string>();
131 const environments = (environmentsState.data ?? []).filter(
132 (environment) =>
133 removedEnvironmentLinkedAt.get(environment.environmentId) !== environment.linkedAt,
134 );
135 const isInitialLoad =
136 !environmentsState.accountId || (environmentsState.data === null && !environmentsState.error);
137 const errorTraceId = environmentsState.errorTraceId;
138
139 return (
140 <ScrollView
141 className="flex-1 bg-clerk-page"
142 contentContainerClassName="pb-8"
143 contentInsetAdjustmentBehavior="automatic"
144 refreshControl={
145 <RefreshControl
146 refreshing={environmentsState.isPending && environmentsState.data !== null}
147 onRefresh={environmentsState.refresh}
148 />
149 }
150 >
151 <ClerkSectionHeader>Registered servers</ClerkSectionHeader>
152
153 {environmentsState.error ? (
154 <>
155 <ClerkRow
156 title="Could not load T3 Connect environments"
157 subtitle={environmentsState.error}
158 />
159 {errorTraceId ? (
160 <ClerkButtonRow
161 label="Copy trace ID"
162 onPress={() => {
163 copyTextWithHaptic(errorTraceId, { target: "connection-trace-id" });
164 }}
165 />
166 ) : null}
167 </>
168 ) : isInitialLoad ? (
169 <View className="flex-row items-center gap-3 px-6 py-4">
170 <ActivityIndicator colorClassName={"accent-clerk-foreground-muted"} size="small" />
171 <Text className="text-base text-clerk-foreground-muted">Loading environments</Text>
172 </View>
173 ) : environments.length > 0 ? (
174 environments.map((environment) => (
175 <ClerkRow
176 key={environment.environmentId}
177 title={environment.label}
178 subtitle={`${linkedAtLabel(environment.linkedAt)} · ${endpointLabel(environment)}`}
179 accessory={
180 deregisteringEnvironmentId === environment.environmentId ? (
181 <ActivityIndicator colorClassName={"accent-clerk-foreground-muted"} size="small" />
182 ) : (
183 <ControlPillMenu
184 actions={ENVIRONMENT_MENU_ACTIONS}
185 isAnchoredToRight
186 onPressAction={() =>
187 confirmDeregister(environment, () => void handleDeregister(environment))
188 }
189 >
190 <Pressable
191 accessibilityLabel={`Actions for ${environment.label}`}
192 accessibilityRole="button"
193 disabled={deregisteringEnvironmentId !== null}
194 className="size-[30px] items-center justify-center active:opacity-60 disabled:opacity-50"
195 >
196 <View className="rotate-90">
197 <SymbolView
198 name="ellipsis"
199 size={18}
200 tintColorClassName={"accent-clerk-foreground-muted"}
201 type="monochrome"
202 />
203 </View>
204 </Pressable>
205 </ControlPillMenu>
206 )
207 }
208 />
209 ))
210 ) : (
211 <ClerkRow
212 title="No servers registered"
213 subtitle="Link a server from its local Settings to reach it through T3 Connect."
214 />
215 )}
216
217 <Text className="px-6 pt-6 text-xs leading-normal text-clerk-foreground-muted">
218 Connections on this device are managed in Settings.
219 </Text>
220 </ScrollView>
221 );
222}
223
224const ENVIRONMENT_MENU_ACTIONS = [
225 { id: "deregister", title: "Deregister", image: "trash", attributes: { destructive: true } },
226] satisfies MenuAction[];
227
228// Layout primitives that mirror clerk-ios ClerkKitUI's profile rows so a custom
229// page reads as one of Clerk's own screens. System font on purpose: Clerk's
230// native views do not use the app's DM Sans.
231
232function ClerkSectionHeader(props: { readonly children: string }) {
233 return (
234 <Text className="min-h-4 border-b border-clerk-border px-6 pt-8 pb-4 text-xs font-medium tracking-[0.3px] text-clerk-foreground-muted uppercase">
235 {props.children}
236 </Text>
237 );
238}
239
240function ClerkRow(props: {
241 readonly title: string;
242 readonly subtitle: string;
243 readonly accessory?: ReactNode;
244}) {
245 return (
246 <View
247 collapsable={false}
248 className="flex-row items-center gap-3 border-b border-clerk-border px-6 py-4"
249 >
250 <View className="min-w-0 flex-1 gap-0.5">
251 <Text className="min-h-[22px] text-base text-clerk-foreground" numberOfLines={1}>
252 {props.title}
253 </Text>
254 <Text className="min-h-5 text-sm text-clerk-foreground-muted" numberOfLines={2}>
255 {props.subtitle}
256 </Text>
257 </View>
258 {props.accessory}
259 </View>
260 );
261}
262
263function ClerkButtonRow(props: { readonly label: string; readonly onPress: () => void }) {
264 return (
265 <Pressable
266 accessibilityRole="button"
267 onPress={props.onPress}
268 className="border-b border-clerk-border px-6 py-4 active:opacity-60"
269 >
270 <Text className="text-base font-semibold text-clerk-foreground">{props.label}</Text>
271 </Pressable>
272 );
273}
apps/mobile/src/features/cloud/managedRelayState.ts+22
...@@ -1,9 +1,15 @@...@@ -1,9 +1,15 @@
1import { useAtomValue } from "@effect/atom-react";1import { useAtomValue } from "@effect/atom-react";
2import {2import {
3 createManagedRelayQueryManager,3 createManagedRelayQueryManager,
4 deregisterManagedRelayEnvironment,
4 managedRelaySessionAtom,5 managedRelaySessionAtom,
5 readManagedRelaySnapshotState,6 readManagedRelaySnapshotState,
6} from "@t3tools/client-runtime/relay";7} from "@t3tools/client-runtime/relay";
8import {
9 createAtomCommandScheduler,
10 createRuntimeCommand,
11} from "@t3tools/client-runtime/state/runtime";
12import type { EnvironmentId } from "@t3tools/contracts";
7import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay";13import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay";
8import { AsyncResult, Atom } from "effect/unstable/reactivity";14import { AsyncResult, Atom } from "effect/unstable/reactivity";
9import { useCallback, useEffect } from "react";15import { useCallback, useEffect } from "react";
...@@ -19,6 +25,22 @@ export const managedRelayQueryManager = createManagedRelayQueryManager(managedRe...@@ -19,6 +25,22 @@ export const managedRelayQueryManager = createManagedRelayQueryManager(managedRe
19 cloudDebugLog(`query:${event.operation}:${event.stage}:${event.phase}`, { ...event }),25 cloudDebugLog(`query:${event.operation}:${event.stage}:${event.phase}`, { ...event }),
20});26});
2127
28const managedRelayMutationScheduler = createAtomCommandScheduler();
29
30export const deregisterManagedRelayEnvironmentCommand = createRuntimeCommand(
31 managedRelayAtomRuntime,
32 {
33 label: "mobile:managed-relay:deregister-environment",
34 scheduler: managedRelayMutationScheduler,
35 concurrency: {
36 mode: "serial",
37 key: (input: { readonly accountId: string; readonly environmentId: EnvironmentId }) =>
38 input.accountId,
39 },
40 execute: (input, registry) => deregisterManagedRelayEnvironment(registry, input),
41 },
42);
43
22const EMPTY_ENVIRONMENTS_ATOM = Atom.make(44const EMPTY_ENVIRONMENTS_ATOM = Atom.make(
23 AsyncResult.success<ReadonlyArray<RelayClientEnvironmentRecord>>([]),45 AsyncResult.success<ReadonlyArray<RelayClientEnvironmentRecord>>([]),
24).pipe(Atom.keepAlive, Atom.withLabel("managed-relay:mobile:environments:null"));46).pipe(Atom.keepAlive, Atom.withLabel("managed-relay:mobile:environments:null"));
apps/mobile/src/features/settings/SettingsAuthRouteScreen.tsx+17-2
...@@ -1,10 +1,21 @@...@@ -1,10 +1,21 @@
1import { useAuth } from "@clerk/expo";1import { useAuth } from "@clerk/expo";
2import { AuthView, UserProfileView } from "@clerk/expo/native";2import { AuthView, type UserProfileCustomPage, UserProfileView } from "@clerk/expo/native";
3import { StackActions, useNavigation } from "@react-navigation/native";3import { StackActions, useNavigation } from "@react-navigation/native";
4import { useCallback, useEffect, useLayoutEffect, useRef } from "react";4import { useCallback, useEffect, useLayoutEffect, useRef } from "react";
5import { View } from "react-native";5import { View } from "react-native";
66
7import { hasCloudPublicConfig } from "../cloud/publicConfig";7import { hasCloudPublicConfig } from "../cloud/publicConfig";
8import { T3ConnectProfilePage } from "../cloud/T3ConnectProfilePage";
9
10// Custom rows in Clerk's native profile. Mirrors the web UserButton pages.
11const USER_PROFILE_CUSTOM_PAGES = [
12 {
13 path: "t3-connect",
14 label: "T3 Connect",
15 icon: "globe",
16 content: <T3ConnectProfilePage />,
17 },
18] satisfies UserProfileCustomPage[];
819
9export function SettingsAuthRouteScreen() {20export function SettingsAuthRouteScreen() {
10 const navigation = useNavigation();21 const navigation = useNavigation();
...@@ -40,7 +51,11 @@ function ConfiguredSettingsAuthRouteScreen() {...@@ -40,7 +51,11 @@ function ConfiguredSettingsAuthRouteScreen() {
40 <View collapsable={false} className="flex-1 overflow-hidden bg-sheet">51 <View collapsable={false} className="flex-1 overflow-hidden bg-sheet">
41 {isLoaded ? (52 {isLoaded ? (
42 hasBeenSignedIn.current ? (53 hasBeenSignedIn.current ? (
43 <UserProfileView isDismissible={false} onHostBack={handleHostBack} />54 <UserProfileView
55 customPages={USER_PROFILE_CUSTOM_PAGES}
56 isDismissible={false}
57 onHostBack={handleHostBack}
58 />
44 ) : (59 ) : (
45 <AuthView isDismissible={false} onHostBack={handleHostBack} />60 <AuthView isDismissible={false} onHostBack={handleHostBack} />
46 )61 )
patches/@clerk__expo@4.6.6.patch+32-5
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1diff --git a/ios/ClerkAuthNativeView.swift b/ios/ClerkAuthNativeView.swift1diff --git a/ios/ClerkAuthNativeView.swift b/ios/ClerkAuthNativeView.swift
2index e76a8be1b1c8faa64ec6dfa83764764094133aff..17b36ad1319765e2b6db0551d32e07d7140e482f 1006442index 5f8d2ba66d5b232890af75c22b173c86d2ef546b..b170304cbf2fbaae8032db8dd6f89b6a59c6e3fe 100644
3--- a/ios/ClerkAuthNativeView.swift3--- a/ios/ClerkAuthNativeView.swift
4+++ b/ios/ClerkAuthNativeView.swift4+++ b/ios/ClerkAuthNativeView.swift
5@@ -108,7 +108,12 @@ public class ClerkAuthNativeView: ClerkNativeViewHost {5@@ -111,7 +111,12 @@ public class ClerkAuthNativeView: ClerkNativeViewHost {
6 6
7 override func makeHostedController() -> UIViewController? {7 override func makeHostedController() -> UIViewController? {
8 let hostBackAction: (() -> Void)? = currentHostBackButton8 let hostBackAction: (() -> Void)? = currentHostBackButton
...@@ -16,8 +16,35 @@ index e76a8be1b1c8faa64ec6dfa83764764094133aff..17b36ad1319765e2b6db0551d32e07d7...@@ -16,8 +16,35 @@ index e76a8be1b1c8faa64ec6dfa83764764094133aff..17b36ad1319765e2b6db0551d32e07d7
16 : nil16 : nil
17 17
18 return ClerkNativeBridge.shared.makeAuthViewController(18 return ClerkNativeBridge.shared.makeAuthViewController(
19diff --git a/ios/ClerkNativeBridge.swift b/ios/ClerkNativeBridge.swift
20index c90ad2ef1162cc2ac025c8b0b4aa135e8913dc21..ce8cb68764b1a3d3537672fc83e4e3077fceb97a 100644
21--- a/ios/ClerkNativeBridge.swift
22+++ b/ios/ClerkNativeBridge.swift
23@@ -1470,6 +1470,8 @@ private struct ClerkReactEmbeddedUserProfileCustomPage: View {
24 }
25
26 private struct ClerkReactUserProfileCustomPageContent: View {
27+ @Environment(\.clerkTheme) private var theme
28+
29 let path: String
30 let rows: [ClerkUserProfileCustomRowConfig]
31 let state: ClerkUserProfileCustomPageState
32@@ -1483,8 +1485,13 @@ private struct ClerkReactUserProfileCustomPageContent: View {
33 }
34 }
35 .frame(maxWidth: .infinity, maxHeight: .infinity)
36+ // Match Clerk's own profile pages so the navigation bar and page share
37+ // the theme's page background instead of a bare hosting container.
38+ .background(theme.colors.muted)
39 .navigationTitle(userProfileCustomPageLabel(for: path, rows: rows))
40 .navigationBarTitleDisplayMode(.inline)
41+ .preGlassSolidNavBar()
42+ .background(theme.colors.background)
43 }
44 }
45
19diff --git a/ios/ClerkNativeViewHost.swift b/ios/ClerkNativeViewHost.swift46diff --git a/ios/ClerkNativeViewHost.swift b/ios/ClerkNativeViewHost.swift
20index 0d91f0e749f121595c17bc803663df6ac90e4163..8f8a89df97a54168b8b45d0e9c197ca3953b7028 10064447index 4e6540aceeaaab56ddf327bb3f16e120438f066f..cc16c94ca7a6eb91a97e76d5de02dc586f02a766 100644
21--- a/ios/ClerkNativeViewHost.swift48--- a/ios/ClerkNativeViewHost.swift
22+++ b/ios/ClerkNativeViewHost.swift49+++ b/ios/ClerkNativeViewHost.swift
23@@ -5,6 +5,7 @@ public class ClerkNativeViewHost: ExpoView {50@@ -5,6 +5,7 @@ public class ClerkNativeViewHost: ExpoView {
...@@ -60,10 +87,10 @@ index 0d91f0e749f121595c17bc803663df6ac90e4163..8f8a89df97a54168b8b45d0e9c197ca3...@@ -60,10 +87,10 @@ index 0d91f0e749f121595c17bc803663df6ac90e4163..8f8a89df97a54168b8b45d0e9c197ca3
60 guard configuredObserver == nil else { return }87 guard configuredObserver == nil else { return }
61 88
62diff --git a/ios/ClerkUserProfileNativeView.swift b/ios/ClerkUserProfileNativeView.swift89diff --git a/ios/ClerkUserProfileNativeView.swift b/ios/ClerkUserProfileNativeView.swift
63index 12d6248b1dc4b4779b252c9954c2f89145907310..d838283b7adcf49fec2a63bff53251b9bee31bf8 10064490index f2ca604d41f50d05de8ac334879126cdc0414f84..46f406d5361b9a560a7c4ce855e344cbddd1d0b6 100644
64--- a/ios/ClerkUserProfileNativeView.swift91--- a/ios/ClerkUserProfileNativeView.swift
65+++ b/ios/ClerkUserProfileNativeView.swift92+++ b/ios/ClerkUserProfileNativeView.swift
66@@ -44,7 +44,12 @@ public class ClerkUserProfileNativeView: ClerkNativeViewHost {93@@ -47,7 +47,12 @@ public class ClerkUserProfileNativeView: ClerkUserProfileCustomPageHost {
67 94
68 override func makeHostedController() -> UIViewController? {95 override func makeHostedController() -> UIViewController? {
69 let hostBackAction: (() -> Void)? = currentHostBackButton96 let hostBackAction: (() -> Void)? = currentHostBackButton
pnpm-lock.yaml+5-5
...@@ -85,7 +85,7 @@ overrides:...@@ -85,7 +85,7 @@ overrides:
85packageExtensionsChecksum: sha256-k/dT9NFDl5hihRPaoFKeY11hzyutMFs5psfZLFiKJic=85packageExtensionsChecksum: sha256-k/dT9NFDl5hihRPaoFKeY11hzyutMFs5psfZLFiKJic=
8686
87patchedDependencies:87patchedDependencies:
88 '@clerk/expo@4.6.6': 72e426f44fc1cde16fc2cbba3d1e96cdca7c6d957faa73d0fe6b43948608a6c188 '@clerk/expo@4.6.6': a82bb41039ee88a290a87d4ab58be7be4c49fa338603b17ea646e1ec140a1cfb
89 '@effect/vitest@4.0.0-rc.112': a16b1e870d8c29e4a98b17cc4c638a4ff471753d8ca3487f78a22b759caf951b89 '@effect/vitest@4.0.0-rc.112': a16b1e870d8c29e4a98b17cc4c638a4ff471753d8ca3487f78a22b759caf951b
90 '@expo/metro-config@57.0.12': 96f1a75347e6ea02dc4b7034ace815d8ee39e18b8166ebfb573d9e58328f0dc290 '@expo/metro-config@57.0.12': 96f1a75347e6ea02dc4b7034ace815d8ee39e18b8166ebfb573d9e58328f0dc2
91 '@ff-labs/fff-node@0.9.4': c4e3cc2420ceb9dc650f9d189e9c24baf7e83f342998bacda5f459a4ce7927a891 '@ff-labs/fff-node@0.9.4': c4e3cc2420ceb9dc650f9d189e9c24baf7e83f342998bacda5f459a4ce7927a8
...@@ -231,7 +231,7 @@ importers:...@@ -231,7 +231,7 @@ importers:
231 dependencies:231 dependencies:
232 '@clerk/expo':232 '@clerk/expo':
233 specifier: 4.6.6233 specifier: 4.6.6
234 version: 4.6.6(patch_hash=72e426f44fc1cde16fc2cbba3d1e96cdca7c6d957faa73d0fe6b43948608a6c1)(dbc31631339ce74330e188d5d7a88158)234 version: 4.6.6(patch_hash=a82bb41039ee88a290a87d4ab58be7be4c49fa338603b17ea646e1ec140a1cfb)(dbc31631339ce74330e188d5d7a88158)
235 '@effect/atom-react':235 '@effect/atom-react':
236 specifier: 4.0.0-rc.112236 specifier: 4.0.0-rc.112
237 version: 4.0.0-rc.112(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2))(react@19.2.3)(scheduler@0.27.0)237 version: 4.0.0-rc.112(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2))(react@19.2.3)(scheduler@0.27.0)
...@@ -774,7 +774,7 @@ importers:...@@ -774,7 +774,7 @@ importers:
774 version: link:../../packages/shared774 version: link:../../packages/shared
775 alchemy:775 alchemy:
776 specifier: 2.0.0-beta.76776 specifier: 2.0.0-beta.76
777 version: 2.0.0-beta.76(b3825b36417e56a486ccb5f22a7f3d7e)777 version: 2.0.0-beta.76(197b68e0d20fdf20cf352009eca17c9f)
778 drizzle-orm:778 drizzle-orm:
779 specifier: 1.0.0-rc.5-ab785fc779 specifier: 1.0.0-rc.5-ab785fc
780 version: 1.0.0-rc.5-ab785fc(8ab70e2706da13c78d64d8a92fef1884)780 version: 1.0.0-rc.5-ab785fc(8ab70e2706da13c78d64d8a92fef1884)
...@@ -12437,7 +12437,7 @@ snapshots:...@@ -12437,7 +12437,7 @@ snapshots:
12437 electron-store: 8.2.012437 electron-store: 8.2.0
12438 react-dom: 19.2.6(react@19.2.6)12438 react-dom: 19.2.6(react@19.2.6)
1243912439
12440 '@clerk/expo@4.6.6(patch_hash=72e426f44fc1cde16fc2cbba3d1e96cdca7c6d957faa73d0fe6b43948608a6c1)(dbc31631339ce74330e188d5d7a88158)':12440 '@clerk/expo@4.6.6(patch_hash=a82bb41039ee88a290a87d4ab58be7be4c49fa338603b17ea646e1ec140a1cfb)(dbc31631339ce74330e188d5d7a88158)':
12441 dependencies:12441 dependencies:
12442 '@clerk/clerk-js': 6.31.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)12442 '@clerk/clerk-js': 6.31.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
12443 '@clerk/react': 6.15.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)12443 '@clerk/react': 6.15.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
...@@ -16445,7 +16445,7 @@ snapshots:...@@ -16445,7 +16445,7 @@ snapshots:
16445 json-schema-traverse: 1.0.016445 json-schema-traverse: 1.0.0
16446 require-from-string: 2.0.216446 require-from-string: 2.0.2
1644716447
16448 alchemy@2.0.0-beta.76(b3825b36417e56a486ccb5f22a7f3d7e):16448 alchemy@2.0.0-beta.76(197b68e0d20fdf20cf352009eca17c9f):
16449 dependencies:16449 dependencies:
16450 '@alchemy.run/cloudflare-runtime': 2.0.0-beta.76(@distilled.cloud/cloudflare@1.0.0-rc.8(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2)))(@effect/platform-bun@4.0.0-rc.112(bufferutil@4.1.0)(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2))(utf-8-validate@6.0.6))(@effect/platform-node@4.0.0-rc.112(bufferutil@4.1.0)(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2))(rolldown@1.2.5)(typescript@7.0.2)16450 '@alchemy.run/cloudflare-runtime': 2.0.0-beta.76(@distilled.cloud/cloudflare@1.0.0-rc.8(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2)))(@effect/platform-bun@4.0.0-rc.112(bufferutil@4.1.0)(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2))(utf-8-validate@6.0.6))(@effect/platform-node@4.0.0-rc.112(bufferutil@4.1.0)(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@0.3.0(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2))(rolldown@1.2.5)(typescript@7.0.2)
16451 '@alchemy.run/floci': 2.0.0-beta.76(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2))16451 '@alchemy.run/floci': 2.0.0-beta.76(effect@4.0.0-rc.112(patch_hash=8bef799f35729cf3465eb428196f617b434a7c9f658d56acb2874d74b21b98b2))