@ramonclaudio/create-vexpo 0.1.0 → 0.1.2
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.
- package/README.md +10 -10
- package/dist/index.js +8 -7
- package/dist/templates/default/.eas/workflows/asc-events.yml +9 -6
- package/dist/templates/default/.eas/workflows/deploy-production.yml +28 -15
- package/dist/templates/default/.eas/workflows/e2e-tests.yml +3 -2
- package/dist/templates/default/.eas/workflows/pr-preview.yml +12 -21
- package/dist/templates/default/.eas/workflows/release.yml +3 -7
- package/dist/templates/default/.eas/workflows/rollback.yml +54 -28
- package/dist/templates/default/.eas/workflows/rollout.yml +27 -33
- package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +1 -5
- package/dist/templates/default/.eas/workflows/testflight.yml +3 -7
- package/dist/templates/default/.github/workflows/check.yml +20 -12
- package/dist/templates/default/.maestro/launch.yaml +19 -10
- package/dist/templates/default/AGENTS.md +25 -8
- package/dist/templates/default/DESIGN.md +14 -10
- package/dist/templates/default/README.md +83 -78
- package/dist/templates/default/SETUP.md +159 -152
- package/dist/templates/default/__tests__/convex/_auth-harness.test.ts +112 -0
- package/dist/templates/default/__tests__/convex/_harness.ts +132 -0
- package/dist/templates/default/__tests__/convex/appAttest.test.ts +172 -0
- package/dist/templates/default/__tests__/convex/appAttestStore.test.ts +48 -0
- package/dist/templates/default/__tests__/convex/pushTokens-remove.test.ts +106 -0
- package/dist/templates/default/__tests__/convex/pushTokens-upsert.test.ts +146 -0
- package/dist/templates/default/__tests__/convex/users-deleteAccount.test.ts +140 -0
- package/dist/templates/default/__tests__/convex/users-deleteAvatar.test.ts +104 -0
- package/dist/templates/default/__tests__/convex/users-getMe.test.ts +98 -0
- package/dist/templates/default/__tests__/convex/users-getUser.test.ts +120 -0
- package/dist/templates/default/__tests__/convex/users-hardDeleteExpired.test.ts +67 -0
- package/dist/templates/default/__tests__/convex/users-restoreAccount.test.ts +96 -0
- package/dist/templates/default/__tests__/convex/users-updateAvatar.test.ts +92 -0
- package/dist/templates/default/__tests__/convex/users-updateProfile.test.ts +126 -0
- package/dist/templates/default/__tests__/convex/webhook.test.ts +31 -0
- package/dist/templates/default/__tests__/lib/deep-link.test.ts +51 -6
- package/dist/templates/default/__tests__/lib/schemas.test.ts +205 -0
- package/dist/templates/default/__tests__/lib/text-style.test.ts +31 -0
- package/dist/templates/default/_env.example +7 -7
- package/dist/templates/default/_gitattributes +1 -1
- package/dist/templates/default/_gitignore +17 -2
- package/dist/templates/default/_npmrc +7 -0
- package/dist/templates/default/_oxlintrc.json +1 -1
- package/dist/templates/default/app-store/accessibility.config.json +20 -0
- package/dist/templates/default/app-store/privacy.config.json +27 -0
- package/dist/templates/default/app.config.ts +105 -33
- package/dist/templates/default/app.json +1 -9
- package/dist/templates/default/convex/_generated/api.d.ts +12 -0
- package/dist/templates/default/convex/admin.ts +0 -13
- package/dist/templates/default/convex/appAttest.ts +467 -0
- package/dist/templates/default/convex/appAttestStore.ts +141 -0
- package/dist/templates/default/convex/apple.ts +53 -0
- package/dist/templates/default/convex/auth.ts +6 -45
- package/dist/templates/default/convex/constants.ts +2 -7
- package/dist/templates/default/convex/crons.ts +12 -5
- package/dist/templates/default/convex/email.ts +4 -24
- package/dist/templates/default/convex/env.ts +0 -4
- package/dist/templates/default/convex/errors.ts +0 -7
- package/dist/templates/default/convex/functions.ts +0 -26
- package/dist/templates/default/convex/http.ts +3 -5
- package/dist/templates/default/convex/log.ts +2 -25
- package/dist/templates/default/convex/pushSender.ts +145 -0
- package/dist/templates/default/convex/pushTokens.ts +110 -13
- package/dist/templates/default/convex/rateLimit.ts +8 -39
- package/dist/templates/default/convex/schema.ts +48 -5
- package/dist/templates/default/convex/tsconfig.json +1 -0
- package/dist/templates/default/convex/users.ts +143 -61
- package/dist/templates/default/convex/validators.ts +1 -38
- package/dist/templates/default/convex/webhook.ts +1 -31
- package/dist/templates/default/convex.json +1 -2
- package/dist/templates/default/metro.config.js +9 -1
- package/dist/templates/default/package.json +67 -70
- package/dist/templates/default/plugins/README.md +5 -1
- package/dist/templates/default/scripts/README.md +9 -9
- package/dist/templates/default/scripts/_run.mjs +3 -20
- package/dist/templates/default/scripts/clean.ts +81 -69
- package/dist/templates/default/scripts/gen-update-cert.mjs +98 -0
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home)/index.tsx +21 -6
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home,search)/_layout.tsx +9 -8
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(search)/index.tsx +26 -24
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/_layout.tsx +3 -4
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/_layout.tsx +10 -6
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/index.tsx +81 -51
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/preferences.tsx +72 -12
- package/dist/templates/default/src/app/(app)/_layout.tsx +147 -0
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/_layout.tsx +4 -5
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/forgot-password.tsx +15 -9
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/reset-password.tsx +88 -14
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-in.tsx +65 -35
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-up.tsx +131 -196
- package/dist/templates/default/src/app/(app)/debug.tsx +479 -0
- package/dist/templates/default/{app → src/app}/(app)/help.tsx +76 -64
- package/dist/templates/default/{app → src/app}/(app)/linked.tsx +21 -27
- package/dist/templates/default/{app → src/app}/(app)/privacy.tsx +35 -8
- package/dist/templates/default/src/app/(app)/profile/change-password.tsx +264 -0
- package/dist/templates/default/{app/(app)/profile.tsx → src/app/(app)/profile/index.tsx} +179 -255
- package/dist/templates/default/src/app/(app)/restore-account.tsx +192 -0
- package/dist/templates/default/src/app/(app)/sessions.tsx +287 -0
- package/dist/templates/default/src/app/(app)/welcome.tsx +194 -0
- package/dist/templates/default/src/app/+native-intent.tsx +25 -0
- package/dist/templates/default/src/app/+not-found.tsx +43 -0
- package/dist/templates/default/{app → src/app}/_layout.tsx +28 -37
- package/dist/templates/default/src/components/auth/apple-button.tsx +51 -0
- package/dist/templates/default/{components → src/components}/auth/otp-verification.tsx +79 -58
- package/dist/templates/default/{components → src/components}/auth/password-field.tsx +74 -18
- package/dist/templates/default/src/components/auth/segmented-toggle.tsx +71 -0
- package/dist/templates/default/src/components/ui/content-unavailable.tsx +81 -0
- package/dist/templates/default/src/components/ui/convex-error.tsx +21 -0
- package/dist/templates/default/src/components/ui/error-boundary.tsx +89 -0
- package/dist/templates/default/{components → src/components}/ui/loading-screen.tsx +5 -4
- package/dist/templates/default/{components → src/components}/ui/material.tsx +50 -17
- package/dist/templates/default/src/components/ui/offline-banner.tsx +59 -0
- package/dist/templates/default/{components → src/components}/ui/prominent-button.tsx +8 -11
- package/dist/templates/default/{components → src/components}/ui/skeleton.tsx +31 -13
- package/dist/templates/default/src/components/ui/status-text.tsx +64 -0
- package/dist/templates/default/src/components/ui/update-banner.tsx +85 -0
- package/dist/templates/default/{constants → src/constants}/layout.ts +0 -6
- package/dist/templates/default/{constants → src/constants}/theme.ts +49 -64
- package/dist/templates/default/{constants → src/constants}/ui.ts +13 -4
- package/dist/templates/default/src/hooks/use-debounce.ts +12 -0
- package/dist/templates/default/src/hooks/use-deep-link.ts +51 -0
- package/dist/templates/default/src/hooks/use-delete-account.ts +35 -0
- package/dist/templates/default/src/hooks/use-motion-screen-options.ts +13 -0
- package/dist/templates/default/src/hooks/use-network.ts +34 -0
- package/dist/templates/default/{hooks → src/hooks}/use-notifications.ts +39 -30
- package/dist/templates/default/src/hooks/use-reduce-transparency.ts +30 -0
- package/dist/templates/default/{hooks → src/hooks}/use-theme.ts +0 -5
- package/dist/templates/default/src/lib/appAttest.ts +78 -0
- package/dist/templates/default/src/lib/assets.ts +9 -0
- package/dist/templates/default/src/lib/deep-link.ts +82 -0
- package/dist/templates/default/{lib → src/lib}/dev-menu.ts +0 -4
- package/dist/templates/default/{lib → src/lib}/device.ts +1 -13
- package/dist/templates/default/{lib → src/lib}/dynamic-font.ts +13 -10
- package/dist/templates/default/src/lib/dynamic-symbol-size.ts +33 -0
- package/dist/templates/default/src/lib/masks.ts +21 -0
- package/dist/templates/default/src/lib/native-state.ts +20 -0
- package/dist/templates/default/{lib → src/lib}/notifications.ts +7 -45
- package/dist/templates/default/{lib → src/lib}/preferences.ts +0 -2
- package/dist/templates/default/{lib → src/lib}/schemas.ts +19 -16
- package/dist/templates/default/src/lib/text-style.ts +20 -0
- package/dist/templates/default/{lib → src/lib}/updates.ts +0 -7
- package/dist/templates/default/store.config.json +1 -1
- package/dist/templates/default/tsconfig.json +3 -1
- package/dist/templates/default/vitest.config.ts +8 -1
- package/package.json +5 -5
- package/dist/templates/default/app/(app)/_layout.tsx +0 -73
- package/dist/templates/default/app/(app)/debug.tsx +0 -389
- package/dist/templates/default/app/(app)/sessions.tsx +0 -191
- package/dist/templates/default/app/(app)/welcome.tsx +0 -140
- package/dist/templates/default/app/+native-intent.tsx +0 -14
- package/dist/templates/default/app/+not-found.tsx +0 -51
- package/dist/templates/default/bun.lock +0 -1860
- package/dist/templates/default/components/auth/segmented-toggle.tsx +0 -47
- package/dist/templates/default/components/ui/convex-error.tsx +0 -32
- package/dist/templates/default/components/ui/error-boundary.tsx +0 -57
- package/dist/templates/default/components/ui/offline-banner.tsx +0 -58
- package/dist/templates/default/components/ui/status-text.tsx +0 -49
- package/dist/templates/default/components/ui/update-banner.tsx +0 -82
- package/dist/templates/default/fingerprint.config.js +0 -9
- package/dist/templates/default/hooks/use-debounce.ts +0 -20
- package/dist/templates/default/hooks/use-deep-link.ts +0 -43
- package/dist/templates/default/hooks/use-network.ts +0 -11
- package/dist/templates/default/lib/assets.ts +0 -17
- package/dist/templates/default/lib/deep-link.ts +0 -71
- package/dist/templates/default/patches/PR-368.patch +0 -91
- package/dist/templates/default/patches/convex-dev-better-auth-0.12.2.tgz +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-navigation-tracking.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-onboarding.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-reduced-motion.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-updates.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/a11y.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/app.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/auth-client.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/convex-auth.tsx +0 -0
- /package/dist/templates/default/{lib → src/lib}/env.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/haptics.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/storage.ts +0 -0
|
@@ -3,26 +3,34 @@ name: Check
|
|
|
3
3
|
on:
|
|
4
4
|
pull_request:
|
|
5
5
|
branches: [main]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
6
8
|
|
|
7
9
|
jobs:
|
|
8
10
|
check:
|
|
9
11
|
runs-on: ubuntu-latest
|
|
10
12
|
steps:
|
|
11
|
-
- uses: actions/checkout@
|
|
12
|
-
- uses:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- run:
|
|
17
|
-
- run:
|
|
18
|
-
- run:
|
|
13
|
+
- uses: actions/checkout@v6
|
|
14
|
+
- uses: actions/setup-node@v6
|
|
15
|
+
with:
|
|
16
|
+
node-version: 22
|
|
17
|
+
cache: npm
|
|
18
|
+
- run: npm ci
|
|
19
|
+
- run: npx expo-doctor
|
|
20
|
+
- run: npm run typecheck
|
|
21
|
+
- run: npm run lint
|
|
22
|
+
- run: npm run format:check
|
|
23
|
+
- run: npm run test
|
|
19
24
|
|
|
20
25
|
fingerprint:
|
|
21
26
|
runs-on: ubuntu-latest
|
|
22
27
|
steps:
|
|
23
|
-
- uses: actions/checkout@
|
|
28
|
+
- uses: actions/checkout@v6
|
|
24
29
|
with:
|
|
25
30
|
fetch-depth: 0
|
|
26
|
-
- uses:
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
- uses: actions/setup-node@v6
|
|
32
|
+
with:
|
|
33
|
+
node-version: 22
|
|
34
|
+
cache: npm
|
|
35
|
+
- run: npm ci
|
|
36
|
+
- run: npm run fp:diff
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
# Smoke test: app
|
|
1
|
+
# Smoke test: the app cold-starts and renders without crashing, plus a screenshot.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# The UI is @expo/ui SwiftUI. `testID` props map to the native accessibilityIdentifier
|
|
4
|
+
# and DO surface to Maestro as `resource-id`, so `assertVisible: { id: "..." }` resolves
|
|
5
|
+
# against any tagged control or screen. Every interactive control and screen root
|
|
6
|
+
# carries a stable id (grep `testID=` under src/), so real id-based flows are viable,
|
|
7
|
+
# not just this no-crash smoke test.
|
|
8
|
+
#
|
|
9
|
+
# Driving note: Maestro taps an element's center. Buttons whose center holds content
|
|
10
|
+
# (the profile card, the ProminentButton submits) tap fine by `id`. The capsule list
|
|
11
|
+
# rows put a Spacer in the middle, so tap those by their label text (`tapOn: "Debug"`),
|
|
12
|
+
# not by `id`, or the tap lands on empty space. Native tab-bar taps can be flaky;
|
|
13
|
+
# assert-then-tap or retry.
|
|
14
|
+
#
|
|
15
|
+
# Maestro auto-derives the appId from the build's bundle id, so the same flow
|
|
16
|
+
# runs against the dev simulator binary (`com.example.<pkg>` fallback in
|
|
17
|
+
# app.config.ts) and the production binary (`com.<your-team>.<app>`) without edits.
|
|
7
18
|
#
|
|
8
19
|
# Run locally: `maestro test .maestro/launch.yaml`
|
|
9
20
|
# Run on EAS: triggered by `.eas/workflows/e2e-tests.yml` on every PR.
|
|
@@ -11,8 +22,6 @@ appId: ${MAESTRO_APP_ID}
|
|
|
11
22
|
---
|
|
12
23
|
- launchApp:
|
|
13
24
|
clearState: true
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- assertVisible: "Your new app starts here."
|
|
18
|
-
- takeScreenshot: welcome
|
|
25
|
+
- waitForAnimationToEnd:
|
|
26
|
+
timeout: 15000
|
|
27
|
+
- takeScreenshot: launch
|
|
@@ -10,10 +10,9 @@ hand. These are conventions, not magic.
|
|
|
10
10
|
calls. Everything goes through `convex/` (server) and `convex/react`
|
|
11
11
|
(client). After running `npx convex ai-files install`, read
|
|
12
12
|
`convex/_generated/ai/guidelines.md` before touching anything in `convex/`.
|
|
13
|
-
- **Auth.** Better Auth via `@convex-dev/better-auth`.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **Mobile.** Expo SDK 56 canary, RN 0.85, React 19. **iOS only today.**
|
|
13
|
+
- **Auth.** Better Auth via `@convex-dev/better-auth@0.12.3`. Email
|
|
14
|
+
verification is gated on the `REQUIRE_EMAIL_VERIFICATION` Convex env var.
|
|
15
|
+
- **Mobile.** Expo SDK 56, RN 0.85, React 19. **iOS only today.**
|
|
17
16
|
Native UI exclusively via `@expo/ui/swift-ui`. No NativeWind, no Tailwind,
|
|
18
17
|
no `react-native-paper`.
|
|
19
18
|
- **CI/CD.** EAS Workflows (`.eas/workflows/*.yml`) for everything
|
|
@@ -45,7 +44,7 @@ hand. These are conventions, not magic.
|
|
|
45
44
|
|
|
46
45
|
- **Convex functions**: every query/mutation needs both server-side
|
|
47
46
|
validators and matching client types. The `convex/_generated/` directory is
|
|
48
|
-
the contract. Run `
|
|
47
|
+
the contract. Run `npx convex codegen` after schema or function changes.
|
|
49
48
|
- **HTTP routes** (`convex/http.ts`): every public endpoint must use
|
|
50
49
|
`convex/webhook.ts` `withWebhook()` factory for HMAC verification + body
|
|
51
50
|
cap + structured logging, or document why it doesn't. Inbound webhooks are
|
|
@@ -56,18 +55,36 @@ hand. These are conventions, not magic.
|
|
|
56
55
|
`APPLE_KEY_ID`, `APPLE_SERVICES_ID`, `CONVEX_DEPLOY_KEY`).
|
|
57
56
|
- **Push notifications**: only work on a physical device. iOS Simulator does
|
|
58
57
|
not deliver APNs. Don't try to test push flows in the simulator.
|
|
59
|
-
- **`store.config.json`**: ships with placeholder values. `
|
|
58
|
+
- **`store.config.json`**: ships with placeholder values. `npx vexpo
|
|
60
59
|
rebrand` fills them in. App Review will reject builds with placeholder
|
|
61
60
|
contact info.
|
|
62
61
|
|
|
63
62
|
## When in doubt
|
|
64
63
|
|
|
65
|
-
- Run `
|
|
64
|
+
- Run `npx vexpo doctor` to check that `.env.local`, Convex env, EAS env,
|
|
66
65
|
and `app.config.ts` agree.
|
|
67
|
-
- Use `
|
|
66
|
+
- Use `npx eas <subcommand>` for canonical EAS operations. **Don't reinvent
|
|
68
67
|
EAS.** That's the vexpo design principle.
|
|
69
68
|
- Read `SETUP.md` for the long-form orchestration walkthrough.
|
|
70
69
|
|
|
70
|
+
## Agent setup
|
|
71
|
+
|
|
72
|
+
- **Claude Code:** install Expo's official agent skills with
|
|
73
|
+
`/plugin marketplace add expo/skills` then `/plugin install expo`. For
|
|
74
|
+
Codex, Cursor, or any other agent, run `npx skills add expo/skills`. The
|
|
75
|
+
Convex agent skills install separately via `npx convex ai-files install`.
|
|
76
|
+
- **Pre-approved commands:** `.claude/settings.json` allows read-only
|
|
77
|
+
`git`/`expo`/`eas`/`convex`/`vexpo` calls + the project's `npm run`
|
|
78
|
+
scripts (`typecheck`, `lint`, `test`, `format`, `dev`, `fp`) without
|
|
79
|
+
per-step permission prompts. Destructive ops (`git push`, `git reset`,
|
|
80
|
+
`npm install`, `expo deploy`) still ask.
|
|
81
|
+
- **EAS Convex bootstrap:** `eas integrations:convex:connect` is the
|
|
82
|
+
upstream SDK 56 path for provisioning a Convex backend, writing
|
|
83
|
+
`CONVEX_DEPLOY_KEY` + `EXPO_PUBLIC_CONVEX_URL`, and registering the env
|
|
84
|
+
vars across Production/Preview/Development. `npx vexpo full` is the
|
|
85
|
+
broader path that also wires Better Auth, Resend, and App Store identity
|
|
86
|
+
in one shot. Use vexpo for a complete starter, EAS for Convex alone.
|
|
87
|
+
|
|
71
88
|
<!-- convex-ai-start -->
|
|
72
89
|
|
|
73
90
|
This project uses [Convex](https://convex.dev) as its backend.
|
|
@@ -158,7 +158,9 @@ In-app typography uses the same Geist face but caps at Bold (700) for nav titles
|
|
|
158
158
|
| Body emphasis | 600 SemiBold | 16 | 24 | 0 |
|
|
159
159
|
| Caption | 400 Regular | 13-14 | 20-22 | 0 |
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
Every label scales with the user's Larger Text accessibility setting via `src/lib/dynamic-font.ts`. The `useDynamicFont` hook maps the declared point size to a SwiftUI `Font.TextStyle` and passes it to `@expo/ui/swift-ui`'s `font()` modifier (upstream `expo/expo#46007`), so the Geist family rides Apple's Dynamic Type curves natively. SwiftUI rescales the text when the setting changes, no JS re-render. The declared size is the base, so default-size rendering is unchanged. Don't bypass this hook. If you do, your text stops scaling at Larger Text.
|
|
162
|
+
|
|
163
|
+
Native compact chrome does not scale with Dynamic Type, by iOS design, and that is not a bug to chase. Segmented `Picker` titles (Preferences' Appearance and Reduce Motion, the auth Sign in/Sign up and Email/Username toggles), inline navigation-bar titles, and tab-bar labels stay fixed at accessibility text sizes. SwiftUI's `pickerStyle("segmented")` renders titles in `UISegmentedControl`'s own font and ignores any `dfont` on the segments or the picker (verified on device). `react-native-screens` builds nav-title fonts at a fixed size (`scaleMultiplier: 1.0`, no `UIFontMetrics`), matching UIKit's compact inline title. Neither is `@expo/ui` or template code forcing it. iOS exposes this chrome to accessibility through the large-content HUD on long-press, not inline growth, so the behavior is correct and consistent. To make a control grow anyway you must leave the native control: reflow the segmented picker to a vertical option list at accessibility sizes, or use a large title (which does scale) instead of an inline one.
|
|
162
164
|
|
|
163
165
|
Geist's variable axes are not currently exercised at runtime (we use static TTFs for individual weights via `expo-font`). The variable TTF ships in `assets/fonts/` for future use. If runtime needs variable weight interpolation, point `expo-font` at the variable file and use SwiftUI's `.fontWeight()` modifier.
|
|
164
166
|
|
|
@@ -269,17 +271,19 @@ The system uses `@expo/ui/swift-ui` primitives exclusively for native rendering.
|
|
|
269
271
|
| `ProgressView` | Spinner or determinate progress | Loading states |
|
|
270
272
|
| `ContentUnavailableView` | Empty state with SF Symbol + title + description | Empty home, no results |
|
|
271
273
|
|
|
274
|
+
Text inputs bind `text` to a `useNativeState("")` and mask synchronously: a `"worklet"` `onTextChange` rewrites the field on the same frame the keystroke lands (digits-only OTP, lowercase usernames), so the raw character never paints. Reusable masks live in `lib/masks.ts`.
|
|
275
|
+
|
|
272
276
|
### Custom composition (in `components/ui/`)
|
|
273
277
|
|
|
274
|
-
| Component
|
|
275
|
-
|
|
|
276
|
-
| `Material`
|
|
277
|
-
| `OfflineBanner`
|
|
278
|
-
| `LoadingScreen`
|
|
279
|
-
| `ErrorBoundary`
|
|
280
|
-
| `ConvexError`
|
|
281
|
-
| `
|
|
282
|
-
| `StatusText`
|
|
278
|
+
| Component | Purpose | Notes |
|
|
279
|
+
| :------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------- |
|
|
280
|
+
| `Material` | Translucent surface with iOS 26+ Liquid Glass / iOS 16.4-25 BlurView fallback | Reserve for navigation chrome only |
|
|
281
|
+
| `OfflineBanner` | Top-of-screen notification banner using `Material` chrome variant | Shows when network unavailable |
|
|
282
|
+
| `LoadingScreen` | Brand-icon + spinner, themed by appearance | Suspense fallback |
|
|
283
|
+
| `ErrorBoundary` | Top-level crash boundary with brand recovery UI | Wraps each route segment |
|
|
284
|
+
| `ConvexError` | Maps Convex errors to user-readable copy | Used in error displays |
|
|
285
|
+
| `SkeletonProfile` / `SkeletonSessions` | Static loading placeholders. No animation, so nothing to suppress under Reduce Motion. Filler bars carry an empty `accessibilityLabel` so VoiceOver skips the placeholder shapes. | Profile loading, sessions loading |
|
|
286
|
+
| `StatusText` | `ErrorText` + `SuccessText` with accessibility announcements | Form feedback |
|
|
283
287
|
|
|
284
288
|
### Custom hooks (in `hooks/`)
|
|
285
289
|
|
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Expo SDK 56 + Convex + Better Auth + Resend, wired end-to-end for iOS. Native SwiftUI via `@expo/ui/swift-ui`, email + password + email OTP + Apple Sign In, APNs push, Universal Links, profile + active sessions with avatar uploads and device-by-device revocation. EAS for the whole build surface: 10 workflows, fingerprint-gated OTA-or-build, TestFlight, rollback, rollout, ASC events, and the Apple Sign In JWT rotation cron.
|
|
4
4
|
|
|
5
|
+
A lot of the SwiftUI modifiers the template reaches for, `clipShape("capsule")`, `defaultScrollAnchorForRole`, `scrollTargetBehavior`, `scrollPosition`, `textInputAutocapitalization`, `textContentType`, the `Alert` component, the Dynamic Type pair (`textStyle` scaling on `font`, `dynamicTypeSize` bounds), `accessibilityHidden`, are upstream PRs we wrote and got merged into `expo/expo`. Full ledger in [`../../docs/UPSTREAM.md`](../../docs/UPSTREAM.md).
|
|
6
|
+
|
|
5
7
|
## Quick start
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
|
|
10
|
+
npm install
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
npx vexpo lite # 60-second path: Convex + Better Auth, simulator-ready
|
|
13
|
+
npx vexpo lite --new # same, plus a Convex signup walkthrough if you don't have one
|
|
12
14
|
```
|
|
13
15
|
|
|
14
16
|
Then in two terminals:
|
|
15
17
|
|
|
16
18
|
```bash
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
npm run convex:dev # terminal 1
|
|
20
|
+
npm run ios # terminal 2
|
|
19
21
|
```
|
|
20
22
|
|
|
21
23
|
Lite mode skips Apple / EAS / Resend entirely. `REQUIRE_EMAIL_VERIFICATION` is off on Convex so sign-up auto-verifies, the user lands in the app with one tap, and the UI hides the OTP / password-reset / change-email flows that need Resend to work.
|
|
@@ -23,33 +25,38 @@ Lite mode skips Apple / EAS / Resend entirely. `REQUIRE_EMAIL_VERIFICATION` is o
|
|
|
23
25
|
When you're ready to ship, swap `lite` for `full`:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
npx vexpo full # provisions Resend, Apple Sign In, EAS, rebrand wizard
|
|
29
|
+
npx vexpo full --new # same, plus walks Apple / Convex / Expo / Resend signups
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
`full` writes `.env.local`, sets Convex env vars (`REQUIRE_EMAIL_VERIFICATION=true` once Resend is wired), validates the ASC API key, signs the SIWA JWT, runs `eas init` and `eas env:push`, prompts the rebrand wizard. Prints the `eas build` command at the end. vexpo doesn't run it for you, you run `
|
|
32
|
+
`full` writes `.env.local`, sets Convex env vars (`REQUIRE_EMAIL_VERIFICATION=true` once Resend is wired), validates the ASC API key, signs the SIWA JWT, runs `eas init` and `eas env:push`, prompts the rebrand wizard. Prints the `eas build` command at the end. vexpo doesn't run it for you, you run `npx eas build -p ios --profile production --auto-submit-with-profile testflight` when you're ready.
|
|
31
33
|
|
|
32
|
-
Run `
|
|
34
|
+
Run `npx vexpo doctor` any time to auth-check every credential against the real service and cross-reference IDs across `.env.local`, Convex env, EAS env, and `app.config.ts`. Catches "wrong .p8 from another project" or ".env.prod copied from a different fork" in seconds.
|
|
33
35
|
|
|
34
36
|
Long-form walkthrough with every prompt, every env-var alternative, and recovery paths: [`SETUP.md`](./SETUP.md).
|
|
35
37
|
|
|
36
38
|
## What's wired up
|
|
37
39
|
|
|
38
|
-
- Convex backend with reactive queries, storage, real-time sync, and rate
|
|
39
|
-
- Better Auth via `@convex-dev/better-auth` (sessions, accounts
|
|
40
|
+
- Convex backend with reactive queries, storage, real-time sync, and `@convex-dev/rate-limiter` on every application mutation. Auth-route rate limits ship via Better Auth at the HTTP layer.
|
|
41
|
+
- Better Auth via `@convex-dev/better-auth` (sessions, accounts; per-device revocation via `session.userAgent`)
|
|
42
|
+
- App Attest device attestation via `@expo/app-integrity` with server-side verification in Convex
|
|
40
43
|
- Resend via `@convex-dev/resend` for OTP, password reset, change-email, with webhook delivery events
|
|
41
|
-
- Apple Sign In via Apple's official `AppleAuthenticationButton`, HIG-compliant BLACK
|
|
44
|
+
- Apple Sign In via Apple's official `AppleAuthenticationButton`, HIG-compliant (BLACK in dark mode, WHITE in light; `WHITE_OUTLINE` isn't used), SIWA Services ID + ES256 JWT signing (180-day expiry, auto-rotated every 90 days)
|
|
42
45
|
- APNs push via `expo-notifications` with token registration on sign-in
|
|
43
46
|
- Apple Universal Links from Convex's HTTP router (AASA at `/.well-known/apple-app-site-association`)
|
|
44
47
|
- Profile editing with avatar uploads to Convex storage
|
|
45
48
|
- Active sessions screen with device-by-device revocation
|
|
46
|
-
-
|
|
49
|
+
- Account soft-delete with a 30-day grace window and a restore-or-confirm screen on next sign-in
|
|
50
|
+
- Pull-to-refresh on home and sessions, plus an interactive update banner on iOS 26
|
|
51
|
+
- Theme switching, haptics toggle, reduced motion, VoiceOver labels everywhere (decorative views hidden from the rotor)
|
|
52
|
+
- Native Dynamic Type end to end: every label scales with the Larger Text setting via `textStyle`, bounded with `dynamicTypeSize` ceilings on the seven fixed-geometry controls that would clip instead of wrap
|
|
47
53
|
- Spotlight-style search tab (debounced, scored, keyword-aware)
|
|
48
54
|
- Skeleton placeholders during initial query loads
|
|
49
55
|
- Debug screen at `/debug` gated by toggle, off in production by default
|
|
50
56
|
- Liquid Glass on iOS 26+ via `expo-glass-effect`, UIVisualEffectView blur fallback on iOS 16.4-25 via `expo-blur`, both behind a `<Material>` primitive
|
|
51
|
-
-
|
|
52
|
-
-
|
|
57
|
+
- OTA updates code-signed end-to-end (`expo-updates` code signing; generate the cert with `npm run updates:gen-cert`), so only signed bundles install
|
|
58
|
+
- EAS Build / Update / Submit / Metadata. `runtimeVersion: { policy: "fingerprint" }` (auto-bumps on native code changes), branch/channel model, `appVersionSource: "remote"`. ASC API key managed by EAS (`eas credentials -p ios`), no `eas.json` patches. `@expo/fingerprint >= 0.19.3` makes the policy deterministic across machines and CI out of the box, so the earlier `fingerprint.config.js` + `.fingerprintignore` jsi knobs were dropped.
|
|
59
|
+
- 10 EAS Workflows under `.eas/workflows/`: dev builds, PR previews with `github-comment` + QR + fingerprint-gated OTA-or-build, production deploy, TestFlight on `beta/*`, manual rollback / rollout, ASC event triggers to Slack, the SIWA JWT rotation cron, Maestro E2E. PR previews, Maestro E2E, and the production deploy are manual-only (`workflow_dispatch`) by default: the first two to conserve EAS build credits (restore their `pull_request` triggers to run on every PR), the deploy so a merge to `main` can't build, submit, and ship an OTA by surprise (add a `push: main` trigger if you want that)
|
|
53
60
|
- GitHub Actions for general-purpose checks: typecheck, lint, format, tests, fingerprint diff on PR + push to `main`
|
|
54
61
|
|
|
55
62
|
## Pre-reqs
|
|
@@ -62,92 +69,90 @@ Long-form walkthrough with every prompt, every env-var alternative, and recovery
|
|
|
62
69
|
## Scripts
|
|
63
70
|
|
|
64
71
|
```
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
72
|
+
npm run dev Metro + dev client
|
|
73
|
+
npm run start Metro with cleared cache
|
|
74
|
+
npm run ios Clean prebuild + compile + run on simulator
|
|
75
|
+
npm run ios:dev Run on simulator (skip prebuild, fast)
|
|
76
|
+
npm run ios:device Clean prebuild + compile + run on physical device
|
|
77
|
+
npm run prebuild Generate iOS native project from config
|
|
78
|
+
|
|
79
|
+
npm run convex:dev Convex dev server (watch mode)
|
|
80
|
+
npm run convex:deploy Deploy Convex functions to production
|
|
81
|
+
npm run convex:logs Tail dev deployment logs
|
|
82
|
+
npm run convex:logs:prod Tail prod deployment logs
|
|
83
|
+
npm run convex:env List dev env vars
|
|
84
|
+
npm run convex:env:prod List prod env vars
|
|
85
|
+
npm run convex:insights OCC conflicts + resource limits (dev)
|
|
86
|
+
npm run convex:insights:prod Same for prod
|
|
87
|
+
npm run convex:dashboard Open the Convex dashboard
|
|
88
|
+
npm run convex:codegen Regenerate convex/_generated/
|
|
89
|
+
|
|
90
|
+
npm run eas:dev eas build -p ios --profile development:simulator
|
|
91
|
+
npm run eas:dev:device eas build -p ios --profile development:device
|
|
92
|
+
npm run eas:tf eas build -p ios --profile production --auto-submit-with-profile testflight
|
|
93
|
+
npm run eas:prod eas build -p ios --profile production
|
|
94
|
+
npm run metadata:lint eas metadata:lint
|
|
95
|
+
npm run metadata:push eas metadata:lint && eas metadata:push
|
|
96
|
+
npm run metadata:pull eas metadata:pull
|
|
97
|
+
npm run env:pull eas env:pull --environment development
|
|
98
|
+
npm run env:pull:prod eas env:pull --environment production
|
|
99
|
+
|
|
100
|
+
npm run clean Trash node_modules, ios, caches, then reinstall
|
|
101
|
+
npm run clean:metro Trash Metro/Babel/Haste caches only
|
|
102
|
+
npm run clean:state Wipe .setup-state.json + standard clean
|
|
103
|
+
npm run typecheck tsc --noEmit
|
|
104
|
+
npm run lint oxlint
|
|
105
|
+
npm run format oxfmt
|
|
106
|
+
npm run format:check oxfmt --check
|
|
107
|
+
npm run test vitest run
|
|
108
|
+
npm run test:watch vitest
|
|
109
|
+
npm run fp Print Expo fingerprint hash
|
|
110
|
+
npm run fp:diff Diff fingerprint vs base ref
|
|
111
|
+
npm run upgrade expo install expo@next && expo install --fix
|
|
112
|
+
npm run upgrade:stable expo install expo@latest && expo install --fix
|
|
106
113
|
```
|
|
107
114
|
|
|
108
|
-
Setup is one-shot, not a `package.json` script. Run `
|
|
115
|
+
Setup is one-shot, not a `package.json` script. Run `npx vexpo lite` / `npx vexpo full` / `npx vexpo doctor` directly. All deletions go through `trash` (macOS Trash, recoverable).
|
|
109
116
|
|
|
110
117
|
## Project structure
|
|
111
118
|
|
|
112
119
|
```
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
src/
|
|
121
|
+
app/ Expo Router screens
|
|
122
|
+
(app)/ Authenticated stack (auth modal, tabs, profile, ...)
|
|
123
|
+
(tabs)/ Home, search, settings
|
|
124
|
+
auth/ Sign in, sign up, forgot/reset password (modal)
|
|
125
|
+
profile/ index.tsx + change-password.tsx
|
|
126
|
+
welcome.tsx, sessions.tsx, restore-account.tsx, debug.tsx, ...
|
|
127
|
+
+native-intent.tsx Deep link validation
|
|
128
|
+
+not-found.tsx 404 fallback
|
|
129
|
+
components/ Reusable UI (auth/, ui/)
|
|
130
|
+
constants/ Theme, layout, UI tokens
|
|
131
|
+
hooks/ useNetwork, useTheme, useUpdates, etc.
|
|
132
|
+
lib/ Auth client, haptics, env, deep links, native state
|
|
122
133
|
convex/ Convex backend
|
|
123
|
-
hooks/ useNetwork, useTheme, useUpdates, etc.
|
|
124
|
-
lib/ Auth client, haptics, env, deep links
|
|
125
134
|
plugins/
|
|
126
|
-
with-auto-signing.js
|
|
135
|
+
with-auto-signing.js CODE_SIGN_STYLE=Automatic + DEVELOPMENT_TEAM
|
|
127
136
|
with-pod-deployment-target.js Forces every pod to iOS 16.4
|
|
128
137
|
.eas/workflows/ 10 EAS Workflow YAML files
|
|
129
138
|
.github/workflows/check.yml Typecheck, lint, format, tests, fingerprint diff
|
|
130
139
|
scripts/
|
|
131
140
|
clean.ts Trash + reinstall
|
|
132
141
|
rotate-apple-jwt.mjs CI: re-sign JWT from env vars
|
|
133
|
-
__tests__/ Convex
|
|
142
|
+
__tests__/ Convex + lib unit tests (validators, HMAC, deep link, schemas)
|
|
134
143
|
```
|
|
135
144
|
|
|
136
145
|
## Long-form docs
|
|
137
146
|
|
|
138
147
|
- [`SETUP.md`](./SETUP.md). Every setup phase with full prompts, env-var alternatives for non-interactive runs, recovery paths.
|
|
139
148
|
- [`DESIGN.md`](./DESIGN.md). Color palette, typography, spacing, radius ladder, materials, the SwiftUI primitives + custom composition surface.
|
|
149
|
+
- [`../../docs/UPSTREAM.md`](../../docs/UPSTREAM.md). Every upstream PR powering the template: `@expo/ui/swift-ui` modifiers, `expo-modules-core` fixes, `expo-tools` resolution, CI workflow guards.
|
|
140
150
|
- [`AGENTS.md`](./AGENTS.md). Guidance for AI coding agents working in this codebase.
|
|
141
151
|
|
|
142
152
|
## Version pinning
|
|
143
153
|
|
|
144
|
-
Every `expo-*` package
|
|
145
|
-
|
|
146
|
-
`package.json` overrides:
|
|
147
|
-
|
|
148
|
-
- `@better-auth/passkey: 1.6.9` prevents better-auth from pulling SolidJS deps that break Metro.
|
|
149
|
-
- `@expo/ui: 56.0.0-canary-20260506-03817f5` unifies the version across transitive deps.
|
|
154
|
+
Every `expo-*` package tracks the same SDK 56 release. Mismatched versions cause subtle runtime crashes. `npm run upgrade:stable` runs `expo install expo@latest && expo install --fix` to roll all of them forward together; `npm run upgrade` (`expo@next`) tracks the next SDK preview.
|
|
150
155
|
|
|
151
|
-
|
|
156
|
+
`@convex-dev/better-auth@0.12.0` is the minimum compatible with `better-auth@1.6.x` (peer-dep range is `>=1.6.9 <1.7.0`). Earlier versions peer-dep `better-auth <1.6.0` and reject the `mode` field newer better-auth adds to adapter queries, breaking signup. The template pins `better-auth@1.6.16` + `@convex-dev/better-auth@0.12.3`.
|
|
152
157
|
|
|
153
|
-
|
|
158
|
+
`convex` is pinned `~1.40.0` for now: 1.41.0 adds a `transactionLimits` options param to `runMutation` that `@convex-dev/resend@0.2.4`'s ctx types reject, which breaks the `convex/http.ts` typecheck. Widen the range back to `^1.40.0` once resend's types accept 1.41.
|