@ramonclaudio/create-vexpo 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +10 -10
  2. package/dist/index.js +8 -7
  3. package/dist/templates/default/.eas/workflows/asc-events.yml +9 -6
  4. package/dist/templates/default/.eas/workflows/deploy-production.yml +28 -15
  5. package/dist/templates/default/.eas/workflows/e2e-tests.yml +3 -2
  6. package/dist/templates/default/.eas/workflows/pr-preview.yml +12 -21
  7. package/dist/templates/default/.eas/workflows/release.yml +3 -7
  8. package/dist/templates/default/.eas/workflows/rollback.yml +54 -28
  9. package/dist/templates/default/.eas/workflows/rollout.yml +27 -33
  10. package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +1 -5
  11. package/dist/templates/default/.eas/workflows/testflight.yml +3 -7
  12. package/dist/templates/default/.github/workflows/check.yml +20 -12
  13. package/dist/templates/default/.maestro/launch.yaml +19 -10
  14. package/dist/templates/default/AGENTS.md +25 -8
  15. package/dist/templates/default/DESIGN.md +14 -10
  16. package/dist/templates/default/README.md +83 -78
  17. package/dist/templates/default/SETUP.md +159 -152
  18. package/dist/templates/default/__tests__/convex/_auth-harness.test.ts +112 -0
  19. package/dist/templates/default/__tests__/convex/_harness.ts +132 -0
  20. package/dist/templates/default/__tests__/convex/appAttest.test.ts +172 -0
  21. package/dist/templates/default/__tests__/convex/appAttestStore.test.ts +48 -0
  22. package/dist/templates/default/__tests__/convex/pushTokens-remove.test.ts +106 -0
  23. package/dist/templates/default/__tests__/convex/pushTokens-upsert.test.ts +146 -0
  24. package/dist/templates/default/__tests__/convex/users-deleteAccount.test.ts +140 -0
  25. package/dist/templates/default/__tests__/convex/users-deleteAvatar.test.ts +104 -0
  26. package/dist/templates/default/__tests__/convex/users-getMe.test.ts +98 -0
  27. package/dist/templates/default/__tests__/convex/users-getUser.test.ts +120 -0
  28. package/dist/templates/default/__tests__/convex/users-hardDeleteExpired.test.ts +67 -0
  29. package/dist/templates/default/__tests__/convex/users-restoreAccount.test.ts +96 -0
  30. package/dist/templates/default/__tests__/convex/users-updateAvatar.test.ts +92 -0
  31. package/dist/templates/default/__tests__/convex/users-updateProfile.test.ts +126 -0
  32. package/dist/templates/default/__tests__/convex/webhook.test.ts +31 -0
  33. package/dist/templates/default/__tests__/lib/deep-link.test.ts +51 -6
  34. package/dist/templates/default/__tests__/lib/schemas.test.ts +205 -0
  35. package/dist/templates/default/__tests__/lib/text-style.test.ts +31 -0
  36. package/dist/templates/default/_env.example +7 -7
  37. package/dist/templates/default/_gitattributes +1 -1
  38. package/dist/templates/default/_gitignore +17 -2
  39. package/dist/templates/default/_npmrc +7 -0
  40. package/dist/templates/default/_oxlintrc.json +1 -1
  41. package/dist/templates/default/app-store/accessibility.config.json +20 -0
  42. package/dist/templates/default/app-store/privacy.config.json +27 -0
  43. package/dist/templates/default/app.config.ts +105 -33
  44. package/dist/templates/default/app.json +1 -9
  45. package/dist/templates/default/convex/_generated/api.d.ts +12 -0
  46. package/dist/templates/default/convex/admin.ts +0 -13
  47. package/dist/templates/default/convex/appAttest.ts +467 -0
  48. package/dist/templates/default/convex/appAttestStore.ts +141 -0
  49. package/dist/templates/default/convex/apple.ts +53 -0
  50. package/dist/templates/default/convex/auth.ts +6 -45
  51. package/dist/templates/default/convex/constants.ts +2 -7
  52. package/dist/templates/default/convex/crons.ts +12 -5
  53. package/dist/templates/default/convex/email.ts +4 -24
  54. package/dist/templates/default/convex/env.ts +0 -4
  55. package/dist/templates/default/convex/errors.ts +0 -7
  56. package/dist/templates/default/convex/functions.ts +0 -26
  57. package/dist/templates/default/convex/http.ts +3 -5
  58. package/dist/templates/default/convex/log.ts +2 -25
  59. package/dist/templates/default/convex/pushSender.ts +145 -0
  60. package/dist/templates/default/convex/pushTokens.ts +110 -13
  61. package/dist/templates/default/convex/rateLimit.ts +8 -39
  62. package/dist/templates/default/convex/schema.ts +48 -5
  63. package/dist/templates/default/convex/tsconfig.json +1 -0
  64. package/dist/templates/default/convex/users.ts +143 -61
  65. package/dist/templates/default/convex/validators.ts +1 -38
  66. package/dist/templates/default/convex/webhook.ts +1 -31
  67. package/dist/templates/default/convex.json +1 -2
  68. package/dist/templates/default/metro.config.js +9 -1
  69. package/dist/templates/default/package.json +67 -70
  70. package/dist/templates/default/plugins/README.md +5 -1
  71. package/dist/templates/default/scripts/README.md +9 -9
  72. package/dist/templates/default/scripts/_run.mjs +3 -20
  73. package/dist/templates/default/scripts/clean.ts +81 -69
  74. package/dist/templates/default/scripts/gen-update-cert.mjs +98 -0
  75. package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home)/index.tsx +21 -6
  76. package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home,search)/_layout.tsx +9 -8
  77. package/dist/templates/default/{app → src/app}/(app)/(tabs)/(search)/index.tsx +26 -24
  78. package/dist/templates/default/{app → src/app}/(app)/(tabs)/_layout.tsx +3 -4
  79. package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/_layout.tsx +10 -6
  80. package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/index.tsx +81 -51
  81. package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/preferences.tsx +72 -12
  82. package/dist/templates/default/src/app/(app)/_layout.tsx +147 -0
  83. package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/_layout.tsx +4 -5
  84. package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/forgot-password.tsx +15 -9
  85. package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/reset-password.tsx +88 -14
  86. package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-in.tsx +65 -35
  87. package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-up.tsx +131 -196
  88. package/dist/templates/default/src/app/(app)/debug.tsx +479 -0
  89. package/dist/templates/default/{app → src/app}/(app)/help.tsx +76 -64
  90. package/dist/templates/default/{app → src/app}/(app)/linked.tsx +21 -27
  91. package/dist/templates/default/{app → src/app}/(app)/privacy.tsx +35 -8
  92. package/dist/templates/default/src/app/(app)/profile/change-password.tsx +264 -0
  93. package/dist/templates/default/{app/(app)/profile.tsx → src/app/(app)/profile/index.tsx} +179 -255
  94. package/dist/templates/default/src/app/(app)/restore-account.tsx +192 -0
  95. package/dist/templates/default/src/app/(app)/sessions.tsx +287 -0
  96. package/dist/templates/default/src/app/(app)/welcome.tsx +194 -0
  97. package/dist/templates/default/src/app/+native-intent.tsx +25 -0
  98. package/dist/templates/default/src/app/+not-found.tsx +43 -0
  99. package/dist/templates/default/{app → src/app}/_layout.tsx +28 -37
  100. package/dist/templates/default/src/components/auth/apple-button.tsx +51 -0
  101. package/dist/templates/default/{components → src/components}/auth/otp-verification.tsx +79 -58
  102. package/dist/templates/default/{components → src/components}/auth/password-field.tsx +74 -18
  103. package/dist/templates/default/src/components/auth/segmented-toggle.tsx +71 -0
  104. package/dist/templates/default/src/components/ui/content-unavailable.tsx +81 -0
  105. package/dist/templates/default/src/components/ui/convex-error.tsx +21 -0
  106. package/dist/templates/default/src/components/ui/error-boundary.tsx +89 -0
  107. package/dist/templates/default/{components → src/components}/ui/loading-screen.tsx +5 -4
  108. package/dist/templates/default/{components → src/components}/ui/material.tsx +50 -17
  109. package/dist/templates/default/src/components/ui/offline-banner.tsx +59 -0
  110. package/dist/templates/default/{components → src/components}/ui/prominent-button.tsx +8 -11
  111. package/dist/templates/default/{components → src/components}/ui/skeleton.tsx +31 -13
  112. package/dist/templates/default/src/components/ui/status-text.tsx +64 -0
  113. package/dist/templates/default/src/components/ui/update-banner.tsx +85 -0
  114. package/dist/templates/default/{constants → src/constants}/layout.ts +0 -6
  115. package/dist/templates/default/{constants → src/constants}/theme.ts +49 -64
  116. package/dist/templates/default/{constants → src/constants}/ui.ts +13 -4
  117. package/dist/templates/default/src/hooks/use-debounce.ts +12 -0
  118. package/dist/templates/default/src/hooks/use-deep-link.ts +51 -0
  119. package/dist/templates/default/src/hooks/use-delete-account.ts +35 -0
  120. package/dist/templates/default/src/hooks/use-motion-screen-options.ts +13 -0
  121. package/dist/templates/default/src/hooks/use-network.ts +34 -0
  122. package/dist/templates/default/{hooks → src/hooks}/use-notifications.ts +39 -30
  123. package/dist/templates/default/src/hooks/use-reduce-transparency.ts +30 -0
  124. package/dist/templates/default/{hooks → src/hooks}/use-theme.ts +0 -5
  125. package/dist/templates/default/src/lib/appAttest.ts +78 -0
  126. package/dist/templates/default/src/lib/assets.ts +9 -0
  127. package/dist/templates/default/src/lib/deep-link.ts +82 -0
  128. package/dist/templates/default/{lib → src/lib}/dev-menu.ts +0 -4
  129. package/dist/templates/default/{lib → src/lib}/device.ts +1 -13
  130. package/dist/templates/default/{lib → src/lib}/dynamic-font.ts +13 -10
  131. package/dist/templates/default/src/lib/dynamic-symbol-size.ts +33 -0
  132. package/dist/templates/default/src/lib/masks.ts +21 -0
  133. package/dist/templates/default/src/lib/native-state.ts +20 -0
  134. package/dist/templates/default/{lib → src/lib}/notifications.ts +7 -45
  135. package/dist/templates/default/{lib → src/lib}/preferences.ts +0 -2
  136. package/dist/templates/default/{lib → src/lib}/schemas.ts +19 -16
  137. package/dist/templates/default/src/lib/text-style.ts +20 -0
  138. package/dist/templates/default/{lib → src/lib}/updates.ts +0 -7
  139. package/dist/templates/default/store.config.json +1 -1
  140. package/dist/templates/default/tsconfig.json +3 -1
  141. package/dist/templates/default/vitest.config.ts +8 -1
  142. package/package.json +5 -5
  143. package/dist/templates/default/app/(app)/_layout.tsx +0 -73
  144. package/dist/templates/default/app/(app)/debug.tsx +0 -389
  145. package/dist/templates/default/app/(app)/sessions.tsx +0 -191
  146. package/dist/templates/default/app/(app)/welcome.tsx +0 -140
  147. package/dist/templates/default/app/+native-intent.tsx +0 -14
  148. package/dist/templates/default/app/+not-found.tsx +0 -51
  149. package/dist/templates/default/bun.lock +0 -1860
  150. package/dist/templates/default/components/auth/segmented-toggle.tsx +0 -47
  151. package/dist/templates/default/components/ui/convex-error.tsx +0 -32
  152. package/dist/templates/default/components/ui/error-boundary.tsx +0 -57
  153. package/dist/templates/default/components/ui/offline-banner.tsx +0 -58
  154. package/dist/templates/default/components/ui/status-text.tsx +0 -49
  155. package/dist/templates/default/components/ui/update-banner.tsx +0 -82
  156. package/dist/templates/default/fingerprint.config.js +0 -9
  157. package/dist/templates/default/hooks/use-debounce.ts +0 -20
  158. package/dist/templates/default/hooks/use-deep-link.ts +0 -43
  159. package/dist/templates/default/hooks/use-network.ts +0 -11
  160. package/dist/templates/default/lib/assets.ts +0 -17
  161. package/dist/templates/default/lib/deep-link.ts +0 -71
  162. package/dist/templates/default/patches/PR-368.patch +0 -91
  163. package/dist/templates/default/patches/convex-dev-better-auth-0.12.2.tgz +0 -0
  164. /package/dist/templates/default/{hooks → src/hooks}/use-navigation-tracking.ts +0 -0
  165. /package/dist/templates/default/{hooks → src/hooks}/use-onboarding.ts +0 -0
  166. /package/dist/templates/default/{hooks → src/hooks}/use-reduced-motion.ts +0 -0
  167. /package/dist/templates/default/{hooks → src/hooks}/use-updates.ts +0 -0
  168. /package/dist/templates/default/{lib → src/lib}/a11y.ts +0 -0
  169. /package/dist/templates/default/{lib → src/lib}/app.ts +0 -0
  170. /package/dist/templates/default/{lib → src/lib}/auth-client.ts +0 -0
  171. /package/dist/templates/default/{lib → src/lib}/convex-auth.tsx +0 -0
  172. /package/dist/templates/default/{lib → src/lib}/env.ts +0 -0
  173. /package/dist/templates/default/{lib → src/lib}/haptics.ts +0 -0
  174. /package/dist/templates/default/{lib → src/lib}/storage.ts +0 -0
@@ -0,0 +1,479 @@
1
+ import { useEffect, useState } from "react";
2
+ import Constants from "expo-constants";
3
+ import * as Application from "expo-application";
4
+ import { ApplicationReleaseType } from "expo-application";
5
+ import * as Device from "expo-device";
6
+ import {
7
+ Host,
8
+ ScrollView,
9
+ Button,
10
+ Text,
11
+ VStack,
12
+ HStack,
13
+ Spacer,
14
+ ProgressView,
15
+ LabeledContent,
16
+ ShareLink,
17
+ } from "@expo/ui/swift-ui";
18
+ import {
19
+ accessibilityHidden,
20
+ accessibilityLabel,
21
+ background,
22
+ buttonStyle,
23
+ clipShape,
24
+ cornerRadius,
25
+ defaultScrollAnchor,
26
+ foregroundStyle,
27
+ frame,
28
+ padding,
29
+ progressViewStyle,
30
+ scrollDismissesKeyboard,
31
+ textSelection,
32
+ tint,
33
+ } from "@expo/ui/swift-ui/modifiers";
34
+
35
+ import { executionEnvironment, expoRuntimeVersion, sessionId, debugMode } from "@/lib/device";
36
+ import { isEnabled as updatesEnabled, readLogEntries, type UpdatesLogEntry } from "@/lib/updates";
37
+ import { useAppUpdates } from "@/hooks/use-updates";
38
+ import { useColors } from "@/hooks/use-theme";
39
+ import { useDynamicFont } from "@/lib/dynamic-font";
40
+ import { Button as ButtonTokens } from "@/constants/layout";
41
+
42
+ const RELEASE_TYPE_LABELS: Record<number, string> = {
43
+ [ApplicationReleaseType.UNKNOWN]: "Unknown",
44
+ [ApplicationReleaseType.SIMULATOR]: "Simulator",
45
+ [ApplicationReleaseType.ENTERPRISE]: "Enterprise",
46
+ [ApplicationReleaseType.DEVELOPMENT]: "Development",
47
+ [ApplicationReleaseType.AD_HOC]: "Ad Hoc",
48
+ [ApplicationReleaseType.APP_STORE]: "App Store",
49
+ };
50
+
51
+ type InfoRowProps = {
52
+ label: string;
53
+ value: string;
54
+ valueModifiers?: Parameters<typeof Text>[0]["modifiers"];
55
+ valueColor?: string;
56
+ testID?: string;
57
+ };
58
+
59
+ function InfoRow({ label, value, valueModifiers, valueColor, testID }: InfoRowProps) {
60
+ const colors = useColors();
61
+ const dfont = useDynamicFont();
62
+ return (
63
+ <LabeledContent
64
+ label={
65
+ <Text modifiers={[dfont({ size: 15 }), foregroundStyle(colors.mutedForeground as string)]}>
66
+ {label}
67
+ </Text>
68
+ }
69
+ modifiers={[frame({ maxWidth: Infinity }), padding({ horizontal: 16, vertical: 12 })]}
70
+ >
71
+ <Text
72
+ testID={testID}
73
+ modifiers={[
74
+ dfont({ size: 15, weight: "medium" }),
75
+ foregroundStyle((valueColor ?? colors.foreground) as string),
76
+ textSelection(true),
77
+ ...(valueModifiers ?? []),
78
+ ]}
79
+ >
80
+ {value}
81
+ </Text>
82
+ </LabeledContent>
83
+ );
84
+ }
85
+
86
+ function InfoCard({ children }: { children: React.ReactNode }) {
87
+ const colors = useColors();
88
+ return (
89
+ <VStack
90
+ spacing={0}
91
+ alignment="leading"
92
+ modifiers={[
93
+ frame({ maxWidth: Infinity }),
94
+ background(colors.muted as string),
95
+ cornerRadius(20),
96
+ ]}
97
+ >
98
+ {children}
99
+ </VStack>
100
+ );
101
+ }
102
+
103
+ function useUpdateLogEntries(isUpdatePending: boolean, restartCount: number) {
104
+ const [entries, setEntries] = useState<UpdatesLogEntry[]>([]);
105
+ useEffect(() => {
106
+ if (!updatesEnabled) return;
107
+ let cancelled = false;
108
+ (async () => {
109
+ try {
110
+ const all = await readLogEntries();
111
+ if (!cancelled) setEntries(all);
112
+ } catch {}
113
+ })();
114
+ return () => {
115
+ cancelled = true;
116
+ };
117
+ }, [isUpdatePending, restartCount]);
118
+ return entries;
119
+ }
120
+
121
+ function useApplicationInfo() {
122
+ const [installTime, setInstallTime] = useState<string | null>(null);
123
+ const [iosVendorId, setIosVendorId] = useState<string | null>(null);
124
+ const [iosReleaseType, setIosReleaseType] = useState<string | null>(null);
125
+ const [iosPushEnv, setIosPushEnv] = useState<string | null>(null);
126
+ useEffect(() => {
127
+ Application.getInstallationTimeAsync()
128
+ .then((date) => {
129
+ if (date) setInstallTime(date.toLocaleDateString());
130
+ })
131
+ .catch(() => {});
132
+
133
+ Application.getIosIdForVendorAsync()
134
+ .then(setIosVendorId)
135
+ .catch(() => {});
136
+ Application.getIosApplicationReleaseTypeAsync()
137
+ .then((type) => setIosReleaseType(RELEASE_TYPE_LABELS[type] ?? "Unknown"))
138
+ .catch(() => {});
139
+ Application.getIosPushNotificationServiceEnvironmentAsync()
140
+ .then((env) => setIosPushEnv(env ?? "N/A"))
141
+ .catch(() => {});
142
+ }, []);
143
+
144
+ return { installTime, iosVendorId, iosReleaseType, iosPushEnv };
145
+ }
146
+
147
+ export default function DebugScreen() {
148
+ const dfont = useDynamicFont();
149
+ const colors = useColors();
150
+ const appInfo = useApplicationInfo();
151
+ const updates = useAppUpdates();
152
+ const updateLog = useUpdateLogEntries(updates.isUpdatePending, updates.restartCount);
153
+
154
+ const appVersion =
155
+ Application.nativeApplicationVersion ?? Constants.expoConfig?.version ?? "1.0.0";
156
+ const buildNumber = Application.nativeBuildVersion ?? "1";
157
+ const deviceInfo = Device.modelName
158
+ ? `${Device.manufacturer ?? ""} ${Device.modelName}`.trim()
159
+ : "iOS";
160
+ const osVersion = Device.osVersion ? `iOS ${Device.osVersion}` : "iOS";
161
+
162
+ const sectionLabelModifiers = [
163
+ dfont({ size: 13, weight: "semibold" }),
164
+ foregroundStyle(colors.mutedForeground as string),
165
+ padding({ horizontal: 8, top: 4 }),
166
+ ];
167
+
168
+ return (
169
+ <Host testID="debug-screen" style={{ flex: 1, backgroundColor: colors.background }}>
170
+ <ScrollView
171
+ modifiers={[scrollDismissesKeyboard("interactively"), tint(colors.primary as string)]}
172
+ >
173
+ <VStack
174
+ spacing={20}
175
+ alignment="leading"
176
+ modifiers={[padding({ horizontal: 24, top: 24, bottom: 40 })]}
177
+ >
178
+ <VStack spacing={8} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
179
+ <Text modifiers={sectionLabelModifiers}>BUILD</Text>
180
+ <InfoCard>
181
+ <InfoRow
182
+ testID="debug-version-value"
183
+ label="Version"
184
+ value={`${appVersion} (${buildNumber})`}
185
+ />
186
+ <InfoRow
187
+ testID="debug-sdk-value"
188
+ label="Expo SDK"
189
+ value={Constants.expoConfig?.sdkVersion ?? "Unknown"}
190
+ />
191
+ <InfoRow
192
+ testID="debug-app-name-value"
193
+ label="App name"
194
+ value={Application.applicationName ?? "N/A"}
195
+ />
196
+ <InfoRow
197
+ testID="debug-bundle-id-value"
198
+ label="Bundle id"
199
+ value={Application.applicationId ?? "N/A"}
200
+ />
201
+ <InfoRow
202
+ testID="debug-environment-value"
203
+ label="Environment"
204
+ value={executionEnvironment}
205
+ />
206
+ {appInfo.installTime ? (
207
+ <InfoRow
208
+ testID="debug-installed-value"
209
+ label="Installed"
210
+ value={appInfo.installTime}
211
+ />
212
+ ) : null}
213
+ </InfoCard>
214
+ </VStack>
215
+
216
+ {updatesEnabled && !__DEV__ ? (
217
+ <VStack spacing={8} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
218
+ <Text modifiers={sectionLabelModifiers}>OTA UPDATES</Text>
219
+ <InfoCard>
220
+ <InfoRow
221
+ testID="debug-ota-status-value"
222
+ label="Status"
223
+ value={updates.statusText}
224
+ />
225
+ <InfoRow
226
+ testID="debug-ota-channel-value"
227
+ label="Channel"
228
+ value={updates.currentlyRunning.channel ?? "N/A"}
229
+ />
230
+ <InfoRow
231
+ testID="debug-ota-runtime-value"
232
+ label="Runtime"
233
+ value={updates.currentlyRunning.runtimeVersion ?? expoRuntimeVersion ?? "N/A"}
234
+ />
235
+ <InfoRow
236
+ testID="debug-ota-update-id-value"
237
+ label="Update id"
238
+ value={updates.currentlyRunning.updateId?.slice(0, 8) ?? "Embedded"}
239
+ valueModifiers={[dfont({ size: 13, design: "monospaced" })]}
240
+ />
241
+ <InfoRow
242
+ testID="debug-ota-created-value"
243
+ label="Created"
244
+ value={updates.currentlyRunning.createdAt?.toLocaleDateString() ?? "N/A"}
245
+ />
246
+ <InfoRow
247
+ testID="debug-ota-source-value"
248
+ label="Source"
249
+ value={updates.currentlyRunning.isEmbeddedLaunch ? "Embedded" : "OTA Update"}
250
+ />
251
+ {updates.currentlyRunning.launchDuration != null ? (
252
+ <InfoRow
253
+ testID="debug-ota-launch-time-value"
254
+ label="Launch time"
255
+ value={`${updates.currentlyRunning.launchDuration}ms`}
256
+ />
257
+ ) : null}
258
+ {updates.currentlyRunning.isEmergencyLaunch ? (
259
+ <InfoRow
260
+ testID="debug-ota-emergency-launch-value"
261
+ label="Emergency launch"
262
+ value={updates.currentlyRunning.emergencyLaunchReason ?? "Unknown error"}
263
+ valueColor={colors.warning as string}
264
+ />
265
+ ) : null}
266
+ {updates.isDownloading ? (
267
+ <HStack
268
+ modifiers={[
269
+ frame({ maxWidth: Infinity }),
270
+ padding({ horizontal: 16, vertical: 12 }),
271
+ ]}
272
+ >
273
+ <ProgressView
274
+ testID="debug-ota-download-progress"
275
+ value={updates.downloadProgress ?? undefined}
276
+ modifiers={[
277
+ progressViewStyle("linear"),
278
+ frame({ maxWidth: Infinity }),
279
+ accessibilityLabel("Downloading update"),
280
+ ]}
281
+ />
282
+ </HStack>
283
+ ) : null}
284
+ {(updates.checkError ?? updates.downloadError) ? (
285
+ <InfoRow
286
+ testID="debug-ota-error"
287
+ label="Error"
288
+ value={(updates.checkError ?? updates.downloadError)?.message ?? "Unknown"}
289
+ valueColor={colors.destructive as string}
290
+ />
291
+ ) : null}
292
+ {updates.lastCheckForUpdateTimeSinceRestart ? (
293
+ <InfoRow
294
+ testID="debug-ota-last-checked-value"
295
+ label="Last checked"
296
+ value={updates.lastCheckForUpdateTimeSinceRestart.toLocaleTimeString()}
297
+ />
298
+ ) : null}
299
+ </InfoCard>
300
+ {updates.isUpdateAvailable && !updates.isDownloading ? (
301
+ <UpdateActionButton
302
+ testID="debug-update-download"
303
+ label="Download & install"
304
+ onPress={updates.downloadAndApply}
305
+ colors={colors}
306
+ dfont={dfont}
307
+ />
308
+ ) : !updates.isChecking && !updates.isDownloading ? (
309
+ <UpdateActionButton
310
+ testID="debug-update-check"
311
+ label="Check for updates"
312
+ onPress={updates.checkForUpdate}
313
+ colors={colors}
314
+ dfont={dfont}
315
+ />
316
+ ) : null}
317
+ {updateLog.length > 0 ? (
318
+ // upstream expo/expo#43914: defaultScrollAnchor("bottom") anchors
319
+ // the log view to the newest entry, the standard log-tail UX.
320
+ <ScrollView modifiers={[frame({ height: 240 }), defaultScrollAnchor("bottom")]}>
321
+ <InfoCard>
322
+ {updateLog.map((entry) => (
323
+ <InfoRow
324
+ key={`${entry.timestamp}-${entry.code}`}
325
+ testID={`debug-ota-log-${entry.timestamp}-${entry.code}`}
326
+ label={entry.level.toUpperCase()}
327
+ value={`${entry.code}: ${entry.message}`}
328
+ />
329
+ ))}
330
+ </InfoCard>
331
+ </ScrollView>
332
+ ) : null}
333
+ </VStack>
334
+ ) : null}
335
+
336
+ {appInfo.iosReleaseType || appInfo.iosPushEnv || appInfo.iosVendorId ? (
337
+ <VStack spacing={8} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
338
+ <Text modifiers={sectionLabelModifiers}>iOS</Text>
339
+ <InfoCard>
340
+ {appInfo.iosReleaseType ? (
341
+ <InfoRow
342
+ testID="debug-ios-release-type-value"
343
+ label="Release type"
344
+ value={appInfo.iosReleaseType}
345
+ />
346
+ ) : null}
347
+ {appInfo.iosPushEnv ? (
348
+ <InfoRow
349
+ testID="debug-ios-push-env-value"
350
+ label="Push env"
351
+ value={appInfo.iosPushEnv}
352
+ />
353
+ ) : null}
354
+ {appInfo.iosVendorId ? (
355
+ <InfoRow
356
+ testID="debug-ios-vendor-id-value"
357
+ label="Vendor id"
358
+ value={appInfo.iosVendorId}
359
+ valueModifiers={[dfont({ size: 13, design: "monospaced" })]}
360
+ />
361
+ ) : null}
362
+ </InfoCard>
363
+ </VStack>
364
+ ) : null}
365
+
366
+ <VStack spacing={8} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
367
+ <Text modifiers={sectionLabelModifiers}>RUNTIME</Text>
368
+ <InfoCard>
369
+ <InfoRow
370
+ testID="debug-session-id-value"
371
+ label="Session id"
372
+ value={sessionId.slice(0, 8)}
373
+ valueModifiers={[dfont({ size: 13, design: "monospaced" })]}
374
+ />
375
+ <InfoRow
376
+ testID="debug-build-mode-value"
377
+ label="Build mode"
378
+ value={debugMode ? "Debug" : "Release"}
379
+ />
380
+ </InfoCard>
381
+ </VStack>
382
+
383
+ <VStack spacing={8} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
384
+ <Text modifiers={sectionLabelModifiers}>DEVICE</Text>
385
+ <InfoCard>
386
+ <InfoRow testID="debug-device-model-value" label="Model" value={deviceInfo} />
387
+ <InfoRow testID="debug-device-os-value" label="OS" value={osVersion} />
388
+ </InfoCard>
389
+ </VStack>
390
+
391
+ <ShareLink
392
+ testID="debug-share-build-info"
393
+ item={`App v${appVersion} (${buildNumber})`}
394
+ subject="Build info"
395
+ modifiers={[frame({ maxWidth: Infinity })]}
396
+ >
397
+ <HStack
398
+ alignment="center"
399
+ modifiers={[
400
+ frame({ maxWidth: Infinity, minHeight: ButtonTokens.height }),
401
+ padding({ horizontal: 16 }),
402
+ background(colors.muted as string),
403
+ clipShape("capsule"),
404
+ ]}
405
+ >
406
+ <Spacer />
407
+ <Text
408
+ modifiers={[
409
+ dfont({ size: 16, weight: "medium" }),
410
+ foregroundStyle(colors.foreground as string),
411
+ ]}
412
+ >
413
+ Share build info
414
+ </Text>
415
+ <Spacer />
416
+ </HStack>
417
+ </ShareLink>
418
+
419
+ <HStack modifiers={[frame({ maxWidth: Infinity }), padding({ top: 8 })]}>
420
+ <Spacer />
421
+ <Text
422
+ testID="debug-footer-version-value"
423
+ // duplicates the BUILD > Version row above, so hide the footer
424
+ // stamp from VoiceOver instead of announcing the version twice.
425
+ modifiers={[
426
+ dfont({ size: 12 }),
427
+ foregroundStyle(colors.mutedForeground as string),
428
+ accessibilityHidden(true),
429
+ ]}
430
+ >
431
+ v{appVersion} ({buildNumber})
432
+ </Text>
433
+ <Spacer />
434
+ </HStack>
435
+ </VStack>
436
+ </ScrollView>
437
+ </Host>
438
+ );
439
+ }
440
+
441
+ function UpdateActionButton({
442
+ testID,
443
+ label,
444
+ onPress,
445
+ colors,
446
+ dfont,
447
+ }: {
448
+ testID: string;
449
+ label: string;
450
+ onPress: () => void;
451
+ colors: ReturnType<typeof useColors>;
452
+ dfont: ReturnType<typeof useDynamicFont>;
453
+ }) {
454
+ return (
455
+ <Button
456
+ testID={testID}
457
+ modifiers={[
458
+ buttonStyle("plain"),
459
+ frame({ maxWidth: Infinity }),
460
+ background(colors.muted as string),
461
+ // upstream expo/expo#43158: capsule (and ellipse) were silently rendering
462
+ // as a rectangle before. The fix wires the ShapeType enum through both
463
+ // ClipShapeModifier and MaskModifier.
464
+ clipShape("capsule"),
465
+ ]}
466
+ onPress={onPress}
467
+ >
468
+ <Text
469
+ modifiers={[
470
+ frame({ maxWidth: Infinity, minHeight: ButtonTokens.height }),
471
+ dfont({ size: 16, weight: "medium" }),
472
+ foregroundStyle(colors.foreground as string),
473
+ ]}
474
+ >
475
+ {label}
476
+ </Text>
477
+ </Button>
478
+ );
479
+ }