@codyswann/lisa 2.110.1 → 2.112.0

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 (193) hide show
  1. package/package.json +1 -1
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa/commands/repair-intake.md +2 -2
  5. package/plugins/lisa/rules/config-resolution.md +2 -2
  6. package/plugins/lisa/skills/repair-intake/SKILL.md +86 -9
  7. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  8. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  9. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  11. package/plugins/lisa-expo/.mcp.json +3 -3
  12. package/plugins/lisa-expo/THIRD-PARTY-NOTICES.md +57 -0
  13. package/plugins/lisa-expo/commands/e2e-coverage-gaps.md +7 -0
  14. package/plugins/lisa-expo/commands/exploratory-qa.md +2 -2
  15. package/plugins/lisa-expo/skills/add-app-clip/SKILL.md +280 -0
  16. package/plugins/lisa-expo/skills/add-app-clip/agents/openai.yaml +4 -0
  17. package/plugins/lisa-expo/skills/add-app-clip/references/native-module.md +96 -0
  18. package/plugins/lisa-expo/skills/building-native-ui/SKILL.md +321 -0
  19. package/plugins/lisa-expo/skills/building-native-ui/agents/openai.yaml +4 -0
  20. package/plugins/lisa-expo/skills/building-native-ui/references/animations.md +220 -0
  21. package/plugins/lisa-expo/skills/building-native-ui/references/controls.md +272 -0
  22. package/plugins/lisa-expo/skills/building-native-ui/references/form-sheet.md +253 -0
  23. package/plugins/lisa-expo/skills/building-native-ui/references/gradients.md +106 -0
  24. package/plugins/lisa-expo/skills/building-native-ui/references/icons.md +213 -0
  25. package/plugins/lisa-expo/skills/building-native-ui/references/media.md +198 -0
  26. package/plugins/lisa-expo/skills/building-native-ui/references/route-structure.md +229 -0
  27. package/plugins/lisa-expo/skills/building-native-ui/references/search.md +248 -0
  28. package/plugins/lisa-expo/skills/building-native-ui/references/storage.md +121 -0
  29. package/plugins/lisa-expo/skills/building-native-ui/references/tabs.md +433 -0
  30. package/plugins/lisa-expo/skills/building-native-ui/references/toolbar-and-headers.md +284 -0
  31. package/plugins/lisa-expo/skills/building-native-ui/references/visual-effects.md +197 -0
  32. package/plugins/lisa-expo/skills/building-native-ui/references/webgpu-three.md +605 -0
  33. package/plugins/lisa-expo/skills/building-native-ui/references/zoom-transitions.md +158 -0
  34. package/plugins/lisa-expo/skills/e2e-coverage-gaps/SKILL.md +105 -0
  35. package/plugins/lisa-expo/skills/e2e-coverage-gaps/agents/openai.yaml +4 -0
  36. package/plugins/lisa-expo/skills/eas-update-insights/SKILL.md +228 -0
  37. package/plugins/lisa-expo/skills/eas-update-insights/agents/openai.yaml +4 -0
  38. package/plugins/lisa-expo/skills/eas-update-insights/references/channel-insights-schema.md +47 -0
  39. package/plugins/lisa-expo/skills/eas-update-insights/references/update-insights-schema.md +69 -0
  40. package/plugins/lisa-expo/skills/exploratory-qa/SKILL.md +100 -93
  41. package/plugins/lisa-expo/skills/exploratory-qa/agents/openai.yaml +2 -2
  42. package/plugins/lisa-expo/skills/expo-api-routes/SKILL.md +369 -0
  43. package/plugins/lisa-expo/skills/expo-api-routes/agents/openai.yaml +4 -0
  44. package/plugins/lisa-expo/skills/expo-brownfield/SKILL.md +54 -0
  45. package/plugins/lisa-expo/skills/expo-brownfield/agents/openai.yaml +4 -0
  46. package/plugins/lisa-expo/skills/expo-brownfield/references/brownfield-integrated.md +526 -0
  47. package/plugins/lisa-expo/skills/expo-brownfield/references/brownfield-isolated.md +402 -0
  48. package/plugins/lisa-expo/skills/expo-brownfield/references/comparison.md +63 -0
  49. package/plugins/lisa-expo/skills/expo-brownfield/references/troubleshooting.md +88 -0
  50. package/plugins/lisa-expo/skills/expo-cicd-workflows/SKILL.md +92 -0
  51. package/plugins/lisa-expo/skills/expo-cicd-workflows/agents/openai.yaml +4 -0
  52. package/plugins/lisa-expo/skills/expo-cicd-workflows/scripts/fetch.js +113 -0
  53. package/plugins/lisa-expo/skills/expo-cicd-workflows/scripts/package.json +11 -0
  54. package/plugins/lisa-expo/skills/expo-cicd-workflows/scripts/validate.js +85 -0
  55. package/plugins/lisa-expo/skills/expo-deployment/SKILL.md +190 -0
  56. package/plugins/lisa-expo/skills/expo-deployment/agents/openai.yaml +4 -0
  57. package/plugins/lisa-expo/skills/expo-deployment/references/app-store-metadata.md +479 -0
  58. package/plugins/lisa-expo/skills/expo-deployment/references/ios-app-store.md +355 -0
  59. package/plugins/lisa-expo/skills/expo-deployment/references/play-store.md +246 -0
  60. package/plugins/lisa-expo/skills/expo-deployment/references/testflight.md +58 -0
  61. package/plugins/lisa-expo/skills/expo-deployment/references/workflows.md +200 -0
  62. package/plugins/lisa-expo/skills/expo-dev-client/SKILL.md +164 -0
  63. package/plugins/lisa-expo/skills/expo-dev-client/agents/openai.yaml +4 -0
  64. package/plugins/lisa-expo/skills/expo-module/SKILL.md +141 -0
  65. package/plugins/lisa-expo/skills/expo-module/agents/openai.yaml +4 -0
  66. package/plugins/lisa-expo/skills/expo-module/references/config-plugin.md +90 -0
  67. package/plugins/lisa-expo/skills/expo-module/references/create-expo-module.md +206 -0
  68. package/plugins/lisa-expo/skills/expo-module/references/lifecycle.md +127 -0
  69. package/plugins/lisa-expo/skills/expo-module/references/module-config.md +48 -0
  70. package/plugins/lisa-expo/skills/expo-module/references/native-module.md +286 -0
  71. package/plugins/lisa-expo/skills/expo-module/references/native-view.md +171 -0
  72. package/plugins/lisa-expo/skills/expo-tailwind-setup/SKILL.md +480 -0
  73. package/plugins/lisa-expo/skills/expo-tailwind-setup/agents/openai.yaml +4 -0
  74. package/plugins/lisa-expo/skills/expo-ui-jetpack-compose/SKILL.md +40 -0
  75. package/plugins/lisa-expo/skills/expo-ui-jetpack-compose/agents/openai.yaml +4 -0
  76. package/plugins/lisa-expo/skills/expo-ui-swift-ui/SKILL.md +39 -0
  77. package/plugins/lisa-expo/skills/expo-ui-swift-ui/agents/openai.yaml +4 -0
  78. package/plugins/lisa-expo/skills/native-data-fetching/SKILL.md +507 -0
  79. package/plugins/lisa-expo/skills/native-data-fetching/agents/openai.yaml +4 -0
  80. package/plugins/lisa-expo/skills/native-data-fetching/references/expo-router-loaders.md +344 -0
  81. package/plugins/lisa-expo/skills/upgrading-expo/SKILL.md +134 -0
  82. package/plugins/lisa-expo/skills/upgrading-expo/agents/openai.yaml +4 -0
  83. package/plugins/lisa-expo/skills/upgrading-expo/references/expo-av-to-audio.md +132 -0
  84. package/plugins/lisa-expo/skills/upgrading-expo/references/expo-av-to-video.md +160 -0
  85. package/plugins/lisa-expo/skills/upgrading-expo/references/native-tabs.md +124 -0
  86. package/plugins/lisa-expo/skills/upgrading-expo/references/new-architecture.md +79 -0
  87. package/plugins/lisa-expo/skills/upgrading-expo/references/react-19.md +79 -0
  88. package/plugins/lisa-expo/skills/upgrading-expo/references/react-compiler.md +59 -0
  89. package/plugins/lisa-expo/skills/upgrading-expo/references/react-navigation-to-expo-router.md +61 -0
  90. package/plugins/lisa-expo/skills/use-dom/SKILL.md +417 -0
  91. package/plugins/lisa-expo/skills/use-dom/agents/openai.yaml +4 -0
  92. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  94. package/plugins/lisa-harper-fabric/commands/e2e-coverage-gaps.md +7 -0
  95. package/plugins/lisa-harper-fabric/commands/exploratory-qa.md +2 -2
  96. package/plugins/lisa-harper-fabric/skills/e2e-coverage-gaps/SKILL.md +105 -0
  97. package/plugins/lisa-harper-fabric/skills/e2e-coverage-gaps/agents/openai.yaml +4 -0
  98. package/plugins/lisa-harper-fabric/skills/exploratory-qa/SKILL.md +100 -93
  99. package/plugins/lisa-harper-fabric/skills/exploratory-qa/agents/openai.yaml +2 -2
  100. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  102. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  104. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  105. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  106. package/plugins/lisa-rails/commands/e2e-coverage-gaps.md +7 -0
  107. package/plugins/lisa-rails/commands/exploratory-qa.md +2 -2
  108. package/plugins/lisa-rails/skills/e2e-coverage-gaps/SKILL.md +105 -0
  109. package/plugins/lisa-rails/skills/e2e-coverage-gaps/agents/openai.yaml +4 -0
  110. package/plugins/lisa-rails/skills/exploratory-qa/SKILL.md +100 -93
  111. package/plugins/lisa-rails/skills/exploratory-qa/agents/openai.yaml +2 -2
  112. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  114. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  115. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  116. package/plugins/lisa-wiki/templates/llm-wiki-contract.md +12 -0
  117. package/plugins/src/base/commands/repair-intake.md +2 -2
  118. package/plugins/src/base/rules/config-resolution.md +2 -2
  119. package/plugins/src/base/skills/repair-intake/SKILL.md +86 -9
  120. package/plugins/src/expo/.mcp.json +3 -3
  121. package/plugins/src/expo/THIRD-PARTY-NOTICES.md +57 -0
  122. package/plugins/src/expo/commands/e2e-coverage-gaps.md +7 -0
  123. package/plugins/src/expo/commands/exploratory-qa.md +2 -2
  124. package/plugins/src/expo/skills/add-app-clip/SKILL.md +280 -0
  125. package/plugins/src/expo/skills/add-app-clip/references/native-module.md +96 -0
  126. package/plugins/src/expo/skills/building-native-ui/SKILL.md +321 -0
  127. package/plugins/src/expo/skills/building-native-ui/references/animations.md +220 -0
  128. package/plugins/src/expo/skills/building-native-ui/references/controls.md +272 -0
  129. package/plugins/src/expo/skills/building-native-ui/references/form-sheet.md +253 -0
  130. package/plugins/src/expo/skills/building-native-ui/references/gradients.md +106 -0
  131. package/plugins/src/expo/skills/building-native-ui/references/icons.md +213 -0
  132. package/plugins/src/expo/skills/building-native-ui/references/media.md +198 -0
  133. package/plugins/src/expo/skills/building-native-ui/references/route-structure.md +229 -0
  134. package/plugins/src/expo/skills/building-native-ui/references/search.md +248 -0
  135. package/plugins/src/expo/skills/building-native-ui/references/storage.md +121 -0
  136. package/plugins/src/expo/skills/building-native-ui/references/tabs.md +433 -0
  137. package/plugins/src/expo/skills/building-native-ui/references/toolbar-and-headers.md +284 -0
  138. package/plugins/src/expo/skills/building-native-ui/references/visual-effects.md +197 -0
  139. package/plugins/src/expo/skills/building-native-ui/references/webgpu-three.md +605 -0
  140. package/plugins/src/expo/skills/building-native-ui/references/zoom-transitions.md +158 -0
  141. package/plugins/src/expo/skills/e2e-coverage-gaps/SKILL.md +105 -0
  142. package/plugins/src/expo/skills/eas-update-insights/SKILL.md +228 -0
  143. package/plugins/src/expo/skills/eas-update-insights/references/channel-insights-schema.md +47 -0
  144. package/plugins/src/expo/skills/eas-update-insights/references/update-insights-schema.md +69 -0
  145. package/plugins/src/expo/skills/exploratory-qa/SKILL.md +100 -93
  146. package/plugins/src/expo/skills/expo-api-routes/SKILL.md +369 -0
  147. package/plugins/src/expo/skills/expo-brownfield/SKILL.md +54 -0
  148. package/plugins/src/expo/skills/expo-brownfield/references/brownfield-integrated.md +526 -0
  149. package/plugins/src/expo/skills/expo-brownfield/references/brownfield-isolated.md +402 -0
  150. package/plugins/src/expo/skills/expo-brownfield/references/comparison.md +63 -0
  151. package/plugins/src/expo/skills/expo-brownfield/references/troubleshooting.md +88 -0
  152. package/plugins/src/expo/skills/expo-cicd-workflows/SKILL.md +92 -0
  153. package/plugins/src/expo/skills/expo-cicd-workflows/scripts/fetch.js +113 -0
  154. package/plugins/src/expo/skills/expo-cicd-workflows/scripts/package.json +11 -0
  155. package/plugins/src/expo/skills/expo-cicd-workflows/scripts/validate.js +85 -0
  156. package/plugins/src/expo/skills/expo-deployment/SKILL.md +190 -0
  157. package/plugins/src/expo/skills/expo-deployment/references/app-store-metadata.md +479 -0
  158. package/plugins/src/expo/skills/expo-deployment/references/ios-app-store.md +355 -0
  159. package/plugins/src/expo/skills/expo-deployment/references/play-store.md +246 -0
  160. package/plugins/src/expo/skills/expo-deployment/references/testflight.md +58 -0
  161. package/plugins/src/expo/skills/expo-deployment/references/workflows.md +200 -0
  162. package/plugins/src/expo/skills/expo-dev-client/SKILL.md +164 -0
  163. package/plugins/src/expo/skills/expo-module/SKILL.md +141 -0
  164. package/plugins/src/expo/skills/expo-module/references/config-plugin.md +90 -0
  165. package/plugins/src/expo/skills/expo-module/references/create-expo-module.md +206 -0
  166. package/plugins/src/expo/skills/expo-module/references/lifecycle.md +127 -0
  167. package/plugins/src/expo/skills/expo-module/references/module-config.md +48 -0
  168. package/plugins/src/expo/skills/expo-module/references/native-module.md +286 -0
  169. package/plugins/src/expo/skills/expo-module/references/native-view.md +171 -0
  170. package/plugins/src/expo/skills/expo-tailwind-setup/SKILL.md +480 -0
  171. package/plugins/src/expo/skills/expo-ui-jetpack-compose/SKILL.md +40 -0
  172. package/plugins/src/expo/skills/expo-ui-swift-ui/SKILL.md +39 -0
  173. package/plugins/src/expo/skills/native-data-fetching/SKILL.md +507 -0
  174. package/plugins/src/expo/skills/native-data-fetching/references/expo-router-loaders.md +344 -0
  175. package/plugins/src/expo/skills/upgrading-expo/SKILL.md +134 -0
  176. package/plugins/src/expo/skills/upgrading-expo/references/expo-av-to-audio.md +132 -0
  177. package/plugins/src/expo/skills/upgrading-expo/references/expo-av-to-video.md +160 -0
  178. package/plugins/src/expo/skills/upgrading-expo/references/native-tabs.md +124 -0
  179. package/plugins/src/expo/skills/upgrading-expo/references/new-architecture.md +79 -0
  180. package/plugins/src/expo/skills/upgrading-expo/references/react-19.md +79 -0
  181. package/plugins/src/expo/skills/upgrading-expo/references/react-compiler.md +59 -0
  182. package/plugins/src/expo/skills/upgrading-expo/references/react-navigation-to-expo-router.md +61 -0
  183. package/plugins/src/expo/skills/use-dom/SKILL.md +417 -0
  184. package/plugins/src/harper-fabric/commands/e2e-coverage-gaps.md +7 -0
  185. package/plugins/src/harper-fabric/commands/exploratory-qa.md +2 -2
  186. package/plugins/src/harper-fabric/skills/e2e-coverage-gaps/SKILL.md +105 -0
  187. package/plugins/src/harper-fabric/skills/exploratory-qa/SKILL.md +100 -93
  188. package/plugins/src/rails/commands/e2e-coverage-gaps.md +7 -0
  189. package/plugins/src/rails/commands/exploratory-qa.md +2 -2
  190. package/plugins/src/rails/skills/e2e-coverage-gaps/SKILL.md +105 -0
  191. package/plugins/src/rails/skills/exploratory-qa/SKILL.md +100 -93
  192. package/plugins/src/wiki/templates/llm-wiki-contract.md +12 -0
  193. package/scripts/generate-codex-plugin-artifacts.mjs +7 -2
@@ -0,0 +1,344 @@
1
+ # Expo Router Data Loaders
2
+
3
+ Route-level data loading for web apps using Expo SDK 55+. Loaders are async functions exported from route files that load data before the route renders, following the Remix/React Router loader model.
4
+
5
+ **Dual execution model:**
6
+
7
+ - **Initial page load (SSR):** The loader runs server-side. Its return value is serialized as JSON and embedded in the HTML response.
8
+ - **Client-side navigation:** The browser fetches the loader data from the server via HTTP. The route renders once the data arrives.
9
+
10
+ You write one function and the framework manages when and how it executes.
11
+
12
+ ## Configuration
13
+
14
+ **Requirements:** Expo SDK 55+, web output mode (`npx expo serve` or `npx expo export --platform web`) set in `app.json` or `app.config.js`.
15
+
16
+ **Server rendering:**
17
+
18
+ ```json
19
+ {
20
+ "expo": {
21
+ "web": {
22
+ "output": "server"
23
+ },
24
+ "plugins": [
25
+ ["expo-router", {
26
+ "unstable_useServerDataLoaders": true,
27
+ "unstable_useServerRendering": true
28
+ }]
29
+ ]
30
+ }
31
+ }
32
+ ```
33
+
34
+ **Static/SSG:**
35
+
36
+ ```json
37
+ {
38
+ "expo": {
39
+ "web": {
40
+ "output": "static"
41
+ },
42
+ "plugins": [
43
+ ["expo-router", {
44
+ "unstable_useServerDataLoaders": true
45
+ }]
46
+ ]
47
+ }
48
+ }
49
+ ```
50
+
51
+ | | `"server"` | `"static"` |
52
+ |---|-----------|------------|
53
+ | `unstable_useServerDataLoaders` | Required | Required |
54
+ | `unstable_useServerRendering` | Required | Not required |
55
+ | Loader runs on | Live server (every request) | Build time (static generation) |
56
+ | `request` object | Full access (headers, cookies) | Not available |
57
+ | Hosting | Node.js server (EAS Hosting) | Any static host (Netlify, Vercel, S3) |
58
+
59
+ ## Imports
60
+
61
+ Loaders use two packages:
62
+
63
+ - **`expo-router`** — `useLoaderData` hook
64
+ - **`expo-server`** — `LoaderFunction` type, `StatusError`, `setResponseHeaders`. Always available (dependency of `expo-router`), no install needed.
65
+
66
+ ## Basic Loader
67
+
68
+ For loaders without params, a plain async function works:
69
+
70
+ ```tsx
71
+ // app/posts/index.tsx
72
+ import { Suspense } from "react";
73
+ import { useLoaderData } from "expo-router";
74
+ import { ActivityIndicator, View, Text } from "react-native";
75
+
76
+ export async function loader() {
77
+ const response = await fetch("https://api.example.com/posts");
78
+ const posts = await response.json();
79
+ return { posts };
80
+ }
81
+
82
+ function PostList() {
83
+ const { posts } = useLoaderData<typeof loader>();
84
+
85
+ return (
86
+ <View>
87
+ {posts.map((post) => (
88
+ <Text key={post.id}>{post.title}</Text>
89
+ ))}
90
+ </View>
91
+ );
92
+ }
93
+
94
+ export default function Posts() {
95
+ return (
96
+ <Suspense fallback={<ActivityIndicator size="large" />}>
97
+ <PostList />
98
+ </Suspense>
99
+ );
100
+ }
101
+ ```
102
+
103
+ `useLoaderData` is typed via `typeof loader` — the generic parameter infers the return type.
104
+
105
+ ## Dynamic Routes
106
+
107
+ For loaders with params, use the `LoaderFunction<T>` type from `expo-server`. The first argument is the request (an immutable `Request`-like object, or `undefined` in static mode). The second is `params` (`Record<string, string | string[]>`), which contains **path parameters only**. Access individual params with a cast like `params.id as string`. For query parameters, use `new URL(request.url).searchParams`:
108
+
109
+ ```tsx
110
+ // app/posts/[id].tsx
111
+ import { Suspense } from "react";
112
+ import { useLoaderData } from "expo-router";
113
+ import { StatusError, type LoaderFunction } from "expo-server";
114
+ import { ActivityIndicator, View, Text } from "react-native";
115
+
116
+ type Post = {
117
+ id: number;
118
+ title: string;
119
+ body: string;
120
+ };
121
+
122
+ export const loader: LoaderFunction<{ post: Post }> = async (
123
+ request,
124
+ params,
125
+ ) => {
126
+ const id = params.id as string;
127
+ const response = await fetch(`https://api.example.com/posts/${id}`);
128
+
129
+ if (!response.ok) {
130
+ throw new StatusError(404, `Post ${id} not found`);
131
+ }
132
+
133
+ const post: Post = await response.json();
134
+ return { post };
135
+ };
136
+
137
+ function PostContent() {
138
+ const { post } = useLoaderData<typeof loader>();
139
+
140
+ return (
141
+ <View>
142
+ <Text>{post.title}</Text>
143
+ <Text>{post.body}</Text>
144
+ </View>
145
+ );
146
+ }
147
+
148
+ export default function PostDetail() {
149
+ return (
150
+ <Suspense fallback={<ActivityIndicator size="large" />}>
151
+ <PostContent />
152
+ </Suspense>
153
+ );
154
+ }
155
+ ```
156
+
157
+ Catch-all routes access `params.slug` the same way:
158
+
159
+ ```tsx
160
+ // app/docs/[...slug].tsx
161
+ import { type LoaderFunction } from "expo-server";
162
+
163
+ type Doc = { title: string; content: string };
164
+
165
+ export const loader: LoaderFunction<{ doc: Doc }> = async (request, params) => {
166
+ const slug = params.slug as string[];
167
+ const path = slug.join("/");
168
+ const doc = await fetchDoc(path);
169
+ return { doc };
170
+ };
171
+ ```
172
+
173
+ Query parameters are available via the `request` object (server output mode only):
174
+
175
+ ```tsx
176
+ // app/search.tsx
177
+ import { type LoaderFunction } from "expo-server";
178
+
179
+ export const loader: LoaderFunction<{ results: any[]; query: string }> = async (request) => {
180
+ if (!request) {
181
+ return { results: [], query: "" };
182
+ }
183
+ // Assuming request.url is `/search?q=expo&page=2`
184
+ const url = new URL(request.url);
185
+ const query = url.searchParams.get("q") ?? "";
186
+ const page = Number(url.searchParams.get("page") ?? "1");
187
+
188
+ const results = await fetchSearchResults(query, page);
189
+ return { results, query };
190
+ };
191
+ ```
192
+
193
+ ## Server-Side Secrets & Request Access
194
+
195
+ Loaders run on the server, so you can access secrets and server-only resources directly:
196
+
197
+ ```tsx
198
+ // app/dashboard.tsx
199
+ import { type LoaderFunction } from "expo-server";
200
+
201
+ export const loader: LoaderFunction<{ balance: any; isAuthenticated: boolean }> = async (
202
+ request,
203
+ params,
204
+ ) => {
205
+ const data = await fetch("https://api.stripe.com/v1/balance", {
206
+ headers: {
207
+ Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
208
+ },
209
+ });
210
+
211
+ const sessionToken = request?.headers.get("cookie")?.match(/session=([^;]+)/)?.[1];
212
+
213
+ const balance = await data.json();
214
+ return { balance, isAuthenticated: !!sessionToken };
215
+ };
216
+ ```
217
+
218
+ The `request` object is available in server output mode. In static output mode, `request` is always `undefined`.
219
+
220
+ ## Response Utilities
221
+
222
+ ### Setting Response Headers
223
+
224
+ ```tsx
225
+ // app/products.tsx
226
+ import { setResponseHeaders } from "expo-server";
227
+
228
+ export async function loader() {
229
+ setResponseHeaders({
230
+ "Cache-Control": "public, max-age=300",
231
+ });
232
+
233
+ const products = await fetchProducts();
234
+ return { products };
235
+ }
236
+ ```
237
+
238
+ ### Throwing HTTP Errors
239
+
240
+ ```tsx
241
+ // app/products/[id].tsx
242
+ import { StatusError, type LoaderFunction } from "expo-server";
243
+
244
+ export const loader: LoaderFunction<{ product: Product }> = async (request, params) => {
245
+ const id = params.id as string;
246
+ const product = await fetchProduct(id);
247
+
248
+ if (!product) {
249
+ throw new StatusError(404, "Product not found");
250
+ }
251
+
252
+ return { product };
253
+ };
254
+ ```
255
+
256
+ ## Suspense & Error Boundaries
257
+
258
+ ### Loading States with Suspense
259
+
260
+ `useLoaderData()` suspends during client-side navigation. Push it into a child component and wrap with `<Suspense>`:
261
+
262
+ ```tsx
263
+ // app/posts/index.tsx
264
+ import { Suspense } from "react";
265
+ import { useLoaderData } from "expo-router";
266
+ import { ActivityIndicator, View, Text } from "react-native";
267
+
268
+ export async function loader() {
269
+ const response = await fetch("https://api.example.com/posts");
270
+ return { posts: await response.json() };
271
+ }
272
+
273
+ function PostList() {
274
+ const { posts } = useLoaderData<typeof loader>();
275
+
276
+ return (
277
+ <View>
278
+ {posts.map((post) => (
279
+ <Text key={post.id}>{post.title}</Text>
280
+ ))}
281
+ </View>
282
+ );
283
+ }
284
+
285
+ export default function Posts() {
286
+ return (
287
+ <Suspense
288
+ fallback={
289
+ <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
290
+ <ActivityIndicator size="large" />
291
+ </View>
292
+ }
293
+ >
294
+ <PostList />
295
+ </Suspense>
296
+ );
297
+ }
298
+ ```
299
+
300
+ The `<Suspense>` boundary must be above the component calling `useLoaderData()`. On initial page load the data is already in the HTML, suspension only occurs during client-side navigation.
301
+
302
+ ### Error Boundaries
303
+
304
+ ```tsx
305
+ // app/posts/[id].tsx
306
+ export function ErrorBoundary({ error }: { error: Error }) {
307
+ return (
308
+ <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
309
+ <Text>Error: {error.message}</Text>
310
+ </View>
311
+ );
312
+ }
313
+ ```
314
+
315
+ When a loader throws (including `StatusError`), the nearest `ErrorBoundary` catches it.
316
+
317
+ ## Static vs Server Rendering
318
+
319
+ | | Server (`"server"`) | Static (`"static"`) |
320
+ |---|---|---|
321
+ | **When loader runs** | Every request (live) | At build time (`npx expo export`) |
322
+ | **Data freshness** | Fresh on initial server request | Stale until next build |
323
+ | **`request` object** | Full access | Not available |
324
+ | **Hosting** | Node.js server (EAS Hosting) | Any static host |
325
+ | **Use case** | Personalized/dynamic content | Marketing pages, blogs, docs |
326
+
327
+ **Choose server** when data changes frequently or content is personalized (cookies, auth, headers).
328
+
329
+ **Choose static** when content is the same for all users and changes infrequently.
330
+
331
+ ## Best Practices
332
+
333
+ - Loaders are web-only; use client-side fetching (React Query, fetch) for native
334
+ - Loaders cannot be used in `_layout` files — only in route files
335
+ - Use `LoaderFunction<T>` from `expo-server` to type loaders that use params
336
+ - The request object is immutable — use optional chaining (`request?.headers`) as it may be `undefined` in static mode
337
+ - Return only JSON-serializable values (no `Date`, `Map`, `Set`, class instances, functions)
338
+ - Use non-prefixed `process.env` vars for secrets in loaders, not `EXPO_PUBLIC_` (which is embedded in the client bundle)
339
+ - Use `StatusError` from `expo-server` for HTTP error responses
340
+ - Use `setResponseHeaders` from `expo-server` to set headers
341
+ - Export `ErrorBoundary` from route files to handle loader failures gracefully
342
+ - Validate and sanitize user input (params, query strings) before using in database queries or API calls
343
+ - Handle errors gracefully with try/catch; log server-side for debugging
344
+ - Loader data is currently cached for the session. This is a known limitation that will be lifted in a future release
@@ -0,0 +1,134 @@
1
+ ---
2
+ name: upgrading-expo
3
+ description: Guidelines for upgrading Expo SDK versions and fixing dependency issues
4
+ version: 1.0.0
5
+ license: MIT
6
+ ---
7
+
8
+ ## References
9
+
10
+ - ./references/react-19.md -- SDK +54: React 19 changes (useContext → use, Context.Provider → Context, forwardRef removal)
11
+ - ./references/new-architecture.md -- SDK +53: New Architecture migration guide
12
+ - ./references/react-compiler.md -- SDK +54: React Compiler setup and migration guide
13
+ - ./references/native-tabs.md -- SDK +55: Native tabs changes (Icon/Label/Badge now accessed via NativeTabs.Trigger.\*)
14
+ - ./references/expo-av-to-audio.md -- SDK +55: Migrate audio playback and recording from expo-av to expo-audio
15
+ - ./references/expo-av-to-video.md -- SDK +55: Migrate video playback from expo-av to expo-video
16
+ - ./references/react-navigation-to-expo-router.md -- SDK +56: Migrate `@react-navigation/*` imports to `expo-router` entry points (codemod + manual mapping)
17
+
18
+ ## Beta/Preview Releases
19
+
20
+ Beta versions use `.preview` suffix (e.g., `55.0.0-preview.2`), published under `@next` tag.
21
+
22
+ Check if latest is beta: https://exp.host/--/api/v2/versions (look for `-preview` in `expoVersion`)
23
+
24
+ ```bash
25
+ npx expo install expo@next --fix # install beta
26
+ ```
27
+
28
+ ## Step-by-Step Upgrade Process
29
+
30
+ 1. Upgrade Expo and dependencies
31
+
32
+ ```bash
33
+ npx expo install expo@latest
34
+ npx expo install --fix
35
+ ```
36
+
37
+ 2. Run diagnostics: `npx expo-doctor`
38
+
39
+ 3. Clear caches and reinstall
40
+
41
+ ```bash
42
+ npx expo export -p ios --clear
43
+ rm -rf node_modules .expo
44
+ watchman watch-del-all
45
+ ```
46
+
47
+ ## Breaking Changes Checklist
48
+
49
+ - Check for removed APIs in release notes
50
+ - Update import paths for moved modules
51
+ - Review native module changes requiring prebuild
52
+ - Test all camera, audio, and video features
53
+ - Verify navigation still works correctly
54
+
55
+ ## Prebuild for Native Changes
56
+
57
+ **First check if `ios/` and `android/` directories exist in the project.** If neither directory exists, the project uses Continuous Native Generation (CNG) and native projects are regenerated at build time — skip this section and "Clear caches for bare workflow" entirely.
58
+
59
+ If upgrading requires native changes:
60
+
61
+ ```bash
62
+ npx expo prebuild --clean
63
+ ```
64
+
65
+ This regenerates the `ios` and `android` directories. Ensure the project is not a bare workflow app before running this command.
66
+
67
+ ## Clear caches for bare workflow
68
+
69
+ These steps only apply when `ios/` and/or `android/` directories exist in the project:
70
+
71
+ - Clear the cocoapods cache for iOS: `cd ios && pod install --repo-update`
72
+ - Clear derived data for Xcode: `npx expo run:ios --no-build-cache`
73
+ - Clear the Gradle cache for Android: `cd android && ./gradlew clean`
74
+
75
+ ## Housekeeping
76
+
77
+ - Review release notes for the target SDK version at https://expo.dev/changelog
78
+ - If using Expo SDK 54 or later, ensure react-native-worklets is installed — this is required for react-native-reanimated to work.
79
+ - Enable React Compiler in SDK 54+ by adding `"experiments": { "reactCompiler": true }` to app.json — it's stable and recommended
80
+ - Delete sdkVersion from `app.json` to let Expo manage it automatically
81
+ - Remove implicit packages from `package.json`: `@babel/core`, `babel-preset-expo`, `expo-constants`.
82
+ - If the babel.config.js only contains 'babel-preset-expo', delete the file
83
+ - If the metro.config.js only contains expo defaults, delete the file
84
+
85
+ ## Deprecated Packages
86
+
87
+ | Old Package | Replacement |
88
+ | -------------------- | ---------------------------------------------------- |
89
+ | `expo-av` | `expo-audio` and `expo-video` |
90
+ | `expo-permissions` | Individual package permission APIs |
91
+ | `@expo/vector-icons` | `expo-symbols` (for SF Symbols) |
92
+ | `AsyncStorage` | `expo-sqlite/localStorage/install` |
93
+ | `expo-app-loading` | `expo-splash-screen` |
94
+ | expo-linear-gradient | experimental_backgroundImage + CSS gradients in View |
95
+
96
+ When migrating deprecated packages, update all code usage before removing the old package. For expo-av, consult the migration references to convert Audio.Sound to useAudioPlayer, Audio.Recording to useAudioRecorder, and Video components to VideoView with useVideoPlayer.
97
+
98
+ ## expo.install.exclude
99
+
100
+ Check if package.json has excluded packages:
101
+
102
+ ```json
103
+ {
104
+ "expo": { "install": { "exclude": ["react-native-reanimated"] } }
105
+ }
106
+ ```
107
+
108
+ Exclusions are often workarounds that may no longer be needed after upgrading. Review each one.
109
+ ## Removing patches
110
+
111
+ Check if there are any outdated patches in the `patches/` directory. Remove them if they are no longer needed.
112
+
113
+ ## Postcss
114
+
115
+ - `autoprefixer` isn't needed in SDK +53. Remove it from dependencies and check `postcss.config.js` or `postcss.config.mjs` to remove it from the plugins list.
116
+ - Use `postcss.config.mjs` in SDK +53.
117
+
118
+ ## Metro
119
+
120
+ Remove redundant metro config options:
121
+
122
+ - resolver.unstable_enablePackageExports is enabled by default in SDK +53.
123
+ - `experimentalImportSupport` is enabled by default in SDK +54.
124
+ - `EXPO_USE_FAST_RESOLVER=1` is removed in SDK +54.
125
+ - cjs and mjs extensions are supported by default in SDK +50.
126
+ - Expo webpack is deprecated, migrate to [Expo Router and Metro web](https://docs.expo.dev/router/migrate/from-expo-webpack/).
127
+
128
+ ## Hermes engine v1
129
+
130
+ Since SDK 55, users can opt-in to use Hermes engine v1 for improved runtime performance. This requires setting `useHermesV1: true` in the `expo-build-properties` config plugin, and may require a specific version of the `hermes-compiler` npm package. Hermes v1 will become a default in some future SDK release.
131
+
132
+ ## New Architecture
133
+
134
+ The new architecture is enabled by default, the app.json field `"newArchEnabled": true` is no longer needed as it's the default. Expo Go only supports the new architecture as of SDK +53.
@@ -0,0 +1,4 @@
1
+ display_name: "Upgrading Expo"
2
+ short_description: "Guidelines for upgrading Expo SDK versions and fixing dependency issues"
3
+ default_prompt:
4
+ - "Use $upgrading-expo: Guidelines for upgrading Expo SDK versions and fixing dependency issues."
@@ -0,0 +1,132 @@
1
+ # Migrating from expo-av to expo-audio
2
+
3
+ ## Imports
4
+
5
+ ```tsx
6
+ // Before
7
+ import { Audio } from 'expo-av';
8
+
9
+ // After
10
+ import { useAudioPlayer, useAudioRecorder, RecordingPresets, AudioModule, setAudioModeAsync } from 'expo-audio';
11
+ ```
12
+
13
+ ## Audio Playback
14
+
15
+ ### Before (expo-av)
16
+
17
+ ```tsx
18
+ const [sound, setSound] = useState<Audio.Sound>();
19
+
20
+ async function playSound() {
21
+ const { sound } = await Audio.Sound.createAsync(require('./audio.mp3'));
22
+ setSound(sound);
23
+ await sound.playAsync();
24
+ }
25
+
26
+ useEffect(() => {
27
+ return sound ? () => { sound.unloadAsync(); } : undefined;
28
+ }, [sound]);
29
+ ```
30
+
31
+ ### After (expo-audio)
32
+
33
+ ```tsx
34
+ const player = useAudioPlayer(require('./audio.mp3'));
35
+
36
+ // Play
37
+ player.play();
38
+ ```
39
+
40
+ ## Audio Recording
41
+
42
+ ### Before (expo-av)
43
+
44
+ ```tsx
45
+ const [recording, setRecording] = useState<Audio.Recording>();
46
+
47
+ async function startRecording() {
48
+ await Audio.requestPermissionsAsync();
49
+ await Audio.setAudioModeAsync({ allowsRecordingIOS: true, playsInSilentModeIOS: true });
50
+ const { recording } = await Audio.Recording.createAsync(Audio.RecordingOptionsPresets.HIGH_QUALITY);
51
+ setRecording(recording);
52
+ }
53
+
54
+ async function stopRecording() {
55
+ await recording?.stopAndUnloadAsync();
56
+ const uri = recording?.getURI();
57
+ }
58
+ ```
59
+
60
+ ### After (expo-audio)
61
+
62
+ ```tsx
63
+ const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
64
+
65
+ async function startRecording() {
66
+ await AudioModule.requestRecordingPermissionsAsync();
67
+ await recorder.prepareToRecordAsync();
68
+ recorder.record();
69
+ }
70
+
71
+ async function stopRecording() {
72
+ await recorder.stop();
73
+ const uri = recorder.uri;
74
+ }
75
+ ```
76
+
77
+ ## Audio Mode
78
+
79
+ ### Before (expo-av)
80
+
81
+ ```tsx
82
+ await Audio.setAudioModeAsync({
83
+ allowsRecordingIOS: true,
84
+ playsInSilentModeIOS: true,
85
+ staysActiveInBackground: true,
86
+ interruptionModeIOS: InterruptionModeIOS.DoNotMix,
87
+ });
88
+ ```
89
+
90
+ ### After (expo-audio)
91
+
92
+ ```tsx
93
+ await setAudioModeAsync({
94
+ playsInSilentMode: true,
95
+ shouldPlayInBackground: true,
96
+ interruptionMode: 'doNotMix',
97
+ });
98
+ ```
99
+
100
+ ## API Mapping
101
+
102
+ | expo-av | expo-audio |
103
+ |---------|------------|
104
+ | `Audio.Sound.createAsync()` | `useAudioPlayer(source)` |
105
+ | `sound.playAsync()` | `player.play()` |
106
+ | `sound.pauseAsync()` | `player.pause()` |
107
+ | `sound.setPositionAsync(ms)` | `player.seekTo(seconds)` |
108
+ | `sound.setVolumeAsync(vol)` | `player.volume = vol` |
109
+ | `sound.setRateAsync(rate)` | `player.playbackRate = rate` |
110
+ | `sound.setIsLoopingAsync(loop)` | `player.loop = loop` |
111
+ | `sound.unloadAsync()` | Automatic via hook |
112
+ | `playbackStatus.positionMillis` | `player.currentTime` (seconds) |
113
+ | `playbackStatus.durationMillis` | `player.duration` (seconds) |
114
+ | `playbackStatus.isPlaying` | `player.playing` |
115
+ | `Audio.Recording.createAsync()` | `useAudioRecorder(preset)` |
116
+ | `Audio.RecordingOptionsPresets.*` | `RecordingPresets.*` |
117
+ | `recording.stopAndUnloadAsync()` | `recorder.stop()` |
118
+ | `recording.getURI()` | `recorder.uri` |
119
+ | `Audio.requestPermissionsAsync()` | `AudioModule.requestRecordingPermissionsAsync()` |
120
+
121
+ ## Key Differences
122
+
123
+ - **No auto-reset on finish**: After `play()` completes, the player stays paused at the end. To replay, call `player.seekTo(0)` then `play()`
124
+ - **Time in seconds**: expo-audio uses seconds, not milliseconds (matching web standards)
125
+ - **Immediate loading**: Audio loads immediately when the hook mounts—no explicit preloading needed
126
+ - **Automatic cleanup**: No need to call `unloadAsync()`, hooks handle resource cleanup on unmount
127
+ - **Multiple players**: Create multiple `useAudioPlayer` instances and store them—all load immediately
128
+ - **Direct property access**: Set volume, rate, loop directly on the player object (`player.volume = 0.5`)
129
+
130
+ ## API Reference
131
+
132
+ https://docs.expo.dev/versions/latest/sdk/audio/